ndk-stack-parser.c revision b3682c489e0c54b1ab05f78e8f91d0642bf975e1
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
55/* Crash dumps begin with a string containing this substring. */
56static const char _crash_dump_header[] =
57  "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";
58
59/* Build fingerprint contains this substring. */
60static const char _build_fingerprint_header[] = "Build fingerprint:";
61
62/* Regular expression for the process ID information line. */
63static const char _pid_header[] = "pid: [0-9]+, tid: [0-9]+.*";
64
65/* Regular expression for the signal information line. */
66static const char _sig_header[] = "signal*[ \t][0-9]+";
67
68/* Regular expression for the frame information line. */
69static const char _frame_header[] = "\\#[0-9]+[ |\t]+[pc|eip]+:*[ |\t]+([0-9a-f]{8})*";
70
71#ifndef min
72#define min(a,b) (((a) < (b)) ? a : b)
73#endif
74
75/* Parses a line representing a crash frame.
76 * This routine will try to obtain source file / line information for the
77 * frame's address, and print that information to the specified output handle.
78 * Param:
79 *  parser - NdkCrashParser descriptor, created and initialized with a call to
80 *    NdkCrashParser routine.
81 *  frame - Line containing crash frame.
82 * Return:
83 *  0 If source file information has been found and printed, or -1 if that
84 *  information was not available.
85 */
86static int ParseFrame(NdkCrashParser* parser, const char* frame);
87
88/* Matches a string against a regular expression.
89 * Param:
90 *  line - String to matches against the regular expression.
91 *  regex - Regular expression to match the string against.
92 *  match - Upon successful match contains information about the part of the
93 *    string that matches the regular expression.
94 * Return:
95 *  Boolean: 1 if a match has been found, or 0 if match has not been found in
96 *  the string.
97 */
98static int MatchRegex(const char* line, const char* regex, regmatch_t* match);
99
100/* Returns pointer to the next separator (a space, or a tab) in the string. */
101static const char* next_separator(const char* str);
102
103/* Returns pointer to the next token (a character other than space, or a tab)
104 * in the string.
105 */
106static const char* next_token(const char* str);
107
108/* Gets next token from the string.
109 * param:
110 *  str - String where to get the next token from. Note that if string begins
111 *    with a separator, this routine will return first token after that
112 *    separator. If string begins with a token, this routine will return next
113 *    token after that.
114 *  token - Upon success contains a copy of the next token in the string.
115 *  size - Size of the 'token' buffer.
116 * Return:
117 *  Beginning of the returned token in the string.
118 */
119static const char* get_next_token(const char* str, char* token, size_t size);
120
121NdkCrashParser*
122CreateNdkCrashParser(FILE* out_handle, const char* sym_root)
123{
124  NdkCrashParser* parser = (NdkCrashParser*)malloc(sizeof(NdkCrashParser));
125  if (parser != NULL) {
126    parser->out_handle = out_handle;
127    parser->state = EXPECTS_CRASH_DUMP;
128    parser->sym_root = (char*)malloc(strlen(sym_root) + 1);
129    if (parser->sym_root != NULL) {
130      strcpy(parser->sym_root, sym_root);
131    } else {
132      free(parser);
133      parser = NULL;
134    }
135  }
136
137  return parser;
138}
139
140void
141DestroyNdkCrashParser(NdkCrashParser* parser)
142{
143  if (parser != NULL) {
144    if (parser->sym_root != NULL) {
145      free(parser->sym_root);
146    }
147    free(parser);
148  }
149}
150
151int
152ParseLine(NdkCrashParser* parser, const char* line)
153{
154  regmatch_t match;
155
156  if (line == NULL || *line == '\0') {
157    // Nothing to parse.
158    return 1;
159  }
160
161  // Lets see if this is the beginning of a crash dump.
162  if (strstr(line, _crash_dump_header) != NULL) {
163    if (parser->state != EXPECTS_CRASH_DUMP) {
164      // Printing another crash dump was in progress. Mark the end of it.
165      fprintf(parser->out_handle, "Crash dump is completed\n\n");
166    }
167
168    // New crash dump begins.
169    fprintf(parser->out_handle, "********** Crash dump: **********\n");
170    parser->state = EXPECTS_BUILD_FINGREPRINT_OR_PID;
171
172    return 0;
173  }
174
175  switch (parser->state) {
176    case EXPECTS_BUILD_FINGREPRINT_OR_PID:
177      if (strstr(line, _build_fingerprint_header) != NULL) {
178        fprintf(parser->out_handle, "%s\n",
179                strstr(line, _build_fingerprint_header));
180        parser->state = EXPECTS_PID;
181      }
182      // Let it fall through to the EXPECTS_PID, in case the dump doesn't
183      // contain build fingerprint.
184    case EXPECTS_PID:
185      if (MatchRegex(line, _pid_header, &match)) {
186        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
187        parser->state = EXPECTS_SIGNAL_OR_FRAME;
188        return 0;
189      } else {
190        return 1;
191      }
192
193    case EXPECTS_SIGNAL_OR_FRAME:
194      if (MatchRegex(line, _sig_header, &match)) {
195        fprintf(parser->out_handle, "%s\n", line + match.rm_so);
196        parser->state = EXPECTS_FRAME;
197      }
198      // Let it fall through to the EXPECTS_FRAME, in case the dump doesn't
199      // contain signal fingerprint.
200    case EXPECTS_FRAME:
201      if (MatchRegex(line, _frame_header, &match)) {
202        parser->state = EXPECTS_FRAME;
203        return ParseFrame(parser, line + match.rm_so);
204      } else {
205        return 1;
206      }
207
208    default:
209      return 1;
210  }
211}
212
213static int
214MatchRegex(const char* line, const char* regex, regmatch_t* match)
215{
216  char rerr[4096];
217  regex_t rex;
218  int ok = regcomp(&rex, regex, REG_EXTENDED | REG_NEWLINE);
219  if (!ok) {
220    ok = regexec(&rex, line, 1, match, 0x00400/*REG_TRACE*/);
221#if 0
222    if (ok) {
223        regerror(ok, &rex, rerr, sizeof(rerr));
224        fprintf(stderr, "regexec(%s, %s) has failed: %s\n", line, regex, rerr);
225    }
226#endif
227    regfree(&rex);
228  } else {
229    regerror(ok, &rex, rerr, sizeof(rerr));
230    fprintf(stderr, "regcomp(%s) has failed: %s\n", regex, rerr);
231  }
232
233  return ok == 0;
234}
235
236static const char*
237next_separator(const char* str)
238{
239  return str + strcspn(str, " \t");
240}
241
242static const char*
243next_token(const char* str)
244{
245  str = next_separator(str);
246  return str + strspn(str, " \t");
247}
248
249static const char*
250get_next_token(const char* str, char* token, size_t size)
251{
252  const char* start = next_token(str);
253  const char* end = next_separator(start);
254  if (start != end) {
255    const size_t to_copy = min((end - start), (size - 1));
256    memcpy(token, start, to_copy);
257    token[to_copy] = '\0';
258    return start;
259  } else {
260    return NULL;
261  }
262}
263
264int
265ParseFrame(NdkCrashParser* parser, const char* frame)
266{
267  uint64_t address;
268  const char* wrk;
269  char* eptr;
270  char pc_address[17];
271  char module_path[2048];
272  char sym_file[2048];
273  ELFF_HANDLE elff_handle;
274  Elf_AddressInfo pc_info;
275
276  fprintf(parser->out_handle, "Stack frame %s: ", frame);
277
278  // Advance to the instruction pointer token.
279  wrk = strstr(frame, "pc");
280  if (wrk == NULL) {
281    wrk = strstr(frame, "eip");
282    if (wrk == NULL) {
283      wrk = strstr(frame, "ip");
284      if (wrk == NULL) {
285        fprintf(parser->out_handle,
286                "Parser is unable to locate instruction pointer token.\n");
287        return -1;
288      }
289    }
290  }
291
292  // Next token after the instruction pointer token is its address.
293  wrk = get_next_token(wrk, pc_address, sizeof(pc_address));
294  // PC address is a hex value. Get it.
295  eptr = pc_address + strlen(pc_address);
296  address = strtoul(pc_address, &eptr, 16);
297
298  // Next token is module path.
299  get_next_token(wrk, module_path, sizeof(module_path));
300
301  // Build path to the symbol file.
302  strcpy(sym_file, parser->sym_root);
303  strcat(sym_file, module_path);
304
305  // Init ELFF wrapper for the symbol file.
306  elff_handle = elff_init(sym_file);
307  if (elff_handle == NULL) {
308    fprintf(parser->out_handle,
309            "Unable to open symbol file %s. Error code is %d\n",
310            sym_file, errno);
311    return -1;
312  }
313  // Extract address info from the symbol file.
314  if (!elff_get_pc_address_info(elff_handle, address, &pc_info)) {
315    if (pc_info.dir_name != NULL) {
316      fprintf(parser->out_handle, "Routine %s in %s/%s:%d\n",
317              pc_info.routine_name, pc_info.dir_name, pc_info.file_name,
318              pc_info.line_number);
319    } else {
320      fprintf(parser->out_handle, "Routine %s in %s:%d\n",
321              pc_info.routine_name, pc_info.file_name, pc_info.line_number);
322    }
323    elff_free_pc_address_info(elff_handle, &pc_info);
324    elff_close(elff_handle);
325    return 0;
326  } else {
327    fprintf(parser->out_handle,
328            "Unable to locate routine information for address %x in module %s\n",
329            (uint32_t)address, sym_file);
330    elff_close(elff_handle);
331    return -1;
332  }
333}
334