Home | History | Annotate | Download | only in tests
      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 
     79 void xfunc_die(void)
     80 {
     81   exit(xfunc_error_retval);
     82 }
     83 
     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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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
    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