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 
25 #include "ndk-stack-parser.h"
26 
27 /* Enumerates states of the crash parser.
28  */
29 typedef 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  */
44 struct 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. */
61 static const char _crash_dump_header[] =
62   "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***";
63 
64 /* Build fingerprint contains this substring. */
65 static const char _build_fingerprint_header[] = "Build fingerprint:";
66 
67 /* Regular expression for the process ID information line. */
68 static const char _pid_header[] = "pid: [0-9]+, tid: [0-9]+.*";
69 
70 /* Regular expression for the signal information line. */
71 static const char _sig_header[] = "signal*[ \t][0-9]+";
72 
73 /* Regular expression for the frame information line. */
74 static 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  */
91 static 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  */
103 static 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. */
106 static 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  */
111 static 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  */
124 static const char* get_next_token(const char* str, char* token, size_t size);
125 
126 /* Return pointer to first word "pc", "eip", or "ip" in string "frame"
127  * param:
128  *  frame - a line from dump
129  * Return:
130  *  The first occurrence of "pc", "eip", or "ip"
131  */
132 static const char* find_pc(const char *frame);
133 
134 NdkCrashParser*
CreateNdkCrashParser(FILE * out_handle,const char * sym_root)135 CreateNdkCrashParser(FILE* out_handle, const char* sym_root)
136 {
137   NdkCrashParser* parser;
138 
139   parser = (NdkCrashParser*)calloc(sizeof(*parser), 1);
140   if (parser == NULL)
141       return NULL;
142 
143   parser->out_handle = out_handle;
144   parser->state      = EXPECTS_CRASH_DUMP;
145 
146   parser->sym_root = strdup(sym_root);
147   if (!parser->sym_root)
148       goto BAD_INIT;
149 
150   if (regcomp(&parser->re_pid_header, _pid_header, REG_EXTENDED | REG_NEWLINE) ||
151       regcomp(&parser->re_sig_header, _sig_header, REG_EXTENDED | REG_NEWLINE) ||
152       regcomp(&parser->re_frame_header, _frame_header, REG_EXTENDED | REG_NEWLINE))
153       goto BAD_INIT;
154 
155   return parser;
156 
157 BAD_INIT:
158   DestroyNdkCrashParser(parser);
159   return NULL;
160 }
161 
162 void
DestroyNdkCrashParser(NdkCrashParser * parser)163 DestroyNdkCrashParser(NdkCrashParser* parser)
164 {
165   if (parser != NULL) {
166     /* Release compiled regular expressions */
167     regfree(&parser->re_frame_header);
168     regfree(&parser->re_sig_header);
169     regfree(&parser->re_pid_header);
170     /* Release symbol path */
171     free(parser->sym_root);
172     /* Release parser itself */
173     free(parser);
174   }
175 }
176 
177 int
ParseLine(NdkCrashParser * parser,const char * line)178 ParseLine(NdkCrashParser* parser, const char* line)
179 {
180   regmatch_t match;
181   int found = 0;
182 
183   if (line == NULL || *line == '\0') {
184     // Nothing to parse.
185     return 1;
186   }
187 
188   // Lets see if this is the beginning of a crash dump.
189   if (strstr(line, _crash_dump_header) != NULL) {
190     if (parser->state != EXPECTS_CRASH_DUMP) {
191       // Printing another crash dump was in progress. Mark the end of it.
192       fprintf(parser->out_handle, "Crash dump is completed\n\n");
193     }
194 
195     // New crash dump begins.
196     fprintf(parser->out_handle, "********** Crash dump: **********\n");
197     parser->state = EXPECTS_BUILD_FINGREPRINT_OR_PID;
198 
199     return 0;
200   }
201 
202   switch (parser->state) {
203     case EXPECTS_BUILD_FINGREPRINT_OR_PID:
204       if (strstr(line, _build_fingerprint_header) != NULL) {
205         fprintf(parser->out_handle, "%s\n",
206                 strstr(line, _build_fingerprint_header));
207         parser->state = EXPECTS_PID;
208         found = 1;
209       }
210       // Let it fall through to the EXPECTS_PID, in case the dump doesn't
211       // contain build fingerprint.
212     case EXPECTS_PID:
213       if (MatchRegex(line, &parser->re_pid_header, &match)) {
214         fprintf(parser->out_handle, "%s\n", line + match.rm_so);
215         parser->state = EXPECTS_SIGNAL_OR_FRAME;
216         return 0;
217       } else {
218         return !found;
219       }
220 
221     case EXPECTS_SIGNAL_OR_FRAME:
222       if (MatchRegex(line, &parser->re_sig_header, &match)) {
223         fprintf(parser->out_handle, "%s\n", line + match.rm_so);
224         parser->state = EXPECTS_FRAME;
225         found = 1;
226       }
227       // Let it fall through to the EXPECTS_FRAME, in case the dump doesn't
228       // contain signal fingerprint.
229     case EXPECTS_FRAME:
230       if (!MatchRegex(line, &parser->re_frame_header, &match))
231         return !found;
232       // Regex generated by x86_64-w64-mingw32 compiler erroneously match
233       // frame line with #[0-9]+ in "stack:" section even when the line has
234       //  no word "pc", "eip", or "ip" in it.
235       //
236       //   stack:
237       //      I/DEBUG   ( 1151):     #00  5f09db68  401f01c4  /system/lib/libc.so
238       //
239       // To workaround, let's double check if pc is found!
240       //
241       if (!(find_pc(line)))
242         return !found;
243 
244       parser->state = EXPECTS_FRAME;
245       return ParseFrame(parser, line + match.rm_so);
246 
247     default:
248       return 1;
249   }
250 }
251 
252 static int
MatchRegex(const char * line,const regex_t * regex,regmatch_t * match)253 MatchRegex(const char* line, const regex_t* regex, regmatch_t* match)
254 {
255   int err = regexec(regex, line, 1, match, 0x00400/*REG_TRACE*/);
256 #if 0
257   char rerr[4096];
258   if (err) {
259     regerror(err, regex, rerr, sizeof(rerr));
260     fprintf(stderr, "regexec(%s, %s) has failed: %s\n", line, regex, rerr);
261   }
262 #endif
263 
264   return err == 0;
265 }
266 
267 static const char*
next_separator(const char * str)268 next_separator(const char* str)
269 {
270   return str + strcspn(str, " \t");
271 }
272 
273 static const char*
next_token(const char * str)274 next_token(const char* str)
275 {
276   str = next_separator(str);
277   return str + strspn(str, " \t");
278 }
279 
280 static const char*
get_next_token(const char * str,char * token,size_t size)281 get_next_token(const char* str, char* token, size_t size)
282 {
283   const char* start = next_token(str);
284   const char* end = next_separator(start);
285   if (start != end) {
286     const size_t to_copy = min((size_t)(end - start), (size - 1));
287     memcpy(token, start, to_copy);
288     token[to_copy] = '\0';
289     return start;
290   } else {
291     return NULL;
292   }
293 }
294 
295 static const char *
find_pc(const char * frame)296 find_pc(const char *frame)
297 {
298   const char *pcstrs[] = { "pc", "eip", "ip" };
299   int i;
300   for (i=0; i<sizeof(pcstrs)/sizeof(pcstrs[0]); i++) {
301     const char *p = strstr(frame, pcstrs[i]);
302     // check it's a word, not part of filename or something
303     if (p && p!=frame) {
304       char l = p[-1];
305       char r = p[strlen(pcstrs[i])];
306       if ((l==' ' || l=='\t') && (r==' ' || r=='\t'))
307         return p;
308     }
309   }
310   return NULL;
311 }
312 
313 int
ParseFrame(NdkCrashParser * parser,const char * frame)314 ParseFrame(NdkCrashParser* parser, const char* frame)
315 {
316   uint64_t address;
317   const char* wrk;
318   char* eptr;
319   char pc_address[17];
320   char module_path[2048];
321   char* module_name;
322   char sym_file[2048];
323   const int ac = 5;
324   char *av[ac];
325   FILE *f;
326 
327   fprintf(parser->out_handle, "Stack frame %s", frame);
328 
329   // Advance to the instruction pointer token.
330   if ((wrk=find_pc(frame)) == NULL) {
331     fprintf(parser->out_handle,
332             "Parser is unable to locate instruction pointer token.\n");
333     return -1;
334   }
335 
336   // Next token after the instruction pointer token is its address.
337   wrk = get_next_token(wrk, pc_address, sizeof(pc_address));
338   // PC address is a hex value. Get it.
339   eptr = pc_address + strlen(pc_address);
340   address = strtoul(pc_address, &eptr, 16);
341 
342   // Next token is module path.
343   get_next_token(wrk, module_path, sizeof(module_path));
344 
345   // Extract basename of module, we should not care about its path
346   // on the device.
347   module_name = strrchr(module_path,'/');
348   if (module_name == NULL)
349       module_name = module_path;
350   else {
351       module_name += 1;
352       if (*module_name == '\0') {
353           /* Trailing slash in the module path, this should not happen */
354           /* Back-off with the full module-path */
355           module_name = module_path;
356       }
357   }
358 
359   // Build path to the symbol file.
360   snprintf(sym_file, sizeof(sym_file), "%s/%s", parser->sym_root, module_name);
361 
362   if ((f=fopen(sym_file, "r")) == NULL) {
363     if (errno == ENOENT) {
364         printf("\n");
365     } else {
366         printf(": Unable to open symbol file %s. Error (%d): %s\n",
367                 sym_file, errno, strerror(errno));
368     }
369     return -1;
370   }
371 
372   // call addr2line if sym_file exist
373   extern int addr2line_main (int argc, char **argv);
374 
375   av[0] = "ndk-stack";
376   av[1] = "-fpC";  // f:function, p:pretty-print, C:demangle
377   av[2] = "-e";    // e:exe-filename
378   av[3] = sym_file;
379   av[4] = pc_address;
380   (void)address;
381 
382   printf(": Routine ");
383   return addr2line_main(ac, av);
384 }
385