diff options
Diffstat (limited to 'lib/plugins/CCpp.cpp')
-rw-r--r-- | lib/plugins/CCpp.cpp | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/lib/plugins/CCpp.cpp b/lib/plugins/CCpp.cpp new file mode 100644 index 00000000..629da665 --- /dev/null +++ b/lib/plugins/CCpp.cpp @@ -0,0 +1,1064 @@ +/* + CCpp.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include <set> +#include <iomanip> +//#include <nss.h> +//#include <sechash.h> +#include "abrtlib.h" +#include "strbuf.h" +#include "CCpp.h" +#include "abrt_exception.h" +#include "debug_dump.h" +#include "comm_layer_inner.h" +#include "Polkit.h" +#include "backtrace.h" +#include "CCpp_sha1.h" + +using namespace std; + +#define CORE_PATTERN_IFACE "/proc/sys/kernel/core_pattern" +#define CORE_PATTERN "|"CCPP_HOOK_PATH" "DEBUG_DUMPS_DIR" %p %s %u %c" +#define CORE_PIPE_LIMIT_IFACE "/proc/sys/kernel/core_pipe_limit" +/* core_pipe_limit specifies how many dump_helpers might run at the same time +0 - means unlimited, but the it's not guaranteed that /proc/<pid> of crashing +process might not be available for dump_helper +4 - means that 4 dump_helpers can run at the same time, which should be enough +for ABRT, we can miss some crashes, but what are the odds that more processes +crash at the same time? This value has been recommended by nhorman +*/ +#define CORE_PIPE_LIMIT "4" + +#define DEBUGINFO_CACHE_DIR LOCALSTATEDIR"/cache/abrt-di" + +CAnalyzerCCpp::CAnalyzerCCpp() : + m_bBacktrace(true), + m_bBacktraceRemotes(false), + m_bMemoryMap(false), + m_bInstallDebugInfo(true), + m_nDebugInfoCacheMB(4000) +{} + +static string create_hash(const char *pInput) +{ + unsigned int len; + +#if 0 +{ + char hash_str[SHA1_LENGTH*2 + 1]; + unsigned char hash[SHA1_LENGTH]; + HASHContext *hc; + hc = HASH_Create(HASH_AlgSHA1); + if (!hc) + { + error_msg_and_die("HASH_Create(HASH_AlgSHA1) failed"); /* paranoia */ + } + HASH_Begin(hc); + HASH_Update(hc, (const unsigned char*)pInput, strlen(pInput)); + HASH_End(hc, hash, &len, sizeof(hash)); + HASH_Destroy(hc); + + char *d = hash_str; + unsigned char *s = hash; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; +//log("hash1:%s str:'%s'", hash_str, pInput); +} +#endif + + char hash_str[SHA1_RESULT_LEN*2 + 1]; + unsigned char hash2[SHA1_RESULT_LEN]; + sha1_ctx_t sha1ctx; + sha1_begin(&sha1ctx); + sha1_hash(pInput, strlen(pInput), &sha1ctx); + sha1_end(hash2, &sha1ctx); + len = SHA1_RESULT_LEN; + + char *d = hash_str; + unsigned char *s = hash2; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; +//log("hash2:%s str:'%s'", hash_str, pInput); + + return hash_str; +} + +/* Returns status. See `man 2 wait` for status information. */ +static int ExecVP(char **pArgs, uid_t uid, int redirect_stderr, string& pOutput) +{ + /* Nuke everything which may make setlocale() switch to non-POSIX locale: + * we need to avoid having gdb output in some obscure language. + */ + static const char *const unsetenv_vec[] = { + "LANG", + "LC_ALL", + "LC_COLLATE", + "LC_CTYPE", + "LC_MESSAGES", + "LC_MONETARY", + "LC_NUMERIC", + "LC_TIME", + NULL + }; + + int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETGUID | EXECFLG_SETSID | EXECFLG_QUIET; + if (redirect_stderr) + flags |= EXECFLG_ERR2OUT; + VERB1 flags &= ~EXECFLG_QUIET; + + int pipeout[2]; + pid_t child = fork_execv_on_steroids(flags, pArgs, pipeout, (char**)unsetenv_vec, /*dir:*/ NULL, uid); + + /* We use this function to run gdb and unstrip. Bugs in gdb or corrupted + * coredumps were observed to cause gdb to enter infinite loop. + * Therefore we have a (largish) timeout, after which we kill the child. + */ + int t = time(NULL); /* int is enough, no need to use time_t */ + int endtime = t + 60; + while (1) + { + int timeout = endtime - t; + if (timeout < 0) + { + kill(child, SIGKILL); + pOutput += "\nTimeout exceeded: 60 second, killing "; + pOutput += pArgs[0]; + pOutput += "\n"; + break; + } + + /* We don't check poll result - checking read result is enough */ + struct pollfd pfd; + pfd.fd = pipeout[0]; + pfd.events = POLLIN; + poll(&pfd, 1, timeout * 1000); + + char buff[1024]; + int r = read(pipeout[0], buff, sizeof(buff) - 1); + if (r <= 0) + break; + buff[r] = '\0'; + pOutput += buff; + t = time(NULL); + } + close(pipeout[0]); + + int status; + waitpid(child, &status, 0); /* prevent having zombie child process */ + + return status; +} + +static void GetBacktrace(const char *pDebugDumpDir, + const char *pDebugInfoDirs, + string& pBacktrace) +{ + update_client(_("Generating backtrace")); + + string UID; + string executable; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(CD_UID, UID); + } + + // Workaround for + // http://sourceware.org/bugzilla/show_bug.cgi?id=9622 + unsetenv("TERM"); + // This is not necessary, and was observed to cause + // environmant 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 it: + // (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) + { + 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"; + string file = ssprintf("file %s", executable.c_str()); + args[5] = (char*)file.c_str(); + + args[6] = (char*)"-ex"; + string corefile = ssprintf("core-file %s/"FILENAME_COREDUMP, pDebugDumpDir); + args[7] = (char*)corefile.c_str(); + + 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"; + while (1) + { + string cmd = ssprintf("%sbacktrace %u%s", thread_apply_all, bt_depth, full); + args[9] = (char*)cmd.c_str(); + pBacktrace = ""; + ExecVP(args, xatoi_u(UID.c_str()), /*redirect_stderr:*/ 1, pBacktrace); + if (bt_depth <= 64 || pBacktrace.size() < 256*1024) + return; + 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 = ""; + } + } +} + +static void GetIndependentBuildIdPC(const char *unstrip_n_output, + string& pIndependentBuildIdPC) +{ + // lines look like this: + // 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe] + // 0x400000+0x209000 ab3c8286aac6c043fd1bb1cc2a0b88ec29517d3e@0x40024c /bin/sleep /usr/lib/debug/bin/sleep.debug [exe] + // 0x7fff313ff000+0x1000 389c7475e3d5401c55953a425a2042ef62c4c7df@0x7fff313ff2f8 . - linux-vdso.so.1 + const char *line = unstrip_n_output; + while (*line) + { + const char *eol = strchrnul(line, '\n'); + const char *plus = (char*)memchr(line, '+', eol - line); + if (plus) + { + while (++plus < eol && *plus != '@') + { + if (!isspace(*plus)) + { + pIndependentBuildIdPC += *plus; + } + } + } + if (*eol != '\n') break; + line = eol + 1; + } +} + +static string run_unstrip_n(const char *pDebugDumpDir) +{ + string UID; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(CD_UID, UID); + } + + char* args[4]; + args[0] = (char*)"eu-unstrip"; + args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, pDebugDumpDir); + args[2] = (char*)"-n"; + args[3] = NULL; + + string output; + ExecVP(args, xatoi_u(UID.c_str()), /*redirect_stderr:*/ 0, output); + + free(args[1]); + + return output; +} + +/* Needs gdb feature from here: https://bugzilla.redhat.com/show_bug.cgi?id=528668 + * It is slated to be in F12/RHEL6. + */ +static void InstallDebugInfos(const char *pDebugDumpDir, + const char *debuginfo_dirs, + string& build_ids) +{ + update_client(_("Starting the debuginfo installation")); + + int pipeout[2]; //TODO: can we use ExecVP? + xpipe(pipeout); + + fflush(NULL); + pid_t child = fork(); + if (child < 0) + { + /*close(pipeout[0]); - why bother */ + /*close(pipeout[1]); */ + perror_msg_and_die("fork"); + } + if (child == 0) + { + close(pipeout[0]); + xmove_fd(pipeout[1], STDOUT_FILENO); + xmove_fd(xopen("/dev/null", O_RDONLY), STDIN_FILENO); + + char *coredump = xasprintf("%s/"FILENAME_COREDUMP, pDebugDumpDir); + /* SELinux guys are not happy with /tmp, using /var/run/abrt */ + char *tempdir = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu", (long)getpid(), (long)time(NULL)); + /* log() goes to stderr/syslog, it's ok to use it here */ + VERB1 log("Executing: %s %s %s %s", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs); + /* We want parent to see errors in the same stream */ + xdup2(STDOUT_FILENO, STDERR_FILENO); + execlp("abrt-debuginfo-install", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs, NULL); + perror_msg("Can't execute '%s'", "abrt-debuginfo-install"); + /* Serious error (1 means "some debuginfos not found") */ + exit(2); + } + + close(pipeout[1]); + + FILE *pipeout_fp = fdopen(pipeout[0], "r"); + if (pipeout_fp == NULL) /* never happens */ + { + close(pipeout[0]); + waitpid(child, NULL, 0); + return; + } + + /* With 126 debuginfos I've seen lines 9k+ chars long... + * yet, having it truly unlimited is bad too, + * therefore we are using LARGE, but still limited buffer. + */ + char *buff = (char*) xmalloc(64*1024); + while (fgets(buff, 64*1024, pipeout_fp)) + { + strchrnul(buff, '\n')[0] = '\0'; + + if (strncmp(buff, "MISSING:", 8) == 0) + { + build_ids += "Debuginfo absent: "; + build_ids += buff + 8; + build_ids += "\n"; + continue; + } + + const char *p = buff; + while (*p == ' ' || *p == '\t') + { + p++; + } + if (*p) + { + VERB1 log("%s", buff); + update_client("%s", buff); + } + } + free(buff); + fclose(pipeout_fp); + + int status = 0; + while (waitpid(child, &status, 0) < 0 && errno == EINTR) + continue; + if (WIFEXITED(status)) + { + if (WEXITSTATUS(status) > 1) + error_msg("%s exited with %u", "abrt-debuginfo-install", (int)WEXITSTATUS(status)); + } + else + { + error_msg("%s killed by signal %u", "abrt-debuginfo-install", (int)WTERMSIG(status)); + } +} + +static double get_dir_size(const char *dirname, + string *worst_file, + double *maxsz) +{ + DIR *dp = opendir(dirname); + if (dp == NULL) + return 0; + + struct dirent *ep; + struct stat stats; + double size = 0; + while ((ep = readdir(dp)) != NULL) + { + if (dot_or_dotdot(ep->d_name)) + continue; + string dname = concat_path_file(dirname, ep->d_name); + if (lstat(dname.c_str(), &stats) != 0) + continue; + if (S_ISDIR(stats.st_mode)) + { + double sz = get_dir_size(dname.c_str(), worst_file, maxsz); + size += sz; + } + else if (S_ISREG(stats.st_mode)) + { + double sz = stats.st_size; + size += sz; + + if (worst_file) + { + /* Calculate "weighted" size and age + * w = sz_kbytes * age_mins */ + sz /= 1024; + long age = (time(NULL) - stats.st_mtime) / 60; + if (age > 0) + sz *= age; + + if (sz > *maxsz) + { + *maxsz = sz; + *worst_file = dname; + } + } + } + } + closedir(dp); + return size; +} + +static void trim_debuginfo_cache(unsigned max_mb) +{ + while (1) + { + string worst_file; + double maxsz = 0; + double cache_sz = get_dir_size(DEBUGINFO_CACHE_DIR, &worst_file, &maxsz); + if (cache_sz / (1024 * 1024) < max_mb) + break; + VERB1 log("%s is %.0f bytes (over %u MB), deleting '%s'", + DEBUGINFO_CACHE_DIR, cache_sz, max_mb, worst_file.c_str()); + if (unlink(worst_file.c_str()) != 0) + perror_msg("Can't unlink '%s'", worst_file.c_str()); + } +} + +string CAnalyzerCCpp::GetLocalUUID(const char *pDebugDumpDir) +{ + string executable; + string package; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(FILENAME_PACKAGE, package); + } + + string unstrip_n_output = run_unstrip_n(pDebugDumpDir); + string independentBuildIdPC; + GetIndependentBuildIdPC(unstrip_n_output.c_str(), independentBuildIdPC); + + /* package variable has "firefox-3.5.6-1.fc11[.1]" format */ + /* Remove distro suffix and maybe least significant version number */ + char *trimmed_package = xstrdup(package.c_str()); + char *p = trimmed_package; + while (*p) + { + if (*p == '.' && (p[1] < '0' || p[1] > '9')) + { + /* We found "XXXX.nondigitXXXX", trim this part */ + *p = '\0'; + break; + } + p++; + } + char *first_dot = strchr(trimmed_package, '.'); + if (first_dot) + { + char *last_dot = strrchr(first_dot, '.'); + if (last_dot != first_dot) + { + /* There are more than one dot: "1.2.3" + * Strip last part, we don't want to distinquish crashes + * in packages which differ only by minor release number. + */ + *last_dot = '\0'; + } + } + string hash_str = trimmed_package + executable + independentBuildIdPC; + free(trimmed_package); + return create_hash(hash_str.c_str()); +} + +string CAnalyzerCCpp::GetGlobalUUID(const char *pDebugDumpDir) +{ + CDebugDump dd; + dd.Open(pDebugDumpDir); + if (dd.Exist(FILENAME_GLOBAL_UUID)) + { + string uuid; + dd.LoadText(FILENAME_GLOBAL_UUID, uuid); + return uuid; + } + else + { + // Compatibility code. + // This whole block should be deleted for Fedora 14. + log(_("Getting global universal unique identification...")); + + string backtrace_path = concat_path_file(pDebugDumpDir, FILENAME_BACKTRACE); + string executable; + string package; + string uid_str; + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(FILENAME_PACKAGE, package); + if (m_bBacktrace) + dd.LoadText(CD_UID, uid_str); + + string independent_backtrace; + if (m_bBacktrace) + { + /* Run abrt-backtrace to get independent backtrace suitable + to UUID calculation. */ + char *args[7]; + args[0] = (char*)"abrt-backtrace"; + args[1] = (char*)"--single-thread"; + args[2] = (char*)"--remove-exit-handlers"; + args[3] = (char*)"--frame-depth=5"; + args[4] = (char*)"--remove-noncrash-frames"; + args[5] = (char*)backtrace_path.c_str(); + args[6] = NULL; + + int pipeout[2]; + xpipe(pipeout); /* stdout of abrt-backtrace */ + + fflush(NULL); + pid_t child = fork(); + if (child == -1) + perror_msg_and_die("fork"); + if (child == 0) + { + VERB1 log("Executing %s", args[0]); + + xmove_fd(pipeout[1], STDOUT_FILENO); + close(pipeout[0]); /* read side of the pipe */ + + /* abrt-backtrace is executed under the user's uid and gid. */ + uid_t uid = xatoi_u(uid_str.c_str()); + struct passwd* pw = getpwuid(uid); + gid_t gid = pw ? pw->pw_gid : uid; + setgroups(1, &gid); + xsetregid(gid, gid); + xsetreuid(uid, uid); + + execvp(args[0], args); + VERB1 perror_msg("Can't execute '%s'", args[0]); + exit(1); + } + + close(pipeout[1]); /* write side of the pipe */ + + /* Read the result from abrt-backtrace. */ + int r; + char buff[1024]; + while ((r = safe_read(pipeout[0], buff, sizeof(buff) - 1)) > 0) + { + buff[r] = '\0'; + independent_backtrace += buff; + } + close(pipeout[0]); + + /* Wait until it exits, and check the exit status. */ + errno = 0; + int status; + waitpid(child, &status, 0); + if (!WIFEXITED(status)) + { + perror_msg("abrt-backtrace not executed properly, " + "status: %x signal: %d", status, WIFSIGNALED(status)); + } + else + { + int exit_status = WEXITSTATUS(status); + if (exit_status == 79) /* EX_PARSINGFAILED */ + { + /* abrt-backtrace returns alternative backtrace + representation in this case, so everything will work + as expected except worse duplication detection */ + log_msg("abrt-backtrace failed to parse the backtrace"); + } + else if (exit_status == 80) /* EX_THREADDETECTIONFAILED */ + { + /* abrt-backtrace returns backtrace with all threads + in this case, so everything will work as expected + except worse duplication detection */ + log_msg("abrt-backtrace failed to determine crash frame"); + } + else if (exit_status != 0) + { + /* this is unexpected problem and it should be investigated */ + error_msg("abrt-backtrace run failed, exit value: %d", + exit_status); + } + } + + /*VERB1 log("abrt-backtrace result: %s", independent_backtrace.c_str());*/ + } + /* else: no backtrace, independent_backtrace == "" + no backtrace => rating = 0 + */ + else + { + dd.SaveText(FILENAME_RATING, "0"); + } + + string hash_base = package + executable + independent_backtrace; + return create_hash(hash_base.c_str()); + } +} + +static bool DebuginfoCheckPolkit(uid_t uid) +{ + fflush(NULL); + int child_pid = fork(); + if (child_pid < 0) + { + perror_msg_and_die("fork"); + } + if (child_pid == 0) + { + //child + xsetreuid(uid, uid); + PolkitResult result = polkit_check_authorization(getpid(), + "org.fedoraproject.abrt.install-debuginfos"); + exit(result != PolkitYes); //exit 1 (failure) if not allowed + } + + //parent + int status; + if (waitpid(child_pid, &status, 0) > 0 + && WIFEXITED(status) + && WEXITSTATUS(status) == 0 + ) { + return true; //authorization OK + } + log("UID %d is not authorized to install debuginfos", uid); + return false; +} + +void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) +{ + string package, executable, UID; + + CDebugDump dd; + dd.Open(pDebugDumpDir); + + /* Skip remote crashes. */ + if (dd.Exist(FILENAME_REMOTE)) + { + std::string remote_str; + dd.LoadText(FILENAME_REMOTE, remote_str); + bool remote = (remote_str.find('1') != std::string::npos); + if (remote && !m_bBacktraceRemotes) + return; + } + + if (!m_bBacktrace) + return; + + if (!force) + { + bool bt_exists = dd.Exist(FILENAME_BACKTRACE); + if (bt_exists) + return; /* backtrace already exists */ + } + + dd.LoadText(FILENAME_PACKAGE, package); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(CD_UID, UID); + dd.Close(); /* do not keep dir locked longer than needed */ + + string build_ids; + if (m_bInstallDebugInfo && DebuginfoCheckPolkit(xatoi_u(UID.c_str()))) + { + if (m_nDebugInfoCacheMB > 0) + trim_debuginfo_cache(m_nDebugInfoCacheMB); + InstallDebugInfos(pDebugDumpDir, m_sDebugInfoDirs.c_str(), build_ids); + } + else + VERB1 log(_("Skipping the debuginfo installation")); + + /* Create and store backtrace. */ + string backtrace_str; + GetBacktrace(pDebugDumpDir, m_sDebugInfoDirs.c_str(), backtrace_str); + dd.Open(pDebugDumpDir); + dd.SaveText(FILENAME_BACKTRACE, (backtrace_str + build_ids).c_str()); + + if (m_bMemoryMap) + dd.SaveText(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.c_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.c_str()); + strbuf_prepend_str(bt, package.c_str()); + dd.SaveText(FILENAME_GLOBAL_UUID, create_hash(bt->buf).c_str()); + strbuf_free(bt); + + /* Compute and store backtrace rating. */ + /* 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.SaveText(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.SaveText(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.c_str()); + strbuf_prepend_str(ibt, executable.c_str()); + strbuf_prepend_str(ibt, package.c_str()); + dd.SaveText(FILENAME_GLOBAL_UUID, create_hash(ibt->buf).c_str()); + strbuf_free(ibt); + + /* Compute and store backtrace rating. */ + /* Crash frame is not known so store nothing. */ + dd.SaveText(FILENAME_RATING, to_string(backtrace_rate_old(backtrace_str.c_str())).c_str()); + } + + dd.Close(); +} + +/* + this is just a workaround until kernel changes it's behavior + when handling pipes in core_pattern +*/ +#ifdef HOSTILE_KERNEL +#define CORE_SIZE_PATTERN "Max core file size=1:unlimited" +static int isdigit_str(char *str) +{ + do { + if (*str < '0' || *str > '9') + return 0; + } while (*++str); + return 1; +} + +static int set_limits() +{ + DIR *dir = opendir("/proc"); + if (!dir) { + /* this shouldn't fail, but to be safe.. */ + return 1; + } + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (!isdigit_str(ent->d_name)) + continue; + + char limits_name[sizeof("/proc/%s/limits") + sizeof(long)*3]; + snprintf(limits_name, sizeof(limits_name), "/proc/%s/limits", ent->d_name); + FILE *limits_fp = fopen(limits_name, "r"); + if (!limits_fp) { + break; + } + + char line[128]; + char *ulimit_c = NULL; + while (1) { + if (fgets(line, sizeof(line)-1, limits_fp) == NULL) + break; + if (strncmp(line, "Max core file size", sizeof("Max core file size")-1) == 0) { + ulimit_c = skip_whitespace(line + sizeof("Max core file size")-1); + skip_non_whitespace(ulimit_c)[0] = '\0'; + break; + } + } + fclose(limits_fp); + if (!ulimit_c || ulimit_c[0] != '0' || ulimit_c[1] != '\0') { + /*process has nonzero ulimit -c, so need to modify it*/ + continue; + } + /* echo -n 'Max core file size=1:unlimited' >/proc/PID/limits */ + int fd = open(limits_name, O_WRONLY); + if (fd >= 0) { + errno = 0; + /*full_*/ + ssize_t n = write(fd, CORE_SIZE_PATTERN, sizeof(CORE_SIZE_PATTERN)-1); + if (n < sizeof(CORE_SIZE_PATTERN)-1) + log("warning: can't write core_size limit to: %s", limits_name); + close(fd); + } + else + { + log("warning: can't open %s for writing", limits_name); + } + } + closedir(dir); + return 0; +} +#endif /* HOSTILE_KERNEL */ + +void CAnalyzerCCpp::Init() +{ + FILE *fp = fopen(CORE_PATTERN_IFACE, "r"); + if (fp) + { + char line[PATH_MAX]; + if (fgets(line, sizeof(line), fp)) + m_sOldCorePattern = line; + fclose(fp); + } + if (m_sOldCorePattern[0] == '|') + { + if (m_sOldCorePattern == CORE_PATTERN) + { + log("warning: %s already contains %s, " + "did abrt daemon crash recently?", + CORE_PATTERN_IFACE, CORE_PATTERN); + /* There is no point in "restoring" CORE_PATTERN_IFACE + * to CORE_PATTERN on exit. Will restore to a default value: + */ + m_sOldCorePattern = "core"; + } else { + log("warning: %s was already set to run a crash analyser (%s), " + "abrt may interfere with it", + CORE_PATTERN_IFACE, CORE_PATTERN); + } + } +#ifdef HOSTILE_KERNEL + if (set_limits() != 0) + log("warning: failed to set core_size limit, ABRT won't detect crashes in" + "compiled apps"); +#endif + + fp = fopen(CORE_PATTERN_IFACE, "w"); + if (fp) + { + fputs(CORE_PATTERN, fp); + fclose(fp); + } + + /* read the core_pipe_limit and change it if it's == 0 + otherwise the abrt-hook-ccpp won't be able to read /proc/<pid> + of the crashing process + */ + fp = fopen(CORE_PIPE_LIMIT_IFACE, "r"); + if (fp) + { + /* we care only about the first char, if it's + * not '0' then we don't have to change it, + * because it means that it's already != 0 + */ + char pipe_limit[2]; + if (!fgets(pipe_limit, sizeof(pipe_limit), fp)) + pipe_limit[0] = '1'; /* not 0 */ + fclose(fp); + if (pipe_limit[0] == '0') + { + fp = fopen(CORE_PIPE_LIMIT_IFACE, "w"); + if (fp) + { + fputs(CORE_PIPE_LIMIT, fp); + fclose(fp); + } + else + { + log("warning: failed to set core_pipe_limit, ABRT won't detect" + "crashes in compiled apps if kernel > 2.6.31"); + } + } + } +} + +void CAnalyzerCCpp::DeInit() +{ + /* no need to restore the core_pipe_limit, because it's only used + when there is s pipe in core_pattern + */ + FILE *fp = fopen(CORE_PATTERN_IFACE, "w"); + if (fp) + { + fputs(m_sOldCorePattern.c_str(), fp); + fclose(fp); + } +} + +void CAnalyzerCCpp::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Backtrace"); + if (it != end) + { + m_bBacktrace = string_to_bool(it->second.c_str()); + } + it = pSettings.find("BacktraceRemotes"); + if (it != end) + { + m_bBacktraceRemotes = string_to_bool(it->second.c_str()); + } + it = pSettings.find("MemoryMap"); + if (it != end) + { + m_bMemoryMap = string_to_bool(it->second.c_str()); + } + it = pSettings.find("DebugInfo"); + if (it != end) + { + m_sDebugInfo = it->second; + } + it = pSettings.find("DebugInfoCacheMB"); + if (it != end) + { + m_nDebugInfoCacheMB = xatou(it->second.c_str()); + } + it = pSettings.find("InstallDebugInfo"); + if (it == end) //compat, remove after 0.0.11 + it = pSettings.find("InstallDebuginfo"); + if (it != end) + { + m_bInstallDebugInfo = string_to_bool(it->second.c_str()); + } + m_sDebugInfoDirs = DEBUGINFO_CACHE_DIR; + it = pSettings.find("ReadonlyLocalDebugInfoDirs"); + if (it != end) + { + m_sDebugInfoDirs += ':'; + m_sDebugInfoDirs += it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CAnalyzerCCpp::GetSettings() +//{ +// m_pSettings["MemoryMap"] = m_bMemoryMap ? "yes" : "no"; +// m_pSettings["DebugInfo"] = m_sDebugInfo; +// m_pSettings["DebugInfoCacheMB"] = to_string(m_nDebugInfoCacheMB); +// m_pSettings["InstallDebugInfo"] = m_bInstallDebugInfo ? "yes" : "no"; +// +// return m_pSettings; +//} + +PLUGIN_INFO(ANALYZER, + CAnalyzerCCpp, + "CCpp", + "0.0.1", + _("Analyzes crashes in C/C++ programs"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); |