Calculate the call stack hash sum 79/231379/10
authorMateusz Moscicki <m.moscicki2@partner.samsung.com>
Tue, 21 Apr 2020 11:30:03 +0000 (13:30 +0200)
committerMateusz Moscicki <m.moscicki2@partner.samsung.com>
Tue, 8 Sep 2020 12:40:33 +0000 (14:40 +0200)
The hash is used to identify crash reports that contain the same call
stack.

Change-Id: Ib08e6775d3f3c671b660d92449de78c0d3bb74ec

packaging/crash-worker.spec
src/crash-manager/crash-manager.c
src/crash-manager/crash-manager.h
src/crash-manager/dbus_notify.c
src/crash-stack/CMakeLists.txt
src/crash-stack/crash-stack.c
src/crash-stack/crash-stack.h
src/crash-stack/report_info.c
tests/system/dbus_notify/dbus_notify.sh.template

index 2e8cdb7..a747158 100644 (file)
@@ -32,6 +32,7 @@ BuildRequires:  pkgconfig(libunwind-generic)
 BuildRequires:  libdw-devel libdw
 BuildRequires:  libcap-devel
 BuildRequires:  pkgconfig(json-c)
+BuildRequires:  pkgconfig(openssl1.1)
 
 %if %{with doc}
 BuildRequires:  doxygen
index b465cdd..156bc55 100644 (file)
@@ -63,6 +63,8 @@
 
 #define WAIT_FOR_OPT_TIMEOUT_SEC 60
 #define SPACE_REQUIRED_KB        1024
+#define HASH_LEN_MAX             0x100
+#define HASH_SEP                 '='
 
 #define MINICOREDUMPER_TIMEOUT_MS  DEFAULT_COMMAND_TIMEOUT_MS
 #define LIVEDUMPER_TIMEOUT_MS      DEFAULT_COMMAND_TIMEOUT_MS
@@ -649,6 +651,7 @@ static void launch_dbus_notify(struct crash_info *cinfo)
                        "--prstatus_fd", prstatus_fd_str,
                        "--signal", sig_str,
                        "--tid-comm", cinfo->comm,
+                       "--hash_native", cinfo->call_stack_hash ?: "none",
                        legacy_notification_str,
                        NULL };
 
@@ -754,7 +757,7 @@ out:
        return is_ok;
 }
 
-static bool execute_crash_stack(const struct crash_info *cinfo, int *exit_code)
+static bool execute_crash_stack(struct crash_info *cinfo, int *exit_code)
 {
        char pid_str[11], tid_str[11], sig_str[11], prstatus_fd_str[11];
        bool is_ok = false;
@@ -769,19 +772,40 @@ static bool execute_crash_stack(const struct crash_info *cinfo, int *exit_code)
                         "--pid", pid_str,
                         "--tid", tid_str,
                         "--sig", sig_str,
+                        "--output", cinfo->info_path,
                         "--output-json", cinfo->info_json,
                         "--prstatus_fd", prstatus_fd_str,
                         NULL };
 
-       int fd = open(cinfo->info_path, O_WRONLY | O_CREAT, 0600);
-       if (fd < 0) {
-               _E("open %s error: %m", cinfo->info_path);
+       int p[2];
+       if (pipe2(p, O_NONBLOCK) < 0) {
+               _E("Cannot create a pipe (%d): %m", errno);
                return false;
        }
 
-       spawn_param_s param = { .fn = spawn_setstdout, .u.int_val = fd };
+       spawn_param_s param = { .fn = spawn_setstdout, .u.int_val = p[1] };
        is_ok = spawn_wait(args, NULL, &param, CRASH_STACK_TIMEOUT_MS, exit_code);
-       close(fd);
+
+       char hash[HASH_LEN_MAX];
+       if (read(p[0], hash, sizeof(hash)) == -1)
+               _E("Read hash error: %m");
+       else
+               _D("Call stack hash: %s", hash);
+
+       hash[HASH_LEN_MAX-1] = '\0';
+       close(p[0]);
+       close(p[1]);
+
+       char *separator = strchr(hash, HASH_SEP);
+       char *endofline = strchr(hash, '\n');
+       if (separator == NULL || endofline == NULL || endofline < (separator + 1) || endofline < 0 || separator < 0) {
+               _E("Unsuspected hash format");
+               goto out;
+       }
+
+       cinfo->call_stack_hash = strndup(separator + 1, endofline - separator - 1);
+       if (cinfo->call_stack_hash == NULL)
+               _E("Saving the hash failed: %m");
 
 out:
        return is_ok;
@@ -1227,6 +1251,7 @@ static void free_crash_info(struct crash_info *cinfo)
        free(cinfo->appid);
        free(cinfo->pkgid);
        free(cinfo->executable_path);
+       free(cinfo->call_stack_hash);
 }
 
 void crash_info_init(struct crash_info *cinfo)
@@ -1255,6 +1280,7 @@ void crash_info_init(struct crash_info *cinfo)
        cinfo->appid = NULL;
        cinfo->pkgid = NULL;
        cinfo->lock_fd = -1;
+       cinfo->call_stack_hash = NULL;
 }
 
 static void release_crashed_process()
index 5e7c02b..e5db78f 100644 (file)
@@ -51,6 +51,7 @@ struct crash_info {
        char *executable_path;
        char *appid;
        char *pkgid;
+       char *call_stack_hash;
        time_t time_info;
        int lock_fd;
 };
index d802a0d..b4926ee 100644 (file)
 #define ARM_REG_PC 15
 #define AARCH64_REG_LR 30
 
+enum signal_type {
+       SIG_NORMAL = 0,
+       SIG_EXTENDED = 1
+};
+
 struct RegInfo {
        char *name;
        long long int value;
@@ -63,6 +68,7 @@ struct NotifyParams {
        char *appid;
        char *pkgid;
        char *tid_comm;
+       char *hash_native;
 };
 
 static int _get_important_registers(int fd, struct RegInfo **reg_info)
@@ -157,7 +163,7 @@ static GVariant *build_legacy_message_data(const struct NotifyParams *notify_par
        return g_variant_new("(ssss)", notify_params->cmd_name, notify_params->cmd_path, notify_params->appid, notify_params->pkgid);
 }
 
-static GVariant *build_message_data(const struct NotifyParams *notify_params)
+static GVariant *build_message_data(const struct NotifyParams *notify_params, const enum signal_type sig_type)
 {
        assert(notify_params);
 
@@ -188,6 +194,13 @@ static GVariant *build_message_data(const struct NotifyParams *notify_params)
                g_variant_builder_close(&md_builder);
        }
 
+       if (sig_type == SIG_NORMAL) {
+               g_variant_builder_open(&md_builder, G_VARIANT_TYPE("{sv}"));
+               g_variant_builder_add(&md_builder, "s", "sys.callstack_native_hash");
+               g_variant_builder_add(&md_builder, "v", g_variant_new_string(notify_params->hash_native));
+               g_variant_builder_close(&md_builder);
+       }
+
        struct RegInfo *reg_info;
        int regs_count = _get_important_registers(notify_params->prstatus_fd, &reg_info);
 
@@ -246,7 +259,7 @@ static bool send_one_signal(GDBusConnection *conn, const char *method_name, GVar
  */
 static bool send_signals(GDBusConnection *conn, const struct NotifyParams *notify_params)
 {
-       GVariant *data = build_message_data(notify_params);
+       GVariant *data = build_message_data(notify_params, SIG_EXTENDED);
        if (!data) {
                _E("Error while preparing data for " PROCESS_CRASHED_EX " signal");
                return false;
@@ -254,20 +267,17 @@ static bool send_signals(GDBusConnection *conn, const struct NotifyParams *notif
 
        (void)g_variant_ref_sink(data);
        send_one_signal(conn, PROCESS_CRASHED_EX, data);
+       g_variant_unref(data);
 
-       GVariant *legacy = notify_params->legacy_notification
+       data = notify_params->legacy_notification
                         ? build_legacy_message_data(notify_params)
-                        : data;
+                        : build_message_data(notify_params, SIG_NORMAL);
 
-       if (legacy)
-               send_one_signal(conn, PROCESS_CRASHED, legacy);
+       if (data)
+               send_one_signal(conn, PROCESS_CRASHED, data);
        else
                _W("Error while preparing data for " PROCESS_CRASHED " signal");
 
-       /* No need to free `legacy' GVariant as it's either already freed by the send call
-        * (ie. it's floating ref in legacy case) or referencing sinked ref, which is freed
-        * below.
-        */
        g_variant_unref(data);
 
        GError *error = NULL;
@@ -296,6 +306,7 @@ static bool parse_cmdline(int ac, char *av[], struct NotifyParams *params)
                FLAG_PRSTATUS_FD,
                FLAG_SIGNAL,
                FLAG_TID_COMM,
+               FLAG_HASH_NATIVE,
                FLAG_LEGACY_NOTIFICATION,
        };
        static const struct option options[] = {
@@ -309,6 +320,7 @@ static bool parse_cmdline(int ac, char *av[], struct NotifyParams *params)
                { .name = "prstatus_fd", .has_arg = required_argument, .flag = NULL, .val = FLAG_PRSTATUS_FD },
                { .name = "signal",      .has_arg = required_argument, .flag = NULL, .val = FLAG_SIGNAL },
                { .name = "tid-comm",    .has_arg = required_argument, .flag = NULL, .val = FLAG_TID_COMM },
+               { .name = "hash_native", .has_arg = required_argument, .flag = NULL, .val = FLAG_HASH_NATIVE },
                { .name = "legacy-sig",  .has_arg = no_argument,       .flag = NULL, .val = FLAG_LEGACY_NOTIFICATION },
                { NULL },
        };
@@ -337,6 +349,8 @@ static bool parse_cmdline(int ac, char *av[], struct NotifyParams *params)
                        params->signal = atoi(optarg);
                else if (FLAG_TID_COMM == val)
                        params->tid_comm = optarg;
+               else if (FLAG_HASH_NATIVE == val)
+                       params->hash_native = optarg;
                else if (FLAG_LEGACY_NOTIFICATION == val)
                        params->legacy_notification = true;
        } while (val != -1);
@@ -348,7 +362,7 @@ static void usage(const char *const progname)
 {
        assert(progname);
 
-       printf("%s --prstatus_fd N --pid PID --tid TID --cmdline CMDLINE --cmdpath CMDPATH --appid APPID --pkgid PKGID --signal SIGNALNR [--legacy-sig]\n", progname);
+       printf("%s --prstatus_fd N --pid PID --tid TID --cmdline CMDLINE --cmdpath CMDPATH --appid APPID --pkgid PKGID --signal SIGNALNR --hash_native HASH [--legacy-sig]\n", progname);
 }
 
 int main(int ac, char *av[])
index 34b42b0..d2a8dd8 100644 (file)
@@ -31,10 +31,11 @@ include(FindPkgConfig)
 pkg_check_modules(LIBUNWIND REQUIRED libunwind-generic)
 pkg_check_modules(DLOG REQUIRED dlog)
 pkg_check_modules(JSON REQUIRED json-c)
-set_property(TARGET ${CRASH_STACK_BIN} APPEND_STRING PROPERTY COMPILE_FLAGS "${JSON_CFLAGS} ${LIBUNWIND_CFLAGS} ${DLOG_CFLAGS}")
+pkg_check_modules(LIBCRYPTO REQUIRED libcrypto1.1)
+set_property(TARGET ${CRASH_STACK_BIN} APPEND_STRING PROPERTY COMPILE_FLAGS "${JSON_CFLAGS} ${LIBUNWIND_CFLAGS} ${DLOG_CFLAGS} ${LIBCRYPTO_CFLAGS}")
 
 # Linking
-target_link_libraries(${CRASH_STACK_BIN} ${DLOG_LIBRARIES} ${LIBUNWIND_LIBRARIES} ${EBL_LIBRARY} ${JSON_LIBRARIES} dl stdc++)
+target_link_libraries(${CRASH_STACK_BIN} ${DLOG_LIBRARIES} ${LIBUNWIND_LIBRARIES} ${EBL_LIBRARY} ${JSON_LIBRARIES} ${LIBCRYPTO_LIBRARIES} dl stdc++)
 
 # Installing
 install(TARGETS ${CRASH_STACK_BIN} DESTINATION libexec)
index 5e4c82d..a7165cb 100644 (file)
@@ -33,6 +33,7 @@
 #include <json-c/json.h>
 #include <limits.h>
 #include <linux/prctl.h>
+#include <openssl/sha.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -213,6 +214,59 @@ static void __print_callstack(Callstack *callstack, json_object *jobj, pid_t pid
        json_object_object_add(jobj, K_CALLSTACK, j_callstack);
 }
 
+static void __print_hash(Callstack *callstack, json_object *jobj, struct addr_node *maps)
+{
+       assert(callstack);
+       assert(maps);
+
+       SHA256_CTX hash_ctx;
+       SHA256_Init(&hash_ctx);
+
+       for (size_t i = 0; i < callstack->elems; i++) {
+               // We won't be able to substract the start address so we skip
+               // this entry
+               if (callstack->proc[i].module_name == NULL)
+                       continue;
+
+               struct addr_node *m_node = maps;
+               while (m_node) {
+                       if (m_node->fpath == NULL)
+                               continue;
+
+                       if (strcmp(m_node->fpath, callstack->proc[i].module_name) == 0)
+                               break;
+
+                       m_node = m_node->next;
+               }
+               if (!m_node) {
+                       _W("Not found maps entry for %s", callstack->proc[i].module_name);
+                       continue;
+               }
+               uintptr_t addr = callstack->proc[i].addr + callstack->proc[i].module_offset - m_node->startaddr;
+               SHA256_Update(&hash_ctx, &addr, sizeof(addr));
+       }
+
+       unsigned char hash[SHA256_DIGEST_LENGTH];
+       SHA256_Final(hash, &hash_ctx);
+
+       char hash_str[SHA256_DIGEST_LENGTH*2+1];
+
+       for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
+               snprintf(&hash_str[i*2], 2 + 1, "%02x", hash[i]);
+
+       static const char *hash_desc = "STACK_SHA256";
+       char *hash_str_full;
+       if (asprintf(&hash_str_full, "%s=%s", hash_desc, hash_str) == -1 || hash_str_full == NULL) {
+               _E("Hash saving error: %m");
+               return;
+       }
+
+       _I("%s", hash_str_full);
+       json_object_object_add(jobj, K_NATIVE_HASH, json_object_new_string(hash_str));
+       fprintf(stdout, "%s\n", hash_str_full);
+       free(hash_str_full);
+}
+
 void callstack_constructor(Callstack *callstack)
 {
        size_t it;
@@ -300,7 +354,7 @@ static void __crash_stack_print_threads(json_object *jobj, pid_t pid, pid_t tid)
  * @param jobj JSON object
  * @param pid PID of the inspected process
  */
-static void __crash_stack_print_maps(json_object *jobj, pid_t pid)
+static struct addr_node* __crash_stack_print_maps(json_object *jobj, pid_t pid)
 {
        char file_path[PATH_MAX];
        struct addr_node *head = NULL;
@@ -317,7 +371,7 @@ static void __crash_stack_print_maps(json_object *jobj, pid_t pid)
                close(fd);
        }
        if (head == NULL)
-               return;
+               return NULL;
 
        t_node = head;
        json_object *j_maps = json_object_new_array();
@@ -341,7 +395,7 @@ static void __crash_stack_print_maps(json_object *jobj, pid_t pid)
        }
 
        json_object_object_add(jobj, K_MAPS, j_maps);
-       free_all_nodes(head);
+       return head;
 }
 
 static struct addr_node *get_addr_list_from_maps(int fd)
@@ -563,6 +617,7 @@ int main(int argc, char **argv)
        pid_t pid = 0;
        pid_t tid = 0;
        char *p = NULL;
+       int res = 0;
        json_object *j_root = json_object_new_object();
        json_object_object_add(j_root, K_REP_VER, json_object_new_int(INFO_JSON_VERSION));
 
@@ -613,17 +668,20 @@ int main(int argc, char **argv)
        __crash_stack_print_threads(j_root, pid, tid);
 
        /* Maps information */
-       __crash_stack_print_maps(j_root, pid);
+       struct addr_node *maps = __crash_stack_print_maps(j_root, pid);
 
        if (pid <= 1) {
                _E( "Usage: %s [--output file] [--erroutput file] [--output-json file] [--pid <pid> [--tid <tid>]]",
                                argv[0]);
 
-               return 1;
+               res = 1;
+               goto exit;
        }
 
-       if (-1 == __get_registers_fd(tid, prstatus_fd))
-               return 3333;
+       if (-1 == __get_registers_fd(tid, prstatus_fd)) {
+               res = 3333;
+               goto exit;
+       }
 
        /* Unwind call stack */
        Callstack callstack;
@@ -638,6 +696,9 @@ int main(int argc, char **argv)
        /* Print the results */
        __print_callstack(&callstack, j_root, tid);
 
+       if (maps)
+               __print_hash(&callstack, j_root, maps);
+
        if (jsonoutputfile != NULL) {
                fprintf(jsonoutputfile, "%s", json_object_to_json_string_ext(j_root, JSON_C_TO_STRING_PRETTY));
                fclose(jsonoutputfile);
@@ -652,5 +713,8 @@ int main(int argc, char **argv)
        callstack_destructor(&callstack);
        json_object_put(j_root);
 
-       return 0;
+exit:
+       if (maps)
+               free_all_nodes(maps);
+       return res;
 }
index 7beaea8..1d9641e 100644 (file)
@@ -69,6 +69,7 @@
 #define K_CALL_ADDR       "addr"
 #define K_CALL_M_NAME     "module_name"
 #define K_CALL_M_OFFSET   "module_offset"
+#define K_NATIVE_HASH     "native_hash"
 
 #define REG_AARCH64_SP  31
 #define REG_AARCH64_PC  32
index 1cbb2f7..84ad36f 100644 (file)
@@ -294,6 +294,24 @@ static void print_callstack(json_object *j_root, FILE *fp)
        fprintf(fp, "End of Call Stack\n");
 }
 
+static void print_native_hash(json_object *j_root, FILE *fp)
+{
+       assert(j_root);
+       assert(fp);
+
+       json_object *j_native_hash;
+       if (!json_object_object_get_ex(j_root, K_NATIVE_HASH, &j_native_hash)) {
+               _W("No native hash in the JSON object");
+               return;
+       }
+
+       json_object_get_string(j_native_hash);
+       fprintf(fp,
+               "STACK_SHA256: %s\n",
+               json_object_get_string(j_native_hash));
+}
+
+
 void print_report_info(json_object *j_root, FILE *fp)
 {
        assert(j_root);
@@ -306,5 +324,6 @@ void print_report_info(json_object *j_root, FILE *fp)
        print_threads(j_root, fp);
        print_maps(j_root, fp);
        print_callstack(j_root, fp);
+       print_native_hash(j_root, fp);
 }
 
index a41be8e..4da2362 100644 (file)
@@ -63,6 +63,12 @@ wait_for_app crash-manager
 for TMPFILE in $TMP1 $TMP2; do
        PATTERN="path=/Org/Tizen/System/Crash/Crash; interface=org\.tizen\.system\.crash\.Crash; member=ProcessCrashed"
        score=0
+       if [ "${TMPFILE}" = ${TMP1} ]; then
+               MAX_SCORE=6
+       else
+               MAX_SCORE=5
+       fi
+
        if egrep "$PATTERN" $TMPFILE; then
                if egrep "string \"kenny" $TMPFILE; then
                        score=$(($score + 1))
@@ -92,7 +98,13 @@ for TMPFILE in $TMP1 $TMP2; do
                        has_process_crashed_ex=yes
                fi
 
-               if [ $score -eq 5 ]; then
+               if [ "${TMPFILE}" = ${TMP1} ]; then
+                 if egrep -A1 "string \"sys.callstack_native_hash" $TMPFILE | egrep 'variant.*string \"57b32ee3a2bb53a816dd102657ca210d84b9b9ac9a765f811f8131011b40a2a9'; then
+                         score=$(($score + 1))
+                 fi
+               fi
+
+               if [ $score -eq ${MAX_SCORE} ]; then
                        continue
                fi
        fi