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