diff options
| author | Michal Toman <mtoman@redhat.com> | 2010-10-19 10:57:16 +0200 |
|---|---|---|
| committer | Michal Toman <mtoman@redhat.com> | 2010-10-19 10:57:16 +0200 |
| commit | 8ccb8e59a6f295481b1d0b664701bb421b041e28 (patch) | |
| tree | b05ba340714831c049dc830c5d6db0b9f313d46f /src | |
| parent | 2ba325a217d4d08fb867d1197a89c53f49ab85d5 (diff) | |
| parent | 41473dbc4a685a4e33debaf2a9103da8bcd6429f (diff) | |
Merge branch 'master' of ssh://git.fedorahosted.org/git/abrt
Diffstat (limited to 'src')
34 files changed, 4646 insertions, 535 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index c2576c4e..00227a47 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1 +1 @@ -SUBDIRS = hooks daemon applet gui cli utils +SUBDIRS = hooks btparser daemon applet gui cli diff --git a/src/btparser/Makefile.am b/src/btparser/Makefile.am new file mode 100644 index 00000000..b610f30c --- /dev/null +++ b/src/btparser/Makefile.am @@ -0,0 +1,44 @@ +lib_LTLIBRARIES = libbtparser.la +libbtparser_la_SOURCES = \ + backtrace.h backtrace.c \ + frame.h frame.c \ + location.h location.c \ + normalize.h normalize.c \ + normalize_dbus.c \ + normalize_gdk.c \ + normalize_glib.c \ + normalize_glibc.c \ + normalize_libstdcpp.c \ + normalize_linux.c \ + normalize_xorg.c \ + thread.h thread.c \ + utils.h utils.c +libbtparser_la_CFLAGS = -Wall -Werror -D_GNU_SOURCE -I../../lib/utils +libbtparser_la_LDFLAGS = -version-info 1:1:0 +libbtparser_la_LIBADD = ../../lib/utils/libABRTUtils.la + +# From http://www.seul.org/docs/autotut/ +# Version consists 3 numbers: CURRENT, REVISION, AGE. +# CURRENT is the version of the interface the library implements. +# Whenever a new function is added, or its name changed, or +# the number or type of its parameters (the prototype -- in +# libraries we call this the function signature), this number +# goes up. And it goes up exactly by one. +# +# REVISION is the revision of the implementation of this +# interface, i.e., when you change the library by only modifying +# code inside the functions (fixing bugs, optimizing internal +# behavior, or adding/removing/changing signatures of functions +# that are private to the library -- used only internally) you +# raise the revision number only. +# +# Age is the difference between the newest and oldest interface +# the library currently implements. Let's say you had 8 versions +# of your library's interface, 0 through 7. You are now on +# the 4th revision of the 8th interface, that is, 7:3:X (remember +# we start counting on zero). And when you had to make choices +# for what old interfaces you would keep support -- for backward +# compatibility purposes, you chose to keep support for +# interfaces 5, 6 and (obviously) the current, 7. The libtool +# version of your library would be 7:3:2 , because the Age +# is 7-5 = 2. diff --git a/src/btparser/backtrace.c b/src/btparser/backtrace.c new file mode 100644 index 00000000..139b315d --- /dev/null +++ b/src/btparser/backtrace.c @@ -0,0 +1,445 @@ +/* + backtrace.c + + Copyright (C) 2010 Red Hat, 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 "thread.h" +#include "frame.h" +#include "utils.h" +#include "strbuf.h" +#include "location.h" +#include "normalize.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +struct btp_backtrace * +btp_backtrace_new() +{ + struct btp_backtrace *backtrace = btp_malloc(sizeof(struct btp_backtrace)); + btp_backtrace_init(backtrace); + return backtrace; +} + +void +btp_backtrace_init(struct btp_backtrace *backtrace) +{ + backtrace->threads = NULL; + backtrace->crash = NULL; +} + +void +btp_backtrace_free(struct btp_backtrace *backtrace) +{ + if (!backtrace) + return; + + while (backtrace->threads) + { + struct btp_thread *rm = backtrace->threads; + backtrace->threads = rm->next; + btp_thread_free(rm); + } + + if (backtrace->crash) + btp_frame_free(backtrace->crash); + + free(backtrace); +} + +struct btp_backtrace * +btp_backtrace_dup(struct btp_backtrace *backtrace) +{ + struct btp_backtrace *result = btp_backtrace_new(); + memcpy(result, backtrace, sizeof(struct btp_backtrace)); + + if (backtrace->crash) + backtrace->crash = btp_frame_dup(backtrace->crash, false); + if (backtrace->threads) + backtrace->threads = btp_thread_dup(backtrace->threads, true); + + return result; +} + +int +btp_backtrace_get_thread_count(struct btp_backtrace *backtrace) +{ + struct btp_thread *thread = backtrace->threads; + int count = 0; + while (thread) + { + thread = thread->next; + ++count; + } + return count; +} + +void +btp_backtrace_remove_threads_except_one(struct btp_backtrace *backtrace, + struct btp_thread *thread) +{ + while (backtrace->threads) + { + struct btp_thread *delete_thread = backtrace->threads; + backtrace->threads = delete_thread->next; + if (delete_thread != thread) + btp_thread_free(delete_thread); + } + + thread->next = NULL; + backtrace->threads = thread; +} + +/** + * 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 btp_thread * +btp_backtrace_find_crash_thread_from_crash_frame(struct btp_backtrace *backtrace, + bool require_abort) +{ + if (btp_debug_parser) + printf("%s(backtrace, %s)\n", __FUNCTION__, require_abort ? "true" : "false"); + + assert(backtrace->threads); /* checked by the caller */ + if (!backtrace->crash || !backtrace->crash->function_name) + return NULL; + + struct btp_thread *result = NULL; + struct btp_thread *thread = backtrace->threads; + while (thread) + { + struct btp_frame *top_frame = thread->frames; + bool same_name = top_frame && + top_frame->function_name && + 0 == strcmp(top_frame->function_name, backtrace->crash->function_name); + bool abort_requirement_satisfied = !require_abort || + btp_glibc_thread_find_exit_frame(thread); + if (btp_debug_parser) + { + printf(" - thread #%d: same_name %s, abort_satisfied %s\n", + thread->number, + same_name ? "true" : "false", + abort_requirement_satisfied ? "true" : "false"); + } + + if (same_name && abort_requirement_satisfied) + { + if (NULL == result) + result = thread; + else + { + /* Second frame with the same function. Failure. */ + return NULL; + } + } + + thread = thread->next; + } + + return result; +} + +struct btp_thread * +btp_backtrace_find_crash_thread(struct btp_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 btp_thread *thread; + thread = btp_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 = btp_backtrace_find_crash_thread_from_crash_frame(backtrace, true); + + /* We might want to search a thread with known abort function, and + * without the crash frame here. However, it hasn't been needed so + * far. + */ + return thread; /* result or null */ +} + + +void +btp_backtrace_limit_frame_depth(struct btp_backtrace *backtrace, + int depth) +{ + assert(depth > 0); + struct btp_thread *thread = backtrace->threads; + while (thread) + { + btp_thread_remove_frames_below_n(thread, depth); + thread = thread->next; + } +} + +float +btp_backtrace_quality_simple(struct btp_backtrace *backtrace) +{ + int ok_count = 0, all_count = 0; + struct btp_thread *thread = backtrace->threads; + while (thread) + { + btp_thread_quality_counts(thread, &ok_count, &all_count); + thread = thread->next; + } + + if (all_count == 0) + return 0; + + return ok_count / (float)all_count; +} + +float +btp_backtrace_quality_complex(struct btp_backtrace *backtrace) +{ + backtrace = btp_backtrace_dup(backtrace); + + /* Find the crash thread, and then normalize the backtrace. It is + * not possible to find the crash thread after the backtrace has + * been normalized. + */ + struct btp_thread *crash_thread = btp_backtrace_find_crash_thread(backtrace); + btp_normalize_backtrace(backtrace); + + /* Get the quality q1 of the full backtrace. */ + float q1 = btp_backtrace_quality_simple(backtrace); + + if (!crash_thread) + { + btp_backtrace_free(backtrace); + return q1; + } + + /* Get the quality q2 of the crash thread. */ + float q2 = btp_thread_quality(crash_thread); + + /* Get the quality q3 of the frames around the crash. First, + * duplicate the crash thread so we can cut it. Then find an exit + * frame, and remove it and everything above it + * (__run_exit_handlers and such). Then remove all the redundant + * frames (assert calls etc.) Then limit the frame count to 5. + */ + btp_thread_remove_frames_below_n(crash_thread, 5); + float q3 = btp_thread_quality(crash_thread); + + btp_backtrace_free(backtrace); + + /* Compute and return the final backtrace quality q. */ + return 0.25f * q1 + 0.35f * q2 + 0.4f * q3; +} + +char * +btp_backtrace_to_text(struct btp_backtrace *backtrace, bool verbose) +{ + struct strbuf *str = strbuf_new(); + if (verbose) + { + strbuf_append_strf(str, "Thread count: %d\n", + btp_backtrace_get_thread_count(backtrace)); + } + + if (backtrace->crash && verbose) + { + strbuf_append_str(str, "Crash frame: "); + btp_frame_append_to_str(backtrace->crash, str, verbose); + } + + struct btp_thread *thread = backtrace->threads; + while (thread) + { + btp_thread_append_to_str(thread, str, verbose); + thread = thread->next; + } + + return strbuf_free_nobuf(str); +} + +struct btp_frame * +btp_backtrace_get_crash_frame(struct btp_backtrace *backtrace) +{ + backtrace = btp_backtrace_dup(backtrace); + + struct btp_thread *crash_thread = btp_backtrace_find_crash_thread(backtrace); + if (!crash_thread) + { + btp_backtrace_free(backtrace); + return NULL; + } + + btp_normalize_backtrace(backtrace); + struct btp_frame *crash_frame = crash_thread->frames; + crash_frame = btp_frame_dup(crash_frame, false); + btp_backtrace_free(backtrace); + return crash_frame; +} + +char * +btp_backtrace_get_duplication_hash(struct btp_backtrace *backtrace) +{ + backtrace = btp_backtrace_dup(backtrace); + struct btp_thread *crash_thread = btp_backtrace_find_crash_thread(backtrace); + if (crash_thread) + btp_backtrace_remove_threads_except_one(backtrace, crash_thread); + + btp_normalize_backtrace(backtrace); + btp_backtrace_limit_frame_depth(backtrace, 3); + char *hash = btp_backtrace_to_text(backtrace, false); + btp_backtrace_free(backtrace); + return hash; +} + +struct btp_backtrace * +btp_backtrace_parse(char **input, + struct btp_location *location) +{ + char *local_input = *input; + struct btp_backtrace *imbacktrace = btp_backtrace_new(); /* im - intermediate */ + + /* The header is mandatory, but it might contain no frame header, + * in some broken backtraces. In that case, backtrace.crash value + * is kept as NULL. + */ + if (!btp_backtrace_parse_header(&local_input, + &imbacktrace->crash, + location)) + { + btp_backtrace_free(imbacktrace); + return NULL; + } + + struct btp_thread *thread, *prevthread = NULL; + while ((thread = btp_thread_parse(&local_input, location))) + { + if (prevthread) + { + btp_thread_add_sibling(prevthread, thread); + prevthread = thread; + } + else + imbacktrace->threads = prevthread = thread; + } + if (!imbacktrace->threads) + { + btp_backtrace_free(imbacktrace); + return NULL; + } + + *input = local_input; + return imbacktrace; +} + +bool +btp_backtrace_parse_header(char **input, + struct btp_frame **frame, + struct btp_location *location) +{ + int first_thread_line, first_thread_column; + char *first_thread = btp_strstr_location(*input, + "\nThread ", + &first_thread_line, + &first_thread_column); + + /* Skip the newline. */ + if (first_thread) + { + ++first_thread; + first_thread_line += 1; + first_thread_column = 0; + } + + int first_frame_line, first_frame_column; + char *first_frame = btp_strstr_location(*input, + "\n#", + &first_frame_line, + &first_frame_column); + + /* Skip the newline. */ + if (first_frame) + { + ++first_frame; + first_frame_line += 1; + first_frame_column = 0; + } + + if (first_thread) + { + if (first_frame && first_frame < first_thread) + { + /* Common case. The crash frame is present in the input + * before the list of threads begins. + */ + *input = first_frame; + btp_location_add(location, first_frame_line, first_frame_column); + } + else + { + /* Uncommon case (caused by some kernel bug) where the + * frame is missing from the header. The backtrace + * contains just threads. We silently skip the header and + * return true. + */ + *input = first_thread; + btp_location_add(location, + first_thread_line, + first_thread_column); + *frame = NULL; + return true; + } + } + else if (first_frame) + { + /* Degenerate case when the backtrace contains no thread, but + * the frame is there. + */ + *input = first_frame; + btp_location_add(location, first_frame_line, first_frame_column); + } + else + { + /* Degenerate case where the input is empty or completely + * meaningless. Report a failure. + */ + location->message = "No frame and no thread found."; + return false; + } + + /* Parse the frame header. */ + *frame = btp_frame_parse(input, location); + return *frame; +} diff --git a/src/btparser/backtrace.h b/src/btparser/backtrace.h new file mode 100644 index 00000000..d5de3ff3 --- /dev/null +++ b/src/btparser/backtrace.h @@ -0,0 +1,269 @@ +/* + backtrace.h + + Copyright (C) 2010 Red Hat, 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 BTPARSER_BACKTRACE_H +#define BTPARSER_BACKTRACE_H + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct btp_thread; +struct btp_frame; +struct btp_location; + +/** + * A backtrace obtained at the time of a program crash, consisting of + * several threads which contains frames. + */ +struct btp_backtrace +{ + struct btp_thread *threads; + /** + * The frame where the crash happened according to debugger. It + * might be that we can not tell to which thread this frame + * belongs, because some threads end with mutually + * indistinguishable frames. + */ + struct btp_frame *crash; +}; + +/** + * Creates and initializes a new backtrace structure. + * @returns + * It never returns NULL. The returned pointer must be released by + * calling the function btp_backtrace_free(). + */ +struct btp_backtrace * +btp_backtrace_new(); + +/** + * Initializes all members of the backtrace structure to their default + * values. No memory is released, members are simply overwritten. + * This is useful for initializing a backtrace structure placed on the + * stack. + */ +void +btp_backtrace_init(struct btp_backtrace *backtrace); + +/** + * Releases the memory held by the backtrace, its threads and frames. + * @param backtrace + * If the backtrace is NULL, no operation is performed. + */ +void +btp_backtrace_free(struct btp_backtrace *backtrace); + +/** + * Creates a duplicate of the backtrace. + * @param backtrace + * The backtrace to be copied. It's not modified by this function. + * @returns + * This function never returns NULL. If the returned duplicate is not + * shallow, it must be released by calling the function + * btp_backtrace_free(). + */ +struct btp_backtrace * +btp_backtrace_dup(struct btp_backtrace *backtrace); + +/** + * Returns a number of threads in the backtrace. + * @param backtrace + * It's not modified by calling this function. + */ +int +btp_backtrace_get_thread_count(struct btp_backtrace *backtrace); + +/** + * Removes all threads from the backtrace and deletes them, except the + * one provided as a parameter. + * @param thread + * This function does not check whether the thread is a member of the backtrace. + * If it's not, all threads are removed from the backtrace and then deleted. + */ +void +btp_backtrace_remove_threads_except_one(struct btp_backtrace *backtrace, + struct btp_thread *thread); + +/** + * Search all threads and tries to find the one that caused the crash. + * It might return NULL if the thread cannot be determined. + * @param backtrace + * It must be non-NULL pointer. It's not modified by calling this + * function. + */ +struct btp_thread * +btp_backtrace_find_crash_thread(struct btp_backtrace *backtrace); + +/** + * Remove frames from the bottom of threads in the backtrace, until + * all threads have at most 'depth' frames. + * @param backtrace + * Must be non-NULL pointer. + */ +void +btp_backtrace_limit_frame_depth(struct btp_backtrace *backtrace, + int depth); + +/** + * Evaluates the quality of the backtrace. The quality is the ratio of + * the number of frames with function name fully known to the number + * of all frames. This function does not take into account that some + * frames are more important than others. + * @param backtrace + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @returns + * A number between 0 and 1. 0 means the lowest quality, 1 means full + * backtrace is known. + */ +float +btp_backtrace_quality_simple(struct btp_backtrace *backtrace); + +/** + * Evaluates the quality of the backtrace. The quality is determined + * depending on the ratio of frames with function name fully known to + * all frames. + * @param backtrace + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @returns + * A number between 0 and 1. 0 means the lowest quality, 1 means full + * backtrace is known. The returned value takes into account that the + * thread which caused the crash is more important than the other + * threads, and the frames around the crash frame are more important + * than distant frames. + */ +float +btp_backtrace_quality_complex(struct btp_backtrace *backtrace); + +/** + * Returns textual representation of the backtrace. + * @param backtrace + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @returns + * This function never returns NULL. The caller is responsible for + * releasing the returned memory using function free(). + */ +char * +btp_backtrace_to_text(struct btp_backtrace *backtrace, + bool verbose); + +/** + * Analyzes the backtrace to get the frame where a crash occurred. + * @param backtrace + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @returns + * The returned value must be released by calling btp_frame_free(), + * when it's no longer needed. NULL is returned if the crash frame is + * not found. + */ +struct btp_frame * +btp_backtrace_get_crash_frame(struct btp_backtrace *backtrace); + +/** + * Calculates the duplication hash string of the backtrace. + * @param backtrace + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @returns + * This function never returns NULL. The caller is responsible for + * releasing the returned memory using function free(). + */ +char * +btp_backtrace_get_duplication_hash(struct btp_backtrace *backtrace); + +/** + * Parses a textual backtrace and puts it into a structure. If + * parsing fails, the input parameter is not changed and NULL is + * returned. + * @code + * struct btp_location location; + * btp_location_init(&location); + * char *input = "..."; + * struct btp_backtrace *backtrace = btp_backtrace_parse(input, location; + * if (!backtrace) + * { + * fprintf(stderr, + * "Failed to parse the backtrace.\n" + * "Line %d, column %d: %s\n", + * location.line, + * location.column, + * location.message); + * exit(-1); + * } + * btp_backtrace_free(backtrace); + * @endcode + * @param input + * Pointer to the string with the backtrace. If this function returns + * true, this pointer is modified to point after the backtrace that + * was just parsed. + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + * @returns + * A newly allocated backtrace structure or NULL. A backtrace struct + * is returned when at least one thread was parsed from the input and + * no error occurred. The returned structure should be released by + * btp_backtrace_free(). + */ +struct btp_backtrace * +btp_backtrace_parse(char **input, + struct btp_location *location); + +/** + * Parse backtrace header if it is available in the backtrace. The + * header usually contains frame where the program crashed. + * @param input + * Pointer moved to point behind the header if the header is + * successfully detected and parsed. + * @param frame + * If this function succeeds and returns true, *frame contains the + * crash frame that is usually a part of the header. If no frame is + * detected in the header, *frame is set to NULL. + * @code + * [New Thread 11919] + * [New Thread 11917] + * Core was generated by `evince file:///tmp/Factura04-05-2010.pdf'. + * Program terminated with signal 8, Arithmetic exception. + * #0 0x000000322a2362b9 in repeat (image=<value optimized out>, + * mask=<value optimized out>, mask_bits=<value optimized out>) + * at pixman-bits-image.c:145 + * 145 pixman-bits-image.c: No such file or directory. + * in pixman-bits-image.c + * @endcode + */ +bool +btp_backtrace_parse_header(char **input, + struct btp_frame **frame, + struct btp_location *location); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/btparser/frame.c b/src/btparser/frame.c new file mode 100644 index 00000000..2bfae070 --- /dev/null +++ b/src/btparser/frame.c @@ -0,0 +1,1027 @@ +/* + frame.c + + Copyright (C) 2010 Red Hat, 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 "frame.h" +#include "strbuf.h" +#include "utils.h" +#include "location.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +struct btp_frame * +btp_frame_new() +{ + struct btp_frame *frame = btp_malloc(sizeof(struct btp_frame)); + btp_frame_init(frame); + return frame; +} + +void +btp_frame_init(struct btp_frame *frame) +{ + frame->function_name = NULL; + frame->function_type = NULL; + frame->number = 0; + frame->source_file = NULL; + frame->source_line = -1; + frame->signal_handler_called = false; + frame->address = -1; + frame->next = NULL; +} + +void +btp_frame_free(struct btp_frame *frame) +{ + if (!frame) + return; + free(frame->function_name); + free(frame->function_type); + free(frame->source_file); + free(frame); +} + +struct btp_frame * +btp_frame_dup(struct btp_frame *frame, bool siblings) +{ + struct btp_frame *result = btp_frame_new(); + memcpy(result, frame, sizeof(struct btp_frame)); + + /* Handle siblings. */ + if (siblings) + { + if (result->next) + result->next = btp_frame_dup(result->next, true); + } + else + result->next = NULL; /* Do not copy that. */ + + /* Duplicate all strings if the copy is not shallow. */ + if (result->function_name) + result->function_name = btp_strdup(result->function_name); + if (result->function_type) + result->function_type = btp_strdup(result->function_type); + if (result->source_file) + result->source_file = btp_strdup(result->source_file); + + return result; +} + +bool +btp_frame_calls_func(struct btp_frame *frame, + const char *function_name) +{ + return frame->function_name && + 0 == strcmp(frame->function_name, function_name); +} + +bool +btp_frame_calls_func_in_file(struct btp_frame *frame, + const char *function_name, + const char *source_file) +{ + return frame->function_name && + 0 == strcmp(frame->function_name, function_name) && + frame->source_file && + NULL != strstr(frame->source_file, source_file); +} + +bool +btp_frame_calls_func_in_file2(struct btp_frame *frame, + const char *function_name, + const char *source_file0, + const char *source_file1) +{ + return frame->function_name && + 0 == strcmp(frame->function_name, function_name) && + frame->source_file && + (NULL != strstr(frame->source_file, source_file0) || + NULL != strstr(frame->source_file, source_file1)); +} + +bool +btp_frame_calls_func_in_file3(struct btp_frame *frame, + const char *function_name, + const char *source_file0, + const char *source_file1, + const char *source_file2) +{ + return frame->function_name && + 0 == strcmp(frame->function_name, function_name) && + frame->source_file && + (NULL != strstr(frame->source_file, source_file0) || + NULL != strstr(frame->source_file, source_file1) || + NULL != strstr(frame->source_file, source_file2)); +} + +bool +btp_frame_calls_func_in_file4(struct btp_frame *frame, + const char *function_name, + const char *source_file0, + const char *source_file1, + const char *source_file2, + const char *source_file3) +{ + return frame->function_name && + 0 == strcmp(frame->function_name, function_name) && + frame->source_file && + (NULL != strstr(frame->source_file, source_file0) || + NULL != strstr(frame->source_file, source_file1) || + NULL != strstr(frame->source_file, source_file2) || + NULL != strstr(frame->source_file, source_file3)); +} + +int +btp_frame_cmp(struct btp_frame *f1, + struct btp_frame *f2, + bool compare_number) +{ + /* Singnal handler. */ + if (f1->signal_handler_called) + { + if (!f2->signal_handler_called) + return 1; + + /* Both contain signal handler called. */ + return 0; + } + else + { + if (f2->signal_handler_called) + return -1; + /* No signal handler called, continue. */ + } + + /* Function. */ + int function_name = btp_strcmp0(f1->function_name, f2->function_name); + if (function_name != 0) + return function_name; + int function_type = btp_strcmp0(f1->function_type, f2->function_type); + if (function_type != 0) + return function_type; + + /* Sourcefile. */ + int source_file = btp_strcmp0(f1->source_file, f2->source_file); + if (source_file != 0) + return source_file; + + /* Sourceline. */ + int source_line = f1->source_line - f2->source_line; + if (source_line != 0) + return source_line; + + /* Frame number. */ + if (compare_number) + { + int number = f1->number - f2->number; + if (number != 0) + return number; + } + + return 0; +} + +void +btp_frame_add_sibling(struct btp_frame *a, struct btp_frame *b) +{ + struct btp_frame *aa = a; + while (aa->next) + aa = aa->next; + + aa->next = b; +} + +void +btp_frame_append_to_str(struct btp_frame *frame, + struct strbuf *str, + bool verbose) +{ + if (verbose) + strbuf_append_strf(str, " #%d", frame->number); + else + strbuf_append_str(str, " "); + + if (frame->function_type) + strbuf_append_strf(str, " %s", frame->function_type); + if (frame->function_name) + strbuf_append_strf(str, " %s", frame->function_name); + if (verbose && frame->source_file) + { + if (frame->function_name) + strbuf_append_str(str, " at"); + strbuf_append_strf(str, " %s", frame->source_file); + } + + if (frame->signal_handler_called) + strbuf_append_str(str, " <signal handler called>"); + + strbuf_append_str(str, "\n"); +} + +/** + * Find string a or b in input, whatever comes first. + * If no string is found, return the \0 character at the end of input. + */ +static char * +findfirstabnul(char *input, const char *a, const char *b) +{ + size_t alen = strlen(a); + size_t blen = strlen(b); + char *p = input; + while (*p) + { + if (strncmp(p, a, alen) == 0) + return p; + if (strncmp(p, b, blen) == 0) + return p; + ++p; + } + return p; +} + +struct btp_frame * +btp_frame_parse(char **input, + struct btp_location *location) +{ + char *local_input = *input; + struct btp_frame *header = btp_frame_parse_header(input, location); + if (!header) + return NULL; + + /* Skip the variables section for now. */ + /* Todo: speedup by implementing strstrnul. */ + local_input = findfirstabnul(local_input, "\n#", "\nThread"); + if (*local_input != '\0') + ++local_input; /* ++ skips the newline */ + + if (btp_debug_parser) + { + printf("frame #%u %s\n", + header->number, + header->function_name ? header->function_name : "signal handler called"); + } + + *input = local_input; + return header; +} + +int +btp_frame_parse_frame_start(char **input, unsigned *number) +{ + char *local_input = *input; + + /* Read the hash sign. */ + if (!btp_skip_char(&local_input, '#')) + return 0; + int count = 1; + + /* Read the frame position. */ + int digits = btp_parse_unsigned_integer(&local_input, number); + count += digits; + if (0 == digits) + return 0; + + /* Read all the spaces after the positon. */ + int spaces = btp_skip_char_sequence(&local_input, ' '); + count += spaces; + if (0 == spaces) + return 0; + + *input = local_input; + return count; +} + +int +btp_frame_parseadd_operator(char **input, struct strbuf *target) +{ + char *local_input = *input; + if (0 == btp_skip_string(&local_input, "operator")) + return 0; + +#define OP(x) \ + if (0 < btp_skip_string(&local_input, x)) \ + { \ + strbuf_append_str(target, "operator"); \ + strbuf_append_str(target, x); \ + int length = local_input - *input; \ + *input = local_input; \ + return length; \ + } + + OP(">>=")OP(">>")OP(">=")OP(">"); + OP("<<=")OP("<<")OP("<=")OP("<"); + OP("->*")OP("->")OP("-"); + OP("==")OP("="); + OP("&&")OP("&=")OP("&"); + OP("||")OP("|=")OP("|"); + OP("++")OP("+=")OP("+"); + OP("--")OP("-=")OP("-"); + OP("/=")OP("/"); + OP("*=")OP("*"); + OP("%=")OP("%"); + OP("!=")OP("!"); + OP("~"); + OP("()"); + OP("[]"); + OP(","); + OP("^=")OP("^"); + OP(" new[]")OP(" new"); + OP(" delete[]")OP(" delete"); + /* User defined operators are not parsed. + Should they be? */ +#undef OP + return 0; +} + +#define FUNCTION_NAME_CHARS BTP_alnum "@.:=!*+-[]~&/%^|,_" + +int +btp_frame_parse_function_name_chunk(char **input, + bool space_allowed, + char **target) +{ + char *local_input = *input; + struct strbuf *buf = strbuf_new(); + while (*local_input) + { + if (0 < btp_frame_parseadd_operator(&local_input, buf)) + { + /* Space is allowed after operator even when it + is not normally allowed. */ + if (btp_skip_char(&local_input, ' ')) + { + /* ...but if ( follows, it is not allowed. */ + if (btp_skip_char(&local_input, '(')) + { + /* Return back both the space and (. */ + local_input -= 2; + } + else + strbuf_append_char(buf, ' '); + } + } + + if (strchr(FUNCTION_NAME_CHARS, *local_input) == NULL) + { + if (!space_allowed || strchr(" ", *local_input) == NULL) + break; + } + + strbuf_append_char(buf, *local_input); + ++local_input; + } + + if (buf->len == 0) + { + strbuf_free(buf); + return 0; + } + + *target = strbuf_free_nobuf(buf); + int total_char_count = local_input - *input; + *input = local_input; + return total_char_count; +} + +int +btp_frame_parse_function_name_braces(char **input, char **target) +{ + char *local_input = *input; + if (!btp_skip_char(&local_input, '(')) + return 0; + + struct strbuf *buf = strbuf_new(); + strbuf_append_char(buf, '('); + while (true) + { + char *namechunk = NULL; + if (0 < btp_frame_parse_function_name_chunk(&local_input, true, &namechunk) || + 0 < btp_frame_parse_function_name_braces(&local_input, &namechunk) || + 0 < btp_frame_parse_function_name_template(&local_input, &namechunk)) + { + strbuf_append_str(buf, namechunk); + free(namechunk); + } + else + break; + } + + if (!btp_skip_char(&local_input, ')')) + { + strbuf_free(buf); + return 0; + } + + strbuf_append_char(buf, ')'); + *target = strbuf_free_nobuf(buf); + int total_char_count = local_input - *input; + *input = local_input; + return total_char_count; +} + +int +btp_frame_parse_function_name_template(char **input, char **target) +{ + char *local_input = *input; + if (!btp_skip_char(&local_input, '<')) + return 0; + + struct strbuf *buf = strbuf_new(); + strbuf_append_char(buf, '<'); + while (true) + { + char *namechunk = NULL; + if (0 < btp_frame_parse_function_name_chunk(&local_input, true, &namechunk) || + 0 < btp_frame_parse_function_name_braces(&local_input, &namechunk) || + 0 < btp_frame_parse_function_name_template(&local_input, &namechunk)) + { + strbuf_append_str(buf, namechunk); + free(namechunk); + } + else + break; + } + + if (!btp_skip_char(&local_input, '>')) + { + strbuf_free(buf); + return 0; + } + + strbuf_append_char(buf, '>'); + *target = strbuf_free_nobuf(buf); + int total_char_count = local_input - *input; + *input = local_input; + return total_char_count; +} + +bool +btp_frame_parse_function_name(char **input, + char **function_name, + char **function_type, + struct btp_location *location) +{ + /* Handle unknown function name, represended by double question + mark. */ + if (btp_parse_string(input, "??", function_name)) + { + *function_type = NULL; + location->column += 2; + return true; + } + + char *local_input = *input; + /* Up to three parts of function name. */ + struct strbuf *buf0 = strbuf_new(), *buf1 = NULL; + + /* First character: + '~' for destructor + '*' for ???? + '_a-zA-Z' for mangled/nonmangled function name + '(' to start "(anonymous namespace)::" or something + */ + char first; + char *namechunk; + if (btp_parse_char_limited(&local_input, "~*_" BTP_alpha, &first)) + { + /* If it's a start of 'o'perator, put the 'o' back! */ + if (first == 'o') + --local_input; + else + { + strbuf_append_char(buf0, first); + ++location->column; + } + } + else + { + int chars = btp_frame_parse_function_name_braces(&local_input, + &namechunk); + if (0 < chars) + { + strbuf_append_str(buf0, namechunk); + free(namechunk); + location->column += chars; + } + else + { + location->message = "Expected function name."; + strbuf_free(buf0); + return false; + } + } + + /* The rest consists of function name, braces, templates...*/ + while (true) + { + char *namechunk = NULL; + int chars = btp_frame_parse_function_name_chunk(&local_input, + false, + &namechunk); + + if (0 == chars) + { + chars = btp_frame_parse_function_name_braces(&local_input, + &namechunk); + } + + if (0 == chars) + { + chars = btp_frame_parse_function_name_template(&local_input, + &namechunk); + } + + if (0 == chars) + break; + + strbuf_append_str(buf0, namechunk); + free(namechunk); + location->column += chars; + } + + /* Function name MUST be ended by empty space. */ + char space; + if (!btp_parse_char_limited(&local_input, BTP_space, &space)) + { + strbuf_free(buf0); + location->message = "Space or newline expected after function name."; + return false; + } + + /* Some C++ function names and function types might contain suffix + " const". */ + int chars = btp_skip_string(&local_input, "const"); + if (0 < chars) + { + strbuf_append_char(buf0, space); + btp_location_eat_char(location, space); + strbuf_append_str(buf0, "const"); + location->column += chars; + + /* Check the empty space after function name again.*/ + if (!btp_parse_char_limited(&local_input, BTP_space, &space)) + { + /* Function name MUST be ended by empty space. */ + strbuf_free(buf0); + location->message = "Space or newline expected after function name."; + return false; + } + } + + /* Maybe the first series was just a type of the function, and now + the real function follows. Now, we know it must not start with + '(', nor with '<'. */ + chars = btp_frame_parse_function_name_chunk(&local_input, + false, + &namechunk); + if (0 < chars) + { + /* Eat the space separator first. */ + btp_location_eat_char(location, space); + + buf1 = strbuf_new(); + strbuf_append_str(buf1, namechunk); + free(namechunk); + location->column += chars; + + /* The rest consists of a function name parts, braces, templates...*/ + while (true) + { + char *namechunk = NULL; + chars = btp_frame_parse_function_name_chunk(&local_input, + false, + &namechunk); + if (0 == chars) + { + chars = btp_frame_parse_function_name_braces(&local_input, + &namechunk); + } + if (0 == chars) + { + chars = btp_frame_parse_function_name_template(&local_input, + &namechunk); + } + if (0 == chars) + break; + + strbuf_append_str(buf1, namechunk); + free(namechunk); + location->column += chars; + } + + /* Function name MUST be ended by empty space. */ + if (!btp_parse_char_limited(&local_input, BTP_space, &space)) + { + strbuf_free(buf0); + strbuf_free(buf1); + location->message = "Space or newline expected after function name."; + return false; + } + } + + /* Again, some C++ function names might contain suffix " const" */ + chars = btp_skip_string(&local_input, "const"); + if (0 < chars) + { + struct strbuf *buf = buf1 ? buf1 : buf0; + strbuf_append_char(buf, space); + btp_location_eat_char(location, space); + strbuf_append_str(buf, "const"); + location->column += chars; + + /* Check the empty space after function name again.*/ + if (!btp_skip_char_limited(&local_input, BTP_space)) + { + /* Function name MUST be ended by empty space. */ + strbuf_free(buf0); + strbuf_free(buf1); + location->message = "Space or newline expected after function name."; + return false; + } + } + + /* Return back to the empty space. */ + --local_input; + + if (buf1) + { + *function_name = strbuf_free_nobuf(buf1); + *function_type = strbuf_free_nobuf(buf0); + } + else + { + *function_name = strbuf_free_nobuf(buf0); + *function_type = NULL; + } + + *input = local_input; + return true; +} + +bool +btp_frame_skip_function_args(char **input, struct btp_location *location) +{ + char *local_input = *input; + if (!btp_skip_char(&local_input, '(')) + { + location->message = "Expected '(' to start function argument list."; + return false; + } + location->column += 1; + + int depth = 0; + bool string = false; + bool escape = false; + do + { + if (string) + { + if (escape) + escape = false; + else if (*local_input == '\\') + escape = true; + else if (*local_input == '"') + string = false; + } + else + { + if (*local_input == '"') + string = true; + else if (*local_input == '(') + ++depth; + else if (*local_input == ')') + { + if (depth > 0) + --depth; + else + break; + } + } + btp_location_eat_char(location, *local_input); + ++local_input; + } + while (*local_input); + + if (depth != 0 || string || escape) + { + location->message = "Unbalanced function parameter list."; + return false; + } + + if (!btp_skip_char(&local_input, ')')) + { + location->message = "Expected ')' to close the function parameter list."; + return false; + } + location->column += 1; + + *input = local_input; + return true; +} + +bool +btp_frame_parse_function_call(char **input, + char **function_name, + char **function_type, + struct btp_location *location) +{ + char *local_input = *input; + char *name = NULL, *type = NULL; + if (!btp_frame_parse_function_name(&local_input, + &name, + &type, + location)) + { + /* The location message is set by the function returning + * false, no need to update it here. */ + return false; + } + + int line, column; + if (0 == btp_skip_char_span_location(&local_input, + " \n", + &line, + &column)) + { + free(name); + free(type); + location->message = "Expected a space or newline after the function name."; + return false; + } + btp_location_add(location, line, column); + + if (!btp_frame_skip_function_args(&local_input, location)) + { + free(name); + free(type); + /* The location message is set by the function returning + * false, no need to update it here. */ + return false; + } + + *function_name = name; + *function_type = type; + *input = local_input; + return true; +} + +bool +btp_frame_parse_address_in_function(char **input, + uint64_t *address, + char **function_name, + char **function_type, + struct btp_location *location) +{ + char *local_input = *input; + + /* Read memory address in hexadecimal format. */ + int digits = btp_parse_hexadecimal_number(&local_input, address); + location->column += digits; + if (0 == digits) + { + location->message = "Hexadecimal number representing memory address expected."; + return false; + } + + /* Skip spaces. */ + int chars = btp_skip_char_sequence(&local_input, ' '); + location->column += chars; + if (0 == chars) + { + location->message = "Space expected after memory address."; + return false; + } + + /* Skip keyword "in". */ + chars = btp_skip_string(&local_input, "in"); + location->column += chars; + if (0 == chars) + { + location->message = "Keyword \"in\" expected after memory address."; + return false; + } + + /* Skip spaces. */ + chars = btp_skip_char_sequence(&local_input, ' '); + location->column += chars; + if (0 == chars) + { + location->message = "Space expected after 'in'."; + return false; + } + + /* C++ specific case for "0xfafa in vtable for function ()" */ + chars = btp_skip_string(&local_input, "vtable"); + location->column += chars; + if (0 < chars) + { + chars = btp_skip_char_sequence(&local_input, ' '); + location->column += chars; + if (0 == chars) + { + location->message = "Space expected after 'vtable'."; + return false; + } + + chars = btp_skip_string(&local_input, "for"); + location->column += chars; + if (0 == chars) + { + location->message = "Keyword \"for\" expected."; + return false; + } + + chars = btp_skip_char_sequence(&local_input, ' '); + location->column += chars; + if (0 == chars) + { + location->message = "Space expected after 'for'."; + return false; + } + } + + if (!btp_frame_parse_function_call(&local_input, + function_name, + function_type, + location)) + { + /* Do not update location here, it has been modified by the + called function. */ + return false; + } + + *input = local_input; + return true; +} + +bool +btp_frame_parse_file_location(char **input, + char **file, + unsigned *fileline, + struct btp_location *location) +{ + char *local_input = *input; + int line, column; + if (0 == btp_skip_char_span_location(&local_input, " \n", &line, &column)) + { + location->message = "Expected a space or a newline."; + return false; + } + btp_location_add(location, line, column); + + int chars = btp_skip_string(&local_input, "at"); + if (0 == chars) + { + chars = btp_skip_string(&local_input, "from"); + if (0 == chars) + { + location->message = "Expected 'at' or 'from'."; + return false; + } + } + location->column += chars; + + int spaces = btp_skip_char_sequence(&local_input, ' '); + location->column += spaces; + if (0 == spaces) + { + location->message = "Expected a space before file location."; + return false; + } + + char *file_name; + chars = btp_parse_char_span(&local_input, BTP_alnum "_/\\+.-", &file_name); + location->column += chars; + if (0 == chars) + { + location->message = "Expected a file name."; + return false; + } + + if (btp_skip_char(&local_input, ':')) + { + location->column += 1; + int digits = btp_parse_unsigned_integer(&local_input, fileline); + location->column += digits; + if (0 == digits) + { + free(file_name); + location->message = "Expected a line number."; + return false; + } + } + else + *fileline = -1; + + *file = file_name; + *input = local_input; + return true; +} + +struct btp_frame * +btp_frame_parse_header(char **input, + struct btp_location *location) +{ + char *local_input = *input; + struct btp_frame *imframe = btp_frame_new(); /* im - intermediate */ + int chars = btp_frame_parse_frame_start(&local_input, + &imframe->number); + + location->column += chars; + if (0 == chars) + { + location->message = "Frame start sequence expected."; + btp_frame_free(imframe); + return NULL; + } + + struct btp_location internal_location; + btp_location_init(&internal_location); + if (btp_frame_parse_address_in_function(&local_input, + &imframe->address, + &imframe->function_name, + &imframe->function_type, + &internal_location)) + { + btp_location_add(location, + internal_location.line, + internal_location.column); + + /* Optional section " from file.c:65" */ + /* Optional section " at file.c:65" */ + btp_location_init(&internal_location); + if (btp_frame_parse_file_location(&local_input, + &imframe->source_file, + &imframe->source_line, + &internal_location)) + { + btp_location_add(location, + internal_location.line, + internal_location.column); + } + } + else + { + btp_location_init(&internal_location); + if (btp_frame_parse_function_call(&local_input, + &imframe->function_name, + &imframe->function_type, + &internal_location)) + { + btp_location_add(location, + internal_location.line, + internal_location.column); + + /* Mandatory section " at file.c:65" */ + btp_location_init(&internal_location); + if (!btp_frame_parse_file_location(&local_input, + &imframe->source_file, + &imframe->source_line, + &internal_location)) + { + location->message = "Function call in the frame header " + "misses mandatory \"at file.c:xy\" section"; + btp_frame_free(imframe); + return NULL; + } + + btp_location_add(location, + internal_location.line, + internal_location.column); + } + else + { + int chars = btp_skip_string(&local_input, "<signal handler called>"); + if (0 < chars) + { + location->column += chars; + imframe->signal_handler_called = true; + } + else + { + location->message = "Frame header variant not recognized."; + btp_frame_free(imframe); + return NULL; + } + } + } + + *input = local_input; + return imframe; +} diff --git a/src/btparser/frame.h b/src/btparser/frame.h new file mode 100644 index 00000000..966dd5d2 --- /dev/null +++ b/src/btparser/frame.h @@ -0,0 +1,470 @@ +/* + frame.h + + Copyright (C) 2010 Red Hat, 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 BTPARSER_FRAME_H +#define BTPARSER_FRAME_H + +#include <stdbool.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct strbuf; +struct btp_location; + +/** + * A frame representing a function call or a signal handler on a call + * stack of a thread. + */ +struct btp_frame +{ + /** + * A function name or NULL. If it's NULL, signal_handler_called is + * true. + */ + char *function_name; + /** + * A function type, or NULL if it isn't present. + */ + char *function_type; + /** + * A frame number in a thread. It does not necessarily show the + * actual position in the thread, as this number is set by the + * parser and never updated. + */ + unsigned number; + /** + * The name of the source file containing the function definition, + * or the name of the binary file (.so) with the binary code of + * the function, or NULL. + */ + char *source_file; + /** + * A line number in the source file, determining the position of + * the function definition, or -1 when unknown. + */ + unsigned source_line; + /** + * Signal handler was called on this frame. + */ + bool signal_handler_called; + /** + * The function address in the computer memory, or -1 when the + * address is unknown. + */ + uint64_t address; + /** + * A sibling frame residing below this one, or NULL if this is the + * last frame in the parent thread. + */ + struct btp_frame *next; +}; + +/** + * Creates and initializes a new frame structure. + * @returns + * It never returns NULL. The returned pointer must be released by + * calling the function btp_frame_free(). + */ +struct btp_frame * +btp_frame_new(); + +/** + * Initializes all members of the frame structure to their default + * values. No memory is released, members are simply overwritten. + * This is useful for initializing a frame structure placed on the + * stack. + */ +void +btp_frame_init(struct btp_frame *frame); + +/** + * Releases the memory held by the frame. The frame siblings are not + * released. + * @param frame + * If the frame is NULL, no operation is performed. + */ +void +btp_frame_free(struct btp_frame *frame); + +/** + * Creates a duplicate of the frame. + * @param frame + * It must be non-NULL pointer. The frame is not modified by calling + * this function. + * @param siblings + * Whether to duplicate also siblings referenced by frame->next. If + * false, frame->next is not duplicated for the new frame, but it is + * set to NULL. + * @returns + * This function never returns NULL. If the returned duplicate is not + * shallow, it must be released by calling the function + * btp_frame_free(). + */ +struct btp_frame * +btp_frame_dup(struct btp_frame *frame, + bool siblings); + +/** + * Checks whether the frame represents a call of function with certain + * function name. + */ +bool +btp_frame_calls_func(struct btp_frame *frame, + const char *function_name); + +/** + * Checks whether the frame represents a call of function with certain + * function name, which resides in a source file. + * @param source_file + * The frame's source_file is searched for the source_file as a + * substring. + */ +bool +btp_frame_calls_func_in_file(struct btp_frame *frame, + const char *function_name, + const char *source_file); + +/** + * Checks whether the frame represents a call of function with certain + * function name, which resides in one of the source files. + * @param source_file0 + * The frame's source_file is searched for the source_file0 as a + * substring. + * @returns + * True if the frame corresponds to a function with function_name, + * residing in the source_file0, or source_file1. + */ +bool +btp_frame_calls_func_in_file2(struct btp_frame *frame, + const char *function_name, + const char *source_file0, + const char *source_file1); + +/** + * Checks whether the frame represents a call of function with certain + * function name, which resides in one of the source files. + * @param source_file0 + * The frame's source_file is searched for the source_file0 as a + * substring. + * @returns + * True if the frame corresponds to a function with function_name, + * residing in the source_file0, source_file1, or source_file2. + */ +bool +btp_frame_calls_func_in_file3(struct btp_frame *frame, + const char *function_name, + const char *source_file0, + const char *source_file1, + const char *source_file2); + +/** + * Checks whether the frame represents a call of function with certain + * function name, which resides in one of the source files. + * @param source_file0 + * The frame's source_file is searched for the source_file0 as a + * substring. + * @returns + * True if the frame corresponds to a function with function_name, + * residing in the source_file0, source_file1, source_file2, or + * source_file3. + */ +bool +btp_frame_calls_func_in_file4(struct btp_frame *frame, + const char *function_name, + const char *source_file0, + const char *source_file1, + const char *source_file2, + const char *source_file3); + +/** + * Compares two frames. + * @param f1 + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @param f2 + * It must be non-NULL pointer. It's not modified by calling this + * function. + * @param compare_number + * Indicates whether to include the frame numbers in the + * comparsion. If set to false, the frame numbers are ignored. + * @returns + * Returns 0 if the frames are same. Returns negative number if f1 is + * found to be 'less' than f2. Returns positive number if f1 is found + * to be 'greater' than f2. + */ +int +btp_frame_cmp(struct btp_frame *f1, + struct btp_frame *f2, + bool compare_number); + +/** + * Puts the frame 'b' to the bottom of the stack 'a'. In other words, + * it finds the last sibling of the frame 'a', and appends the frame + * 'b' to this last sibling. + */ +void +btp_frame_add_sibling(struct btp_frame *a, + struct btp_frame *b); + +/** + * Appends the textual representation of the frame to the string + * buffer. + * @param frame + * It must be non-NULL pointer. It's not modified by calling this + * function. + */ +void +btp_frame_append_to_str(struct btp_frame *frame, + struct strbuf *str, + bool verbose); + +/** + * If the input contains a complete frame, this function parses the + * frame text, returns it in a structure, and moves the input pointer + * after the frame. If the input does not contain proper, complete + * frame, the function does not modify input and returns NULL. + * @returns + * Allocated pointer with a frame structure. The pointer should be + * released by btp_frame_free(). + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. When this function returns NULL, the structure will contain + * the error line, column, and message. The line and column members + * of the location are gradually increased as the parser handles the + * input, so the location should be initialized before calling this + * function to get reasonable values. + */ +struct btp_frame * +btp_frame_parse(char **input, + struct btp_location *location); + +/** + * If the input contains a proper frame start section, parse the frame + * number, and move the input pointer after this section. Otherwise do + * not modify input. + * @returns + * The number of characters parsed from input. 0 if the input does not + * contain a frame start. + * @code + * "#1 " + * "#255 " + * @endcode + */ +int +btp_frame_parse_frame_start(char **input, unsigned *number); + +/** + * Parses C++ operator on input. + * Supports even 'operator new[]' and 'operator delete[]'. + * @param target + * The parsed operator name is appened to the string buffer provided, + * if an operator is found. Otherwise the string buffer is not + * changed. + * @returns + * The number of characters parsed from input. 0 if the input does not + * contain operator. + */ +int +btp_frame_parseadd_operator(char **input, + struct strbuf *target); + +/** + * Parses a part of function name from the input. + * @param target + * Pointer to a non-allocated pointer. This function will set + * the pointer to newly allocated memory containing the name chunk, + * if it returns positive, nonzero value. + * @returns + * The number of characters parsed from input. 0 if the input does not + * contain a part of function name. + */ +int +btp_frame_parse_function_name_chunk(char **input, + bool space_allowed, + char **target); + +/** + * If the input buffer contains part of function name containing braces, + * for example "(anonymous namespace)", parse it, append the contents + * to target and move input after the braces. + * Otherwise do not modify niether the input nor the target. + * @returns + * The number of characters parsed from input. 0 if the input does not + * contain a braced part of function name. + */ +int +btp_frame_parse_function_name_braces(char **input, + char **target); + +/** + * @returns + * The number of characters parsed from input. 0 if the input does not + * contain a template part of function name. + */ +int +btp_frame_parse_function_name_template(char **input, + char **target); + +/** + * Parses the function name, which is a part of the frame header, from + * the input. If the frame header contains also the function type, + * it's also parsed. + * @param function_name + * A pointer pointing to an uninitialized pointer. This function + * allocates a string and sets the pointer to it if it parses the + * function name from the input successfully. The memory returned + * this way must be released by the caller using the function free(). + * If this function returns true, this pointer is guaranteed to be + * non-NULL. + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + * @returns + * True if the input stream contained a function name, which has been + * parsed. False otherwise. + */ +bool +btp_frame_parse_function_name(char **input, + char **function_name, + char **function_type, + struct btp_location *location); + +/** + * Skips function arguments which are a part of the frame header, in + * the input stream. + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + */ +bool +btp_frame_skip_function_args(char **input, + struct btp_location *location); + +/** + * If the input contains proper function call, parse the function + * name and store it to result, move the input pointer after whole + * function call, and return true. Otherwise do not modify the input + * and return false. + * + * If this function returns true, the caller is responsible to free + * the the function_name. + * @todo + * Parse and return the function call arguments. + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + */ +bool +btp_frame_parse_function_call(char **input, + char **function_name, + char **function_type, + struct btp_location *location); + +/** + * If the input contains address and function call, parse them, move + * the input pointer after this sequence, and return true. + * Otherwise do not modify the input and return false. + * + * If this function returns true, the caller is responsible to free + * the parameter function. + * + * @code + * 0x000000322160e7fd in fsync () + * 0x000000322222987a in write_to_temp_file ( + * filename=0x18971b0 "/home/jfclere/.recently-used.xbel", + * contents=<value optimized out>, length=29917, error=0x7fff3cbe4110) + * @endcode + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + */ +bool +btp_frame_parse_address_in_function(char **input, + uint64_t *address, + char **function_name, + char **function_type, + struct btp_location *location); + +/** + * If the input contains sequence "from path/to/file:fileline" or "at + * path/to/file:fileline", parse it, move the input pointer after this + * sequence and return true. Otherwise do not modify the input and + * return false. + * + * The ':' followed by line number is optional. If it is not present, + * the fileline is set to -1. + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + */ +bool +btp_frame_parse_file_location(char **input, + char **file, + unsigned *fileline, + struct btp_location *location); + +/** + * If the input contains proper frame header, this function + * parses the frame header text, moves the input pointer + * after the frame header, and returns a frame struct. + * If the input does not contain proper frame header, this function + * returns NULL and does not modify input. + * @param location + * The caller must provide a pointer to an instance of btp_location + * here. The line and column members of the location are gradually + * increased as the parser handles the input, so the location should + * be initialized before calling this function to get reasonable + * values. When this function returns false (an error occurred), the + * structure will contain the error line, column, and message. + * @returns + * Newly created frame struct or NULL. The returned frame struct + * should be released by btp_frame_free(). + */ +struct btp_frame * +btp_frame_parse_header(char **input, + struct btp_location *location); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/btparser/location.c b/src/btparser/location.c new file mode 100644 index 00000000..ade706f2 --- /dev/null +++ b/src/btparser/location.c @@ -0,0 +1,78 @@ +/* + location.c + + Copyright (C) 2010 Red Hat, 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 "location.h" +#include <stdlib.h> /* contains NULL */ + +void +btp_location_init(struct btp_location *location) +{ + location->line = 1; + location->column = 0; + location->message = NULL; +} + +void +btp_location_add(struct btp_location *location, + int add_line, + int add_column) +{ + btp_location_add_ext(&location->line, + &location->column, + add_line, + add_column); +} + +void +btp_location_add_ext(int *line, + int *column, + int add_line, + int add_column) +{ + if (add_line > 1) + { + *line += add_line - 1; + *column = add_column; + } + else + *column += add_column; +} + +void +btp_location_eat_char(struct btp_location *location, + char c) +{ + btp_location_eat_char_ext(&location->line, + &location->column, + c); +} + +void +btp_location_eat_char_ext(int *line, + int *column, + char c) +{ + if (c == '\n') + { + *line += 1; + *column = 0; + } + else + *column += 1; +} diff --git a/src/btparser/location.h b/src/btparser/location.h new file mode 100644 index 00000000..0d620205 --- /dev/null +++ b/src/btparser/location.h @@ -0,0 +1,118 @@ +/* + location.h + + Copyright (C) 2010 Red Hat, 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 BTPARSER_LOCATION_H +#define BTPARSER_LOCATION_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A location in the backtrace file with an attached message. + * It's used for error reporting: the line and the column points to + * the place where a parser error occurred, and the message explains + * what the parser expected and didn't find on that place. + */ +struct btp_location +{ + /** Starts from 1. */ + int line; + /** Starts from 0. */ + int column; + /** + * Error message related to the line and column. Do not release + * the memory this pointer points to. + */ + const char *message; +}; + +/** + * Initializes all members of the location struct to their default + * values. No memory is allocated or released by this function. + */ +void +btp_location_init(struct btp_location *location); + +/** + * Adds a line and a column to specific location. + * @note + * If the line is not 1 (meaning the first line), the column in the + * location structure is overwritten by the provided add_column value. + * Otherwise the add_column value is added to the column member of the + * location structure. + * @param location + * The structure to be modified. It must be a valid pointer. + * @param add_line + * Starts from 1. It means that if add_line is 1, the line member of the + * location structure is not changed. + * @param add_column + * Starts from 0. + */ +void +btp_location_add(struct btp_location *location, + int add_line, + int add_column); + +/** + * Adds a line column pair to another line column pair. + * @note + * If the add_line is not 1 (meaning the frist line), the column is + * overwritten by the provided add_column value. Otherwise the + * add_column value is added to the column. + * @param add_line + * Starts from 1. It means that if add_line is 1, the line is not + * changed. + * @param add_column + * Starts from 0. + */ +void +btp_location_add_ext(int *line, + int *column, + int add_line, + int add_column); + +/** + * Updates the line and column of the location by moving "after" the + * char c. If c is a newline character, the line number is increased + * and the column is set to 0. Otherwise the column is increased by 1. + */ +void +btp_location_eat_char(struct btp_location *location, + char c); + +/** + * Updates the line and the column by moving "after" the char c. If c + * is a newline character, the line number is increased and the column + * is set to 0. Otherwise the column is increased. + * @param line + * Must be a valid pointer. + * @param column + * Must be a valid pointer. + */ +void +btp_location_eat_char_ext(int *line, + int *column, + char c); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/btparser/normalize.c b/src/btparser/normalize.c new file mode 100644 index 00000000..4bbee99c --- /dev/null +++ b/src/btparser/normalize.c @@ -0,0 +1,68 @@ +/* + normalize.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include "backtrace.h" +#include <string.h> + +void +btp_normalize_thread(struct btp_thread *thread) +{ + btp_normalize_dbus_thread(thread); + btp_normalize_gdk_thread(thread); + btp_normalize_glib_thread(thread); + btp_normalize_glibc_thread(thread); + btp_normalize_libstdcpp_thread(thread); + btp_normalize_linux_thread(thread); + btp_normalize_xorg_thread(thread); + + /* If the last frame has address 0x0000 and its name is '??', + * remove it. This frame is not really invalid, and it affects + * backtrace quality rating. See Red Hat Bugzilla bug #592523. + * @code + * #2 0x00007f4dcebbd62d in clone () + * at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112 + * No locals. + * #3 0x0000000000000000 in ?? () + * @endcode + */ + struct btp_frame *last = thread->frames; + while (last && last->next) + last = last->next; + if (last && + last->address == 0x0000 && + last->function_name && + 0 == strcmp(last->function_name, "??")) + { + btp_thread_remove_frame(thread, last); + } +} + +void +btp_normalize_backtrace(struct btp_backtrace *backtrace) +{ + struct btp_thread *thread = backtrace->threads; + while (thread) + { + btp_normalize_thread(thread); + thread = thread->next; + } +} diff --git a/src/btparser/normalize.h b/src/btparser/normalize.h new file mode 100644 index 00000000..35fd2836 --- /dev/null +++ b/src/btparser/normalize.h @@ -0,0 +1,74 @@ +/* + normalize.h + + Copyright (C) 2010 Red Hat, 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 BTPARSER_NORMALIZE_H +#define BTPARSER_NORMALIZE_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct btp_thread; +struct btp_backtrace; + +void +btp_normalize_thread(struct btp_thread *thread); + +void +btp_normalize_backtrace(struct btp_backtrace *backtrace); + +/** + */ +void +btp_normalize_dbus_thread(struct btp_thread *thread); + +void +btp_normalize_gdk_thread(struct btp_thread *thread); + +void +btp_normalize_glib_thread(struct btp_thread *thread); + +/** + * Checks whether the thread it contains some function used to exit + * application. If a frame with the function is found, it is + * returned. If there are multiple frames with abort function, the + * lowest one is returned. + * @returns + * Returns NULL if such a frame is not found. + */ +struct btp_frame * +btp_glibc_thread_find_exit_frame(struct btp_thread *thread); + +void +btp_normalize_glibc_thread(struct btp_thread *thread); + +void +btp_normalize_libstdcpp_thread(struct btp_thread *thread); + +void +btp_normalize_linux_thread(struct btp_thread *thread); + +void +btp_normalize_xorg_thread(struct btp_thread *thread); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/btparser/normalize_dbus.c b/src/btparser/normalize_dbus.c new file mode 100644 index 00000000..d3d6a13a --- /dev/null +++ b/src/btparser/normalize_dbus.c @@ -0,0 +1,44 @@ +/* + normalize_dbus.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include <stdbool.h> + +void +btp_normalize_dbus_thread(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Remove frames which are not a cause of the crash. */ + bool removable = + btp_frame_calls_func_in_file(frame, "gerror_to_dbus_error_message", "dbus-gobject.c") || + btp_frame_calls_func_in_file(frame, "dbus_g_method_return_error", "dbus-gobject.c"); + if (removable) + { + btp_thread_remove_frame(thread, frame); + } + + frame = next_frame; + } +} diff --git a/src/btparser/normalize_gdk.c b/src/btparser/normalize_gdk.c new file mode 100644 index 00000000..e9c4ef6a --- /dev/null +++ b/src/btparser/normalize_gdk.c @@ -0,0 +1,44 @@ +/* + normalize_gdk.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include <stdbool.h> + +void +btp_normalize_gdk_thread(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Remove frames which are not a cause of the crash. */ + bool removable = + btp_frame_calls_func_in_file(frame, "gdk_x_error", "gdkmain-x11.c"); + + if (removable) + { + btp_thread_remove_frame(thread, frame); + } + + frame = next_frame; + } +} diff --git a/src/btparser/normalize_glib.c b/src/btparser/normalize_glib.c new file mode 100644 index 00000000..b5c12b60 --- /dev/null +++ b/src/btparser/normalize_glib.c @@ -0,0 +1,60 @@ +/* + normalize_glib.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include "utils.h" +#include <stdbool.h> +#include <string.h> + +void +btp_normalize_glib_thread(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Normalize frame names. */ + if (frame->function_name && + 0 == strncmp(frame->function_name, "IA__g_", strlen("IA__g_"))) + { + /* Remove the IA__ prefix. The strcpy function cannot be + * used for that because the source and destination + * pointers overlap. */ + char *p = frame->function_name; + while ((*p = p[4]) != '\0') + ++p; + } + + /* Remove frames which are not a cause of the crash. */ + bool removable = + btp_frame_calls_func_in_file2(frame, "g_log", "gmessages.c", "libglib") || + btp_frame_calls_func_in_file2(frame, "g_logv", "gmessages.c", "libglib") || + btp_frame_calls_func_in_file2(frame, "g_assertion_message", "gtestutils.c", "libglib") || + btp_frame_calls_func_in_file2(frame, "g_assertion_message_expr", "gtestutils.c", "libglib"); + if (removable) + { + btp_thread_remove_frame(thread, frame); + } + + frame = next_frame; + } +} diff --git a/src/btparser/normalize_glibc.c b/src/btparser/normalize_glibc.c new file mode 100644 index 00000000..ea40ba9d --- /dev/null +++ b/src/btparser/normalize_glibc.c @@ -0,0 +1,120 @@ +/* + normalize_glibc.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include "utils.h" +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +struct btp_frame * +btp_glibc_thread_find_exit_frame(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + struct btp_frame *result = NULL; + while (frame) + { + bool is_exit_frame = + btp_frame_calls_func_in_file(frame, "__run_exit_handlers", "exit.c") || + btp_frame_calls_func_in_file4(frame, "raise", "pt-raise.c", "libc.so", "libc-", "libpthread.so") || + btp_frame_calls_func_in_file(frame, "exit", "exit.c") || + btp_frame_calls_func_in_file3(frame, "abort", "abort.c", "libc.so", "libc-") || + /* Terminates a function in case of buffer overflow. */ + btp_frame_calls_func_in_file2(frame, "__chk_fail", "chk_fail.c", "libc.so"); + + if (is_exit_frame) + result = frame; + + frame = frame->next; + } + + return result; +} + + +void +btp_normalize_glibc_thread(struct btp_thread *thread) +{ + /* Find the exit frame and remove everything above it. */ + struct btp_frame *exit_frame = btp_glibc_thread_find_exit_frame(thread); + if (exit_frame) + { + bool success = btp_thread_remove_frames_above(thread, exit_frame); + assert(success); /* if this fails, some code become broken */ + success = btp_thread_remove_frame(thread, exit_frame); + assert(success); /* if this fails, some code become broken */ + } + + /* Standard function filtering loop. */ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Normalize frame names. */ +#define NORMALIZE_ARCH_SPECIFIC(func) \ + if (btp_frame_calls_func_in_file2(frame, "__" func "_sse2", func ".S", "libc.so") || \ + btp_frame_calls_func_in_file2(frame, "__" func "_ssse3", func ".S", "libc.so") || \ + btp_frame_calls_func_in_file2(frame, "__" func "_ia32", func ".S", "libc.so")) \ + { \ + strcpy(frame->function_name, func); \ + } + + NORMALIZE_ARCH_SPECIFIC("memchr"); + NORMALIZE_ARCH_SPECIFIC("memcmp"); + NORMALIZE_ARCH_SPECIFIC("memcpy"); + NORMALIZE_ARCH_SPECIFIC("memset"); + NORMALIZE_ARCH_SPECIFIC("rawmemchr"); + NORMALIZE_ARCH_SPECIFIC("strcat"); + NORMALIZE_ARCH_SPECIFIC("strchr"); + NORMALIZE_ARCH_SPECIFIC("strchrnul"); + NORMALIZE_ARCH_SPECIFIC("strcmp"); + NORMALIZE_ARCH_SPECIFIC("strcpy"); + NORMALIZE_ARCH_SPECIFIC("strcspn"); + NORMALIZE_ARCH_SPECIFIC("strlen"); + NORMALIZE_ARCH_SPECIFIC("strncmp"); + NORMALIZE_ARCH_SPECIFIC("strpbrk"); + NORMALIZE_ARCH_SPECIFIC("strrchr"); + NORMALIZE_ARCH_SPECIFIC("strspn"); + NORMALIZE_ARCH_SPECIFIC("strtok"); + + /* Remove frames which are not a cause of the crash. */ + bool removable = + btp_frame_calls_func(frame, "__assert_fail") || + btp_frame_calls_func(frame, "__strcat_chk") || + btp_frame_calls_func(frame, "__strcpy_chk") || + btp_frame_calls_func(frame, "__strncpy_chk") || + btp_frame_calls_func(frame, "__vsnprintf_chk") || + btp_frame_calls_func(frame, "___vsnprintf_chk") || + btp_frame_calls_func(frame, "__snprintf_chk") || + btp_frame_calls_func(frame, "___snprintf_chk") || + btp_frame_calls_func(frame, "__vasprintf_chk"); + if (removable) + { + bool success = btp_thread_remove_frames_above(thread, frame); + assert(success); + success = btp_thread_remove_frame(thread, frame); + assert(success); + } + + frame = next_frame; + } +} diff --git a/src/btparser/normalize_libstdcpp.c b/src/btparser/normalize_libstdcpp.c new file mode 100644 index 00000000..1f833ded --- /dev/null +++ b/src/btparser/normalize_libstdcpp.c @@ -0,0 +1,46 @@ +/* + normalize_libstdcpp.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include <stdbool.h> + +void +btp_normalize_libstdcpp_thread(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Remove frames which are not a cause of the crash. */ + bool removable = + btp_frame_calls_func_in_file(frame, "__gnu_cxx::__verbose_terminate_handler", "vterminate.cc") || + btp_frame_calls_func_in_file(frame, "__cxxabiv1::__terminate", "eh_terminate.cc") || + btp_frame_calls_func_in_file(frame, "std::terminate", "eh_terminate.cc") || + btp_frame_calls_func_in_file(frame, "__cxxabiv1::__cxa_throw", "eh_throw.cc"); + if (removable) + { + btp_thread_remove_frame(thread, frame); + } + + frame = next_frame; + } +} diff --git a/src/btparser/normalize_linux.c b/src/btparser/normalize_linux.c new file mode 100644 index 00000000..8df9f9c2 --- /dev/null +++ b/src/btparser/normalize_linux.c @@ -0,0 +1,41 @@ +/* + normalize_linux.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "utils.h" +#include "frame.h" +#include "thread.h" +#include <stdbool.h> + +void +btp_normalize_linux_thread(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Remove frames which are not a cause of the crash. */ + bool removable = btp_frame_calls_func(frame, "__kernel_vsyscall"); + if (removable) + btp_thread_remove_frame(thread, frame); + + frame = next_frame; + } +} diff --git a/src/btparser/normalize_xorg.c b/src/btparser/normalize_xorg.c new file mode 100644 index 00000000..11e8d624 --- /dev/null +++ b/src/btparser/normalize_xorg.c @@ -0,0 +1,46 @@ +/* + normalize_xorg.c + + Copyright (C) 2010 Red Hat, 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 "normalize.h" +#include "frame.h" +#include "thread.h" +#include <stdbool.h> + +void +btp_normalize_xorg_thread(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + struct btp_frame *next_frame = frame->next; + + /* Remove frames which are not a cause of the crash. */ + bool removable = + btp_frame_calls_func_in_file(frame, "_XReply", "xcb_io.c") || + btp_frame_calls_func_in_file(frame, "_XError", "XlibInt.c") || + btp_frame_calls_func_in_file(frame, "XSync", "Sync.c") || + btp_frame_calls_func_in_file(frame, "process_responses", "xcb_io.c"); + if (removable) + { + btp_thread_remove_frame(thread, frame); + } + + frame = next_frame; + } +} diff --git a/src/btparser/thread.c b/src/btparser/thread.c new file mode 100644 index 00000000..af480eb3 --- /dev/null +++ b/src/btparser/thread.c @@ -0,0 +1,364 @@ +/* + thread.c + + Copyright (C) 2010 Red Hat, 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 "thread.h" +#include "frame.h" +#include "strbuf.h" +#include "utils.h" +#include "location.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +struct btp_thread * +btp_thread_new() +{ + struct btp_thread *thread = btp_malloc(sizeof(struct btp_thread)); + btp_thread_init(thread); + return thread; +} + +void +btp_thread_init(struct btp_thread *thread) +{ + thread->number = -1; + thread->frames = NULL; + thread->next = NULL; +} + +void +btp_thread_free(struct btp_thread *thread) +{ + if (!thread) + return; + + while (thread->frames) + { + struct btp_frame *rm = thread->frames; + thread->frames = rm->next; + btp_frame_free(rm); + } + + free(thread); +} + +struct btp_thread * +btp_thread_dup(struct btp_thread *thread, bool siblings) +{ + struct btp_thread *result = btp_thread_new(); + memcpy(result, thread, sizeof(struct btp_thread)); + + /* Handle siblings. */ + if (siblings) + { + if (result->next) + result->next = btp_thread_dup(result->next, true); + } + else + result->next = NULL; /* Do not copy that. */ + + result->frames = btp_frame_dup(result->frames, true); + + return result; +} + +int +btp_thread_cmp(struct btp_thread *t1, struct btp_thread *t2) +{ + int number = t1->number - t2->number; + if (number != 0) + return number; + struct btp_frame *f1 = t1->frames, *f2 = t2->frames; + do { + if (f1 && !f2) + return 1; + else if (f2 && !f1) + return -1; + else if (f1 && f2) + { + int frames = btp_frame_cmp(f1, f2, true); + if (frames != 0) + return frames; + f1 = f1->next; + f2 = f2->next; + } + } while (f1 || f2); + + return 0; +} + +struct btp_thread * +btp_thread_add_sibling(struct btp_thread *a, struct btp_thread *b) +{ + struct btp_thread *aa = a; + while (aa->next) + aa = aa->next; + + aa->next = b; + return a; +} + +int +btp_thread_get_frame_count(struct btp_thread *thread) +{ + struct btp_frame *frame = thread->frames; + int count = 0; + while (frame) + { + frame = frame->next; + ++count; + } + return count; +} + +void +btp_thread_quality_counts(struct btp_thread *thread, + int *ok_count, + int *all_count) +{ + struct btp_frame *frame = thread->frames; + while (frame) + { + *all_count += 1; + if (frame->signal_handler_called || + (frame->function_name + && 0 != strcmp(frame->function_name, "??"))) + { + *ok_count += 1; + } + frame = frame->next; + } +} + +float +btp_thread_quality(struct btp_thread *thread) +{ + int ok_count = 0, all_count = 0; + btp_thread_quality_counts(thread, &ok_count, &all_count); + if (0 == all_count) + return 1; + return ok_count/(float)all_count; +} + +bool +btp_thread_remove_frame(struct btp_thread *thread, + struct btp_frame *frame) +{ + struct btp_frame *loop_frame = thread->frames, *prev_frame = NULL; + while (loop_frame) + { + if (loop_frame == frame) + { + if (prev_frame) + prev_frame->next = loop_frame->next; + else + thread->frames = loop_frame->next; + + btp_frame_free(loop_frame); + return true; + } + prev_frame = loop_frame; + loop_frame = loop_frame->next; + } + return false; +} + +bool +btp_thread_remove_frames_above(struct btp_thread *thread, + struct btp_frame *frame) +{ + /* Check that the frame is present in the thread. */ + struct btp_frame *loop_frame = thread->frames; + while (loop_frame) + { + if (loop_frame == frame) + break; + loop_frame = loop_frame->next; + } + + if (!loop_frame) + return false; + + /* Delete all the frames up to the frame. */ + while (thread->frames != frame) + { + loop_frame = thread->frames->next; + btp_frame_free(thread->frames); + thread->frames = loop_frame; + } + + return true; +} + +void +btp_thread_remove_frames_below_n(struct btp_thread *thread, + int n) +{ + assert(n >= 0); + + /* Skip some frames to get the required stack depth. */ + int i = n; + struct btp_frame *frame = thread->frames, *last_frame = NULL; + while (frame && i) + { + last_frame = frame; + frame = frame->next; + --i; + } + + /* Delete the remaining frames. */ + if (last_frame) + last_frame->next = NULL; + else + thread->frames = NULL; + + while (frame) + { + struct btp_frame *delete_frame = frame; + frame = frame->next; + btp_frame_free(delete_frame); + } +} + +void +btp_thread_append_to_str(struct btp_thread *thread, + struct strbuf *str, + bool verbose) +{ + int framecount = btp_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 btp_frame *frame = thread->frames; + while (frame) + { + btp_frame_append_to_str(frame, str, verbose); + frame = frame->next; + } +} + +struct btp_thread * +btp_thread_parse(char **input, + struct btp_location *location) +{ + char *local_input = *input; + + /* Read the Thread keyword, which is mandatory. */ + int chars = btp_skip_string(&local_input, "Thread"); + location->column += chars; + if (0 == chars) + { + location->message = "\"Thread\" header expected"; + return NULL; + } + + /* Skip spaces, at least one space is mandatory. */ + int spaces = btp_skip_char_sequence(&local_input, ' '); + location->column += spaces; + if (0 == spaces) + { + location->message = "Space expected after the \"Thread\" keyword."; + return NULL; + } + + /* Read thread number. */ + struct btp_thread *imthread = btp_thread_new(); + int digits = btp_parse_unsigned_integer(&local_input, &imthread->number); + location->column += digits; + if (0 == digits) + { + location->message = "Thread number expected."; + btp_thread_free(imthread); + return NULL; + } + + /* Skip spaces after the thread number and before the parenthesis. */ + spaces = btp_skip_char_sequence(&local_input, ' '); + location->column += spaces; + if (0 == spaces) + { + location->message = "Space expected after the thread number."; + btp_thread_free(imthread); + return NULL; + } + + /* Read the Thread keyword in parenthesis, which is mandatory. */ + chars = btp_skip_string(&local_input, "(Thread "); + location->column += chars; + if (0 == chars) + { + location->message = "Thread keyword in the parenthesis expected in the form '(Thread '."; + btp_thread_free(imthread); + return NULL; + } + + /* Read the thread identification number. */ + digits = btp_skip_unsigned_integer(&local_input); + location->column += digits; + if (0 == digits) + { + location->message = "The thread identification number expected."; + btp_thread_free(imthread); + return NULL; + } + + /* Read the end of the parenthesis. */ + chars = btp_skip_string(&local_input, "):\n"); + if (0 == chars) + { + location->message = "The end of the parenthesis expected in the form of '):\\n'."; + btp_thread_free(imthread); + return NULL; + } + /* Add the newline from the last btp_skip_string. */ + btp_location_add(location, 2, 0); + + /* Read the frames. */ + struct btp_frame *frame, *prevframe = NULL; + struct btp_location frame_location; + btp_location_init(&frame_location); + while ((frame = btp_frame_parse(&local_input, &frame_location))) + { + if (prevframe) + { + btp_frame_add_sibling(prevframe, frame); + prevframe = frame; + } + else + imthread->frames = prevframe = frame; + + btp_location_add(location, + frame_location.line, + frame_location.column); + } + if (!imthread->frames) + { + location->message = frame_location.message; + btp_thread_free(imthread); + return NULL; + } + + *input = local_input; + return imthread; +} diff --git a/src/btparser/thread.h b/src/btparser/thread.h new file mode 100644 index 00000000..f7287385 --- /dev/null +++ b/src/btparser/thread.h @@ -0,0 +1,204 @@ +/* + thread.h + + Copyright (C) 2010 Red Hat, 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 BTPARSER_THREAD_H +#define BTPARSER_THREAD_H + +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct btp_frame; +struct strbuf; +struct btp_location; + +/** + * Represents a thread containing frames. + */ +struct btp_thread +{ + unsigned number; + /** + * Thread's frames, starting from the top of the stack. + */ + struct btp_frame *frames; + /** + * A sibling thread, or NULL if this is the last thread in a backtrace. + */ + struct btp_thread *next; +}; + +/** + * Creates and initializes a new frame structure. + * @returns + * It never returns NULL. The returned pointer must be released by + * calling the function btp_thread_free(). + */ +struct btp_thread * +btp_thread_new(); + +/** + * Initializes all members of the thread to default values. + * No memory is released, members are simply overwritten. + * This is useful for initializing a thread structure placed on the + * stack. + */ +void +btp_thread_init(struct btp_thread *thread); + +/** + * Releases the memory held by the thread. The thread siblings are not + * released. + * @param thread + * If thread is NULL, no operation is performed. + */ +void +btp_thread_free(struct btp_thread *thread); + +/** + * Creates a duplicate of the thread. + * @param thread + * It must be non-NULL pointer. The thread is not modified by calling + * this function. + * @param siblings + * Whether to duplicate also siblings referenced by thread->next. If + * false, thread->next is not duplicated for the new frame, but it is + * set to NULL. + */ +struct btp_thread * +btp_thread_dup(struct btp_thread *thread, + bool siblings); + +/** + * Compares two threads. When comparing the threads, it compares also + * their frames, including the frame numbers. + * @returns + * Returns 0 if the threads are same. Returns negative number if t1 + * is found to be 'less' than t2. Returns positive number if t1 is + * found to be 'greater' than t2. + */ +int +btp_thread_cmp(struct btp_thread *t1, struct btp_thread *t2); + +/** + * Puts the thread 'b' to the bottom of the stack 'a'. In other words, + * it finds the last sibling of the thread 'a', and appends the thread + * 'b' to this last sibling. + */ +struct btp_thread * +btp_thread_add_sibling(struct btp_thread *a, struct btp_thread *b); + +/** + * Returns the number of frames of the thread. + */ +int +btp_thread_get_frame_count(struct btp_thread *thread); + +/** + * Counts the number of 'good' frames and the number of all frames in + * a thread. Good means that the function name is known (so it's not + * just '??'). + * @param ok_count + * @param all_count + * Not zeroed. This function just adds the numbers to ok_count and + * all_count. + */ +void +btp_thread_quality_counts(struct btp_thread *thread, + int *ok_count, + int *all_count); + +/** + * Returns the quality of the thread. The quality is the ratio of the + * number of frames with function name fully known to the number of + * all frames. This function does not take into account that some + * frames are more important than others. + * @param thread + * Must be a non-NULL pointer. It's not modified in this function. + * @returns + * A number between 0 and 1. 0 means the lowest quality, 1 means full + * thread backtrace is known. If the thread contains no frames, this + * function returns 1. + */ +float +btp_thread_quality(struct btp_thread *thread); + +/** + * Removes the frame from the thread and then deletes it. + * @returns + * True if the frame was found in the thread and removed and deleted. + * False if the frame was not found in the thread. + */ +bool +btp_thread_remove_frame(struct btp_thread *thread, + struct btp_frame *frame); + +/** + * Removes all the frames from the thread that are above certain + * frame. + * @returns + * True if the frame was found, and all the frames that were above the + * frame in the thread were removed from the thread and then deleted. + * False if the frame was not found in the thread. + */ +bool +btp_thread_remove_frames_above(struct btp_thread *thread, + struct btp_frame *frame); + +/** + * Keeps only the top n frames in the thread. + */ +void +btp_thread_remove_frames_below_n(struct btp_thread *thread, + int n); + +/** + * Appends a textual representation of 'thread' to the 'str'. + */ +void +btp_thread_append_to_str(struct btp_thread *thread, + struct strbuf *str, + bool verbose); + +/** + * If the input contains proper thread with frames, parse the thread, + * move the input pointer after the thread, and return a structure + * representing the thread. Otherwise to not modify the input pointer + * and return NULL. + * @param location + * The caller must provide a pointer to struct btp_location here. The + * line and column members are gradually increased as the parser + * handles the input, keep this in mind to get reasonable values. + * When this function returns NULL (an error occurred), the structure + * will contain the error line, column, and message. + * @returns + * NULL or newly allocated structure, which should be released by + * calling btp_thread_free(). + */ +struct btp_thread * +btp_thread_parse(char **input, + struct btp_location *location); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/btparser/utils.c b/src/btparser/utils.c new file mode 100644 index 00000000..1de329a7 --- /dev/null +++ b/src/btparser/utils.c @@ -0,0 +1,423 @@ +/* + utils.c + + Copyright (C) 2010 Red Hat, 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 "utils.h" +#include "location.h" +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <regex.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +bool btp_debug_parser = false; + +void * +btp_malloc(size_t size) +{ + void *ptr = malloc(size); + if (ptr == NULL) + { + fprintf(stderr, "btp: out of memory"); + exit(1); + } + return ptr; +} + +char * +btp_vasprintf(const char *format, va_list p) +{ + int r; + char *string_ptr; + +#if 1 + // GNU extension + r = vasprintf(&string_ptr, format, p); +#else + // Bloat for systems that haven't got the GNU extension. + va_list p2; + va_copy(p2, p); + r = vsnprintf(NULL, 0, format, p); + string_ptr = xmalloc(r+1); + r = vsnprintf(string_ptr, r+1, format, p2); + va_end(p2); +#endif + + if (r < 0) + { + fprintf(stderr, "btp: out of memory"); + exit(1); + } + + return string_ptr; +} + +char * +btp_strdup(const char *s) +{ + return btp_strndup(s, strlen(s)); +} + +char * +btp_strndup(const char *s, size_t n) +{ + char *result = strndup(s, n); + if (result == NULL) + { + fprintf(stderr, "btp: out of memory"); + exit(1); + } + return result; +} + +int +btp_strcmp0(const char *s1, const char *s2) +{ + if (!s1) + { + if (s2) + return -1; + return 0; + } + else + { + if (!s2) + return 1; + /* Both are non-null. */ + return strcmp(s1, s2); + } +} + +char * +btp_strchr_location(const char *s, int c, int *line, int *column) +{ + *line = 1; + *column = 0; + + /* Scan s for the character. When this loop is finished, + s will either point to the end of the string or the + character we were looking for. */ + while (*s != '\0' && *s != (char)c) + { + btp_location_eat_char_ext(line, column, *s); + ++s; + } + return ((*s == c) ? (char*)s : NULL); +} + +char * +btp_strstr_location(const char *haystack, + const char *needle, + int *line, + int *column) +{ + *line = 1; + *column = 0; + size_t needlelen; + + /* Check for the null needle case. */ + if (*needle == '\0') + return (char*)haystack; + + needlelen = strlen(needle); + int chrline, chrcolumn; + for (;(haystack = btp_strchr_location(haystack, *needle, &chrline, &chrcolumn)) != NULL; ++haystack) + { + btp_location_add_ext(line, column, chrline, chrcolumn); + + if (strncmp(haystack, needle, needlelen) == 0) + return (char*)haystack; + + btp_location_eat_char_ext(line, column, *haystack); + } + return NULL; +} + +size_t +btp_strspn_location(const char *s, + const char *accept, + int *line, + int *column) +{ + *line = 1; + *column = 0; + const char *sc; + for (sc = s; *sc != '\0'; ++sc) + { + if (strchr(accept, *sc) == NULL) + return (sc - s); + + btp_location_eat_char_ext(line, column, *sc); + } + return sc - s; /* terminating nulls don't match */ +} + +char * +btp_file_to_string(const char *filename) +{ + /* Open input file, and parse it. */ + int fd = open(filename, O_RDONLY | O_LARGEFILE); + if (fd < 0) + { + fprintf(stderr, "Unable to open '%s': %s.\n", + filename, strerror(errno)); + return NULL; + } + + off_t size = lseek(fd, 0, SEEK_END); + if (size < 0) /* EOVERFLOW? */ + { + fprintf(stderr, "Unable to seek in '%s': %s.\n", + filename, strerror(errno)); + } + + lseek(fd, 0, SEEK_SET); /* No reason to fail. */ + + static const size_t FILE_SIZE_LIMIT = 20000000; /* ~ 20 MB */ + if (size > FILE_SIZE_LIMIT) + { + fprintf(stderr, "Input file too big (%lld). Maximum size is %zd.\n", + (long long)size, FILE_SIZE_LIMIT); + close(fd); + return NULL; + } + + char *contents = btp_malloc(size + 1); + if (size != read(fd, contents, size)) + { + fprintf(stderr, "Unable to read from '%s'.\n", filename); + close(fd); + free(contents); + return NULL; + } + + /* Just reading, so no need to check the returned value. */ + close(fd); + + contents[size] = '\0'; + return contents; +} + +bool +btp_skip_char(char **input, char c) +{ + if (**input != c) + return false; + ++*input; + return true; +} + +bool +btp_skip_char_limited(char **input, const char *allowed) +{ + if (strchr(allowed, **input) == NULL) + return false; + ++*input; + return true; +} + +bool +btp_parse_char_limited(char **input, const char *allowed, char *result) +{ + if (**input == '\0') + return false; + if (strchr(allowed, **input) == NULL) + return false; + *result = **input; + ++*input; + return true; +} + +int +btp_skip_char_sequence(char **input, char c) +{ + int count = 0; + + /* Skip all the occurences of c. */ + while (btp_skip_char(input, c)) + ++count; + + return count; +} + +int +btp_skip_char_span(char **input, const char *chars) +{ + size_t count = strspn(*input, chars); + if (0 == count) + return count; + *input += count; + return count; +} + +int +btp_skip_char_span_location(char **input, + const char *chars, + int *line, + int *column) +{ + size_t count = btp_strspn_location(*input, chars, line, column); + if (0 == count) + return count; + *input += count; + return count; +} + +int +btp_parse_char_span(char **input, const char *accept, char **result) +{ + size_t count = strspn(*input, accept); + if (count == 0) + return 0; + *result = btp_strndup(*input, count); + *input += count; + return count; +} + +bool +btp_parse_char_cspan(char **input, const char *reject, char **result) +{ + size_t count = strcspn(*input, reject); + if (count == 0) + return false; + *result = btp_strndup(*input, count); + *input += count; + return true; +} + +int +btp_skip_string(char **input, const char *string) +{ + char *local_input = *input; + const char *local_string = string; + while (*local_string && *local_input && *local_input == *local_string) + { + ++local_input; + ++local_string; + } + if (*local_string != '\0') + return 0; + int count = local_input - *input; + *input = local_input; + return count; +} + +bool +btp_parse_string(char **input, const char *string, char **result) +{ + char *local_input = *input; + const char *local_string = string; + while (*local_string && *local_input && *local_input == *local_string) + { + ++local_input; + ++local_string; + } + if (*local_string != '\0') + return false; + *result = btp_strndup(string, local_input - *input); + *input = local_input; + return true; +} + +char +btp_parse_digit(char **input) +{ + char digit = **input; + if (digit < '0' || digit > '9') + return '\0'; + ++*input; + return digit; +} + +int +btp_skip_unsigned_integer(char **input) +{ + return btp_skip_char_span(input, "0123456789"); +} + +int +btp_parse_unsigned_integer(char **input, unsigned *result) +{ + char *local_input = *input; + char *numstr; + int length = btp_parse_char_span(&local_input, + "0123456789", + &numstr); + if (0 == length) + return 0; + + char *endptr; + errno = 0; + unsigned long r = strtoul(numstr, &endptr, 10); + bool failure = (errno || numstr == endptr || *endptr != '\0' + || r > UINT_MAX); + free(numstr); + if (failure) /* number too big or some other error */ + return 0; + *result = r; + *input = local_input; + return length; +} + +int +btp_skip_hexadecimal_number(char **input) +{ + char *local_input = *input; + if (!btp_skip_char(&local_input, '0')) + return 0; + if (!btp_skip_char(&local_input, 'x')) + return 0; + int count = 2; + count += btp_skip_char_span(&local_input, "abcdef0123456789"); + if (2 == count) /* btp_skip_char_span returned 0 */ + return 0; + *input = local_input; + return count; +} + +int +btp_parse_hexadecimal_number(char **input, uint64_t *result) +{ + char *local_input = *input; + if (!btp_skip_char(&local_input, '0')) + return 0; + if (!btp_skip_char(&local_input, 'x')) + return 0; + int count = 2; + char *numstr; + count += btp_parse_char_span(&local_input, + "abcdef0123456789", + &numstr); + + if (2 == count) /* btp_parse_char_span returned 0 */ + return 0; + char *endptr; + errno = 0; + unsigned long long r = strtoull(numstr, &endptr, 16); + bool failure = (errno || numstr == endptr || *endptr != '\0'); + free(numstr); + if (failure) /* number too big or some other error */ + return 0; + *result = r; + *input = local_input; + return count; +} diff --git a/src/btparser/utils.h b/src/btparser/utils.h new file mode 100644 index 00000000..680e67e0 --- /dev/null +++ b/src/btparser/utils.h @@ -0,0 +1,284 @@ +/* + utils.h + + Copyright (C) 2010 Red Hat, 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 BTPARSER_UTILS_H +#define BTPARSER_UTILS_H + +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define BTP_lower "abcdefghijklmnopqrstuvwxyz" +#define BTP_upper "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define BTP_alpha BTP_lower BTP_upper +#define BTP_space " \t\r\n\v\f" +#define BTP_digit "0123456789" +#define BTP_alnum BTP_alpha BTP_digit + +/** + * Debugging output to stdout while parsing. + * Default value is false. + */ +extern bool +btp_debug_parser; + +/** + * Never returns NULL. + */ +void * +btp_malloc(size_t size); + +/** + * Never returns NULL. + */ +char * +btp_vasprintf(const char *format, va_list p); + +/** + * Never returns NULL. + */ +char * +btp_strdup(const char *s); + +/** + * Never returns NULL. + */ +char * +btp_strndup(const char *s, size_t n); + +/** + * A strcmp() variant that works also with NULL parameters. NULL is + * considered to be less than a string. + */ +int +btp_strcmp0(const char *s1, const char *s2); + +/** + * A strchr() variant providing line and column in the string s + * indicating where the char c was found. + * @param line + * Starts from 1. Its value is valid only when this function does not + * return NULL. + * @param column + * Starts from 0. Its value is valid only when this function does not + * return NULL. + */ +char * +btp_strchr_location(const char *s, int c, int *line, int *column); + +/** + * A strstr() variant providing line and column of the haystick + * indicating where the needle was found. + * @param line + * Starts from 1. Its value is valid only when this function does not + * return NULL. + * @param column + * Starts from 0. Its value is valid only when this function does not + * return NULL. + */ +char * +btp_strstr_location(const char *haystack, + const char *needle, + int *line, + int *column); + +/** + * A strspn() variant providing line and column of the string s which + * corresponds to the returned length. + * @param line + * Starts from 1. + * @param column + * Starts from 0. + */ +size_t +btp_strspn_location(const char *s, + const char *accept, + int *line, + int *column); + +/** + * Loads file contents to a string. + * @returns + * File contents. If file opening/reading fails, NULL is returned. + */ +char * +btp_file_to_string(const char *filename); + +/** + * If the input contains character c in the current positon, move the + * input pointer after the character, and return true. Otherwise do + * not modify the input and return false. + */ +bool +btp_skip_char(char **input, char c); + +/** + * If the input contains one of allowed characters, move + * the input pointer after that character, and return true. + * Otherwise do not modify the input and return false. + */ +bool +btp_skip_char_limited(char **input, const char *allowed); + +/** + * If the input contains one of allowed characters, store + * the character to the result, move the input pointer after + * that character, and return true. Otherwise do not modify + * the input and return false. + */ +bool +btp_parse_char_limited(char **input, const char *allowed, char *result); + +/** + * If the input contains the character c one or more times, update it + * so that the characters are skipped. Returns the number of characters + * skipped, thus zero if **input does not contain c. + */ +int +btp_skip_char_sequence(char **input, char c); + +/** + * If the input contains one or more characters from string chars, + * move the input pointer after the sequence. Otherwise do not modify + * the input. + * @returns + * The number of characters skipped. + */ +int +btp_skip_char_span(char **input, const char *chars); + +/** + * If the input contains one or more characters from string chars, + * move the input pointer after the sequence. Otherwise do not modify + * the input. + * @param line + * Starts from 1. Corresponds to the returned number. + * @param column + * Starts from 0. Corresponds to the returned number. + * @returns + * The number of characters skipped. + */ +int +btp_skip_char_span_location(char **input, + const char *chars, + int *line, + int *column); + +/** + * If the input contains one or more characters from string accept, + * create a string from this sequence and store it to the result, move + * the input pointer after the sequence, and return the lenght of the + * sequence. Otherwise do not modify the input and return 0. + * + * If this function returns nonzero value, the caller is responsible + * to free the result. + */ +int +btp_parse_char_span(char **input, const char *accept, char **result); + +/** + * If the input contains characters which are not in string reject, + * create a string from this sequence and store it to the result, + * move the input pointer after the sequence, and return true. + * Otherwise do not modify the input and return false. + * + * If this function returns true, the caller is responsible to + * free the result. + */ +bool +btp_parse_char_cspan(char **input, const char *reject, char **result); + +/** + * If the input contains the string, move the input pointer after + * the sequence. Otherwise do not modify the input. + * @returns + * Number of characters skipped. 0 if the input does not contain the + * string. + */ +int +btp_skip_string(char **input, const char *string); + +/** + * If the input contains the string, copy the string to result, + * move the input pointer after the string, and return true. + * Otherwise do not modify the input and return false. + * + * If this function returns true, the caller is responsible to free + * the result. + */ +bool +btp_parse_string(char **input, const char *string, char **result); + +/** + * If the input contains digit 0-9, return it as a character + * and move the input pointer after it. Otherwise return + * '\0' and do not modify the input. + */ +char +btp_parse_digit(char **input); + +/** + * If the input contains [0-9]+, move the input pointer + * after the number. + * @returns + * The number of skipped characters. 0 if input does not start with a + * digit. + */ +int +btp_skip_unsigned_integer(char **input); + +/** + * If the input contains [0-9]+, parse it, move the input pointer + * after the number. + * @returns + * Number of parsed characters. 0 if input does not contain a number. + */ +int +btp_parse_unsigned_integer(char **input, unsigned *result); + +/** + * If the input contains 0x[0-9a-f]+, move the input pointer + * after that. + * @returns + * The number of characters processed from input. 0 if the input does + * not contain a hexadecimal number. + */ +int +btp_skip_hexadecimal_number(char **input); + +/** + * If the input contains 0x[0-9a-f]+, parse the number, and move the + * input pointer after it. Otherwise do not modify the input. + * @returns + * The number of characters read from input. 0 if the input does not + * contain a hexadecimal number. + */ +int +btp_parse_hexadecimal_number(char **input, uint64_t *result); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/daemon/Daemon.cpp b/src/daemon/Daemon.cpp index b8c23edd..d7ff5781 100644 --- a/src/daemon/Daemon.cpp +++ b/src/daemon/Daemon.cpp @@ -898,6 +898,13 @@ int main(int argc, char** argv) if (opts & OPT_s) start_syslog_logging(); + /* When dbus daemon starts us, it doesn't set PATH + * (I saw it set only DBUS_STARTER_ADDRESS and DBUS_STARTER_BUS_TYPE). + * In this case, set something sane: + */ + if (!getenv("PATH")) + putenv((char*)"PATH=/usr/sbin:/usr/bin:/sbin:/bin"); + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); msg_prefix = "abrtd"; /* for log(), error_msg() and such */ diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index 177d4f37..3d307128 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -4,6 +4,7 @@ bin_SCRIPTS = \ sbin_PROGRAMS = abrtd \ abrt-server \ + abrt-action-analyze-c \ abrt-action-generate-backtrace \ abrt-action-save-package-data @@ -58,6 +59,24 @@ abrt_server_CPPFLAGS = \ abrt_server_LDADD = \ ../../lib/utils/libABRTUtils.la +abrt_action_analyze_c_SOURCES = \ + abrt-action-analyze-c.c +abrt_action_analyze_c_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_action_analyze_c_LDADD = \ + ../../lib/utils/libABRTUtils.la + abrt_action_generate_backtrace_SOURCES = \ abrt-action-generate-backtrace.c abrt_action_generate_backtrace_CPPFLAGS = \ @@ -74,7 +93,8 @@ abrt_action_generate_backtrace_CPPFLAGS = \ -D_GNU_SOURCE \ -Wall -Werror abrt_action_generate_backtrace_LDADD = \ - ../../lib/utils/libABRTUtils.la + ../../lib/utils/libABRTUtils.la \ + ../btparser/libbtparser.la abrt_action_save_package_data_SOURCES = \ rpm.h rpm.c \ diff --git a/src/daemon/MiddleWare.cpp b/src/daemon/MiddleWare.cpp index f499f3b7..9e7f96ab 100644 --- a/src/daemon/MiddleWare.cpp +++ b/src/daemon/MiddleWare.cpp @@ -64,8 +64,8 @@ static bool DebugDumpToCrashReport(const char *pDebugDumpDir, map_crash_data_t& { VERB3 log(" DebugDumpToCrashReport('%s')", pDebugDumpDir); - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, pDebugDumpDir, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(pDebugDumpDir, /*flags:*/ 0); + if (!dd) return false; const char *const *v = must_have_files; @@ -177,8 +177,8 @@ mw_result_t CreateCrashReport(const char *crash_id, mw_result_t r = MW_OK; try { - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, row->db_dump_dir, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(row->db_dump_dir, /*flags:*/ 0); + if (!dd) { db_row_free(row); return MW_ERROR; @@ -333,8 +333,8 @@ report_status_t Report(const map_crash_data_t& client_report, const char *backtrace = get_crash_data_item_content_or_NULL(client_report, FILENAME_BACKTRACE); if (comment || reproduce || backtrace) { - struct dump_dir *dd = dd_init(); - if (dd_opendir(dd, pDumpDir.c_str(), 0)) + struct dump_dir *dd = dd_opendir(pDumpDir.c_str(), /*flags:*/ 0); + if (dd) { if (comment) { @@ -351,8 +351,8 @@ report_status_t Report(const map_crash_data_t& client_report, dd_save_text(dd, FILENAME_BACKTRACE, backtrace); add_to_crash_data_ext(stored_report, FILENAME_BACKTRACE, CD_TXT, CD_ISEDITABLE, backtrace); } + dd_close(dd); } - dd_close(dd); } /* Remove BIN filenames from stored_report if they are not present in client's data */ @@ -700,8 +700,8 @@ mw_result_t SaveDebugDump(const char *pDebugDumpDir, { mw_result_t res; - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, pDebugDumpDir, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(pDebugDumpDir, /*flags:*/ 0); + if (!dd) return MW_ERROR; char *time = dd_load_text(dd, FILENAME_TIME); @@ -770,8 +770,8 @@ mw_result_t FillCrashInfo(const char *crash_id, if (!row) return MW_ERROR; - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, row->db_dump_dir, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(row->db_dump_dir, /*flags:*/ 0); + if (!dd) { db_row_free(row); return MW_ERROR; diff --git a/src/daemon/abrt-action-analyze-c.c b/src/daemon/abrt-action-analyze-c.c new file mode 100644 index 00000000..de454daf --- /dev/null +++ b/src/daemon/abrt-action-analyze-c.c @@ -0,0 +1,238 @@ +/* + 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 "abrtlib.h" +#include "parse_options.h" + +#define PROGNAME "abrt-action-analyze-c" + +static void create_hash(char hash_str[SHA1_RESULT_LEN*2 + 1], const char *pInput) +{ + unsigned len; + 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); +} + +static char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) +{ + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return NULL; + char *uid_str = dd_load_text(dd, CD_UID); + dd_close(dd); + unsigned uid = xatoi_u(uid_str); + free(uid_str); + + int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETGUID | EXECFLG_SETSID | EXECFLG_QUIET; + VERB1 flags &= ~EXECFLG_QUIET; + int pipeout[2]; + char* args[4]; + args[0] = (char*)"eu-unstrip"; + args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, dump_dir_name); + args[2] = (char*)"-n"; + args[3] = NULL; + pid_t child = fork_execv_on_steroids(flags, args, pipeout, /*unsetenv_vec:*/ NULL, /*dir:*/ NULL, uid); + free(args[1]); + + /* Bugs in unstrip or corrupted coredumps can cause it 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 + timeout_sec; + struct strbuf *buf_out = strbuf_new(); + while (1) + { + int timeout = endtime - t; + if (timeout < 0) + { + kill(child, SIGKILL); + strbuf_append_strf(buf_out, "\nTimeout exceeded: %u seconds, killing %s\n", timeout_sec, args[0]); + 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'; + strbuf_append_str(buf_out, buff); + t = time(NULL); + } + close(pipeout[0]); + + /* Prevent having zombie child process */ + int status; + waitpid(child, &status, 0); + + if (status != 0) + { + /* unstrip didnt exit with exitcode 0 */ + strbuf_free(buf_out); + return NULL; + } + + return strbuf_free_nobuf(buf_out); +} + +static void trim_unstrip_output(char *result, const char *unstrip_n_output) +{ + // 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 + // ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // we drop everything except the marked part ^ + + char *dst = result; + 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)) + { + *dst++ = *plus; + } + } + } + if (*eol != '\n') break; + line = eol + 1; + } + *dst = '\0'; +} + +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + /* Can't keep these strings/structs static: _() doesn't support that */ + const char *program_usage_string = _( + PROGNAME" [-vs] -d DIR\n\n" + "Calculates and saves UUID of coredumps" + ); + const char *dump_dir_name = "."; + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_s = 1 << 2, + }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Crash dump directory")), + OPT_BOOL( 's', NULL, NULL, _("Log to syslog" )), + OPT_END() + }; + /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); + + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + +//Maybe we will want this... later +// msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); +// if (opts & OPT_s) +// { +// openlog(msg_prefix, 0, LOG_DAEMON); +// logmode = LOGMODE_SYSLOG; +// } + + /* Run unstrip -n and trim its output, leaving only sizes and build ids */ + + char *unstrip_n_output = run_unstrip_n(dump_dir_name, /*timeout_sec:*/ 30); + if (!unstrip_n_output) + return 1; /* bad dump_dir_name, can't run unstrip, etc... */ + /* modifies unstrip_n_output in-place: */ + trim_unstrip_output(unstrip_n_output, unstrip_n_output); + + /* Hash package + executable + unstrip_n_output and save it as UUID */ + + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + return 1; + + char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); + char *package = dd_load_text(dd, FILENAME_PACKAGE); + /* Package variable has "firefox-3.5.6-1.fc11[.1]" format */ + /* Remove distro suffix and maybe least significant version number */ + char *p = 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(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'; + } + } + + char *string_to_hash = xasprintf("%s%s%s", package, executable, unstrip_n_output); + /*free(package);*/ + /*free(executable);*/ + /*free(unstrip_n_output);*/ + + char hash_str[SHA1_RESULT_LEN*2 + 1]; + create_hash(hash_str, string_to_hash); + /*free(hash_str);*/ + + dd_save_text(dd, CD_UUID, hash_str); + dd_close(dd); + + return 0; +} diff --git a/src/daemon/abrt-action-bugzilla.cpp b/src/daemon/abrt-action-bugzilla.cpp index c7768556..6c989ea0 100644 --- a/src/daemon/abrt-action-bugzilla.cpp +++ b/src/daemon/abrt-action-bugzilla.cpp @@ -569,8 +569,8 @@ static void report_to_bugzilla( const char *dump_dir_name, /*const*/ map_plugin_settings_t& settings) { - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, dump_dir_name, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) { throw CABRTException(EXCEP_PLUGIN, _("Can't open '%s'"), dump_dir_name); } diff --git a/src/daemon/abrt-action-generate-backtrace.c b/src/daemon/abrt-action-generate-backtrace.c index b87c7c9e..9e4fc078 100644 --- a/src/daemon/abrt-action-generate-backtrace.c +++ b/src/daemon/abrt-action-generate-backtrace.c @@ -17,7 +17,9 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "abrtlib.h" -#include "backtrace.h" +#include "../btparser/backtrace.h" +#include "../btparser/frame.h" +#include "../btparser/location.h" #include "parse_options.h" @@ -252,10 +254,10 @@ enum { /* Keep enum above and order of options below in sync! */ static struct options abrt_action_generate_backtrace_options[] = { OPT__VERBOSE(&g_verbose), - OPT_STRING( 'd' , 0, &dump_dir_name, "dir", "Crash dump directory"), - OPT_STRING( 'i' , 0, &i_opt, "dir1[:dir2]...", "Additional debuginfo directories"), - OPT_INTEGER( 't' , 0, &exec_timeout_sec, "Kill gdb if it runs for more than SECONDS"), - OPT_BOOL( 's' , 0, NULL, "Log to syslog even with -d"), + OPT_STRING( 'd', NULL, &dump_dir_name, "DIR", "Crash dump directory"), + OPT_STRING( 'i', NULL, &i_opt, "dir1[:dir2]...", "Additional debuginfo directories"), + OPT_INTEGER('t', NULL, &exec_timeout_sec, "Kill gdb if it runs for more than N seconds"), + OPT_BOOL( 's', NULL, NULL, "Log to syslog"), OPT_END() }; @@ -283,8 +285,8 @@ int main(int argc, char **argv) logmode = LOGMODE_SYSLOG; } - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, dump_dir_name, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) return 1; char *package = dd_load_text(dd, FILENAME_PACKAGE); @@ -299,104 +301,84 @@ int main(int argc, char **argv) VERB3 log("get_backtrace() returns NULL, broken core/gdb?"); } - dd = dd_init(); - if (!dd_opendir(dd, dump_dir_name, DD_CLOSE_ON_OPEN_ERR)) + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) return 1; dd_save_text(dd, FILENAME_BACKTRACE, backtrace_str); /* Compute and store backtrace hash. */ - char *backtrace_cpy = xstrdup(backtrace_str); - struct backtrace *backtrace = backtrace_parse(backtrace_cpy, false, false); - free(backtrace_cpy); - if (backtrace) + struct btp_location location; + btp_location_init(&location); + char *backtrace_str_ptr = backtrace_str; + struct btp_backtrace *backtrace = btp_backtrace_parse(&backtrace_str_ptr, &location); + if (!backtrace) { - /* Get the quality of the full backtrace. */ - float q1 = backtrace_quality(backtrace); - - /* Remove all the other threads except the crash thread. */ - struct thread *crash_thread = backtrace_find_crash_thread(backtrace); - if (crash_thread) - backtrace_remove_threads_except_one(backtrace, crash_thread); - else - log_msg("Detection of crash thread failed"); - - /* Get the quality of the crash thread. */ - float q2 = backtrace_quality(backtrace); - - backtrace_remove_noncrash_frames(backtrace); - - /* Do the frame removal now. */ - backtrace_limit_frame_depth(backtrace, 5); - /* Frame removal can be done before removing exit handlers. */ - backtrace_remove_exit_handlers(backtrace); - - /* Get the quality of frames around the crash. */ - float q3 = backtrace_quality(backtrace); - - /* Compute UUID. */ - struct strbuf *bt = backtrace_tree_as_str(backtrace, false); - strbuf_prepend_str(bt, executable); - strbuf_prepend_str(bt, package); + VERB1 log(_("Backtrace parsing failed for %s"), dump_dir_name); + VERB1 log("%d:%d: %s", location.line, location.column, location.message); + /* If the parser failed compute the UUID from the executable + and package only. This is not supposed to happen often. + Do not store the rating, as we do not know how good the + backtrace is. */ + struct strbuf *emptybt = strbuf_new(); + strbuf_prepend_str(emptybt, executable); + strbuf_prepend_str(emptybt, package); char hash_str[SHA1_RESULT_LEN*2 + 1]; - create_hash(hash_str, bt->buf); + create_hash(hash_str, emptybt->buf); dd_save_text(dd, FILENAME_DUPHASH, hash_str); - strbuf_free(bt); - - /* Compute and store backtrace rating. The crash frame - is more important that the others. The frames around - the crash are more important than the rest. */ - float qtot = 0.25f * q1 + 0.35f * q2 + 0.4f * q3; - - /* Turn the quality to rating. */ - const char *rating; - if (qtot < 0.6f) rating = "0"; - else if (qtot < 0.7f) rating = "1"; - else if (qtot < 0.8f) rating = "2"; - else if (qtot < 0.9f) rating = "3"; - else rating = "4"; - dd_save_text(dd, FILENAME_RATING, rating); - - /* Get the function name from the crash frame. */ - if (crash_thread) - { - struct frame *crash_frame = crash_thread->frames; - struct frame *abort_frame = thread_find_abort_frame(crash_thread); - if (abort_frame) - crash_frame = abort_frame->next; - if (crash_frame && crash_frame->function && 0 != strcmp(crash_frame->function, "??")) - dd_save_text(dd, FILENAME_CRASH_FUNCTION, crash_frame->function); - } - backtrace_free(backtrace); - } - else - { - /* If the parser failed fall back to the independent backtrace. */ - /* If we write and use a hand-written parser instead of the bison one, - the parser never fails, and it will be possible to get rid of - the independent_backtrace and backtrace_rate_old. */ - struct strbuf *ibt = independent_backtrace(backtrace_str); - strbuf_prepend_str(ibt, executable); - strbuf_prepend_str(ibt, package); - char hash_str[SHA1_RESULT_LEN*2 + 1]; - create_hash(hash_str, ibt->buf); - dd_save_text(dd, FILENAME_DUPHASH, hash_str); - strbuf_free(ibt); - - /* Compute and store backtrace rating. */ - /* Crash frame is not known so store nothing. */ - int rate = backtrace_rate_old(backtrace_str); - char rate_buf[sizeof(rate)*3 + 2]; - sprintf(rate_buf, "%d", rate); - dd_save_text(dd, FILENAME_RATING, rate_buf); + strbuf_free(emptybt); + free(backtrace_str); + free(package); + free(executable); + dd_close(dd); + return 2; } + free(backtrace_str); + /* Compute duplication hash. */ + char *str_hash_core = btp_backtrace_get_duplication_hash(backtrace); + struct strbuf *str_hash = strbuf_new(); + strbuf_append_str(str_hash, package); + strbuf_append_str(str_hash, executable); + strbuf_append_str(str_hash, str_hash_core); + char hash_str[SHA1_RESULT_LEN*2 + 1]; + create_hash(hash_str, str_hash->buf); + dd_save_text(dd, FILENAME_DUPHASH, hash_str); + strbuf_free(str_hash); + free(str_hash_core); + + /* Compute the backtrace rating. */ + float quality = btp_backtrace_quality_complex(backtrace); + const char *rating; + if (quality < 0.6f) + rating = "0"; + else if (quality < 0.7f) + rating = "1"; + else if (quality < 0.8f) + rating = "2"; + else if (quality < 0.9f) + rating = "3"; + else + rating = "4"; + dd_save_text(dd, FILENAME_RATING, rating); + + /* Get the function name from the crash frame. */ + struct btp_frame *crash_frame = btp_backtrace_get_crash_frame(backtrace); + if (crash_frame) + { + if (crash_frame->function_name && + 0 != strcmp(crash_frame->function_name, "??")) + { + dd_save_text(dd, FILENAME_CRASH_FUNCTION, crash_frame->function_name); + } + btp_frame_free(crash_frame); + } + btp_backtrace_free(backtrace); dd_close(dd); free(executable); free(package); - free(backtrace_str); return 0; } diff --git a/src/daemon/abrt-action-save-package-data.cpp b/src/daemon/abrt-action-save-package-data.cpp index 9dbddac4..4d397071 100644 --- a/src/daemon/abrt-action-save-package-data.cpp +++ b/src/daemon/abrt-action-save-package-data.cpp @@ -23,6 +23,8 @@ #include "rpm.h" #include "parse_options.h" +#define PROGNAME "abrt-action-save-package-data" + /** * Returns the first full path argument in the command line or NULL. * Skips options are in form "-XXX". @@ -76,8 +78,8 @@ static bool is_path_blacklisted(const char *path) static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) { - struct dump_dir *dd = dd_init(); - if (!dd_opendir(dd, dump_dir_name, DD_CLOSE_ON_OPEN_ERR)) + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) return 1; char *remote_str = dd_load_text(dd, FILENAME_REMOTE); @@ -120,8 +122,8 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) if (g_settings_bProcessUnpackaged || remote) { VERB2 log("Crash in unpackaged executable '%s', proceeding without packaging information", executable); - dd = dd_init(); - if (!dd_opendir(dd, dump_dir_name, DD_CLOSE_ON_OPEN_ERR)) + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) goto ret; /* return 1 (failure) */ dd_save_text(dd, FILENAME_PACKAGE, ""); dd_save_text(dd, FILENAME_DESCRIPTION, "Crashed executable does not belong to any installed package"); @@ -222,8 +224,8 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) component = rpm_get_component(executable); dsc = rpm_get_description(package_short_name); - dd = dd_init(); - if (!dd_opendir(dd, dump_dir_name, DD_CLOSE_ON_OPEN_ERR)) + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) goto ret; /* return 1 (failure) */ } @@ -268,7 +270,10 @@ static int SavePackageDescriptionToDebugDump(const char *dump_dir_name) } static const char *dump_dir_name = "."; -static const char abrt_action_save_package_data_usage[] = "abrt-action-save-package-data [options] -d DIR"; +static const char abrt_action_save_package_data_usage[] = + PROGNAME" [options] -d DIR\n" + "\n" + "Query package database and save package name, component, and description"; enum { OPT_v = 1 << 0, OPT_d = 1 << 1, @@ -277,8 +282,8 @@ enum { /* Keep enum above and order of options below in sync! */ static struct options abrt_action_save_package_data_options[] = { OPT__VERBOSE(&g_verbose), - OPT_STRING( 'd' , 0, &dump_dir_name, "dir", "Crash dump directory"), - OPT_BOOL( 's' , 0, NULL, "Log to syslog"), + OPT_STRING('d', NULL, &dump_dir_name, "DIR", "Crash dump directory"), + OPT_BOOL( 's', NULL, NULL, "Log to syslog"), OPT_END() }; @@ -292,7 +297,7 @@ int main(int argc, char **argv) abrt_action_save_package_data_usage); putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); - msg_prefix = xasprintf("abrt-action-save-package-data[%u]", getpid()); + msg_prefix = xasprintf(PROGNAME"[%u]", getpid()); if (opts & OPT_s) { diff --git a/src/daemon/abrt-server.c b/src/daemon/abrt-server.c index 231fc0bd..67978561 100644 --- a/src/daemon/abrt-server.c +++ b/src/daemon/abrt-server.c @@ -115,11 +115,9 @@ static void create_debug_dump() /* No need to check the path length, as all variables used are limited, and dd_create() fails if the path is too long. */ - struct dump_dir *dd = dd_init(); - if (!dd_create(dd, path, client_uid)) + struct dump_dir *dd = dd_create(path, client_uid); + if (!dd) { - dd_delete(dd); - dd_close(dd); error_msg_and_die("Error creating crash dump %s", path); } diff --git a/src/gui/CCDump.py b/src/gui/CCDump.py index e94d0c9b..e02cb7ad 100644 --- a/src/gui/CCDump.py +++ b/src/gui/CCDump.py @@ -36,7 +36,7 @@ FILENAME_HOSTNAME = "hostname" FILENAME_REMOTE = "remote" CD_UID = "uid" -CD_UUID = "UUID" +CD_UUID = "uuid" CD_INFORMALL = "InformAll" CD_DUMPDIR = "DumpDir" CD_COUNT = "Count" @@ -70,7 +70,7 @@ class Dump(): return "Dump instance" def getUUID(self): - return self.UUID + return self.uuid def getUID(self): return self.uid diff --git a/src/hooks/abrt-hook-ccpp.cpp b/src/hooks/abrt-hook-ccpp.cpp index a35bba11..146de6e0 100644 --- a/src/hooks/abrt-hook-ccpp.cpp +++ b/src/hooks/abrt-hook-ccpp.cpp @@ -388,14 +388,15 @@ int main(int argc, char** argv) close(fd); } - if (strstr(executable, "/abrtd")) + const char *last_slash = strrchr(executable, '/'); + if (last_slash && strncmp(++last_slash, "abrt", 4) == 0) { - /* If abrtd crashes, we don't want to create a _directory_, + /* If abrtd/abrt-foo crashes, we don't want to create a _directory_, * since that can make new copy of abrtd to process it, * and maybe crash again... * Unlike dirs, mere files are ignored by abrtd. */ - snprintf(path, sizeof(path), "%s/abrtd-coredump", dddir); + snprintf(path, sizeof(path), "%s/%s-coredump", dddir, last_slash); int abrt_core_fd = xopen3(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); off_t core_size = copyfd_eof(STDIN_FILENO, abrt_core_fd, COPYFD_SPARSE); if (core_size < 0 || fsync(abrt_core_fd) != 0) @@ -414,8 +415,8 @@ int main(int argc, char** argv) if (path_len >= (sizeof(path) - sizeof("/"FILENAME_COREDUMP))) return 1; - struct dump_dir *dd = dd_init(); - if (dd_create(dd, path, uid)) + struct dump_dir *dd = dd_create(path, uid); + if (dd) { char *cmdline = get_cmdline(pid); /* never NULL */ char *reason = xasprintf("Process %s was killed by signal %s (SIG%s)", executable, signal_str, signame ? signame : signal_str); diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am deleted file mode 100644 index 29e06645..00000000 --- a/src/utils/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -bin_PROGRAMS = abrt-backtrace -abrt_backtrace_CFLAGS = -Wall -abrt_backtrace_SOURCES = abrt-backtrace.c -abrt_backtrace_CPPFLAGS = \ - -I$(srcdir)/../../inc \ - -I$(srcdir)/../../lib/utils -abrt_backtrace_LDADD = \ - ../../lib/utils/libABRTUtils.la - -man_MANS = abrt-backtrace.1 -EXTRA_DIST = $(man_MANS) diff --git a/src/utils/abrt-backtrace.1 b/src/utils/abrt-backtrace.1 deleted file mode 100644 index 4383f1d0..00000000 --- a/src/utils/abrt-backtrace.1 +++ /dev/null @@ -1,59 +0,0 @@ -.TH abrt\-backtrace "1" "23 Nov 2009" "" -.SH NAME -abrt\-backtrace \- a backtrace analyzer for abrt -.SH SYNOPSIS -.B abrt\-backtrace -[option]... [FILE] -.SH DESCRIPTION -.I abrt\-backtrace -is a command line tool that analyzes backtraces produced by -GDB and provides their textual representation useful for -crash duplication detection. - -By default, abrt\-backtrace prints the backtrace tree created by -parsing the input file. - -.SH OPTIONS -.B Basic startup options -.IP "\-V, \-\-version" -Displays version of abrt\-backtrace. -.IP "\-?, \-\-help" -Print a help message describing all of abrt-backtrace’s command-line options. - -.PP -.B Actions -.IP "\-i, \-\-independent" -Prints independent backtrace fallback. The "independent backtrace" -is used as a fallback internal representation of backtrace -when the internal parser fails to process the input due to -unsupported format. - -.PP -.B Various options -.IP "\-n, \-\-single\-thread" -Removes all threads except the one that caused the crash from -the internal representation of the backtrace. -.IP "\-d=N, \-\-frame\-depth=N" -Display only the top N frames in every thread. -Frames that are a part of system error handling do not count to the N. -So more than N frames can be displayed, but N of them are useful for examination. -N must be larger than 1. -.IP "\-r, \-\-remove\-exit\-handlers" -Do not display exit handlers and frames that comes after them in the backtrace. -.IP "\-m, \-\-remove\-noncrash\-frames" -Do not display frames known as not causing the crash, but which are a common -part of backtraces. -.IP "\-a, \-\-rate" -Print backtrace rating from 0 to 4. -.IP "\-c, \-\-crash\-function" -Print crash function if it's successfully detected. -.IP "\-p, \-\-debug\-parser" -Prints debug information when parsing the backtrace. -.IP "\-s, \-\-debug\-scanner" -Prints debug information when scanning the input file for the parser. - -.SH "SEE ALSO" -.IR abrtd (8), -.IR abrt.conf (5), -.IR abrt-cli (1), -.IR abrt-plugins (7) diff --git a/src/utils/abrt-backtrace.c b/src/utils/abrt-backtrace.c deleted file mode 100644 index d02c9633..00000000 --- a/src/utils/abrt-backtrace.c +++ /dev/null @@ -1,339 +0,0 @@ -/* - abrt-backtrace.c - parses command line arguments - - 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 <argp.h> -#include <stdlib.h> -#include <sysexits.h> -#include <string.h> -#include "config.h" -#include "backtrace.h" -#include "strbuf.h" - -/* Too large files are trimmed. */ -#define FILE_SIZE_LIMIT 20000000 /* ~ 20 MB */ - -#define EX_PARSINGFAILED EX__MAX + 1 /* = 79 */ -#define EX_THREADDETECTIONFAILED EX__MAX + 2 /* = 80 */ - -const char *argp_program_version = "abrt-backtrace " VERSION; -const char *argp_program_bug_address = "<crash-catcher@lists.fedorahosted.org>"; - -static char doc[] = "abrt-backtrace -- backtrace analyzer"; - -/* A description of the arguments we accept. */ -static char args_doc[] = "FILE"; - -static struct argp_option options[] = { - {"independent" , 'i', 0 , 0, "Prints independent backtrace (fallback)"}, - {"single-thread" , 'n', 0 , 0, "Display the crash thread only in the backtrace"}, - {"frame-depth" , 'd', "N", 0, "Display only top N frames under the crash frame"}, - {"remove-exit-handlers" , 'r', 0 , 0, "Removes exit handler frames from the displayed backtrace"}, - {"remove-noncrash-frames", 'm', 0 , 0, "Removes common frames known as not causing crash"}, - {"rate" , 'a', 0 , 0, "Prints the backtrace rating from 0 to 4"}, - {"crash-function" , 'c', 0 , 0, "Prints crash function"}, - {"debug-parser" , 'p', 0 , 0, "Prints parser debug information"}, - {"debug-scanner" , 's', 0 , 0, "Prints scanner debug information"}, - {"verbose" , 'v', 0 , 0, "Prints human-friendly superfluous output."}, - { 0 } -}; - -struct arguments -{ - bool independent; - bool single_thread; - int frame_depth; /* negative == do not limit the depth */ - bool remove_exit_handlers; - bool remove_noncrash_frames; - bool debug_parser; - bool debug_scanner; - bool verbose; - bool rate; - bool crash_function; - char *filename; -}; - -static error_t -parse_opt (int key, char *arg, struct argp_state *state) -{ - /* Get the input argument from argp_parse, which we - know is a pointer to our arguments structure. */ - struct arguments *arguments = (struct arguments*)state->input; - - switch (key) - { - case 'i': arguments->independent = true; break; - case 'n': arguments->single_thread = true; break; - case 'd': - if (1 != sscanf(arg, "%d", &arguments->frame_depth)) - { - /* Must be a number. */ - argp_usage(state); - exit(EX_USAGE); /* Invalid argument */ - } - break; - case 'r': arguments->remove_exit_handlers = true; break; - case 'm': arguments->remove_noncrash_frames = true; break; - case 'p': arguments->debug_parser = true; break; - case 's': arguments->debug_scanner = true; break; - case 'v': arguments->verbose = true; break; - case 'a': arguments->rate = true; break; - case 'c': arguments->crash_function = true; break; - - case ARGP_KEY_ARG: - if (arguments->filename) - { - /* Too many arguments. */ - argp_usage(state); - exit(EX_USAGE); /* Invalid argument */ - } - arguments->filename = arg; - break; - - case ARGP_KEY_END: - break; - - default: - return ARGP_ERR_UNKNOWN; - } - return 0; -} - -/* Our argp parser. */ -static struct argp argp = { options, parse_opt, args_doc, doc }; - -#define PYTHON_BACKTRACE_ID1 "\n\nTraceback (most recent call last):\n" -#define PYTHON_BACKTRACE_ID2 "\n\nLocal variables in innermost frame:\n" - -int main(int argc, char **argv) -{ - /* Set options default values and parse program command line. */ - struct arguments arguments; - arguments.independent = false; - arguments.frame_depth = -1; - arguments.single_thread = false; - arguments.remove_exit_handlers = false; - arguments.remove_noncrash_frames = false; - arguments.debug_parser = false; - arguments.debug_scanner = false; - arguments.verbose = false; - arguments.filename = 0; - arguments.rate = false; - arguments.crash_function = false; - argp_parse(&argp, argc, argv, 0, 0, &arguments); - - /* If we are about to rate a backtrace, the other values must be set accordingly, - no matter what the user set on the command line. */ - if (arguments.rate) - { - arguments.independent = false; - arguments.frame_depth = 5; - arguments.single_thread = true; - arguments.remove_exit_handlers = true; - arguments.remove_noncrash_frames = true; - } - - /* If we are about to print the crash function, the other values must be set - accordingly. */ - if (arguments.crash_function) - { - arguments.independent = false; - arguments.single_thread = true; - } - - char *bttext = NULL; - - /* If a filename was provided, read input from file. - Otherwise read from stdin. */ - if (arguments.filename) - { - /* Open input file, and parse it. */ - FILE *fp = fopen(arguments.filename, "r"); - if (!fp) - { - fprintf(stderr, "Unable to open '%s'.\n", arguments.filename); - exit(EX_NOINPUT); /* No such file or directory */ - } - - /* Header and footer of the backtrace is stripped to simplify the parser. - * A drawback is that the backtrace must be loaded to memory. - */ - fseek(fp, 0, SEEK_END); - size_t size = ftell(fp); - fseek(fp, 0, SEEK_SET); - - if (size > FILE_SIZE_LIMIT) - { - fprintf(stderr, "Input file too big (%zd). Maximum size is %d.\n", - size, FILE_SIZE_LIMIT); - exit(EX_IOERR); - } - - /* Handle the case that the input file is empty. - * The code is not designed to support completely empty backtrace. - * Silently exit indicating success. - */ - if (size == 0) - { - fclose(fp); - exit(0); - } - - bttext = malloc(size + 1); - if (!bttext) - { - fclose(fp); - fputs("malloc failed", stderr); - exit(EX_OSERR); - } - - if (1 != fread(bttext, size, 1, fp)) - { - fclose(fp); - fprintf(stderr, "Unable to read from '%s'.\n", arguments.filename); - exit(EX_IOERR); /* IO Error */ - } - - bttext[size] = '\0'; - fclose(fp); - } - else - { - struct strbuf *btin = strbuf_new(); - int c; - while ((c = getchar()) != EOF && c != '\0') - strbuf_append_char(btin, (char)c); - - strbuf_append_char(btin, '\0'); - bttext = btin->buf; - strbuf_free_nobuf(btin); /* free btin, but not its internal buffer */ - } - - /* Detect Python backtraces. If it is a Python backtrace, - * silently exit for now. - */ - if (strstr(bttext, PYTHON_BACKTRACE_ID1) != NULL - && strstr(bttext, PYTHON_BACKTRACE_ID2) != NULL) - { - if (arguments.rate) - puts("4"); - exit(0); - } - - /* Print independent backtrace and exit. */ - if (arguments.independent) - { - struct strbuf *ibt = independent_backtrace(bttext); - puts(ibt->buf); - strbuf_free(ibt); - free(bttext); - return 0; /* OK */ - } - - /* Try to parse the backtrace. */ - struct backtrace *backtrace; - backtrace = backtrace_parse(bttext, arguments.debug_parser, arguments.debug_scanner); - - /* If the parser failed print independent backtrace. */ - if (!backtrace) - { - if (arguments.rate) - { - free(bttext); - puts("0"); - /* Parsing failed, but the output can be used. */ - return EX_PARSINGFAILED; - } - struct strbuf *ibt = independent_backtrace(bttext); - puts(ibt->buf); - strbuf_free(ibt); - free(bttext); - /* Parsing failed, but the output can be used. */ - return EX_PARSINGFAILED; - } - - free(bttext); - - /* [--rate] Get the quality of the full backtrace. */ - float q1 = backtrace_quality(backtrace); - - /* If a single thread is requested, remove all other threads. */ - int retval = 0; - struct thread *crash_thread = NULL; - if (arguments.single_thread) - { - crash_thread = backtrace_find_crash_thread(backtrace); - if (crash_thread) - backtrace_remove_threads_except_one(backtrace, crash_thread); - else - { - fprintf(stderr, "Detection of crash thread failed.\n"); - /* THREAD DETECTION FAILED, BUT THE OUTPUT CAN BE USED */ - retval = EX_THREADDETECTIONFAILED; - } - } - - /* [--rate] Get the quality of the crash thread. */ - float q2 = backtrace_quality(backtrace); - - if (arguments.remove_noncrash_frames) - backtrace_remove_noncrash_frames(backtrace); - - /* If a frame removal is requested, do it now. */ - if (arguments.frame_depth > 0) - backtrace_limit_frame_depth(backtrace, arguments.frame_depth); - - /* Frame removal can be done before removing exit handlers */ - if (arguments.remove_exit_handlers > 0) - backtrace_remove_exit_handlers(backtrace); - - /* [--rate] Get the quality of frames around the crash. */ - float q3 = backtrace_quality(backtrace); - - if (arguments.rate) - { - /* 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"; - puts(rating); - } - - if (arguments.crash_function && 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) - puts(crash_frame->function); - } - - backtrace_print_tree(backtrace, arguments.verbose); - backtrace_free(backtrace); - return retval; -} |
