diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Plugins/CCpp.cpp | 443 | ||||
-rw-r--r-- | lib/Utils/Makefile.am | 9 | ||||
-rw-r--r-- | lib/Utils/backtrace.c | 846 | ||||
-rw-r--r-- | lib/Utils/backtrace.h | 152 | ||||
-rw-r--r-- | lib/Utils/backtrace_parser.y | 684 | ||||
-rw-r--r-- | lib/Utils/strbuf.c | 130 | ||||
-rw-r--r-- | lib/Utils/strbuf.h | 48 | ||||
-rw-r--r-- | lib/Utils/xfuncs.cpp | 8 | ||||
-rw-r--r-- | lib/Utils/xfuncs.h | 104 |
9 files changed, 2129 insertions, 295 deletions
diff --git a/lib/Plugins/CCpp.cpp b/lib/Plugins/CCpp.cpp index 47378690..77cc0eda 100644 --- a/lib/Plugins/CCpp.cpp +++ b/lib/Plugins/CCpp.cpp @@ -23,12 +23,13 @@ //#include <nss.h> //#include <sechash.h> #include "abrtlib.h" +#include "strbuf.h" #include "CCpp.h" #include "ABRTException.h" #include "DebugDump.h" #include "CommLayerInner.h" #include "Polkit.h" - +#include "backtrace.h" #include "CCpp_sha1.h" using namespace std; @@ -40,7 +41,7 @@ using namespace std; 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 +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" @@ -54,7 +55,7 @@ CAnalyzerCCpp::CAnalyzerCCpp() : m_nDebugInfoCacheMB(4000) {} -static string CreateHash(const char *pInput) +static string create_hash(const char *pInput) { unsigned int len; @@ -163,111 +164,6 @@ static int ExecVP(char **pArgs, uid_t uid, int redirect_stderr, string& pOutput) return status; } -enum LineRating -{ - // RATING EXAMPLE - MissingEverything = 0, // #0 0x0000dead in ?? () - MissingFunction = 1, // #0 0x0000dead in ?? () from /usr/lib/libfoobar.so.4 - MissingLibrary = 2, // #0 0x0000dead in foobar() - MissingSourceFile = 3, // #0 0x0000dead in FooBar::FooBar () from /usr/lib/libfoobar.so.4 - Good = 4, // #0 0x0000dead in FooBar::crash (this=0x0) at /home/user/foobar.cpp:204 - BestRating = Good, -}; - -static LineRating rate_line(const char *line) -{ -#define FOUND(x) (strstr(line, x) != NULL) - /* see the "enum LineRating" comments for possible combinations */ - if (FOUND(" at ")) - return Good; - const char *function = strstr(line, " in "); - if (function) - { - if (function[4] == '?') /* " in ??" does not count */ - { - function = NULL; - } - } - bool library = FOUND(" from "); - if (function && library) - return MissingSourceFile; - if (function) - return MissingLibrary; - if (library) - return MissingFunction; - - return MissingEverything; -#undef FOUND -} - -/* returns number of "stars" to show */ -static int rate_backtrace(const char *backtrace) -{ - int i, len; - int multiplier = 0; - int rating = 0; - int best_possible_rating = 0; - char last_lvl = 0; - - /* We look at the frames in reversed order, since: - * - rate_line() checks starting from the first line of the frame - * (note: it may need to look at more than one line!) - * - we increase weight (multiplier) for every frame, - * so that topmost frames end up most important - */ - len = 0; - for (i = strlen(backtrace) - 1; i >= 0; i--) - { - if (backtrace[i] == '#' - && (backtrace[i+1] >= '0' && backtrace[i+1] <= '9') /* #N */ - && (i == 0 || backtrace[i-1] == '\n') /* it's at line start */ - ) { - /* For one, "#0 xxx" always repeats, skip repeats */ - if (backtrace[i+1] == last_lvl) - continue; - last_lvl = backtrace[i+1]; - - char *s = xstrndup(backtrace + i + 1, len); - /* Replace tabs with spaces, rate_line() does not expect tabs. - * Actually, even newlines may be there. Example of multiline frame - * where " at SRCFILE" is on 2nd line: - * #3 0x0040b35d in __libc_message (do_abort=<value optimized out>, - * fmt=<value optimized out>) at ../sysdeps/unix/sysv/linux/libc_fatal.c:186 - */ - for (char *p = s; *p; p++) - if (*p == '\t' || *p == '\n') - *p = ' '; - int lrate = rate_line(s); - multiplier++; - rating += lrate * multiplier; - best_possible_rating += BestRating * multiplier; - //log("lrate:%d rating:%d best_possible_rating:%d s:'%-.40s'", lrate, rating, best_possible_rating, s); - free(s); - len = 0; /* starting new line */ - } - else - { - len++; - } - } - - /* Bogus 'backtrace' with zero frames? */ - if (best_possible_rating == 0) - return 0; - - /* Returning number of "stars" to show */ - if (rating*10 >= best_possible_rating*8) /* >= 0.8 */ - return 4; - if (rating*10 >= best_possible_rating*6) - return 3; - if (rating*10 >= best_possible_rating*4) - return 2; - if (rating*10 >= best_possible_rating*2) - return 1; - - return 0; -} - static void GetBacktrace(const char *pDebugDumpDir, const char *pDebugInfoDirs, string& pBacktrace) @@ -629,121 +525,16 @@ string CAnalyzerCCpp::GetLocalUUID(const char *pDebugDumpDir) } string hash_str = trimmed_package + executable + independentBuildIdPC; free(trimmed_package); - return CreateHash(hash_str.c_str()); + return create_hash(hash_str.c_str()); } string CAnalyzerCCpp::GetGlobalUUID(const char *pDebugDumpDir) { - log(_("Getting global universal unique identification...")); - -//TODO: convert to fork_execv_on_steroids(), nuke static concat_str_vector - - string backtrace_path = concat_path_file(pDebugDumpDir, FILENAME_BACKTRACE); - string executable; - string package; - string uid_str; - { - CDebugDump dd; - dd.Open(pDebugDumpDir); - 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", concat_str_vector(args).c_str()); - - 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 == "" */ - - string hash_base = package + executable + independent_backtrace; - return CreateHash(hash_base.c_str()); + CDebugDump dd; + dd.Open(pDebugDumpDir); + string uuid; + dd.LoadText(FILENAME_GLOBAL_UUID, uuid); + return uuid; } static bool DebuginfoCheckPolkit(uid_t uid) @@ -777,27 +568,23 @@ static bool DebuginfoCheckPolkit(uid_t uid) void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) { - string package; - string UID; + string package, executable, UID; CDebugDump dd; dd.Open(pDebugDumpDir); 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 */ @@ -805,28 +592,106 @@ void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) 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 debuginfo installation")); - } - - string backtrace; - GetBacktrace(pDebugDumpDir, m_sDebugInfoDirs.c_str(), backtrace); + /* Create and store backtrace. */ + string backtrace_str; + GetBacktrace(pDebugDumpDir, m_sDebugInfoDirs.c_str(), backtrace_str); dd.Open(pDebugDumpDir); - dd.SaveText(FILENAME_BACKTRACE, (backtrace + build_ids).c_str()); + 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.\n"); + + /* 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 *abort_frame = thread_find_abort_frame(crash_thread); + if (abort_frame) + { + struct 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); } - dd.SaveText(FILENAME_RATING, to_string(rate_backtrace(backtrace.c_str())).c_str()); + 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 @@ -835,66 +700,66 @@ void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) #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; + 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(int)]; - 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); - } + 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(int)]; + 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; + } + closedir(dir); + return 0; } #endif /* HOSTILE_KERNEL */ @@ -937,7 +802,7 @@ void CAnalyzerCCpp::Init() 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 @@ -972,7 +837,7 @@ void CAnalyzerCCpp::Init() void CAnalyzerCCpp::DeInit() { - /* no need to restore the core_pipe_limit, because it's only used + /* 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"); diff --git a/lib/Utils/Makefile.am b/lib/Utils/Makefile.am index 2667b18a..d3c7a255 100644 --- a/lib/Utils/Makefile.am +++ b/lib/Utils/Makefile.am @@ -2,12 +2,14 @@ # ABRTdUtils has much more. It is used by daemon and plugins only lib_LTLIBRARIES = libABRTUtils.la libABRTdUtils.la +AM_YFLAGS = --verbose + # Not used just yet: # time.cpp # xconnect.cpp libABRTUtils_la_SOURCES = \ - xfuncs.cpp \ + xfuncs.h xfuncs.cpp \ encbase64.cpp \ read_write.cpp \ logging.cpp \ @@ -21,7 +23,10 @@ libABRTUtils_la_SOURCES = \ DebugDump.h DebugDump.cpp \ abrt_dbus.h abrt_dbus.cpp \ CrashTypes.cpp \ - ABRTException.cpp + ABRTException.cpp \ + backtrace.h backtrace.c \ + backtrace_parser.y \ + strbuf.h strbuf.c libABRTUtils_la_CPPFLAGS = \ -Wall -Werror \ -I$(srcdir)/../../inc \ diff --git a/lib/Utils/backtrace.c b/lib/Utils/backtrace.c new file mode 100644 index 00000000..3708eb17 --- /dev/null +++ b/lib/Utils/backtrace.c @@ -0,0 +1,846 @@ +/* -*-mode:c;c-file-style:"bsd";c-basic-offset:4;indent-tabs-mode:nil-*- + 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 "backtrace.h" +#include "strbuf.h" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "xfuncs.h" + +struct frame *frame_new() +{ + struct frame *f = malloc(sizeof(struct frame)); + if (!f) + { + puts("Error while allocating memory for backtrace frame."); + exit(5); + } + + f->function = NULL; + f->number = 0; + f->sourcefile = NULL; + f->signal_handler_called = false; + f->next = NULL; + return f; +} + +void frame_free(struct frame *f) +{ + if (f->function) + free(f->function); + if (f->sourcefile) + free(f->sourcefile); + free(f); +} + +struct frame *frame_add_sibling(struct frame *a, struct frame *b) +{ + struct frame *aa = a; + while (aa->next) + aa = aa->next; + + aa->next = b; + return a; +} + +/* Appends a string representation of 'frame' to the 'str'. */ +static void frame_append_str(struct frame *frame, struct strbuf *str, bool verbose) +{ + if (verbose) + strbuf_append_strf(str, " #%d", frame->number); + else + strbuf_append_str(str, " "); + + if (frame->function) + strbuf_append_strf(str, " %s", frame->function); + if (verbose && frame->sourcefile) + { + if (frame->function) + strbuf_append_str(str, " at"); + strbuf_append_strf(str, " %s", frame->sourcefile); + } + + if (frame->signal_handler_called) + strbuf_append_str(str, " <signal handler called>"); + + strbuf_append_str(str, "\n"); +} + +static bool frame_is_exit_handler(struct frame *frame) +{ + return (frame->function + && frame->sourcefile + && 0 == strcmp(frame->function, "__run_exit_handlers") + && NULL != strstr(frame->sourcefile, "exit.c")); +} + +/* Checks if a frame contains abort function used + * by operating system to exit application. + * E.g. in C it's called "abort" or "raise". + */ +static bool frame_is_abort_frame(struct frame *frame) +{ + if (!frame->function || !frame->sourcefile) + return false; + + if (0 == strcmp(frame->function, "raise") + && (NULL != strstr(frame->sourcefile, "pt-raise.c") + || NULL != strstr(frame->sourcefile, "/libc.so.6"))) + return true; + else if (0 == strcmp(frame->function, "exit") + && NULL != strstr(frame->sourcefile, "exit.c")) + return true; + else if (0 == strcmp(frame->function, "abort") + && (NULL != strstr(frame->sourcefile, "abort.c") + || NULL != strstr(frame->sourcefile, "/libc.so.6"))) + return true; + else if (frame_is_exit_handler(frame)) + return true; + + return false; +} + +static bool frame_is_noncrash_frame(struct frame *frame) +{ + /* Abort frames. */ + if (frame_is_abort_frame(frame)) + return true; + + if (!frame->function) + return false; + + if (0 == strcmp(frame->function, "__kernel_vsyscall")) + return true; + + if (0 == strcmp(frame->function, "__assert_fail")) + return true; + + if (!frame->sourcefile) + return false; + + /* GDK */ + if (0 == strcmp(frame->function, "gdk_x_error") + && 0 == strcmp(frame->sourcefile, "gdkmain-x11.c")) + return true; + + /* X.org */ + if (0 == strcmp(frame->function, "_XReply") + && 0 == strcmp(frame->sourcefile, "xcb_io.c")) + return true; + if (0 == strcmp(frame->function, "_XError") + && 0 == strcmp(frame->sourcefile, "XlibInt.c")) + return true; + if (0 == strcmp(frame->function, "XSync") + && 0 == strcmp(frame->sourcefile, "Sync.c")) + return true; + if (0 == strcmp(frame->function, "process_responses") + && 0 == strcmp(frame->sourcefile, "xcb_io.c")) + return true; + + /* glib */ + if (0 == strcmp(frame->function, "IA__g_log") + && 0 == strcmp(frame->sourcefile, "gmessages.c")) + return true; + if (0 == strcmp(frame->function, "IA__g_logv") + && 0 == strcmp(frame->sourcefile, "gmessages.c")) + return true; + if (0 == strcmp(frame->function, "IA__g_assertion_message") + && 0 == strcmp(frame->sourcefile, "gtestutils.c")) + return true; + if (0 == strcmp(frame->function, "IA__g_assertion_message_expr") + && 0 == strcmp(frame->sourcefile, "gtestutils.c")) + return true; + + /* DBus */ + if (0 == strcmp(frame->function, "gerror_to_dbus_error_message") + && 0 == strcmp(frame->sourcefile, "dbus-gobject.c")) + return true; + if (0 == strcmp(frame->function, "dbus_g_method_return_error") + && 0 == strcmp(frame->sourcefile, "dbus-gobject.c")) + return true; + + /* libstdc++ */ + if (0 == strcmp(frame->function, "__gnu_cxx::__verbose_terminate_handler") + && NULL != strstr(frame->sourcefile, "/vterminate.cc")) + return true; + if (0 == strcmp(frame->function, "__cxxabiv1::__terminate") + && NULL != strstr(frame->sourcefile, "/eh_terminate.cc")) + return true; + if (0 == strcmp(frame->function, "std::terminate") + && NULL != strstr(frame->sourcefile, "/eh_terminate.cc")) + return true; + if (0 == strcmp(frame->function, "__cxxabiv1::__cxa_throw") + && NULL != strstr(frame->sourcefile, "/eh_throw.cc")) + return true; + + return false; +} + +struct thread *thread_new() +{ + struct thread *t = malloc(sizeof(struct thread)); + if (!t) + { + puts("Error while allocating memory for backtrace thread."); + exit(5); + } + + t->number = 0; + t->frames = NULL; + t->next = NULL; + return t; +} + +void thread_free(struct thread *t) +{ + while (t->frames) + { + struct frame *rm = t->frames; + t->frames = rm->next; + frame_free(rm); + } + + free(t); +} + +struct thread *thread_add_sibling(struct thread *a, struct thread *b) +{ + struct thread *aa = a; + while (aa->next) + aa = aa->next; + + aa->next = b; + return a; +} + +static int thread_get_frame_count(struct thread *thread) +{ + struct frame *f = thread->frames; + int count = 0; + while (f) + { + f = f->next; + ++count; + } + return count; +} + +/* Appends string representation of 'thread' to the 'str'. */ +static void thread_append_str(struct thread *thread, struct strbuf *str, bool verbose) +{ + int framecount = thread_get_frame_count(thread); + if (verbose) + strbuf_append_strf(str, "Thread no. %d (%d frames)\n", thread->number, framecount); + else + strbuf_append_str(str, "Thread\n"); + struct frame *frame = thread->frames; + while (frame) + { + frame_append_str(frame, str, verbose); + frame = frame->next; + } +} + +/* + * Checks whether the thread it contains some known "abort" function. + * If a frame with the function is found, it is returned. + * If there are multiple frames with abort function, the lowest + * one is returned. + * Nonrecursive. + */ +struct frame *thread_find_abort_frame(struct thread *thread) +{ + struct frame *frame = thread->frames; + struct frame *result = NULL; + while (frame) + { + if (frame_is_abort_frame(frame)) + result = frame; + + frame = frame->next; + } + + return result; +} + +static void thread_remove_exit_handlers(struct thread *thread) +{ + struct frame *frame = thread->frames; + while (frame) + { + if (frame_is_exit_handler(frame)) + { + /* Delete all frames from the beginning to this frame. */ + while (thread->frames != frame) + { + struct frame *rm = thread->frames; + thread->frames = thread->frames->next; + frame_free(rm); + } + return; + } + + frame = frame->next; + } +} + +static void thread_remove_noncrash_frames(struct thread *thread) +{ + struct frame *prev = NULL; + struct frame *cur = thread->frames; + while (cur) + { + if (frame_is_noncrash_frame(cur)) + { + /* This frame must be skipped, because it will + be deleted. */ + if (prev) + prev->next = cur->next; + else + thread->frames = cur->next; + + frame_free(cur); + + /* Set cur to be valid, as it will be used to + advance to next item. */ + if (prev) + cur = prev; + else + { + cur = thread->frames; + continue; + } + } + + prev = cur; + cur = cur->next; + } +} + +/* Counts the number of quality frames and the number of all frames + * in a thread. + * @param ok_count + * @param all_count + * Not zeroed. This function just adds the numbers to + * ok_count and all_count. + */ +static void thread_rating(struct thread *thread, int *ok_count, int *all_count) +{ + struct frame *frame = thread->frames; + while (frame) + { + *all_count += 1; + if (frame->signal_handler_called || + (frame->function && 0 != strcmp(frame->function, "??"))) + { + *ok_count += 1; + } + frame = frame->next; + } +} + +struct backtrace *backtrace_new() +{ + struct backtrace *bt = malloc(sizeof(struct backtrace)); + if (!bt) + { + puts("Error while allocating memory for backtrace."); + exit(5); + } + + bt->threads = NULL; + bt->crash = NULL; + return bt; +} + +void backtrace_free(struct backtrace *bt) +{ + while (bt->threads) + { + struct thread *rm = bt->threads; + bt->threads = rm->next; + thread_free(rm); + } + + if (bt->crash) + frame_free(bt->crash); + + free(bt); +} + +static int backtrace_get_thread_count(struct backtrace *bt) +{ + struct thread *t = bt->threads; + int count = 0; + while (t) + { + t = t->next; + ++count; + } + return count; +} + +void backtrace_print_tree(struct backtrace *backtrace, bool verbose) +{ + struct strbuf *strbuf = backtrace_tree_as_str(backtrace, verbose); + puts(strbuf->buf); + strbuf_free(strbuf); +} + +struct strbuf *backtrace_tree_as_str(struct backtrace *backtrace, bool verbose) +{ + struct strbuf *str = strbuf_new(); + if (verbose) + strbuf_append_strf(str, "Thread count: %d\n", backtrace_get_thread_count(backtrace)); + + if (backtrace->crash && verbose) + { + strbuf_append_str(str, "Crash frame: "); + frame_append_str(backtrace->crash, str, verbose); + } + + struct thread *thread = backtrace->threads; + while (thread) + { + thread_append_str(thread, str, verbose); + thread = thread->next; + } + + return str; +} + +void backtrace_remove_threads_except_one(struct backtrace *backtrace, + struct thread *one) +{ + while (backtrace->threads) + { + struct thread *rm = backtrace->threads; + backtrace->threads = rm->next; + if (rm != one) + thread_free(rm); + } + + one->next = NULL; + backtrace->threads = one; +} + +/* + * Loop through all threads and if a single one contains the crash frame on the top, + * return it. Otherwise, return NULL. + * + * If require_abort is true, it is also required that the thread containing + * the crash frame contains some known "abort" function. In this case there can be + * multiple threads with the crash frame on the top, but only one of them might + * contain the abort function to succeed. + */ +static struct thread *backtrace_find_crash_thread_from_crash_frame(struct backtrace *backtrace, + bool require_abort) +{ + /* + * This code can be extended to compare something else when the function + * name is not available. + */ + if (!backtrace->threads || !backtrace->crash || !backtrace->crash->function) + return NULL; + + struct thread *result = NULL; + struct thread *thread = backtrace->threads; + while (thread) + { + if (thread->frames + && thread->frames->function + && 0 == strcmp(thread->frames->function, backtrace->crash->function) + && (!require_abort || thread_find_abort_frame(thread))) + { + if (result == NULL) + result = thread; + else + { + /* Second frame with the same function. Failure. */ + return NULL; + } + } + + thread = thread->next; + } + + return result; +} + +struct thread *backtrace_find_crash_thread(struct backtrace *backtrace) +{ + /* If there is no thread, be silent and report NULL. */ + if (!backtrace->threads) + return NULL; + + /* If there is just one thread, it is simple. */ + if (!backtrace->threads->next) + return backtrace->threads; + + /* If we have a crash frame *and* there is just one thread which has + * this frame on the top, it is also simple. + */ + struct thread *thread; + thread = backtrace_find_crash_thread_from_crash_frame(backtrace, false); + if (thread) + return thread; + + /* There are multiple threads with a frame indistinguishable from + * the crash frame on the top of stack. + * Try to search for known abort functions. + */ + thread = backtrace_find_crash_thread_from_crash_frame(backtrace, true); + + return thread; /* result or null */ +} + +void backtrace_limit_frame_depth(struct backtrace *backtrace, int depth) +{ + if (depth <= 0) + return; + + struct thread *thread = backtrace->threads; + while (thread) + { + struct frame *frame = thread_find_abort_frame(thread); + if (frame) + frame = frame->next; /* Start counting from the frame following the abort fr. */ + else + frame = thread->frames; /* Start counting from the first frame. */ + + /* Skip some frames to get the required stack depth. */ + int i = depth; + struct frame *last_frame = NULL; + while (frame && i) + { + last_frame = frame; + frame = frame->next; + --i; + } + + /* Delete the remaining frames. */ + if (last_frame) + last_frame->next = NULL; + + while (frame) + { + struct frame *rm = frame; + frame = frame->next; + frame_free(rm); + } + + thread = thread->next; + } +} + +void backtrace_remove_exit_handlers(struct backtrace *backtrace) +{ + struct thread *thread = backtrace->threads; + while (thread) + { + thread_remove_exit_handlers(thread); + thread = thread->next; + } +} + +void backtrace_remove_noncrash_frames(struct backtrace *backtrace) +{ + struct thread *thread = backtrace->threads; + while (thread) + { + thread_remove_noncrash_frames(thread); + thread = thread->next; + } +} + +/* Belongs to independent_backtrace(). */ +struct header +{ + struct strbuf *text; + struct header *next; +}; + +/* Belongs to independent_backtrace(). */ +static struct header *header_new() +{ + struct header *head = malloc(sizeof(struct header)); + if (!head) + { + puts("Error while allocating memory for backtrace header."); + exit(5); + } + head->text = NULL; + head->next = NULL; + return head; +} + +/* Recursively frees siblings. */ +/* Belongs to independent_backtrace(). */ +static void header_free(struct header *head) +{ + if (head->text) + strbuf_free(head->text); + if (head->next) + header_free(head->next); + free(head); +} + +/* Inserts new header to array if it is not already there. */ +/* Belongs to independent_backtrace(). */ +static void header_set_insert(struct header *cur, struct strbuf *new) +{ + /* Duplicate found case. */ + if (strcmp(cur->text->buf, new->buf) == 0) + return; + + /* Last item case, insert new header here. */ + if (cur->next == NULL) + { + cur->next = header_new(); + cur->next->text = new; + return; + } + + /* Move to next item in array case. */ + header_set_insert(cur->next, new); +} + +struct strbuf *independent_backtrace(const char *input) +{ + struct strbuf *header = strbuf_new(); + bool in_bracket = false; + bool in_quote = false; + bool in_header = false; + bool in_digit = false; + bool has_at = false; + bool has_filename = false; + bool has_bracket = false; + struct header *headers = NULL; + + const char *bk = input; + while (*bk) + { + if (bk[0] == '#' + && bk[1] >= '0' && bk[1] <= '7' + && bk[2] == ' ' /* take only #0...#7 (8 last stack frames) */ + && !in_quote) + { + if (in_header && !has_filename) + strbuf_clear(header); + in_header = true; + } + + if (!in_header) + { + ++bk; + continue; + } + + if (isdigit(*bk) && !in_quote && !has_at) + in_digit = true; + else if (bk[0] == '\\' && bk[1] == '\"') + bk++; + else if (*bk == '\"') + in_quote = in_quote == true ? false : true; + else if (*bk == '(' && !in_quote) + { + in_bracket = true; + in_digit = false; + strbuf_append_char(header, '('); + } + else if (*bk == ')' && !in_quote) + { + in_bracket = false; + has_bracket = true; + in_digit = false; + strbuf_append_char(header, '('); + } + else if (*bk == '\n' && has_filename) + { + if (headers == NULL) + { + headers = header_new(); + headers->text = header; + } + else + header_set_insert(headers, header); + + header = strbuf_new(); + in_bracket = false; + in_quote = false; + in_header = false; + in_digit = false; + has_at = false; + has_filename = false; + has_bracket = false; + } + else if (*bk == ',' && !in_quote) + in_digit = false; + else if (isspace(*bk) && !in_quote) + in_digit = false; + else if (bk[0] == 'a' && bk[1] == 't' && has_bracket && !in_quote) + { + has_at = true; + strbuf_append_char(header, 'a'); + } + else if (bk[0] == ':' && has_at && isdigit(bk[1]) && !in_quote) + has_filename = true; + else if (in_header && !in_digit && !in_quote && !in_bracket) + strbuf_append_char(header, *bk); + + bk++; + } + + strbuf_free(header); + + struct strbuf *result = strbuf_new(); + struct header *loop = headers; + while (loop) + { + strbuf_append_str(result, loop->text->buf); + strbuf_append_char(result, '\n'); + loop = loop->next; + } + + if (headers) + header_free(headers); /* recursive */ + + return result; +} + +/* Belongs to backtrace_rate_old(). */ +enum line_rating +{ + // RATING EXAMPLE + MissingEverything = 0, // #0 0x0000dead in ?? () + MissingFunction = 1, // #0 0x0000dead in ?? () from /usr/lib/libfoobar.so.4 + MissingLibrary = 2, // #0 0x0000dead in foobar() + MissingSourceFile = 3, // #0 0x0000dead in FooBar::FooBar () from /usr/lib/libfoobar.so.4 + Good = 4, // #0 0x0000dead in FooBar::crash (this=0x0) at /home/user/foobar.cpp:204 + BestRating = Good, +}; + +/* Belongs to backtrace_rate_old(). */ +static enum line_rating rate_line(const char *line) +{ +#define FOUND(x) (strstr(line, x) != NULL) + /* see the comments at enum line_rating for possible combinations */ + if (FOUND(" at ")) + return Good; + const char *function = strstr(line, " in "); + if (function && function[4] == '?') /* " in ??" does not count */ + function = NULL; + bool library = FOUND(" from "); + if (function && library) + return MissingSourceFile; + if (function) + return MissingLibrary; + if (library) + return MissingFunction; + + return MissingEverything; +#undef FOUND +} + +/* just a fallback function, to be removed one day */ +int backtrace_rate_old(const char *backtrace) +{ + int i, len; + int multiplier = 0; + int rating = 0; + int best_possible_rating = 0; + char last_lvl = 0; + + /* We look at the frames in reversed order, since: + * - rate_line() checks starting from the first line of the frame + * (note: it may need to look at more than one line!) + * - we increase weight (multiplier) for every frame, + * so that topmost frames end up most important + */ + len = 0; + for (i = strlen(backtrace) - 1; i >= 0; i--) + { + if (backtrace[i] == '#' + && (backtrace[i+1] >= '0' && backtrace[i+1] <= '9') /* #N */ + && (i == 0 || backtrace[i-1] == '\n')) /* it's at line start */ + { + /* For one, "#0 xxx" always repeats, skip repeats */ + if (backtrace[i+1] == last_lvl) + continue; + last_lvl = backtrace[i+1]; + + char *s = xstrndup(backtrace + i + 1, len); + /* Replace tabs with spaces, rate_line() does not expect tabs. + * Actually, even newlines may be there. Example of multiline frame + * where " at SRCFILE" is on 2nd line: + * #3 0x0040b35d in __libc_message (do_abort=<value optimized out>, + * fmt=<value optimized out>) at ../sysdeps/unix/sysv/linux/libc_fatal.c:186 + */ + char *p; + for (p = s; *p; p++) + { + if (*p == '\t' || *p == '\n') + *p = ' '; + } + int lrate = rate_line(s); + multiplier++; + rating += lrate * multiplier; + best_possible_rating += BestRating * multiplier; + //log("lrate:%d rating:%d best_possible_rating:%d s:'%-.40s'", + // lrate, rating, best_possible_rating, s); + free(s); + len = 0; /* starting new line */ + } + else + { + len++; + } + } + + /* Bogus 'backtrace' with zero frames? */ + if (best_possible_rating == 0) + return 0; + + /* Returning number of "stars" to show */ + if (rating*10 >= best_possible_rating*8) /* >= 0.8 */ + return 4; + if (rating*10 >= best_possible_rating*6) + return 3; + if (rating*10 >= best_possible_rating*4) + return 2; + if (rating*10 >= best_possible_rating*2) + return 1; + + return 0; +} + +float backtrace_quality(struct backtrace *backtrace) +{ + int ok_count = 0; + int all_count = 0; + struct thread *thread = backtrace->threads; + while (thread) + { + thread_rating(thread, &ok_count, &all_count); + thread = thread->next; + } + + if (all_count == 0) + return 0; + return ok_count / (float)all_count; +} diff --git a/lib/Utils/backtrace.h b/lib/Utils/backtrace.h new file mode 100644 index 00000000..df5def56 --- /dev/null +++ b/lib/Utils/backtrace.h @@ -0,0 +1,152 @@ +/* + Backtrace parsing and processing. + + If we transform analyzer plugins to separate applications one day, + this functionality should be moved to CCpp analyzer, which will + then easily provide what abrt-backtrace utility provides now. Currently + the code is used by abrt-backtrace, so it is shared in the utils + library. + + Copyright (C) 2009, 2010 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. +*/ +#ifndef BACKTRACE_H +#define BACKTRACE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <stdbool.h> + +struct frame +{ + /* Function name, or NULL. */ + char *function; + /* Frame number. */ + int number; + /* Name of the source file, or binary file, or NULL. */ + char *sourcefile; + bool signal_handler_called; + /* Sibling frame, or NULL if this is the last frame in a thread. */ + struct frame *next; +}; + +struct thread +{ + int number; + struct frame *frames; + /* Sibling thread, or NULL if this is the last thread in a backtrace. */ + struct thread *next; +}; + +struct backtrace +{ + struct thread *threads; + /* + * The frame where the crash happened according to GDB. + * It might be that we can not tell to which thread this frame belongs, + * because all threads end with mutually indistinguishable frames. + */ + struct frame *crash; +}; + +extern struct frame *frame_new(); +extern void frame_free(struct frame *f); +extern struct frame *frame_add_sibling(struct frame *a, struct frame *b); + +extern struct thread *thread_new(); +extern void thread_free(struct thread *t); +extern struct thread *thread_add_sibling(struct thread *a, struct thread *b); +extern struct frame *thread_find_abort_frame(struct thread *thread); + +extern struct backtrace *backtrace_new(); +extern void backtrace_free(struct backtrace *bt); + +/* Prints how internal backtrace representation looks to stdout. */ +extern void backtrace_print_tree(struct backtrace *backtrace, bool verbose); + +/* Returns the backtrace tree string representation. */ +extern struct strbuf *backtrace_tree_as_str(struct backtrace *backtrace, bool verbose); + +/* + * Frees all threads except the one provided as parameters. + * It does not check whether one is a member of backtrace. + * Caller must know that. + */ +extern void backtrace_remove_threads_except_one(struct backtrace *backtrace, + struct thread *one); + +/* + * Search all threads and tries to find the one that caused the crash. + * It might return NULL if the thread cannot be determined. + */ +extern struct thread *backtrace_find_crash_thread(struct backtrace *backtrace); + +extern void backtrace_limit_frame_depth(struct backtrace *backtrace, int depth); + +/* + * Exit handlers are all stack frames above __run_exit_handlers() + */ +extern void backtrace_remove_exit_handlers(struct backtrace *backtrace); + +/* + * Removes frames known as not causing crash, but that are often + * a part of a backtrace. + */ +extern void backtrace_remove_noncrash_frames(struct backtrace *backtrace); + +/* Parses the backtrace and stores it to a structure. + * @returns + * Returns the backtrace struct representation, or NULL if the parser failed. + * Caller of this function is responsible for backtrace_free()ing the returned value. + * Defined in backtrace_parser.y. + */ +extern struct backtrace *backtrace_parse(char *input, bool debug_parser, bool debug_scanner); + +/* Reads the input file and calculates "independent" backtrace from it. "Independent" means + * that the memory addresses that differ from run to run are removed from the backtrace, and + * also variable names and values are removed. + * + * This function can be called when backtrace_parse() call fails. It provides a shorter + * version of backtrace, with a chance that hash calculated from the returned value can be used + * to detect duplicates. However, this kind of duplicate detection is very low-quality. + * @returns + * The independent backtrace. Caller is responsible for calling + * strbuf_free() on it. + */ +extern struct strbuf *independent_backtrace(const char *input); + +/* Get the quality of backtrace, as a number of "stars". + * @returns + * Value 0 to 4. + */ +extern int backtrace_rate_old(const char *backtrace); + +/* Evaluates the quality of the backtrace, meaning the ratio of frames + * with function name fully known to all frames. + * @returns + * A number between 0 and 1. 0 means the lowest quality, + * 1 means full backtrace is known. + */ +extern float backtrace_quality(struct backtrace *backtrace); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Utils/backtrace_parser.y b/lib/Utils/backtrace_parser.y new file mode 100644 index 00000000..8e2fc2b1 --- /dev/null +++ b/lib/Utils/backtrace_parser.y @@ -0,0 +1,684 @@ +%{ /* -*- mode: yacc -*- + 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 <math.h> +#include <stdio.h> +#include <stdlib.h> +#include "backtrace.h" +#include "strbuf.h" + +struct backtrace *g_backtrace; + +#define YYDEBUG 1 +#define YYMAXDEPTH 10000000 +void yyerror(char const *s) +{ + fprintf (stderr, "\nParser error: %s\n", s); +} + +int yylex(); + +%} + +/* This defines the type of yylval */ +%union { + struct backtrace *backtrace; + struct thread *thread; + struct frame *frame; + char *str; + int num; + char c; + + struct strbuf *strbuf; +} + +/* Bison declarations. */ +%token END 0 "end of file" + +%type <backtrace> backtrace +%type <thread> threads + thread +%type <frame> frames + frame + frame_head + frame_head_1 + frame_head_2 + frame_head_3 + frame_head_4 + frame_head_5 +%type <strbuf> identifier + hexadecimal_digit_sequence + hexadecimal_number + file_name + file_location + function_call + function_name + digit_sequence + frame_address_in_function + identifier_braces + identifier_braces_inside + identifier_template + identifier_template_inside +%type <c> nondigit + digit + hexadecimal_digit + file_name_char + identifier_char + identifier_char_no_templates + identifier_first_char + identifier_braces_inside_char + identifier_template_inside_char + variables_char + variables_char_no_framestart + ws + ws_nonl + '(' ')' '+' '-' '/' '.' '_' '~' '[' ']' '\r' '?' '{' '}' + 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' + 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' + 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' + 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' + '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' + '\'' '`' ',' '#' '@' '<' '>' '=' ':' '"' ';' ' ' + '\n' '\t' '\\' '!' '*' '%' '|' '^' '&' '$' +%type <num> frame_start + +%destructor { thread_free($$); } <thread> +%destructor { frame_free($$); } <frame> +%destructor { strbuf_free($$); } <strbuf> + +%start backtrace +%glr-parser +%error-verbose +%locations + +%% /* The grammar follows. */ + +backtrace : /* empty */ %dprec 1 + { $$ = g_backtrace = backtrace_new(); } + | threads wsa %dprec 2 + { + $$ = g_backtrace = backtrace_new(); + $$->threads = $1; + } + | frame_head wss threads wsa %dprec 4 + { + $$ = g_backtrace = backtrace_new(); + $$->threads = $3; + $$->crash = $1; + } + | frame wss threads wsa %dprec 3 + { + $$ = g_backtrace = backtrace_new(); + $$->threads = $3; + $$->crash = $1; + } +; + +threads : thread + | threads '\n' thread { $$ = thread_add_sibling($1, $3); } +; + +thread : keyword_thread wss digit_sequence wsa '(' keyword_thread wss digit_sequence wsa ')' ':' wsa frames + { + $$ = thread_new(); + $$->frames = $13; + + if (sscanf($3->buf, "%d", &$$->number) != 1) + { + printf("Error while parsing thread number '%s'", $3->buf); + exit(5); + } + strbuf_free($3); + strbuf_free($8); + } +; + +frames : frame { $$ = $1; } + | frames frame { $$ = frame_add_sibling($1, $2); } +; + +frame : frame_head_1 wss variables %dprec 3 + | frame_head_2 wss variables %dprec 4 + | frame_head_3 wss variables %dprec 5 + | frame_head_4 wss variables %dprec 2 + | frame_head_5 wss variables %dprec 1 +; + +frame_head : frame_head_1 %dprec 3 + | frame_head_2 %dprec 4 + | frame_head_3 %dprec 5 + | frame_head_4 %dprec 2 + | frame_head_5 %dprec 1 +; + +frame_head_1 : frame_start wss function_call wsa keyword_at wss file_location + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + $$->sourcefile = $7->buf; + strbuf_free_nobuf($7); + } +; + +frame_head_2 : frame_start wss frame_address_in_function wss keyword_at wss file_location + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + $$->sourcefile = $7->buf; + strbuf_free_nobuf($7); + } +; + +frame_head_3 : frame_start wss frame_address_in_function wss keyword_from wss file_location + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + $$->sourcefile = $7->buf; + strbuf_free_nobuf($7); + } +; + +frame_head_4 : frame_start wss frame_address_in_function + { + $$ = frame_new(); + $$->number = $1; + $$->function = $3->buf; + strbuf_free_nobuf($3); + } +; + +frame_head_5 : frame_start wss keyword_sighandler + { + $$ = frame_new(); + $$->number = $1; + $$->signal_handler_called = true; + } + +frame_start: '#' digit_sequence + { + if (sscanf($2->buf, "%d", &$$) != 1) + { + printf("Error while parsing frame number '%s'.\n", $2->buf); + exit(5); + } + strbuf_free($2); + } +; + +frame_address_in_function : hexadecimal_number wss keyword_in wss function_call + { + strbuf_free($1); + $$ = $5; + } + | hexadecimal_number wss keyword_in wss keyword_vtable wss keyword_for wss function_call + { + strbuf_free($1); + $$ = $9; + } +; + +file_location : file_name ':' digit_sequence + { + $$ = $1; + strbuf_free($3); /* line number not needed for now */ + } + | file_name +; + +variables : variables_line '\n' + | variables_line END + | variables_line wss_nonl '\n' + | variables_line wss_nonl END + | variables variables_line '\n' + | variables variables_line END + | variables variables_line wss_nonl '\n' + | variables variables_line wss_nonl END + | variables wss_nonl variables_line '\n' + | variables wss_nonl variables_line END + | variables wss_nonl variables_line wss_nonl '\n' + | variables wss_nonl variables_line wss_nonl END +; + +variables_line : variables_char_no_framestart + | variables_line variables_char + | variables_line wss_nonl variables_char +; + +variables_char : '#' | variables_char_no_framestart +; + +/* Manually synchronized with function_args_char_base, except the first line. */ +variables_char_no_framestart : digit | nondigit | '"' | '(' | ')' | '\\' + | '+' | '-' | '<' | '>' | '/' | '.' + | '[' | ']' | '?' | '\'' | '`' | ',' + | '=' | '{' | '}' | '^' | '&' | '$' + | ':' | ';' | '!' | '@' | '*' + | '%' | '|' | '~' +; + +function_call : function_name wss function_args %dprec 3 + | return_type wss_nonl function_name wss function_args %dprec 2 + { $$ = $3; } + | function_name wss_nonl identifier_template wss function_args %dprec 1 + { $$ = $1; strbuf_free($3); } +; + +return_type : identifier { strbuf_free($1); } +; + +function_name : identifier + | '?' '?' + { + $$ = strbuf_new(); + strbuf_append_str($$, "??"); + } +; + +function_args : '(' wsa ')' + | '(' wsa function_args_sequence wsa ')' +; + +function_args_sequence : function_args_char + | function_args_sequence wsa '(' wsa ')' + | function_args_sequence wsa '(' wsa function_args_string wsa ')' + | function_args_sequence wsa '(' wsa function_args_sequence wsa ')' + | function_args_sequence wsa function_args_char + | function_args_sequence wsa function_args_string +; + +function_args_string : '"' wsa function_args_string_sequence wsa '"' + | '"' wsa '"' +; + +/* Manually synchronized with variables_char_no_framestart, + * except the first line. + */ +function_args_char_base : digit | nondigit | '#' + | '+' | '-' | '<' | '>' | '/' | '.' + | '[' | ']' | '?' | '\'' | '`' | ',' + | '=' | '{' | '}' | '^' | '&' | '$' + | ':' | ';' | '!' | '@' | '*' + | '%' | '|' | '~' +; +function_args_escaped_char : '\\' function_args_char_base + | '\\' '\\' + | '\\' '"' +; +function_args_char : function_args_char_base + | function_args_escaped_char +; + + +function_args_string_sequence : function_args_string_char + | function_args_string_sequence function_args_string_char + | function_args_string_sequence wss_nonl function_args_string_char +; + +function_args_string_char : function_args_char | '(' | ')' +; + +file_name : file_name_char { $$ = strbuf_new(); strbuf_append_char($$, $1); } + | file_name file_name_char { $$ = strbuf_append_char($1, $2); } +; + +file_name_char : digit | nondigit | '-' | '+' | '/' | '.' +; + + /* Function name, sometimes mangled. + * Example: something@GLIB_2_2 + * CClass::operator= + */ +identifier : identifier_first_char %dprec 1 + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | identifier_braces %dprec 1 /* e.g. (anonymous namespace)::WorkerThread */ + | identifier identifier_char %dprec 1 + { $$ = strbuf_append_char($1, $2); } + | identifier identifier_braces %dprec 1 + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } + | identifier identifier_template %dprec 2 + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } +; + +identifier_first_char: nondigit + | '~' /* destructor */ + | '*' +; + +identifier_char_no_templates : digit | nondigit | '@' | '.' | ':' | '=' + | '!' | '*' | '+' | '-' | '[' | ']' + | '~' | '&' | '/' | '%' | '^' + | '|' | ',' +; + +/* Most of the special characters are required to support C++ + * operator overloading. + */ +identifier_char : identifier_char_no_templates | '<'| '>' +; + +identifier_braces : '(' ')' + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + strbuf_append_char($$, $2); + } + | '(' identifier_braces_inside ')' + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + strbuf_append_str($$, $2->buf); + strbuf_free($2); + strbuf_append_char($$, $3); + } +; + +identifier_braces_inside : identifier_braces_inside_char %dprec 1 + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | identifier_braces_inside identifier_braces_inside_char %dprec 1 + { $$ = strbuf_append_char($1, $2); } + | identifier_braces_inside '(' identifier_braces_inside ')' %dprec 1 + { + $$ = strbuf_append_char($1, $2); + $$ = strbuf_append_str($1, $3->buf); + strbuf_free($3); + $$ = strbuf_append_char($1, $4); + } + | identifier_braces_inside '(' ')' %dprec 1 + { + $$ = strbuf_append_char($1, $2); + $$ = strbuf_append_char($1, $3); + } + | identifier_braces_inside identifier_template %dprec 2 + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } +; + +identifier_braces_inside_char : identifier_char | ws_nonl +; + +identifier_template : '<' identifier_template_inside '>' + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + strbuf_append_str($$, $2->buf); + strbuf_free($2); + strbuf_append_char($$, $3); + } +; + +identifier_template_inside : identifier_template_inside_char + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | identifier_template_inside identifier_template_inside_char + { $$ = strbuf_append_char($1, $2); } + | identifier_template_inside '<' identifier_template_inside '>' + { + $$ = strbuf_append_char($1, $2); + $$ = strbuf_append_str($1, $3->buf); + strbuf_free($3); + $$ = strbuf_append_char($1, $4); + } + | identifier_template_inside identifier_braces + { + $$ = strbuf_append_str($1, $2->buf); + strbuf_free($2); + } +; + +identifier_template_inside_char : identifier_char_no_templates | ws_nonl +; + +digit_sequence : digit { $$ = strbuf_new(); strbuf_append_char($$, $1); } + | digit_sequence digit { $$ = strbuf_append_char($1, $2); } +; + +hexadecimal_number : '0' 'x' hexadecimal_digit_sequence + { + $$ = $3; + strbuf_prepend_str($$, "0x"); + } + | '0' 'X' hexadecimal_digit_sequence + { + $$ = $3; + strbuf_prepend_str($$, "0X"); + } +; + +hexadecimal_digit_sequence : hexadecimal_digit + { + $$ = strbuf_new(); + strbuf_append_char($$, $1); + } + | hexadecimal_digit_sequence hexadecimal_digit + { $$ = strbuf_append_char($1, $2); } +; + +hexadecimal_digit : digit + | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' + | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' +; + +digit : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' +; + +nondigit : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' + | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' + | 'x' | 'y' | 'z' + | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' + | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' + | 'X' | 'Y' | 'Z' + | '_' +; + + /* whitespace */ +ws : ws_nonl | '\n' | '\r' +; + + /* No newline.*/ +ws_nonl : '\t' | ' ' +; + + /* whitespace sequence without a newline */ +wss_nonl : ws_nonl + | wss_nonl ws_nonl +; + + /* whitespace sequence */ +wss : ws + | wss ws +; + + /* whitespace sequence allowed */ +wsa : + | wss +; + +keyword_in : 'i' 'n' +; + +keyword_at : 'a' 't' +; + +keyword_for : 'f' 'o' 'r' +; + +keyword_vtable : 'v' 't' 'a' 'b' 'l' 'e' +; + +keyword_from : 'f' 'r' 'o' 'm' +; + +keyword_thread: 'T' 'h' 'r' 'e' 'a' 'd' +; + +keyword_sighandler: '<' 's' 'i' 'g' 'n' 'a' 'l' ' ' 'h' 'a' 'n' 'd' 'l' 'e' 'r' ' ' 'c' 'a' 'l' 'l' 'e' 'd' '>' +; + +%% + +static bool scanner_echo = false; +static char *yyin; + +int yylex() +{ + char c = *yyin; + if (c == '\0') + return END; + ++yyin; + + /* Debug output. */ + if (scanner_echo) + putchar(c); + + yylval.c = c; + + /* Return a single char. */ + return c; +} + +/* This is the function that is actually called from outside. + * @returns + * Backtrace structure. Caller is responsible for calling + * backtrace_free() on this. + * Returns NULL when parsing failed. + */ +struct backtrace *backtrace_parse(char *input, bool debug_parser, bool debug_scanner) +{ + /* Skip the backtrace header information. */ + char *btnoheader_a = strstr(input, "\nThread "); + char *btnoheader_b = strstr(input, "\n#"); + char *btnoheader = input; + if (btnoheader_a) + { + if (btnoheader_b && btnoheader_b < btnoheader_a) + btnoheader = btnoheader_b + 1; + else + btnoheader = btnoheader_a + 1; + } + else if (btnoheader_b) + btnoheader = btnoheader_b + 1; + + /* Bug fixing hack for broken backtraces. + * Sometimes the empty line is missing before new Thread section. + * This is against rules, but a bug (now fixed) in Linux kernel caused + * this. + */ + char *thread_fixer = btnoheader + 1; + while ((thread_fixer = strstr(thread_fixer, "\nThread")) != NULL) + { + if (thread_fixer[-1] != '\n') + thread_fixer[-1] = '\n'; + + ++thread_fixer; + } + + /* Bug fixing hack for GDB - remove wrongly placed newlines from the backtrace. + * Sometimes there is a newline in the local variable section. + * This is caused by some GDB hooks. + * Example: rhbz#538440 + * #1 0x0000000000420939 in sync_deletions (mse=0x0, mfld=0x1b85020) + * at mail-stub-exchange.c:1119 + * status = <value optimized out> + * iter = 0x1af38d0 + * known_messages = 0x1b5c460Traceback (most recent call last): + * File "/usr/share/glib-2.0/gdb/glib.py", line 98, in next + * if long (node["key_hash"]) >= 2: + * RuntimeError: Cannot access memory at address 0x11 + * + * __PRETTY_FUNCTION__ = "sync_deletions" + * #2 0x0000000000423e6b in refresh_folder (stub=0x1b77f10 [MailStubExchange], + * ... + * + * The code removes every empty line (also those containing only spaces), + * which is not followed by a new Thread section. + * + * rhbz#555251 contains empty lines with spaces + */ + char *empty_line = btnoheader; + char *c = btnoheader; + while (*c) + { + if (*c == '\n') + { + char *cend = c + 1; + while (*cend == ' ' || *cend == '\t') + ++cend; + if (*cend == '\n' && 0 != strncmp(cend, "\nThread", strlen("\nThread"))) + memmove(c, cend, strlen(cend) + 1); + } + ++c; + } + while ((empty_line = strstr(empty_line, "\n\n")) != NULL) + { + if (0 != strncmp(empty_line, "\n\nThread", strlen("\n\nThread"))) + { + /* Remove the empty line by converting the first newline to char. */ + empty_line[0] = 'X'; + } + ++empty_line; + } + + /* Prepare for running parser. */ + g_backtrace = 0; + yyin = btnoheader; +#if YYDEBUG == 1 + if (debug_parser) + yydebug = 1; +#endif + scanner_echo = debug_scanner; + + /* Parse. */ + int failure = yyparse(); + + /* Separate debugging output. */ + if (scanner_echo) + putchar('\n'); + + if (failure) + { + if (g_backtrace) + { + backtrace_free(g_backtrace); + g_backtrace = NULL; + } + fprintf(stderr, "Error while parsing backtrace.\n"); + } + + return g_backtrace; +} diff --git a/lib/Utils/strbuf.c b/lib/Utils/strbuf.c new file mode 100644 index 00000000..ab872543 --- /dev/null +++ b/lib/Utils/strbuf.c @@ -0,0 +1,130 @@ +/* -*-mode:c;c-file-style:"bsd";c-basic-offset:4;indent-tabs-mode:nil-*- + String buffer implementation + + 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 "strbuf.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdarg.h> +#include "xfuncs.h" + +struct strbuf *strbuf_new() +{ + struct strbuf *buf = xmalloc(sizeof(struct strbuf)); + buf->alloc = 8; + buf->len = 0; + buf->buf = xmalloc(buf->alloc); + buf->buf[buf->len] = '\0'; + return buf; +} + +void strbuf_free(struct strbuf *strbuf) +{ + free(strbuf->buf); + free(strbuf); +} + +void strbuf_free_nobuf(struct strbuf *strbuf) +{ + free(strbuf); +} + + +void strbuf_clear(struct strbuf *strbuf) +{ + assert(strbuf->alloc > 0); + strbuf->len = 0; + strbuf->buf[0] = '\0'; +} + +/* Ensures that the buffer can be extended by num characters + * without touching malloc/realloc. + */ +static void strbuf_grow(struct strbuf *strbuf, int num) +{ + if (strbuf->len + num + 1 > strbuf->alloc) + { + while (strbuf->len + num + 1 > strbuf->alloc) + strbuf->alloc *= 2; /* huge grow = infinite loop */ + + strbuf->buf = realloc(strbuf->buf, strbuf->alloc); + if (!strbuf->buf) + { + puts("Error while allocating memory for string buffer."); + exit(5); + } + } +} + +struct strbuf *strbuf_append_char(struct strbuf *strbuf, char c) +{ + strbuf_grow(strbuf, 1); + strbuf->buf[strbuf->len++] = c; + strbuf->buf[strbuf->len] = '\0'; + return strbuf; +} + +struct strbuf *strbuf_append_str(struct strbuf *strbuf, const char *str) +{ + int len = strlen(str); + strbuf_grow(strbuf, len); + assert(strbuf->len + len < strbuf->alloc); + strcpy(strbuf->buf + strbuf->len, str); + strbuf->len += len; + return strbuf; +} + +struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, const char *str) +{ + int len = strlen(str); + strbuf_grow(strbuf, len); + assert(strbuf->len + len < strbuf->alloc); + memmove(strbuf->buf + len, strbuf->buf, strbuf->len + 1); + memcpy(strbuf->buf, str, len); + return strbuf; +} + +struct strbuf *strbuf_append_strf(struct strbuf *strbuf, const char *format, ...) +{ + va_list p; + char *string_ptr; + + va_start(p, format); + string_ptr = xvasprintf(format, p); + va_end(p); + + strbuf_append_str(strbuf, string_ptr); + free(string_ptr); + return strbuf; +} + +struct strbuf *strbuf_prepend_strf(struct strbuf *strbuf, const char *format, ...) +{ + va_list p; + char *string_ptr; + + va_start(p, format); + string_ptr = xvasprintf(format, p); + va_end(p); + + strbuf_prepend_str(strbuf, string_ptr); + free(string_ptr); + return strbuf; +} diff --git a/lib/Utils/strbuf.h b/lib/Utils/strbuf.h new file mode 100644 index 00000000..d0c9c981 --- /dev/null +++ b/lib/Utils/strbuf.h @@ -0,0 +1,48 @@ +/* + strbuf.h - string buffer + + 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. +*/ +#ifndef STRBUF_H +#define STRBUF_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct strbuf { + int alloc; + int len; + char *buf; +}; + +extern struct strbuf *strbuf_new(); +extern void strbuf_free(struct strbuf *strbuf); +/* Releases strbuf, but not the internal buffer. */ +extern void strbuf_free_nobuf(struct strbuf *strbuf); +extern void strbuf_clear(struct strbuf *strbuf); +extern struct strbuf *strbuf_append_char(struct strbuf *strbuf, char c); +extern struct strbuf *strbuf_append_str(struct strbuf *strbuf, const char *str); +extern struct strbuf *strbuf_prepend_str(struct strbuf *strbuf, const char *str); +extern struct strbuf *strbuf_append_strf(struct strbuf *strbuf, const char *format, ...); +extern struct strbuf *strbuf_prepend_strf(struct strbuf *strbuf, const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Utils/xfuncs.cpp b/lib/Utils/xfuncs.cpp index 74fdac76..fd29b704 100644 --- a/lib/Utils/xfuncs.cpp +++ b/lib/Utils/xfuncs.cpp @@ -389,12 +389,12 @@ bool string_to_bool(const char *s) void xsetreuid(uid_t ruid, uid_t euid) { - if (setreuid(ruid, euid) != 0) - perror_msg_and_die("can't set %cid %lu", 'u', (long)ruid); + if (setreuid(ruid, euid) != 0) + perror_msg_and_die("can't set %cid %lu", 'u', (long)ruid); } void xsetregid(gid_t rgid, gid_t egid) { - if (setregid(rgid, egid) != 0) - perror_msg_and_die("can't set %cid %lu", 'g', (long)rgid); + if (setregid(rgid, egid) != 0) + perror_msg_and_die("can't set %cid %lu", 'g', (long)rgid); } diff --git a/lib/Utils/xfuncs.h b/lib/Utils/xfuncs.h new file mode 100644 index 00000000..327d9c9a --- /dev/null +++ b/lib/Utils/xfuncs.h @@ -0,0 +1,104 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ +#ifndef ABRT_XFUNCS_H +#define ABRT_XFUNCS_H + +#include <sys/socket.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdbool.h> + +int ndelay_on(int fd); +int ndelay_off(int fd); +int close_on_exec_on(int fd); + +#ifdef __cplusplus +extern "C" { +#endif + +void* xmalloc(size_t size); +void* xrealloc(void *ptr, size_t size); +void* xzalloc(size_t size); +char* xstrdup(const char *s); +char* xstrndup(const char *s, int n); + +#ifdef __cplusplus +} +#endif + +void xpipe(int filedes[2]); +void xdup(int from); +void xdup2(int from, int to); +void xmove_fd(int from, int to); + +extern void xwrite(int fd, const void *buf, size_t count); +extern void xwrite_str(int fd, const char *str); + +off_t xlseek(int fd, off_t offset, int whence); + +void xchdir(const char *path); + +#ifdef __cplusplus +extern "C" { +#endif + +char* xvasprintf(const char *format, va_list p); +char* xasprintf(const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +std::string ssprintf(const char *format, ...); +#endif + +void xsetenv(const char *key, const char *value); +int xsocket(int domain, int type, int protocol); +void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); +void xlisten(int s, int backlog); +ssize_t xsendto(int s, const void *buf, size_t len, const struct sockaddr *to, socklen_t tolen); +void xstat(const char *name, struct stat *stat_buf); + +#ifdef __cplusplus +std::string get_home_dir(uid_t uid); +#endif + +int xopen3(const char *pathname, int flags, int mode); +int xopen(const char *pathname, int flags); +void xunlink(const char *pathname); + +/* Just testing dent->d_type == DT_REG is wrong: some filesystems + * do not report the type, they report DT_UNKNOWN for every dirent + * (and this is not a bug in filesystem, this is allowed by standards). + * This function handles this case. Note: it returns 0 on symlinks + * even if they point to regular files. + */ +int is_regular_file(struct dirent *dent, const char *dirname); +bool dot_or_dotdot(const char *filename); +char *last_char_is(const char *s, int c); +#ifdef __cplusplus +std::string concat_path_file(const char *path, const char *filename); +#endif +bool string_to_bool(const char *s); + +void xsetreuid(uid_t ruid, uid_t euid); +void xsetregid(gid_t rgid, gid_t egid); + +#endif |