mirror of
https://github.com/tobast/libunwind-eh_elf.git
synced 2024-11-25 16:47:38 +01:00
9158e522db
The intention in the test cases is to print the "instruction pointer" value at certain places, and on ARM we will want to get the Program Counter in these cases. IP is a scratch register, and not very interesting.
372 lines
8.2 KiB
C
372 lines
8.2 KiB
C
/*
|
|
* 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).
|
|
*/
|
|
|
|
#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;
|
|
ucontext_t *uc;
|
|
|
|
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, char **argv)
|
|
{
|
|
unw_addr_space_t as;
|
|
struct UCD_info *ui;
|
|
unw_cursor_t c;
|
|
int ret;
|
|
|
|
#define TEST_FRAMES 4
|
|
int testcase = 0;
|
|
int test_cur = 0;
|
|
long test_start_ips[TEST_FRAMES];
|
|
|
|
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;
|
|
long vaddr = strtol(*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)
|
|
{
|
|
test_start_ips[test_cur] = (long) pi.start_ip;
|
|
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;
|
|
}
|
|
|
|
_UCD_destroy(ui);
|
|
|
|
return 0;
|
|
}
|