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