diff options
Diffstat (limited to 'btparser/lib/frame.c')
-rw-r--r-- | btparser/lib/frame.c | 1027 |
1 files changed, 1027 insertions, 0 deletions
diff --git a/btparser/lib/frame.c b/btparser/lib/frame.c new file mode 100644 index 00000000..83680910 --- /dev/null +++ b/btparser/lib/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 btp_strbuf *str, + bool verbose) +{ + if (verbose) + btp_strbuf_append_strf(str, " #%d", frame->number); + else + btp_strbuf_append_str(str, " "); + + if (frame->function_type) + btp_strbuf_append_strf(str, " %s", frame->function_type); + if (frame->function_name) + btp_strbuf_append_strf(str, " %s", frame->function_name); + if (verbose && frame->source_file) + { + if (frame->function_name) + btp_strbuf_append_str(str, " at"); + btp_strbuf_append_strf(str, " %s", frame->source_file); + } + + if (frame->signal_handler_called) + btp_strbuf_append_str(str, " <signal handler called>"); + + btp_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 btp_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)) \ + { \ + btp_strbuf_append_str(target, "operator"); \ + btp_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 btp_strbuf *buf = btp_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 + btp_strbuf_append_char(buf, ' '); + } + } + + if (strchr(FUNCTION_NAME_CHARS, *local_input) == NULL) + { + if (!space_allowed || strchr(" ", *local_input) == NULL) + break; + } + + btp_strbuf_append_char(buf, *local_input); + ++local_input; + } + + if (buf->len == 0) + { + btp_strbuf_free(buf); + return 0; + } + + *target = btp_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 btp_strbuf *buf = btp_strbuf_new(); + btp_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)) + { + btp_strbuf_append_str(buf, namechunk); + free(namechunk); + } + else + break; + } + + if (!btp_skip_char(&local_input, ')')) + { + btp_strbuf_free(buf); + return 0; + } + + btp_strbuf_append_char(buf, ')'); + *target = btp_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 btp_strbuf *buf = btp_strbuf_new(); + btp_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)) + { + btp_strbuf_append_str(buf, namechunk); + free(namechunk); + } + else + break; + } + + if (!btp_skip_char(&local_input, '>')) + { + btp_strbuf_free(buf); + return 0; + } + + btp_strbuf_append_char(buf, '>'); + *target = btp_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 btp_strbuf *buf0 = btp_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 + { + btp_strbuf_append_char(buf0, first); + ++location->column; + } + } + else + { + int chars = btp_frame_parse_function_name_braces(&local_input, + &namechunk); + if (0 < chars) + { + btp_strbuf_append_str(buf0, namechunk); + free(namechunk); + location->column += chars; + } + else + { + location->message = "Expected function name."; + btp_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; + + btp_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)) + { + btp_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) + { + btp_strbuf_append_char(buf0, space); + btp_location_eat_char(location, space); + btp_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. */ + btp_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 = btp_strbuf_new(); + btp_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; + + btp_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)) + { + btp_strbuf_free(buf0); + btp_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 btp_strbuf *buf = buf1 ? buf1 : buf0; + btp_strbuf_append_char(buf, space); + btp_location_eat_char(location, space); + btp_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. */ + btp_strbuf_free(buf0); + btp_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 = btp_strbuf_free_nobuf(buf1); + *function_type = btp_strbuf_free_nobuf(buf0); + } + else + { + *function_name = btp_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; +} |