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 68enum { 69 LOGMODE_NONE = 0, 70 LOGMODE_STDIO = (1 << 0), 71 LOGMODE_SYSLOG = (1 << 1), 72 LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO, 73}; 74const char *msg_prefix = ""; 75const char *msg_eol = "\n"; 76int logmode = LOGMODE_STDIO; 77int xfunc_error_retval = EXIT_FAILURE; 78 79void xfunc_die(void) 80{ 81 exit(xfunc_error_retval); 82} 83 84static 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 150void 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 161void 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 169void 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 178void 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 187void 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 197void 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 206static 207void 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 253static 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 265int 266main(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