summaryrefslogtreecommitdiff
path: root/src/third_party/unwind/dist/tests/test-coredump-unwind.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/third_party/unwind/dist/tests/test-coredump-unwind.c')
-rw-r--r--src/third_party/unwind/dist/tests/test-coredump-unwind.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/src/third_party/unwind/dist/tests/test-coredump-unwind.c b/src/third_party/unwind/dist/tests/test-coredump-unwind.c
new file mode 100644
index 00000000000..53498237c2f
--- /dev/null
+++ b/src/third_party/unwind/dist/tests/test-coredump-unwind.c
@@ -0,0 +1,395 @@
+/*
+ * Example program for unwinding core dumps.
+ *
+ * Compile a-la:
+ * gcc -Os -Wall \
+ * -Wl,--start-group \
+ * -lunwind -lunwind-x86 -lunwind-coredump \
+ * example-core-unwind.c \
+ * -Wl,--end-group \
+ * -oexample-core-unwind
+ *
+ * Run:
+ * eu-unstrip -n --core COREDUMP
+ * figure out which virtual addresses in COREDUMP correspond to which mapped executable files
+ * (binary and libraries), then supply them like this:
+ * ./example-core-unwind COREDUMP 0x400000:/bin/crashed_program 0x3458600000:/lib/libc.so.6 [...]
+ *
+ * Note: Program eu-unstrip is part of elfutils, virtual addresses of shared
+ * libraries can be determined by ldd (at least on linux).
+ */
+
+#include "compiler.h"
+
+#undef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#undef __USE_GNU
+#define __USE_GNU 1
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/poll.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <pwd.h>
+#include <grp.h>
+
+/* For SIGSEGV handler code */
+#include <execinfo.h>
+#include <sys/ucontext.h>
+
+#include <libunwind-coredump.h>
+
+
+/* Utility logging functions */
+
+enum {
+ LOGMODE_NONE = 0,
+ LOGMODE_STDIO = (1 << 0),
+ LOGMODE_SYSLOG = (1 << 1),
+ LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO,
+};
+const char *msg_prefix = "";
+const char *msg_eol = "\n";
+int logmode = LOGMODE_STDIO;
+int xfunc_error_retval = EXIT_FAILURE;
+
+void xfunc_die(void)
+{
+ exit(xfunc_error_retval);
+}
+
+static void verror_msg_helper(const char *s,
+ va_list p,
+ const char* strerr,
+ int flags)
+{
+ char *msg;
+ int prefix_len, strerr_len, msgeol_len, used;
+
+ if (!logmode)
+ return;
+
+ used = vasprintf(&msg, s, p);
+ if (used < 0)
+ return;
+
+ /* This is ugly and costs +60 bytes compared to multiple
+ * fprintf's, but is guaranteed to do a single write.
+ * This is needed for e.g. when multiple children
+ * can produce log messages simultaneously. */
+
+ prefix_len = msg_prefix[0] ? strlen(msg_prefix) + 2 : 0;
+ strerr_len = strerr ? strlen(strerr) : 0;
+ msgeol_len = strlen(msg_eol);
+ /* +3 is for ": " before strerr and for terminating NUL */
+ char *msg1 = (char*) realloc(msg, prefix_len + used + strerr_len + msgeol_len + 3);
+ if (!msg1)
+ {
+ free(msg);
+ return;
+ }
+ msg = msg1;
+ /* TODO: maybe use writev instead of memmoving? Need full_writev? */
+ if (prefix_len)
+ {
+ char *p;
+ memmove(msg + prefix_len, msg, used);
+ used += prefix_len;
+ p = stpcpy(msg, msg_prefix);
+ p[0] = ':';
+ p[1] = ' ';
+ }
+ if (strerr)
+ {
+ if (s[0])
+ {
+ msg[used++] = ':';
+ msg[used++] = ' ';
+ }
+ strcpy(&msg[used], strerr);
+ used += strerr_len;
+ }
+ strcpy(&msg[used], msg_eol);
+
+ if (flags & LOGMODE_STDIO)
+ {
+ fflush(stdout);
+ write(STDERR_FILENO, msg, used + msgeol_len);
+ }
+ msg[used] = '\0'; /* remove msg_eol (usually "\n") */
+ if (flags & LOGMODE_SYSLOG)
+ {
+ syslog(LOG_ERR, "%s", msg + prefix_len);
+ }
+ free(msg);
+}
+
+void log_msg(const char *s, ...)
+{
+ va_list p;
+ va_start(p, s);
+ verror_msg_helper(s, p, NULL, logmode);
+ va_end(p);
+}
+/* It's a macro, not function, since it collides with log() from math.h */
+#undef log
+#define log(...) log_msg(__VA_ARGS__)
+
+void error_msg(const char *s, ...)
+{
+ va_list p;
+ va_start(p, s);
+ verror_msg_helper(s, p, NULL, logmode);
+ va_end(p);
+}
+
+void error_msg_and_die(const char *s, ...)
+{
+ va_list p;
+ va_start(p, s);
+ verror_msg_helper(s, p, NULL, logmode);
+ va_end(p);
+ xfunc_die();
+}
+
+void perror_msg(const char *s, ...)
+{
+ va_list p;
+ va_start(p, s);
+ /* Guard against "<error message>: Success" */
+ verror_msg_helper(s, p, errno ? strerror(errno) : NULL, logmode);
+ va_end(p);
+}
+
+void perror_msg_and_die(const char *s, ...)
+{
+ va_list p;
+ va_start(p, s);
+ /* Guard against "<error message>: Success" */
+ verror_msg_helper(s, p, errno ? strerror(errno) : NULL, logmode);
+ va_end(p);
+ xfunc_die();
+}
+
+void die_out_of_memory(void)
+{
+ error_msg_and_die("Out of memory, exiting");
+}
+
+/* End of utility logging functions */
+
+
+
+static
+void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
+{
+ long ip = 0;
+ ucontext_t *uc UNUSED;
+
+ uc = ucontext;
+#if defined(__linux__)
+#ifdef UNW_TARGET_X86
+ ip = uc->uc_mcontext.gregs[REG_EIP];
+#elif defined(UNW_TARGET_X86_64)
+ ip = uc->uc_mcontext.gregs[REG_RIP];
+#elif defined(UNW_TARGET_ARM)
+ ip = uc->uc_mcontext.arm_pc;
+#endif
+#elif defined(__FreeBSD__)
+#ifdef __i386__
+ ip = uc->uc_mcontext.mc_eip;
+#elif defined(__amd64__)
+ ip = uc->uc_mcontext.mc_rip;
+#else
+#error Port me
+#endif
+#else
+#error Port me
+#endif
+ dprintf(2, "signal:%d address:0x%lx ip:0x%lx\n",
+ sig,
+ /* this is void*, but using %p would print "(null)"
+ * even for ptrs which are not exactly 0, but, say, 0x123:
+ */
+ (long)info->si_addr,
+ ip);
+
+ {
+ /* glibc extension */
+ void *array[50];
+ int size;
+ size = backtrace(array, 50);
+#ifdef __linux__
+ backtrace_symbols_fd(array, size, 2);
+#endif
+ }
+
+ _exit(1);
+}
+
+static void install_sigsegv_handler(void)
+{
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = handle_sigsegv;
+ sa.sa_flags = SA_SIGINFO;
+ sigaction(SIGSEGV, &sa, NULL);
+ sigaction(SIGILL, &sa, NULL);
+ sigaction(SIGFPE, &sa, NULL);
+ sigaction(SIGBUS, &sa, NULL);
+}
+
+int
+main(int argc UNUSED, char **argv)
+{
+ unw_addr_space_t as;
+ struct UCD_info *ui;
+ unw_cursor_t c;
+ int ret;
+
+#define TEST_FRAMES 4
+#define TEST_NAME_LEN 32
+ int testcase = 0;
+ int test_cur = 0;
+ long test_start_ips[TEST_FRAMES];
+ char test_names[TEST_FRAMES][TEST_NAME_LEN];
+
+ install_sigsegv_handler();
+
+ const char *progname = strrchr(argv[0], '/');
+ if (progname)
+ progname++;
+ else
+ progname = argv[0];
+
+ if (!argv[1])
+ error_msg_and_die("Usage: %s COREDUMP [VADDR:BINARY_FILE]...", progname);
+
+ msg_prefix = progname;
+
+ as = unw_create_addr_space(&_UCD_accessors, 0);
+ if (!as)
+ error_msg_and_die("unw_create_addr_space() failed");
+
+ ui = _UCD_create(argv[1]);
+ if (!ui)
+ error_msg_and_die("_UCD_create('%s') failed", argv[1]);
+ ret = unw_init_remote(&c, as, ui);
+ if (ret < 0)
+ error_msg_and_die("unw_init_remote() failed: ret=%d\n", ret);
+
+ argv += 2;
+
+ /* Enable checks for the crasher test program? */
+ if (*argv && !strcmp(*argv, "-testcase"))
+ {
+ testcase = 1;
+ logmode = LOGMODE_NONE;
+ argv++;
+ }
+
+ while (*argv)
+ {
+ char *colon;
+ unsigned long vaddr = strtoul(*argv, &colon, 16);
+ if (*colon != ':')
+ error_msg_and_die("Bad format: '%s'", *argv);
+ if (_UCD_add_backing_file_at_vaddr(ui, vaddr, colon + 1) < 0)
+ error_msg_and_die("Can't add backing file '%s'", colon + 1);
+ argv++;
+ }
+
+ for (;;)
+ {
+ unw_word_t ip;
+ ret = unw_get_reg(&c, UNW_REG_IP, &ip);
+ if (ret < 0)
+ error_msg_and_die("unw_get_reg(UNW_REG_IP) failed: ret=%d\n", ret);
+
+ unw_proc_info_t pi;
+ ret = unw_get_proc_info(&c, &pi);
+ if (ret < 0)
+ error_msg_and_die("unw_get_proc_info(ip=0x%lx) failed: ret=%d\n", (long) ip, ret);
+
+ if (!testcase)
+ printf("\tip=0x%08lx proc=%08lx-%08lx handler=0x%08lx lsda=0x%08lx\n",
+ (long) ip,
+ (long) pi.start_ip, (long) pi.end_ip,
+ (long) pi.handler, (long) pi.lsda);
+
+ if (testcase && test_cur < TEST_FRAMES)
+ {
+ unw_word_t off;
+
+ test_start_ips[test_cur] = (long) pi.start_ip;
+ if (unw_get_proc_name(&c, test_names[test_cur], sizeof(test_names[0]), &off) != 0)
+ {
+ test_names[test_cur][0] = '\0';
+ }
+ test_cur++;
+ }
+
+ log("step");
+ ret = unw_step(&c);
+ log("step done:%d", ret);
+ if (ret < 0)
+ error_msg_and_die("FAILURE: unw_step() returned %d", ret);
+ if (ret == 0)
+ break;
+ }
+ log("stepping ended");
+
+ /* Check that the second and third frames are equal, but distinct of the
+ * others */
+ if (testcase &&
+ (test_cur != 4
+ || test_start_ips[1] != test_start_ips[2]
+ || test_start_ips[0] == test_start_ips[1]
+ || test_start_ips[2] == test_start_ips[3]
+ )
+ )
+ {
+ fprintf(stderr, "FAILURE: start IPs incorrect\n");
+ return -1;
+ }
+
+ if (testcase &&
+ ( strcmp(test_names[0], "a")
+ || strcmp(test_names[1], "b")
+ || strcmp(test_names[2], "b")
+ || strcmp(test_names[3], "main")
+ )
+ )
+ {
+ fprintf(stderr, "FAILURE: procedure names are missing/incorrect\n");
+ return -1;
+ }
+
+ _UCD_destroy(ui);
+ unw_destroy_addr_space(as);
+
+ return 0;
+}