From faa5a0e6751965ec0f196cde9ead672799fffb3b Mon Sep 17 00:00:00 2001 From: Mateusz Moscicki Date: Tue, 21 Apr 2020 13:30:03 +0200 Subject: [PATCH] Calculate the call stack hash sum The hash is used to identify crash reports that contain the same call stack. Change-Id: Ib08e6775d3f3c671b660d92449de78c0d3bb74ec --- packaging/crash-worker.spec | 1 + src/crash-manager/crash-manager.c | 38 +++++++++-- src/crash-manager/crash-manager.h | 1 + src/crash-manager/dbus_notify.c | 36 +++++++---- src/crash-stack/CMakeLists.txt | 5 +- src/crash-stack/crash-stack.c | 80 +++++++++++++++++++++--- src/crash-stack/crash-stack.h | 1 + src/crash-stack/report_info.c | 19 ++++++ tests/system/dbus_notify/dbus_notify.sh.template | 14 ++++- 9 files changed, 167 insertions(+), 28 deletions(-) diff --git a/packaging/crash-worker.spec b/packaging/crash-worker.spec index 2e8cdb7..a747158 100644 --- a/packaging/crash-worker.spec +++ b/packaging/crash-worker.spec @@ -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 diff --git a/src/crash-manager/crash-manager.c b/src/crash-manager/crash-manager.c index b465cdd..156bc55 100644 --- a/src/crash-manager/crash-manager.c +++ b/src/crash-manager/crash-manager.c @@ -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, ¶m, 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() diff --git a/src/crash-manager/crash-manager.h b/src/crash-manager/crash-manager.h index 5e7c02b..e5db78f 100644 --- a/src/crash-manager/crash-manager.h +++ b/src/crash-manager/crash-manager.h @@ -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; }; diff --git a/src/crash-manager/dbus_notify.c b/src/crash-manager/dbus_notify.c index d802a0d..b4926ee 100644 --- a/src/crash-manager/dbus_notify.c +++ b/src/crash-manager/dbus_notify.c @@ -46,6 +46,11 @@ #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, ®_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[]) diff --git a/src/crash-stack/CMakeLists.txt b/src/crash-stack/CMakeLists.txt index 34b42b0..d2a8dd8 100644 --- a/src/crash-stack/CMakeLists.txt +++ b/src/crash-stack/CMakeLists.txt @@ -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) diff --git a/src/crash-stack/crash-stack.c b/src/crash-stack/crash-stack.c index 5e4c82d..a7165cb 100644 --- a/src/crash-stack/crash-stack.c +++ b/src/crash-stack/crash-stack.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -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 [--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; } diff --git a/src/crash-stack/crash-stack.h b/src/crash-stack/crash-stack.h index 7beaea8..1d9641e 100644 --- a/src/crash-stack/crash-stack.h +++ b/src/crash-stack/crash-stack.h @@ -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 diff --git a/src/crash-stack/report_info.c b/src/crash-stack/report_info.c index 1cbb2f7..84ad36f 100644 --- a/src/crash-stack/report_info.c +++ b/src/crash-stack/report_info.c @@ -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); } diff --git a/tests/system/dbus_notify/dbus_notify.sh.template b/tests/system/dbus_notify/dbus_notify.sh.template index a41be8e..4da2362 100644 --- a/tests/system/dbus_notify/dbus_notify.sh.template +++ b/tests/system/dbus_notify/dbus_notify.sh.template @@ -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 -- 2.7.4