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