ndk-stack-parser.c revision 273fd4fcae7d1ee68ff3096108f623c6db6007a8
1/* Copyright (C) 2007-2011 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12
13/*
14 * Contains implementation of a class DumpFile of routines that implements
15 * access to a log file.
16 */
17
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <errno.h>
22#include "regex/regex.h"
23#include "elff/elff_api.h"
24
25#include "ndk-stack-parser.h"
26
27/* Enumerates states of the crash parser.
28 */
29typedef enum NDK_CRASH_PARSER_STATE {
30  /* Parser expects the beginning of the crash dump. */
31  EXPECTS_CRASH_DUMP,
32  /* Parser expects the build fingerprint, or process and thread information. */
33  EXPECTS_BUILD_FINGREPRINT_OR_PID,
34  /* Parser expects the process and thread information. */
35  EXPECTS_PID,
36  /* Parser expects the signal information, or the first crash frame. */
37  EXPECTS_SIGNAL_OR_FRAME,
38  /* Parser expects a crash frame. */
39  EXPECTS_FRAME,
40} NDK_CRASH_PARSER_STATE;
41
42/* Crash parser descriptor.
43 */
44struct NdkCrashParser {
45  /* Handle to the stream where to print the output. */
46  FILE*                 out_handle;
47
48  /* Path to the root folder where symbols are stored. */
49  char*                 sym_root;
50
51  /* Current state of the parser. */
52  NDK_CRASH_PARSER_STATE state;
53
54  /* Compiled regular expressions */
55  regex_t     re_pid_header;
56  regex_t     re_sig_header;
57  regex_t     re_frame_header;
58};
59
60/* Crash dumps begin with a string containing this substring. */
61static const char _crash_dump_header[] =
62  "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";
63
64/* Build fingerprint contains this substring. */
65static const char _build_fingerprint_header[] = "Build fingerprint:";
66
67/* Regular expression for the process ID information line. */
68static const char _pid_header[] = "pid: [0-9]+, tid: [0-9]+.*";
69
70/* Regular expression for the signal information line. */
71static const char _sig_header[] = "signal*[ \t][0-9]+";
72
73/* Regular expression for the frame information line. */
74static const char _frame_header[] = "\\#[0-9]+[ |\t]+[pc|eip]+:*[ |\t]+([0-9a-f]{8})*";
75
76#ifndef min
77#define min(a,b) (((a) < (b)) ? a : b)
78#endif
79
80/* Parses a line representing a crash frame.
81 * This routine will try to obtain source file / line information for the
82 * frame's address, and print that information to the specified output handle.
83 * Param:
84 *  parser - NdkCrashParser descriptor, created and initialized with a call to
85 *    NdkCrashParser routine.
86 *  frame - Line containing crash frame.
87 * Return:
88 *  0 If source file information has been found and printed, or -1 if that
89 *  information was not available.
90 */
91static int ParseFrame(NdkCrashParser* parser, const char* frame);
92
93/* Matches a string against a regular expression.
94 * Param:
95 *  line - String to matches against the regular expression.
96 *  regex - Regular expression to match the string against.
97 *  match - Upon successful match contains information about the part of the
98 *    string that matches the regular expression.
99 * Return:
100 *  Boolean: 1 if a match has been found, or 0 if match has not been found in
101 *  the string.
102 */
103static int MatchRegex(const char* line, const regex_t* regex, regmatch_t* match);
104
105/* Returns pointer to the next separator (a space, or a tab) in the string. */
106static const char* next_separator(const char* str);
107
108/* Returns pointer to the next token (a character other than space, or a tab)
109 * in the string.
110 */
111static const char* next_token(const char* str);
112
113/* Gets next token from the string.
114 * param:
115 *  str - String where to get the next token from. Note that if string begins
116 *    with a separator, this routine will return first token after that
117 *    separator. If string begins with a token, this routine will return next
118 *    token after that.
119 *  token - Upon success contains a copy of the next token in the string.
120 *  size - Size of the 'token' buffer.
121 * Return:
122 *  Beginning of the returned token in the string.
123 */
124static const char* get_next_token(const char* str, char* token, size_t size);
125
126NdkCrashParser*
127CreateNdkCrashParser(FILE* out_handle, const char* sym_root)
128{
129  NdkCrashParser* parser;
130
131  parser = (NdkCrashParser*)calloc(sizeof(*parser), 1);
132  if (parser == NULL)
133      return NULL;
134
135  parser->out_handle = out_handle;
136  parser->state      = EXPECTS_CRASH_DUMP;
137
138  parser->sym_root = strdup(sym_root);
139  if (!parser->sym_root)
140      goto BAD_INIT;
141
142  if (regcomp(&parser->re_pid_header, _pid_header, REG_EXTENDED | REG_NEWLINE) ||
143      regcomp(&parser->re_sig_header, _sig_header, REG_EXTENDED | REG_NEWLINE) ||
144      regcomp(&parser->re_frame_header, _frame_header, REG_EXTENDED | REG_NEWLINE))
145      goto BAD_INIT;
146
147  return parser;
148
149BAD_INIT:
150  DestroyNdkCrashParser(parser);
151  return NULL;
152}
153
154void
155DestroyNdkCrashParser(NdkCrashParser* parser)
156{
157  if (parser != NULL) {
158    /* Release compiled regular expressions */
159    regfree(&parser->re_frame_header);
160    regfree(&parser->re_sig_header);
161    regfree(&parser->re_pid_header);
162    /* Release symbol path */
163    free(parser->sym_root);
164    /* Release parser itself */
165    free(parser);
166  }
167}
168
169int
170ParseLine(NdkCrashParser* parser, const char* line)
171{
172  regmatch_t match;
173
174  if (line == NULL || *line == '\0') {
175    // Nothing to parse.
176    return 1;
177  }
178
179  // Lets see if this is the beginning of a crash dump.
180  if (strstr(line, _crash_dump_header) != NULL) {
181    if (parser->state != EXPECTS_CRASH_DUMP) {
182      // Printing another crash dump was in progress. Mark the end of it.
183      fprintf(parser->out_handle, "Crash dump is completed\n\n");
184    }
185
186    // New crash dump begins.
187    fprintf(parser->out_handle, "********** Crash dump: **********\n");
188    parser->state = EXPECTS_BUILD_FINGREPRINT_OR_PID;
189
190    return 0;
191  }
192
193  switch (parser->state) {
194    case EXPECTS_BUILD_FINGREPRINT_OR_PID:
195      if (strstr(line, _build_fingerprint_header) != NULL) {
196        fprintf(parser->out_handle, "%s\n",
197                strstr(line, _build_fingerprint_header));
198        parser->state = EXPECTS_PID;
199      }
200      // Let it fall through to the EXPECTS_PID, in case the dump doesn't
201      // contain build fingerprint.
202    case EXPECTS_PID:
203      if (MatchRegex(line, &parser->re_pid_header, &match)) {
204        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
205        parser->state = EXPECTS_SIGNAL_OR_FRAME;
206        return 0;
207      } else {
208        return 1;
209      }
210
211    case EXPECTS_SIGNAL_OR_FRAME:
212      if (MatchRegex(line, &parser->re_sig_header, &match)) {
213        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
214        parser->state = EXPECTS_FRAME;
215      }
216      // Let it fall through to the EXPECTS_FRAME, in case the dump doesn't
217      // contain signal fingerprint.
218    case EXPECTS_FRAME:
219      if (MatchRegex(line, &parser->re_frame_header, &match)) {
220        parser->state = EXPECTS_FRAME;
221        return ParseFrame(parser, line + match.rm_so);
222      } else {
223        return 1;
224      }
225
226    default:
227      return 1;
228  }
229}
230
231static int
232MatchRegex(const char* line, const regex_t* regex, regmatch_t* match)
233{
234  int err = regexec(regex, line, 1, match, 0x00400/*REG_TRACE*/);
235#if 0
236  char rerr[4096];
237  if (err) {
238    regerror(err, regex, rerr, sizeof(rerr));
239    fprintf(stderr, "regexec(%s, %s) has failed: %s\n", line, regex, rerr);
240  }
241#endif
242
243  return err == 0;
244}
245
246static const char*
247next_separator(const char* str)
248{
249  return str + strcspn(str, " \t");
250}
251
252static const char*
253next_token(const char* str)
254{
255  str = next_separator(str);
256  return str + strspn(str, " \t");
257}
258
259static const char*
260get_next_token(const char* str, char* token, size_t size)
261{
262  const char* start = next_token(str);
263  const char* end = next_separator(start);
264  if (start != end) {
265    const size_t to_copy = min((size_t)(end - start), (size - 1));
266    memcpy(token, start, to_copy);
267    token[to_copy] = '\0';
268    return start;
269  } else {
270    return NULL;
271  }
272}
273
274int
275ParseFrame(NdkCrashParser* parser, const char* frame)
276{
277  uint64_t address;
278  const char* wrk;
279  char* eptr;
280  char pc_address[17];
281  char module_path[2048];
282  char* module_name;
283  char sym_file[2048];
284  ELFF_HANDLE elff_handle;
285  Elf_AddressInfo pc_info;
286
287  fprintf(parser->out_handle, "Stack frame %s", frame);
288
289  // Advance to the instruction pointer token.
290  wrk = strstr(frame, "pc");
291  if (wrk == NULL) {
292    wrk = strstr(frame, "eip");
293    if (wrk == NULL) {
294      wrk = strstr(frame, "ip");
295      if (wrk == NULL) {
296        fprintf(parser->out_handle,
297                "Parser is unable to locate instruction pointer token.\n");
298        return -1;
299      }
300    }
301  }
302
303  // Next token after the instruction pointer token is its address.
304  wrk = get_next_token(wrk, pc_address, sizeof(pc_address));
305  // PC address is a hex value. Get it.
306  eptr = pc_address + strlen(pc_address);
307  address = strtoul(pc_address, &eptr, 16);
308
309  // Next token is module path.
310  get_next_token(wrk, module_path, sizeof(module_path));
311
312  // Extract basename of module, we should not care about its path
313  // on the device.
314  module_name = strrchr(module_path,'/');
315  if (module_name == NULL)
316      module_name = module_path;
317  else {
318      module_name += 1;
319      if (*module_name == '\0') {
320          /* Trailing slash in the module path, this should not happen */
321          /* Back-off with the full module-path */
322          module_name = module_path;
323      }
324  }
325
326  // Build path to the symbol file.
327  snprintf(sym_file, sizeof(sym_file), "%s/%s", parser->sym_root, module_name);
328
329  // Init ELFF wrapper for the symbol file.
330  elff_handle = elff_init(sym_file);
331  if (elff_handle == NULL) {
332    if (errno == ENOENT) {
333        fprintf(parser->out_handle, "\n");
334    } else {
335        fprintf(parser->out_handle,
336                ": Unable to open symbol file %s. Error (%d): %s\n",
337                sym_file, errno, strerror(errno));
338    }
339    return -1;
340  }
341  // Extract address info from the symbol file.
342  if (!elff_get_pc_address_info(elff_handle, address, &pc_info)) {
343    if (pc_info.dir_name != NULL) {
344      fprintf(parser->out_handle, ": Routine %s in %s/%s:%d\n",
345              pc_info.routine_name, pc_info.dir_name, pc_info.file_name,
346              pc_info.line_number);
347    } else {
348      fprintf(parser->out_handle, ": Routine %s in %s:%d\n",
349              pc_info.routine_name, pc_info.file_name, pc_info.line_number);
350    }
351    elff_free_pc_address_info(elff_handle, &pc_info);
352    elff_close(elff_handle);
353    return 0;
354  } else {
355    fprintf(parser->out_handle,
356            ": Unable to locate routine information for address %x in module %s\n",
357            (uint32_t)address, sym_file);
358    elff_close(elff_handle);
359    return -1;
360  }
361}
362