From 91cdbeb45d11cfd1dd62afe50f80a7c5d4342f5b Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Mon, 24 Nov 2014 14:36:14 +0100 Subject: [SATYR PATCH] unwind: support unwinding from core hook In-memory unwinding of dying processes, related to abrt/abrt#gh829. The implementation mostly mirrors code in src/stack.c of elfutils repo (jankratochvil/corepattern branch). Related to abrt/abrt#829. Signed-off-by: Martin Milata --- include/abrt.h | 8 +++ include/core/unwind.h | 18 ++++++ lib/abrt.c | 35 ++++++++++++ lib/core_unwind.c | 14 +++++ lib/core_unwind_elfutils.c | 136 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+) diff --git a/include/abrt.h b/include/abrt.h index 0a8f5ac..5116446 100644 --- a/include/abrt.h +++ b/include/abrt.h @@ -26,6 +26,7 @@ extern "C" { #include "report_type.h" #include +#include bool sr_abrt_print_report_from_dir(const char *directory, @@ -43,6 +44,13 @@ sr_abrt_create_core_stacktrace_from_gdb(const char *directory, bool hash_fingerprints, char **error_message); +bool +sr_abrt_create_core_stacktrace_from_core_hook(const char *directory, + pid_t thread_id, + const char *executable, + int signum, + char **error_message); + struct sr_rpm_package * sr_abrt_parse_dso_list(const char *text); diff --git a/include/core/unwind.h b/include/core/unwind.h index 8ab9d52..b128c76 100644 --- a/include/core/unwind.h +++ b/include/core/unwind.h @@ -24,6 +24,8 @@ extern "C" { #endif +#include + struct sr_core_stacktrace; struct sr_gdb_stacktrace; @@ -38,6 +40,22 @@ sr_core_stacktrace_from_gdb(const char *gdb_output, const char *executable_filename, char **error_message); +/* This function can be used to unwind stack of live ("dying") process, invoked + * from the core dump hook (/proc/sys/kernel/core_pattern). + * + * Beware: + * + * - It can only unwind one thread of the process, the thread that caused the + * terminating signal to be sent. You must supply that thread's tid. + * - The function calls close() on stdin, meaning that in the core handler you + * cannot access the core image after calling this function. + */ +struct sr_core_stacktrace * +sr_core_stacktrace_from_core_hook(pid_t thread_id, + const char *executable_filename, + int signum, + char **error_message); + #ifdef __cplusplus } #endif diff --git a/lib/abrt.c b/lib/abrt.c index 39bc45d..585fdfb 100644 --- a/lib/abrt.c +++ b/lib/abrt.c @@ -138,6 +138,41 @@ sr_abrt_create_core_stacktrace(const char *directory, error_message); } +bool +sr_abrt_create_core_stacktrace_from_core_hook(const char *directory, + pid_t thread_id, + const char *executable, + int signum, + char **error_message) +{ + + struct sr_core_stacktrace *core_stacktrace; + core_stacktrace = sr_core_stacktrace_from_core_hook(thread_id, executable, + signum, error_message); + + if (!core_stacktrace) + return false; + + fulfill_missing_values(core_stacktrace); + + char *json = sr_core_stacktrace_to_json(core_stacktrace); + + // Add newline to the end of core stacktrace file to make text + // editors happy. + json = sr_realloc(json, strlen(json) + 2); + strcat(json, "\n"); + + char *core_backtrace_filename = sr_build_path(directory, "core_backtrace", NULL); + bool success = sr_string_to_file(core_backtrace_filename, + json, + error_message); + + free(core_backtrace_filename); + free(json); + sr_core_stacktrace_free(core_stacktrace); + return success; +} + struct sr_rpm_package * sr_abrt_parse_dso_list(const char *text) { diff --git a/lib/core_unwind.c b/lib/core_unwind.c index 8b7cc22..7ea66da 100644 --- a/lib/core_unwind.c +++ b/lib/core_unwind.c @@ -52,6 +52,20 @@ sr_parse_coredump(const char *coredump_filename, #endif /* !defined WITH_LIBDWFL && !defined WITH_LIBUNWIND */ +#if !defined WITH_LIBDWFL + +struct sr_core_stacktrace * +sr_core_stacktrace_from_core_hook(pid_t thread_id, + const char *executable_filename, + int signum, + char **error_message); +{ + *error_message = sr_asprintf("satyr is built without live process unwind support"); + return NULL; +} + +#endif /* !defined WITH_LIBDWFL */ + /* FIXME: is there another way to pass the executable name to the find_elf * callback? */ const char *executable_file = NULL; diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c index bbd4813..bea9b5f 100644 --- a/lib/core_unwind_elfutils.c +++ b/lib/core_unwind_elfutils.c @@ -28,6 +28,10 @@ #include #include +#include +#include +#include +#include #define FRAME_LIMIT 256 @@ -202,4 +206,136 @@ fail: return stacktrace; } +struct sr_core_stacktrace * +sr_core_stacktrace_from_core_hook(pid_t tid, + const char *executable, + int signum, + char **error_msg) +{ + struct sr_core_stacktrace *stacktrace = NULL; + + /* Initialize error_msg to 'no error'. */ + if (error_msg) + *error_msg = NULL; + + const Dwfl_Callbacks proc_cb = + { + .find_elf = dwfl_linux_proc_find_elf, + .find_debuginfo = find_debuginfo_none, + }; + + Dwfl *dwfl = dwfl_begin(&proc_cb); + + if (dwfl_linux_proc_report(dwfl, tid) != 0) + { + set_error_dwfl("dwfl_linux_proc_report"); + goto fail; + } + + if (dwfl_report_end(dwfl, NULL, NULL) != 0) + { + set_error_dwfl("dwfl_report_end"); + goto fail; + } + + if (ptrace(PTRACE_SEIZE, tid, NULL, (void *)(uintptr_t)PTRACE_O_TRACEEXIT) != 0) + { + set_error("PTRACE_SEIZE (tid %u) failed: %s", (unsigned)tid, strerror(errno)); + goto fail; + } + + if (close(STDIN_FILENO) != 0) + { + set_error("Failed to close stdin: %s", strerror(errno)); + goto fail; + } + + int status; + pid_t got = waitpid(tid, &status, 0); + if (got == -1) + { + set_error("waitpid failed: %s", strerror(errno)); + goto fail; + } + + if (got != tid) + { + set_error("waitpid returned %u but %u was expected", (unsigned)got, (unsigned)tid); + goto fail; + } + + if (!WIFSTOPPED(status)) + { + set_error("waitpid returned 0x%x but WIFSTOPPED was expected", status); + goto fail; + } + + if ((status >> 8) != (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) + { + set_error("waitpid returned 0x%x but (status >> 8) == " + "(SIGTRAP | (PTRACE_EVENT_EXIT << 8)) was expected", status); + goto fail; + } + + if (dwfl_linux_proc_attach(dwfl, tid, true) != 0) + { + set_error_dwfl("dwfl_linux_proc_attach"); + goto fail; + } + + stacktrace = sr_core_stacktrace_new(); + if (!stacktrace) + { + set_error("Failed to initialize stacktrace memory"); + goto fail; + } + + stacktrace->threads = sr_core_thread_new(); + if (!stacktrace->threads) + { + set_error("Failed to initialize thread memory"); + sr_core_stacktrace_free(stacktrace); + stacktrace = 0; + goto fail; + } + stacktrace->threads->id = tid; + + struct frame_callback_arg frame_arg = + { + .frames_tail = &(stacktrace->threads->frames), + .error_msg = NULL, + .nframes = 0 + }; + + int ret = dwfl_getthread_frames(dwfl, tid, frame_callback, &frame_arg); + if (ret != 0 && ret != CB_STOP_UNWIND) + { + if (ret == -1) + set_error_dwfl("dwfl_getthread_frames"); + else if (ret == DWARF_CB_ABORT) + { + set_error("%s", frame_arg.error_msg); + free(frame_arg.error_msg); + } + else + set_error("Unknown error in dwfl_getthreads"); + + sr_core_stacktrace_free(stacktrace); + stacktrace = NULL; + goto fail; + } + + truncate_long_thread(stacktrace->threads, &frame_arg); + + if (executable) + stacktrace->executable = sr_strdup(executable); + if (signum > 0) + stacktrace->signal = (uint16_t)signum; + stacktrace->crash_thread = stacktrace->threads; + +fail: + dwfl_end(dwfl); + return stacktrace; +} + #endif /* WITH_LIBDWFL */ -- 2.4.3