384 lines
11 KiB
C
Executable file
384 lines
11 KiB
C
Executable file
/* Copyright (C) 2007-2011 The Android Open Source Project
|
|
**
|
|
** This software is licensed under the terms of the GNU General Public
|
|
** License version 2, as published by the Free Software Foundation, and
|
|
** may be copied, distributed, and modified under those terms.
|
|
**
|
|
** 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.
|
|
*/
|
|
|
|
/*
|
|
* Contains implementation of a class DumpFile of routines that implements
|
|
* access to a log file.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include "regex/regex.h"
|
|
|
|
#include "ndk-stack-parser.h"
|
|
|
|
/* Enumerates states of the crash parser.
|
|
*/
|
|
typedef enum NDK_CRASH_PARSER_STATE {
|
|
/* Parser expects the beginning of the crash dump. */
|
|
EXPECTS_CRASH_DUMP,
|
|
/* Parser expects the build fingerprint, or process and thread information. */
|
|
EXPECTS_BUILD_FINGREPRINT_OR_PID,
|
|
/* Parser expects the process and thread information. */
|
|
EXPECTS_PID,
|
|
/* Parser expects the signal information, or the first crash frame. */
|
|
EXPECTS_SIGNAL_OR_FRAME,
|
|
/* Parser expects a crash frame. */
|
|
EXPECTS_FRAME,
|
|
} NDK_CRASH_PARSER_STATE;
|
|
|
|
/* Crash parser descriptor.
|
|
*/
|
|
struct NdkCrashParser {
|
|
/* Handle to the stream where to print the output. */
|
|
FILE* out_handle;
|
|
|
|
/* Path to the root folder where symbols are stored. */
|
|
char* sym_root;
|
|
|
|
/* Current state of the parser. */
|
|
NDK_CRASH_PARSER_STATE state;
|
|
|
|
/* Compiled regular expressions */
|
|
regex_t re_pid_header;
|
|
regex_t re_sig_header;
|
|
regex_t re_frame_header;
|
|
};
|
|
|
|
/* Crash dumps begin with a string containing this substring. */
|
|
static const char _crash_dump_header[] =
|
|
"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";
|
|
|
|
/* Build fingerprint contains this substring. */
|
|
static const char _build_fingerprint_header[] = "Build fingerprint:";
|
|
|
|
/* Regular expression for the process ID information line. */
|
|
static const char _pid_header[] = "pid: [0-9]+, tid: [0-9]+.*";
|
|
|
|
/* Regular expression for the signal information line. */
|
|
static const char _sig_header[] = "signal*[ \t][0-9]+";
|
|
|
|
/* Regular expression for the frame information line. */
|
|
static const char _frame_header[] = "\\#[0-9]+[ |\t]+[pc|eip]+:*[ |\t]+([0-9a-f]{8})*";
|
|
|
|
#ifndef min
|
|
#define min(a,b) (((a) < (b)) ? a : b)
|
|
#endif
|
|
|
|
/* Parses a line representing a crash frame.
|
|
* This routine will try to obtain source file / line information for the
|
|
* frame's address, and print that information to the specified output handle.
|
|
* Param:
|
|
* parser - NdkCrashParser descriptor, created and initialized with a call to
|
|
* NdkCrashParser routine.
|
|
* frame - Line containing crash frame.
|
|
* Return:
|
|
* 0 If source file information has been found and printed, or -1 if that
|
|
* information was not available.
|
|
*/
|
|
static int ParseFrame(NdkCrashParser* parser, const char* frame);
|
|
|
|
/* Matches a string against a regular expression.
|
|
* Param:
|
|
* line - String to matches against the regular expression.
|
|
* regex - Regular expression to match the string against.
|
|
* match - Upon successful match contains information about the part of the
|
|
* string that matches the regular expression.
|
|
* Return:
|
|
* Boolean: 1 if a match has been found, or 0 if match has not been found in
|
|
* the string.
|
|
*/
|
|
static int MatchRegex(const char* line, const regex_t* regex, regmatch_t* match);
|
|
|
|
/* Returns pointer to the next separator (a space, or a tab) in the string. */
|
|
static const char* next_separator(const char* str);
|
|
|
|
/* Returns pointer to the next token (a character other than space, or a tab)
|
|
* in the string.
|
|
*/
|
|
static const char* next_token(const char* str);
|
|
|
|
/* Gets next token from the string.
|
|
* param:
|
|
* str - String where to get the next token from. Note that if string begins
|
|
* with a separator, this routine will return first token after that
|
|
* separator. If string begins with a token, this routine will return next
|
|
* token after that.
|
|
* token - Upon success contains a copy of the next token in the string.
|
|
* size - Size of the 'token' buffer.
|
|
* Return:
|
|
* Beginning of the returned token in the string.
|
|
*/
|
|
static const char* get_next_token(const char* str, char* token, size_t size);
|
|
|
|
/* Return pointer to first word "pc", "eip", or "ip" in string "frame"
|
|
* param:
|
|
* frame - a line from dump
|
|
* Return:
|
|
* The first occurrence of "pc", "eip", or "ip"
|
|
*/
|
|
static const char* find_pc(const char *frame);
|
|
|
|
NdkCrashParser*
|
|
CreateNdkCrashParser(FILE* out_handle, const char* sym_root)
|
|
{
|
|
NdkCrashParser* parser;
|
|
|
|
parser = (NdkCrashParser*)calloc(sizeof(*parser), 1);
|
|
if (parser == NULL)
|
|
return NULL;
|
|
|
|
parser->out_handle = out_handle;
|
|
parser->state = EXPECTS_CRASH_DUMP;
|
|
|
|
parser->sym_root = strdup(sym_root);
|
|
if (!parser->sym_root)
|
|
goto BAD_INIT;
|
|
|
|
if (regcomp(&parser->re_pid_header, _pid_header, REG_EXTENDED | REG_NEWLINE) ||
|
|
regcomp(&parser->re_sig_header, _sig_header, REG_EXTENDED | REG_NEWLINE) ||
|
|
regcomp(&parser->re_frame_header, _frame_header, REG_EXTENDED | REG_NEWLINE))
|
|
goto BAD_INIT;
|
|
|
|
return parser;
|
|
|
|
BAD_INIT:
|
|
DestroyNdkCrashParser(parser);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
DestroyNdkCrashParser(NdkCrashParser* parser)
|
|
{
|
|
if (parser != NULL) {
|
|
/* Release compiled regular expressions */
|
|
regfree(&parser->re_frame_header);
|
|
regfree(&parser->re_sig_header);
|
|
regfree(&parser->re_pid_header);
|
|
/* Release symbol path */
|
|
free(parser->sym_root);
|
|
/* Release parser itself */
|
|
free(parser);
|
|
}
|
|
}
|
|
|
|
int
|
|
ParseLine(NdkCrashParser* parser, const char* line)
|
|
{
|
|
regmatch_t match;
|
|
int found = 0;
|
|
|
|
if (line == NULL || *line == '\0') {
|
|
// Nothing to parse.
|
|
return 1;
|
|
}
|
|
|
|
// Lets see if this is the beginning of a crash dump.
|
|
if (strstr(line, _crash_dump_header) != NULL) {
|
|
if (parser->state != EXPECTS_CRASH_DUMP) {
|
|
// Printing another crash dump was in progress. Mark the end of it.
|
|
fprintf(parser->out_handle, "Crash dump is completed\n\n");
|
|
}
|
|
|
|
// New crash dump begins.
|
|
fprintf(parser->out_handle, "********** Crash dump: **********\n");
|
|
parser->state = EXPECTS_BUILD_FINGREPRINT_OR_PID;
|
|
|
|
return 0;
|
|
}
|
|
|
|
switch (parser->state) {
|
|
case EXPECTS_BUILD_FINGREPRINT_OR_PID:
|
|
if (strstr(line, _build_fingerprint_header) != NULL) {
|
|
fprintf(parser->out_handle, "%s\n",
|
|
strstr(line, _build_fingerprint_header));
|
|
parser->state = EXPECTS_PID;
|
|
found = 1;
|
|
}
|
|
// Let it fall through to the EXPECTS_PID, in case the dump doesn't
|
|
// contain build fingerprint.
|
|
case EXPECTS_PID:
|
|
if (MatchRegex(line, &parser->re_pid_header, &match)) {
|
|
fprintf(parser->out_handle, "%s\n", line + match.rm_so);
|
|
parser->state = EXPECTS_SIGNAL_OR_FRAME;
|
|
return 0;
|
|
} else {
|
|
return !found;
|
|
}
|
|
|
|
case EXPECTS_SIGNAL_OR_FRAME:
|
|
if (MatchRegex(line, &parser->re_sig_header, &match)) {
|
|
fprintf(parser->out_handle, "%s\n", line + match.rm_so);
|
|
parser->state = EXPECTS_FRAME;
|
|
found = 1;
|
|
}
|
|
// Let it fall through to the EXPECTS_FRAME, in case the dump doesn't
|
|
// contain signal fingerprint.
|
|
case EXPECTS_FRAME:
|
|
if (!MatchRegex(line, &parser->re_frame_header, &match))
|
|
return !found;
|
|
// Regex generated by x86_64-w64-mingw32 compiler erroneously match
|
|
// frame line with #[0-9]+ in "stack:" section even when the line has
|
|
// no word "pc", "eip", or "ip" in it.
|
|
//
|
|
// stack:
|
|
// I/DEBUG ( 1151): #00 5f09db68 401f01c4 /system/lib/libc.so
|
|
//
|
|
// To workaround, let's double check if pc is found!
|
|
//
|
|
if (!(find_pc(line)))
|
|
return !found;
|
|
|
|
parser->state = EXPECTS_FRAME;
|
|
return ParseFrame(parser, line + match.rm_so);
|
|
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
MatchRegex(const char* line, const regex_t* regex, regmatch_t* match)
|
|
{
|
|
int err = regexec(regex, line, 1, match, 0x00400/*REG_TRACE*/);
|
|
#if 0
|
|
char rerr[4096];
|
|
if (err) {
|
|
regerror(err, regex, rerr, sizeof(rerr));
|
|
fprintf(stderr, "regexec(%s, %s) has failed: %s\n", line, regex, rerr);
|
|
}
|
|
#endif
|
|
|
|
return err == 0;
|
|
}
|
|
|
|
static const char*
|
|
next_separator(const char* str)
|
|
{
|
|
return str + strcspn(str, " \t");
|
|
}
|
|
|
|
static const char*
|
|
next_token(const char* str)
|
|
{
|
|
str = next_separator(str);
|
|
return str + strspn(str, " \t");
|
|
}
|
|
|
|
static const char*
|
|
get_next_token(const char* str, char* token, size_t size)
|
|
{
|
|
const char* start = next_token(str);
|
|
const char* end = next_separator(start);
|
|
if (start != end) {
|
|
const size_t to_copy = min((size_t)(end - start), (size - 1));
|
|
memcpy(token, start, to_copy);
|
|
token[to_copy] = '\0';
|
|
return start;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
find_pc(const char *frame)
|
|
{
|
|
const char *pcstrs[] = { "pc", "eip", "ip" };
|
|
int i;
|
|
for (i=0; i<sizeof(pcstrs)/sizeof(pcstrs[0]); i++) {
|
|
const char *p = strstr(frame, pcstrs[i]);
|
|
// check it's a word, not part of filename or something
|
|
if (p && p!=frame) {
|
|
char l = p[-1];
|
|
char r = p[strlen(pcstrs[i])];
|
|
if ((l==' ' || l=='\t') && (r==' ' || r=='\t'))
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
ParseFrame(NdkCrashParser* parser, const char* frame)
|
|
{
|
|
uint64_t address;
|
|
const char* wrk;
|
|
char* eptr;
|
|
char pc_address[17];
|
|
char module_path[2048];
|
|
char* module_name;
|
|
char sym_file[2048];
|
|
const int ac = 5;
|
|
char *av[ac];
|
|
FILE *f;
|
|
|
|
fprintf(parser->out_handle, "Stack frame %s", frame);
|
|
|
|
// Advance to the instruction pointer token.
|
|
if ((wrk=find_pc(frame)) == NULL) {
|
|
fprintf(parser->out_handle,
|
|
"Parser is unable to locate instruction pointer token.\n");
|
|
return -1;
|
|
}
|
|
|
|
// Next token after the instruction pointer token is its address.
|
|
wrk = get_next_token(wrk, pc_address, sizeof(pc_address));
|
|
// PC address is a hex value. Get it.
|
|
eptr = pc_address + strlen(pc_address);
|
|
address = strtoul(pc_address, &eptr, 16);
|
|
|
|
// Next token is module path.
|
|
get_next_token(wrk, module_path, sizeof(module_path));
|
|
|
|
// Extract basename of module, we should not care about its path
|
|
// on the device.
|
|
module_name = strrchr(module_path,'/');
|
|
if (module_name == NULL)
|
|
module_name = module_path;
|
|
else {
|
|
module_name += 1;
|
|
if (*module_name == '\0') {
|
|
/* Trailing slash in the module path, this should not happen */
|
|
/* Back-off with the full module-path */
|
|
module_name = module_path;
|
|
}
|
|
}
|
|
|
|
// Build path to the symbol file.
|
|
snprintf(sym_file, sizeof(sym_file), "%s/%s", parser->sym_root, module_name);
|
|
|
|
if ((f=fopen(sym_file, "r")) == NULL) {
|
|
if (errno == ENOENT) {
|
|
printf("\n");
|
|
} else {
|
|
printf(": Unable to open symbol file %s. Error (%d): %s\n",
|
|
sym_file, errno, strerror(errno));
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// call addr2line if sym_file exist
|
|
extern int addr2line_main (int argc, char **argv);
|
|
|
|
av[0] = "ndk-stack";
|
|
av[1] = "-fpC"; // f:function, p:pretty-print, C:demangle
|
|
av[2] = "-e"; // e:exe-filename
|
|
av[3] = sym_file;
|
|
av[4] = pc_address;
|
|
(void)address;
|
|
|
|
printf(": Routine ");
|
|
return addr2line_main(ac, av);
|
|
}
|