• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Example program for unwinding core dumps.
3  *
4  * Compile a-la:
5  * gcc -Os -Wall \
6  *    -Wl,--start-group \
7  *        -lunwind -lunwind-x86 -lunwind-coredump \
8  *        example-core-unwind.c \
9  *    -Wl,--end-group \
10  *    -oexample-core-unwind
11  *
12  * Run:
13  * eu-unstrip -n --core COREDUMP
14  *   figure out which virtual addresses in COREDUMP correspond to which mapped executable files
15  *   (binary and libraries), then supply them like this:
16  * ./example-core-unwind COREDUMP 0x400000:/bin/crashed_program 0x3458600000:/lib/libc.so.6 [...]
17  *
18  * Note: Program eu-unstrip is part of elfutils, virtual addresses of shared
19  * libraries can be determined by ldd (at least on linux).
20  */
21 
22 #include "compiler.h"
23 
24 #undef _GNU_SOURCE
25 #define _GNU_SOURCE 1
26 #undef __USE_GNU
27 #define __USE_GNU 1
28 
29 #include <assert.h>
30 #include <ctype.h>
31 #include <dirent.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <inttypes.h>
35 #include <setjmp.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <stdarg.h>
40 #include <stddef.h>
41 #include <string.h>
42 #include <syslog.h>
43 #include <sys/poll.h>
44 #include <sys/mman.h>
45 #include <sys/socket.h>
46 #include <sys/stat.h>
47 #include <sys/time.h>
48 #include <sys/types.h>
49 #include <sys/wait.h>
50 #include <sys/param.h>
51 #include <termios.h>
52 #include <time.h>
53 #include <unistd.h>
54 #include <stdbool.h>
55 #include <limits.h>
56 #include <pwd.h>
57 #include <grp.h>
58 
59 /* For SIGSEGV handler code */
60 #if HAVE_EXECINFO_H
61 # include <execinfo.h>
62 #else
63   extern int backtrace (void **, int);
64 #endif
65 #include <ucontext.h>
66 
67 #include <libunwind-coredump.h>
68 
69 
70 /* Utility logging functions */
71 
72 enum {
73     LOGMODE_NONE = 0,
74     LOGMODE_STDIO = (1 << 0),
75     LOGMODE_SYSLOG = (1 << 1),
76     LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO,
77 };
78 const char *msg_prefix = "";
79 const char *msg_eol = "\n";
80 int logmode = LOGMODE_STDIO;
81 int xfunc_error_retval = EXIT_FAILURE;
82 
xfunc_die(void)83 void xfunc_die(void)
84 {
85   exit(xfunc_error_retval);
86 }
87 
verror_msg_helper(const char * s,va_list p,const char * strerr,int flags)88 static void verror_msg_helper(const char *s,
89                               va_list p,
90                               const char* strerr,
91                               int flags)
92 {
93   char *msg;
94   int prefix_len, strerr_len, msgeol_len, used;
95 
96   if (!logmode)
97     return;
98 
99   used = vasprintf(&msg, s, p);
100   if (used < 0)
101     return;
102 
103   /* This is ugly and costs +60 bytes compared to multiple
104    * fprintf's, but is guaranteed to do a single write.
105    * This is needed for e.g. when multiple children
106    * can produce log messages simultaneously. */
107 
108   prefix_len = msg_prefix[0] ? strlen(msg_prefix) + 2 : 0;
109   strerr_len = strerr ? strlen(strerr) : 0;
110   msgeol_len = strlen(msg_eol);
111   /* +3 is for ": " before strerr and for terminating NUL */
112   char *msg1 = (char*) realloc(msg, prefix_len + used + strerr_len + msgeol_len + 3);
113   if (!msg1)
114     {
115       free(msg);
116       return;
117     }
118   msg = msg1;
119   /* TODO: maybe use writev instead of memmoving? Need full_writev? */
120   if (prefix_len)
121     {
122       char *p;
123       memmove(msg + prefix_len, msg, used);
124       used += prefix_len;
125       p = stpcpy(msg, msg_prefix);
126       p[0] = ':';
127       p[1] = ' ';
128     }
129   if (strerr)
130     {
131       if (s[0])
132         {
133           msg[used++] = ':';
134           msg[used++] = ' ';
135         }
136       strcpy(&msg[used], strerr);
137       used += strerr_len;
138     }
139   strcpy(&msg[used], msg_eol);
140 
141   if (flags & LOGMODE_STDIO)
142     {
143       fflush(stdout);
144       write(STDERR_FILENO, msg, used + msgeol_len);
145     }
146   msg[used] = '\0'; /* remove msg_eol (usually "\n") */
147   if (flags & LOGMODE_SYSLOG)
148     {
149       syslog(LOG_ERR, "%s", msg + prefix_len);
150     }
151   free(msg);
152 }
153 
log_msg(const char * s,...)154 void log_msg(const char *s, ...)
155 {
156   va_list p;
157   va_start(p, s);
158   verror_msg_helper(s, p, NULL, logmode);
159   va_end(p);
160 }
161 /* It's a macro, not function, since it collides with log() from math.h */
162 #undef log
163 #define log(...) log_msg(__VA_ARGS__)
164 
error_msg(const char * s,...)165 void error_msg(const char *s, ...)
166 {
167   va_list p;
168   va_start(p, s);
169   verror_msg_helper(s, p, NULL, logmode);
170   va_end(p);
171 }
172 
error_msg_and_die(const char * s,...)173 void error_msg_and_die(const char *s, ...)
174 {
175   va_list p;
176   va_start(p, s);
177   verror_msg_helper(s, p, NULL, logmode);
178   va_end(p);
179   xfunc_die();
180 }
181 
perror_msg(const char * s,...)182 void perror_msg(const char *s, ...)
183 {
184   va_list p;
185   va_start(p, s);
186   /* Guard against "<error message>: Success" */
187   verror_msg_helper(s, p, errno ? strerror(errno) : NULL, logmode);
188   va_end(p);
189 }
190 
perror_msg_and_die(const char * s,...)191 void perror_msg_and_die(const char *s, ...)
192 {
193   va_list p;
194   va_start(p, s);
195   /* Guard against "<error message>: Success" */
196   verror_msg_helper(s, p, errno ? strerror(errno) : NULL, logmode);
197   va_end(p);
198   xfunc_die();
199 }
200 
die_out_of_memory(void)201 void die_out_of_memory(void)
202 {
203   error_msg_and_die("Out of memory, exiting");
204 }
205 
206 /* End of utility logging functions */
207 
208 
209 
210 static
handle_sigsegv(int sig,siginfo_t * info,void * ucontext)211 void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
212 {
213   long ip = 0;
214   ucontext_t *uc UNUSED;
215 
216   uc = ucontext;
217 #if defined(__linux__)
218 #ifdef UNW_TARGET_X86
219 	ip = uc->uc_mcontext.gregs[REG_EIP];
220 #elif defined(UNW_TARGET_X86_64)
221 	ip = uc->uc_mcontext.gregs[REG_RIP];
222 #elif defined(UNW_TARGET_ARM)
223 	ip = uc->uc_mcontext.arm_pc;
224 #endif
225 #elif defined(__FreeBSD__)
226 #ifdef __i386__
227 	ip = uc->uc_mcontext.mc_eip;
228 #elif defined(__amd64__)
229 	ip = uc->uc_mcontext.mc_rip;
230 #else
231 #error Port me
232 #endif
233 #else
234 #error Port me
235 #endif
236   dprintf(2, "signal:%d address:0x%lx ip:0x%lx\n",
237 			sig,
238 			/* this is void*, but using %p would print "(null)"
239 			 * even for ptrs which are not exactly 0, but, say, 0x123:
240 			 */
241 			(long)info->si_addr,
242 			ip);
243 
244   {
245     /* glibc extension */
246     void *array[50];
247     int size;
248     size = backtrace(array, 50);
249 #if defined __linux__ && HAVE_EXECINFO_H
250     backtrace_symbols_fd(array, size, 2);
251 #endif
252   }
253 
254   _exit(1);
255 }
256 
install_sigsegv_handler(void)257 static void install_sigsegv_handler(void)
258 {
259   struct sigaction sa;
260   memset(&sa, 0, sizeof(sa));
261   sa.sa_sigaction = handle_sigsegv;
262   sa.sa_flags = SA_SIGINFO;
263   sigaction(SIGSEGV, &sa, NULL);
264   sigaction(SIGILL, &sa, NULL);
265   sigaction(SIGFPE, &sa, NULL);
266   sigaction(SIGBUS, &sa, NULL);
267 }
268 
269 int
main(int argc UNUSED,char ** argv)270 main(int argc UNUSED, char **argv)
271 {
272   unw_addr_space_t as;
273   struct UCD_info *ui;
274   unw_cursor_t c;
275   int ret;
276 
277 #define TEST_FRAMES 4
278 #define TEST_NAME_LEN 32
279   int testcase = 0;
280   int test_cur = 0;
281   long test_start_ips[TEST_FRAMES];
282   char test_names[TEST_FRAMES][TEST_NAME_LEN];
283 
284   install_sigsegv_handler();
285 
286   const char *progname = strrchr(argv[0], '/');
287   if (progname)
288     progname++;
289   else
290     progname = argv[0];
291 
292   if (!argv[1])
293     error_msg_and_die("Usage: %s COREDUMP [VADDR:BINARY_FILE]...", progname);
294 
295   msg_prefix = progname;
296 
297   as = unw_create_addr_space(&_UCD_accessors, 0);
298   if (!as)
299     error_msg_and_die("unw_create_addr_space() failed");
300 
301   ui = _UCD_create(argv[1]);
302   if (!ui)
303     error_msg_and_die("_UCD_create('%s') failed", argv[1]);
304   ret = unw_init_remote(&c, as, ui);
305   if (ret < 0)
306     error_msg_and_die("unw_init_remote() failed: ret=%d\n", ret);
307 
308   argv += 2;
309 
310   /* Enable checks for the crasher test program? */
311   if (*argv && !strcmp(*argv, "-testcase"))
312   {
313     testcase = 1;
314     logmode = LOGMODE_NONE;
315     argv++;
316   }
317 
318   while (*argv)
319     {
320       char *colon;
321       unsigned long vaddr = strtoul(*argv, &colon, 16);
322       if (*colon != ':')
323         error_msg_and_die("Bad format: '%s'", *argv);
324       if (_UCD_add_backing_file_at_vaddr(ui, vaddr, colon + 1) < 0)
325         error_msg("Can't add backing file '%s'", colon + 1);
326       argv++;
327     }
328 
329   for (;;)
330     {
331       unw_word_t ip;
332       ret = unw_get_reg(&c, UNW_REG_IP, &ip);
333       if (ret < 0)
334         error_msg_and_die("unw_get_reg(UNW_REG_IP) failed: ret=%d\n", ret);
335 
336       unw_proc_info_t pi;
337       ret = unw_get_proc_info(&c, &pi);
338       if (ret < 0)
339         error_msg_and_die("unw_get_proc_info(ip=0x%lx) failed: ret=%d\n", (long) ip, ret);
340 
341       if (!testcase) {
342         char proc_name[128];
343         unw_word_t off;
344         unw_get_proc_name(&c, proc_name, sizeof(proc_name), &off);
345 
346         printf("\tip=0x%08lx proc=%08lx-%08lx handler=0x%08lx lsda=0x%08lx %s\n",
347 				(long) ip,
348 				(long) pi.start_ip, (long) pi.end_ip,
349 				(long) pi.handler, (long) pi.lsda, proc_name);
350 	  }
351 
352       if (testcase && test_cur < TEST_FRAMES)
353         {
354            unw_word_t off;
355 
356            test_start_ips[test_cur] = (long) pi.start_ip;
357            if (unw_get_proc_name(&c, test_names[test_cur], sizeof(test_names[0]), &off) != 0)
358            {
359              test_names[test_cur][0] = '\0';
360            }
361            test_cur++;
362         }
363 
364       log("step");
365       ret = unw_step(&c);
366       log("step done:%d", ret);
367       if (ret < 0)
368     	error_msg_and_die("FAILURE: unw_step() returned %d", ret);
369       if (ret == 0)
370         break;
371     }
372   log("stepping ended");
373 
374   /* Check that the second and third frames are equal, but distinct of the
375    * others */
376   if (testcase &&
377        (test_cur != 4
378        || test_start_ips[1] != test_start_ips[2]
379        || test_start_ips[0] == test_start_ips[1]
380        || test_start_ips[2] == test_start_ips[3]
381        )
382      )
383     {
384       fprintf(stderr, "FAILURE: start IPs incorrect\n");
385       return -1;
386     }
387 
388   if (testcase &&
389        (  strcmp(test_names[0], "a")
390        || strcmp(test_names[1], "b")
391        || strcmp(test_names[2], "b")
392        || strcmp(test_names[3], "main")
393        )
394      )
395     {
396       fprintf(stderr, "FAILURE: procedure names are missing/incorrect\n");
397       return -1;
398     }
399 
400   _UCD_destroy(ui);
401   unw_destroy_addr_space(as);
402 
403   return 0;
404 }
405