diff options
| author | Denys Vlasenko <dvlasenk@redhat.com> | 2010-09-20 14:32:34 +0200 |
|---|---|---|
| committer | Denys Vlasenko <dvlasenk@redhat.com> | 2010-09-20 14:32:34 +0200 |
| commit | d5850083f43507efe38becf589e99fe467239e4c (patch) | |
| tree | e82fbf6864c07928445fabb3201670616f5ea588 /lib/plugins | |
| parent | 9cfd64c47925338185d8d4b3252c9d53dc8d7afb (diff) | |
| download | abrt-d5850083f43507efe38becf589e99fe467239e4c.tar.gz abrt-d5850083f43507efe38becf589e99fe467239e4c.tar.xz abrt-d5850083f43507efe38becf589e99fe467239e4c.zip | |
This patch splits off CCpp's backtrace generation into a separate tool:
$ abrt-action-generate-backtrace -z
abrt-action-generate-backtrace: invalid option -- 'z'
Usage: abrt-action-generate-backtrace -d DIR [-i DIR1:DIR2] [-t SECONDS] [-vs]
Generate backtrace, its quality rating, hash, and crashed function
Options:
-d DIR Crash dump directory
-i DIR1:DIR2 Additional debuginfo directories
-t SECONDS Kill gdb if it runs for more than SECONDS
-v Verbose
-s Log to syslog
This also allows for debugging and regression testing of
abrt-action-generate-backtrace separately - it can be simply run
from command-line.
Also it provides a better fault isolation - crash in
abrt-action-generate-backtrace does not take down abrtd.
The code is based on CCpp.cpp.
CCpp analyzer is made to spawn a child to do the backtrace generation.
Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
Diffstat (limited to 'lib/plugins')
| -rw-r--r-- | lib/plugins/CCpp.cpp | 258 |
1 files changed, 37 insertions, 221 deletions
diff --git a/lib/plugins/CCpp.cpp b/lib/plugins/CCpp.cpp index bc1707db..c4a1f08c 100644 --- a/lib/plugins/CCpp.cpp +++ b/lib/plugins/CCpp.cpp @@ -141,136 +141,42 @@ static char* exec_vp(char **args, uid_t uid, int redirect_stderr, unsigned timeo } close(pipeout[0]); - int st; - waitpid(child, &st, 0); /* prevent having zombie child process */ - if (status) - *status = st; + /* Prevent having zombie child process, and maybe collect status + * (note that status == NULL is ok too) */ + waitpid(child, status, 0); return strbuf_free_nobuf(buf_out); } -static char *get_backtrace(const char *pDebugDumpDir, const char *pDebugInfoDirs, unsigned timeout_sec) +static void gen_backtrace(const char *pDebugDumpDir, const char *pDebugInfoDirs, unsigned timeout_sec) { update_client(_("Generating backtrace")); - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, pDebugDumpDir)) - { - dd_close(dd); - VERB1 log(_("Unable to open debug dump '%s'"), pDebugDumpDir); - return NULL; - } - - char *uid_str = dd_load_text(dd, CD_UID); - uid_t uid = xatoi_u(uid_str); - free(uid_str); - char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); - dd_close(dd); - - // Workaround for - // http://sourceware.org/bugzilla/show_bug.cgi?id=9622 - unsetenv("TERM"); - // This is not necessary, and was observed to cause - // environment corruption (because we run in a thread?): - //putenv((char*)"TERM=dumb"); - - char *args[21]; - args[0] = (char*)"gdb"; - args[1] = (char*)"-batch"; - - // when/if gdb supports "set debug-file-directory DIR1:DIR2": - // (https://bugzilla.redhat.com/show_bug.cgi?id=528668): - args[2] = (char*)"-ex"; - string dfd = "set debug-file-directory /usr/lib/debug"; - const char *p = pDebugInfoDirs; - while (1) + pid_t pid = fork(); + if (pid < 0) { - const char *colon_or_nul = strchrnul(p, ':'); - dfd += ':'; - dfd.append(p, colon_or_nul - p); - dfd += "/usr/lib/debug"; - if (*colon_or_nul != ':') - break; - p = colon_or_nul + 1; - } - args[3] = (char*)dfd.c_str(); - - /* "file BINARY_FILE" is needed, without it gdb cannot properly - * unwind the stack. Currently the unwind information is located - * in .eh_frame which is stored only in binary, not in coredump - * or debuginfo. - * - * Fedora GDB does not strictly need it, it will find the binary - * by its build-id. But for binaries either without build-id - * (= built on non-Fedora GCC) or which do not have - * their debuginfo rpm installed gdb would not find BINARY_FILE - * so it is still makes sense to supply "file BINARY_FILE". - * - * Unfortunately, "file BINARY_FILE" doesn't work well if BINARY_FILE - * was deleted (as often happens during system updates): - * gdb uses specified BINARY_FILE - * even if it is completely unrelated to the coredump. - * See https://bugzilla.redhat.com/show_bug.cgi?id=525721 - * - * TODO: check mtimes on COREFILE and BINARY_FILE and not supply - * BINARY_FILE if it is newer (to at least avoid gdb complaining). - */ - args[4] = (char*)"-ex"; - args[5] = xasprintf("file %s", executable); - free(executable); - - args[6] = (char*)"-ex"; - args[7] = xasprintf("core-file %s/"FILENAME_COREDUMP, pDebugDumpDir); - - args[8] = (char*)"-ex"; - /*args[9] = ... see below */ - args[10] = (char*)"-ex"; - args[11] = (char*)"info sharedlib"; - /* glibc's abort() stores its message in __abort_msg variable */ - args[12] = (char*)"-ex"; - args[13] = (char*)"print (char*)__abort_msg"; - args[14] = (char*)"-ex"; - args[15] = (char*)"print (char*)__glib_assert_msg"; - args[16] = (char*)"-ex"; - args[17] = (char*)"info registers"; - args[18] = (char*)"-ex"; - args[19] = (char*)"disassemble"; - args[20] = NULL; - - /* Get the backtrace, but try to cap its size */ - /* Limit bt depth. With no limit, gdb sometimes OOMs the machine */ - unsigned bt_depth = 2048; - const char *thread_apply_all = "thread apply all"; - const char *full = " full"; - char *bt = NULL; - while (1) - { - args[9] = xasprintf("%s backtrace %u%s", thread_apply_all, bt_depth, full); - bt = exec_vp(args, uid, /*redirect_stderr:*/ 1, timeout_sec, NULL); - free(args[9]); - if ((bt && strnlen(bt, 256*1024) < 256*1024) || bt_depth <= 32) - { - break; - } - - free(bt); - bt_depth /= 2; - if (bt_depth <= 64 && thread_apply_all[0] != '\0') - { - /* This program likely has gazillion threads, dont try to bt them all */ - bt_depth = 256; - thread_apply_all = ""; - } - if (bt_depth <= 64 && full[0] != '\0') - { - /* Looks like there are gigantic local structures or arrays, disable "full" bt */ - bt_depth = 256; - full = ""; - } + perror_msg("fork"); + return; } - free(args[5]); - free(args[7]); - return bt; + if (pid == 0) /* child */ + { + char *argv[8]; /* abrt-action-generate-backtrace [-s] -tSEC -d DIR -i DIR1:DIR2 NULL */ + char **pp = argv; + *pp++ = (char*)"abrt-action-generate-backtrace"; + if (logmode & LOGMODE_SYSLOG) + *pp++ = (char*)"-s"; + *pp++ = xasprintf("-t%u", timeout_sec); + *pp++ = (char*)"-d"; + *pp++ = (char*)pDebugDumpDir; + *pp++ = (char*)"-i"; + *pp++ = (char*)pDebugInfoDirs; + *pp = NULL; + + execvp(argv[0], argv); + perror_msg_and_die("Can't execute '%s'", argv[0]); + } + /* parent */ + waitpid(pid, NULL, 0); } static void GetIndependentBuildIdPC(const char *unstrip_n_output, @@ -764,8 +670,6 @@ void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) } } - char *package = dd_load_text(dd, FILENAME_PACKAGE); - char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); char *uid = dd_load_text(dd, CD_UID); dd_close(dd); /* do not keep dir locked longer than needed */ @@ -780,118 +684,30 @@ void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) VERB1 log(_("Skipping the debuginfo installation")); free(uid); - /* Create and store backtrace. */ - char *backtrace_str = get_backtrace(pDebugDumpDir, m_sDebugInfoDirs.c_str(), m_nGdbTimeoutSec); - if (!backtrace_str) - { - backtrace_str = xstrdup(""); - VERB3 log("get_backtrace() returns NULL, broken core/gdb?"); - } - - char *bt_build_ids = xasprintf("%s%s", backtrace_str, (build_ids) ? build_ids : ""); - free(build_ids); + /* Create and store backtrace and its hash. */ + gen_backtrace(pDebugDumpDir, m_sDebugInfoDirs.c_str(), m_nGdbTimeoutSec); dd = dd_init(); if (!dd_opendir(dd, pDebugDumpDir)) { dd_close(dd); + free(build_ids); VERB1 log(_("Unable to open debug dump '%s'"), pDebugDumpDir); return; } + + /* Add build_ids to backtrace */ + char *backtrace_str = dd_load_text(dd, FILENAME_BACKTRACE); + char *bt_build_ids = xasprintf("%s%s", backtrace_str, (build_ids) ? build_ids : ""); dd_save_text(dd, FILENAME_BACKTRACE, bt_build_ids); + free(build_ids); free(bt_build_ids); + free(backtrace_str); + /* TODO: remove, it's much easier to collect in the coredump helper */ if (m_bMemoryMap) dd_save_text(dd, FILENAME_MEMORYMAP, "memory map of the crashed C/C++ application, not implemented yet"); - /* Compute and store UUID from the backtrace. */ - char *backtrace_cpy = xstrdup(backtrace_str); - - struct backtrace *backtrace = backtrace_parse(backtrace_cpy, false, false); - free(backtrace_cpy); - - if (backtrace) - { - /* Get the quality of the full backtrace. */ - float q1 = backtrace_quality(backtrace); - - /* Remove all the other threads except the crash thread. */ - struct thread *crash_thread = backtrace_find_crash_thread(backtrace); - if (crash_thread) - backtrace_remove_threads_except_one(backtrace, crash_thread); - else - log_msg("Detection of crash thread failed"); - - /* Get the quality of the crash thread. */ - float q2 = backtrace_quality(backtrace); - - backtrace_remove_noncrash_frames(backtrace); - - /* Do the frame removal now. */ - backtrace_limit_frame_depth(backtrace, 5); - /* Frame removal can be done before removing exit handlers. */ - backtrace_remove_exit_handlers(backtrace); - - /* Get the quality of frames around the crash. */ - float q3 = backtrace_quality(backtrace); - - /* Compute UUID. */ - struct strbuf *bt = backtrace_tree_as_str(backtrace, false); - strbuf_prepend_str(bt, executable); - strbuf_prepend_str(bt, package); - char hash_str[SHA1_RESULT_LEN*2 + 1]; - create_hash(hash_str, bt->buf); - dd_save_text(dd, FILENAME_GLOBAL_UUID, hash_str); - strbuf_free(bt); - - /* Compute and store backtrace rating. The crash frame - is more important that the others. The frames around - the crash are more important than the rest. */ - float qtot = 0.25f * q1 + 0.35f * q2 + 0.4f * q3; - - /* Turn the quality to rating. */ - const char *rating; - if (qtot < 0.6f) rating = "0"; - else if (qtot < 0.7f) rating = "1"; - else if (qtot < 0.8f) rating = "2"; - else if (qtot < 0.9f) rating = "3"; - else rating = "4"; - dd_save_text(dd, FILENAME_RATING, rating); - - /* Get the function name from the crash frame. */ - if (crash_thread) - { - struct frame *crash_frame = crash_thread->frames; - struct frame *abort_frame = thread_find_abort_frame(crash_thread); - if (abort_frame) - crash_frame = abort_frame->next; - if (crash_frame && crash_frame->function && 0 != strcmp(crash_frame->function, "??")) - dd_save_text(dd, FILENAME_CRASH_FUNCTION, crash_frame->function); - } - - backtrace_free(backtrace); - } - else - { - /* If the parser failed fall back to the independent backtrace. */ - /* If we write and use a hand-written parser instead of the bison one, - the parser never fails, and it will be possible to get rid of - the independent_backtrace and backtrace_rate_old. */ - struct strbuf *ibt = independent_backtrace(backtrace_str); - strbuf_prepend_str(ibt, executable); - strbuf_prepend_str(ibt, package); - char hash_str[SHA1_RESULT_LEN*2 + 1]; - create_hash(hash_str, ibt->buf); - dd_save_text(dd, FILENAME_GLOBAL_UUID, hash_str); - strbuf_free(ibt); - - /* Compute and store backtrace rating. */ - /* Crash frame is not known so store nothing. */ - dd_save_text(dd, FILENAME_RATING, to_string(backtrace_rate_old(backtrace_str)).c_str()); - } - free(executable); - free(package); - free(backtrace_str); dd_close(dd); } |
