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 <inttypes.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <errno.h>
23#include "regex/regex.h"
24#include "elff/elff_api.h"
25
26#include "ndk-stack-parser.h"
27
28/* Enumerates states of the crash parser.
29 */
30typedef enum NDK_CRASH_PARSER_STATE {
31  /* Parser expects the beginning of the crash dump. */
32  EXPECTS_CRASH_DUMP,
33  /* Parser expects the build fingerprint, or process and thread information. */
34  EXPECTS_BUILD_FINGREPRINT_OR_PID,
35  /* Parser expects the process and thread information. */
36  EXPECTS_PID,
37  /* Parser expects the signal information, or the first crash frame. */
38  EXPECTS_SIGNAL_OR_FRAME,
39  /* Parser expects a crash frame. */
40  EXPECTS_FRAME,
41} NDK_CRASH_PARSER_STATE;
42
43/* Crash parser descriptor.
44 */
45struct NdkCrashParser {
46  /* Handle to the stream where to print the output. */
47  FILE*                 out_handle;
48
49  /* Path to the root folder where symbols are stored. */
50  char*                 sym_root;
51
52  /* Current state of the parser. */
53  NDK_CRASH_PARSER_STATE state;
54
55  /* Compiled regular expressions */
56  regex_t     re_pid_header;
57  regex_t     re_sig_header;
58  regex_t     re_frame_header;
59};
60
61/* Crash dumps begin with a string containing this substring. */
62static const char _crash_dump_header[] =
63  "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";
64
65/* Build fingerprint contains this substring. */
66static const char _build_fingerprint_header[] = "Build fingerprint:";
67
68/* Regular expression for the process ID information line. */
69static const char _pid_header[] = "pid: [0-9]+, tid: [0-9]+.*";
70
71/* Regular expression for the signal information line. */
72static const char _sig_header[] = "signal*[ \t][0-9]+";
73
74/* Regular expression for the frame information line. */
75static const char _frame_header[] = "\\#[0-9]+[ |\t]+[pc|eip]+:*[ |\t]+([0-9a-f]{8})*";
76
77#ifndef min
78#define min(a,b) (((a) < (b)) ? a : b)
79#endif
80
81/* Parses a line representing a crash frame.
82 * This routine will try to obtain source file / line information for the
83 * frame's address, and print that information to the specified output handle.
84 * Param:
85 *  parser - NdkCrashParser descriptor, created and initialized with a call to
86 *    NdkCrashParser routine.
87 *  frame - Line containing crash frame.
88 * Return:
89 *  0 If source file information has been found and printed, or -1 if that
90 *  information was not available.
91 */
92static int ParseFrame(NdkCrashParser* parser, const char* frame);
93
94/* Matches a string against a regular expression.
95 * Param:
96 *  line - String to matches against the regular expression.
97 *  regex - Regular expression to match the string against.
98 *  match - Upon successful match contains information about the part of the
99 *    string that matches the regular expression.
100 * Return:
101 *  Boolean: 1 if a match has been found, or 0 if match has not been found in
102 *  the string.
103 */
104static int MatchRegex(const char* line, const regex_t* regex, regmatch_t* match);
105
106/* Returns pointer to the next separator (a space, or a tab) in the string. */
107static const char* next_separator(const char* str);
108
109/* Returns pointer to the next token (a character other than space, or a tab)
110 * in the string.
111 */
112static const char* next_token(const char* str);
113
114/* Gets next token from the string.
115 * param:
116 *  str - String where to get the next token from. Note that if string begins
117 *    with a separator, this routine will return first token after that
118 *    separator. If string begins with a token, this routine will return next
119 *    token after that.
120 *  token - Upon success contains a copy of the next token in the string.
121 *  size - Size of the 'token' buffer.
122 * Return:
123 *  Beginning of the returned token in the string.
124 */
125static const char* get_next_token(const char* str, char* token, size_t size);
126
127/* Return pointer to first word "pc", "eip", or "ip" in string "frame"
128 * param:
129 *  frame - a line from dump
130 * Return:
131 *  The first occurrence of "pc", "eip", or "ip"
132 */
133static const char* find_pc(const char *frame);
134
135NdkCrashParser*
136CreateNdkCrashParser(FILE* out_handle, const char* sym_root)
137{
138  NdkCrashParser* parser;
139
140  parser = (NdkCrashParser*)calloc(sizeof(*parser), 1);
141  if (parser == NULL)
142      return NULL;
143
144  parser->out_handle = out_handle;
145  parser->state      = EXPECTS_CRASH_DUMP;
146
147  parser->sym_root = strdup(sym_root);
148  if (!parser->sym_root)
149      goto BAD_INIT;
150
151  if (regcomp(&parser->re_pid_header, _pid_header, REG_EXTENDED | REG_NEWLINE) ||
152      regcomp(&parser->re_sig_header, _sig_header, REG_EXTENDED | REG_NEWLINE) ||
153      regcomp(&parser->re_frame_header, _frame_header, REG_EXTENDED | REG_NEWLINE))
154      goto BAD_INIT;
155
156  return parser;
157
158BAD_INIT:
159  DestroyNdkCrashParser(parser);
160  return NULL;
161}
162
163void
164DestroyNdkCrashParser(NdkCrashParser* parser)
165{
166  if (parser != NULL) {
167    /* Release compiled regular expressions */
168    regfree(&parser->re_frame_header);
169    regfree(&parser->re_sig_header);
170    regfree(&parser->re_pid_header);
171    /* Release symbol path */
172    free(parser->sym_root);
173    /* Release parser itself */
174    free(parser);
175  }
176}
177
178int
179ParseLine(NdkCrashParser* parser, const char* line)
180{
181  regmatch_t match;
182  int found = 0;
183
184  if (line == NULL || *line == '\0') {
185    // Nothing to parse.
186    return 1;
187  }
188
189  // Lets see if this is the beginning of a crash dump.
190  if (strstr(line, _crash_dump_header) != NULL) {
191    if (parser->state != EXPECTS_CRASH_DUMP) {
192      // Printing another crash dump was in progress. Mark the end of it.
193      fprintf(parser->out_handle, "Crash dump is completed\n\n");
194    }
195
196    // New crash dump begins.
197    fprintf(parser->out_handle, "********** Crash dump: **********\n");
198    parser->state = EXPECTS_BUILD_FINGREPRINT_OR_PID;
199
200    return 0;
201  }
202
203  switch (parser->state) {
204    case EXPECTS_BUILD_FINGREPRINT_OR_PID:
205      if (strstr(line, _build_fingerprint_header) != NULL) {
206        fprintf(parser->out_handle, "%s\n",
207                strstr(line, _build_fingerprint_header));
208        parser->state = EXPECTS_PID;
209        found = 1;
210      }
211      // Let it fall through to the EXPECTS_PID, in case the dump doesn't
212      // contain build fingerprint.
213    case EXPECTS_PID:
214      if (MatchRegex(line, &parser->re_pid_header, &match)) {
215        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
216        parser->state = EXPECTS_SIGNAL_OR_FRAME;
217        return 0;
218      } else {
219        return !found;
220      }
221
222    case EXPECTS_SIGNAL_OR_FRAME:
223      if (MatchRegex(line, &parser->re_sig_header, &match)) {
224        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
225        parser->state = EXPECTS_FRAME;
226        found = 1;
227      }
228      // Let it fall through to the EXPECTS_FRAME, in case the dump doesn't
229      // contain signal fingerprint.
230    case EXPECTS_FRAME:
231      if (!MatchRegex(line, &parser->re_frame_header, &match))
232        return !found;
233      // Regex generated by x86_64-w64-mingw32 compiler erroneously match
234      // frame line with #[0-9]+ in "stack:" section even when the line has
235      //  no word "pc", "eip", or "ip" in it.
236      //
237      //   stack:
238      //      I/DEBUG   ( 1151):     #00  5f09db68  401f01c4  /system/lib/libc.so
239      //
240      // To workaround, let's double check if pc is found!
241      //
242      if (!(find_pc(line)))
243        return !found;
244
245      parser->state = EXPECTS_FRAME;
246      return ParseFrame(parser, line + match.rm_so);
247
248    default:
249      return 1;
250  }
251}
252
253static int
254MatchRegex(const char* line, const regex_t* regex, regmatch_t* match)
255{
256  int err = regexec(regex, line, 1, match, 0x00400/*REG_TRACE*/);
257#if 0
258  char rerr[4096];
259  if (err) {
260    regerror(err, regex, rerr, sizeof(rerr));
261    fprintf(stderr, "regexec(%s, %s) has failed: %s\n", line, regex, rerr);
262  }
263#endif
264
265  return err == 0;
266}
267
268static const char*
269next_separator(const char* str)
270{
271  return str + strcspn(str, " \t");
272}
273
274static const char*
275next_token(const char* str)
276{
277  str = next_separator(str);
278  return str + strspn(str, " \t");
279}
280
281static const char*
282get_next_token(const char* str, char* token, size_t size)
283{
284  const char* start = next_token(str);
285  const char* end = next_separator(start);
286  if (start != end) {
287    const size_t to_copy = min((size_t)(end - start), (size - 1));
288    memcpy(token, start, to_copy);
289    token[to_copy] = '\0';
290    return start;
291  } else {
292    return NULL;
293  }
294}
295
296static const char *
297find_pc(const char *frame)
298{
299  const char *pcstrs[] = { "pc", "eip", "ip" };
300  int i;
301  for (i=0; i<sizeof(pcstrs)/sizeof(pcstrs[0]); i++) {
302    const char *p = strstr(frame, pcstrs[i]);
303    // check it's a word, not part of filename or something
304    if (p && p!=frame) {
305      char l = p[-1];
306      char r = p[strlen(pcstrs[i])];
307      if ((l==' ' || l=='\t') && (r==' ' || r=='\t'))
308        return p;
309    }
310  }
311  return NULL;
312}
313
314int
315ParseFrame(NdkCrashParser* parser, const char* frame)
316{
317  uint64_t address;
318  const char* wrk;
319  char* eptr;
320  char pc_address[17];
321  char module_path[2048];
322  char* module_name;
323  char sym_file[2048];
324#if !defined(WITH_LIBBFD)
325  ELFF_HANDLE elff_handle;
326  Elf_AddressInfo pc_info;
327#else
328  const int ac = 5;
329  char *av[ac];
330  FILE *f;
331#endif
332
333  fprintf(parser->out_handle, "Stack frame %s", frame);
334
335  // Advance to the instruction pointer token.
336  if ((wrk=find_pc(frame)) == NULL) {
337    fprintf(parser->out_handle,
338            "Parser is unable to locate instruction pointer token.\n");
339    return -1;
340  }
341
342  // Next token after the instruction pointer token is its address.
343  wrk = get_next_token(wrk, pc_address, sizeof(pc_address));
344  // PC address is a hex value. Get it.
345  eptr = pc_address + strlen(pc_address);
346  address = strtoul(pc_address, &eptr, 16);
347
348  // Next token is module path.
349  get_next_token(wrk, module_path, sizeof(module_path));
350
351  // Extract basename of module, we should not care about its path
352  // on the device.
353  module_name = strrchr(module_path,'/');
354  if (module_name == NULL)
355      module_name = module_path;
356  else {
357      module_name += 1;
358      if (*module_name == '\0') {
359          /* Trailing slash in the module path, this should not happen */
360          /* Back-off with the full module-path */
361          module_name = module_path;
362      }
363  }
364
365  // Build path to the symbol file.
366  snprintf(sym_file, sizeof(sym_file), "%s/%s", parser->sym_root, module_name);
367
368#if defined(WITH_LIBBFD)
369  if ((f=fopen(sym_file, "r")) == NULL) {
370    if (errno == ENOENT) {
371        printf("\n");
372    } else {
373        printf(": Unable to open symbol file %s. Error (%d): %s\n",
374                sym_file, errno, strerror(errno));
375    }
376    return -1;
377  }
378
379  // call addr2line if sym_file exist
380  extern int addr2line_main (int argc, char **argv);
381
382  av[0] = "ndk-stack";
383  av[1] = "-fpC";  // f:function, p:pretty-print, C:demangle
384  av[2] = "-e";    // e:exe-filename
385  av[3] = sym_file;
386  av[4] = pc_address;
387  (void)address;
388
389  printf(": Routine ");
390  return addr2line_main(ac, av);
391#else
392  // Init ELFF wrapper for the symbol file.
393  elff_handle = elff_init(sym_file);
394  if (elff_handle == NULL) {
395    if (errno == ENOENT) {
396        fprintf(parser->out_handle, "\n");
397    } else {
398        fprintf(parser->out_handle,
399                ": Unable to open symbol file %s. Error (%d): %s\n",
400                sym_file, errno, strerror(errno));
401    }
402    return -1;
403  }
404  // Extract address info from the symbol file.
405  if (!elff_get_pc_address_info(elff_handle, address, &pc_info)) {
406    if (pc_info.dir_name != NULL) {
407      fprintf(parser->out_handle, ": Routine %s in %s/%s:%d\n",
408              pc_info.routine_name, pc_info.dir_name, pc_info.file_name,
409              pc_info.line_number);
410    } else {
411      fprintf(parser->out_handle, ": Routine %s in %s:%d\n",
412              pc_info.routine_name, pc_info.file_name, pc_info.line_number);
413    }
414    elff_free_pc_address_info(elff_handle, &pc_info);
415    elff_close(elff_handle);
416    return 0;
417  } else {
418    fprintf(parser->out_handle,
419            ": Unable to locate routine information for address %x in module %s\n",
420            (uint32_t)address, sym_file);
421    elff_close(elff_handle);
422    return -1;
423  }
424#endif // WITH_LIBBFD
425}
426