mirror of
https://github.com/tobast/libunwind-eh_elf.git
synced 2024-11-25 16:47:38 +01:00
coredump: add test
Program test-coredump-unwind was modified to map backing files based on virtual addresses instead of segment numbers. The crasher.c is a program that essentially calls some functions and then writes to invalid address causing a crash. Before that, it detects which executables are mapped to which virtual addresses and writes this information to a file suitable for consumption by test-coredump-unwind. The mapping information is obtained form /proc/self/maps, so currently it only works on linux. The test itself is a shell script, which first runs the program and then runs test-coredump-unwind on the resulting core and address space map file to check whether the stack trace obtained from the dump roughly corresponds to what it should look like. Signed-off-by: Martin Milata <mmilata@redhat.com>
This commit is contained in:
parent
61a173763e
commit
0f9a540c8c
5 changed files with 149 additions and 20 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -38,10 +38,12 @@ tests/Ltest-nomalloc
|
||||||
tests/Ltest-nocalloc
|
tests/Ltest-nocalloc
|
||||||
tests/Lperf-simple
|
tests/Lperf-simple
|
||||||
tests/check-namespace.sh
|
tests/check-namespace.sh
|
||||||
|
tests/crasher
|
||||||
tests/forker
|
tests/forker
|
||||||
tests/mapper
|
tests/mapper
|
||||||
tests/rs-race
|
tests/rs-race
|
||||||
tests/test-async-sig
|
tests/test-async-sig
|
||||||
|
tests/test-coredump-unwind
|
||||||
tests/test-flush-cache
|
tests/test-flush-cache
|
||||||
tests/test-init-remote
|
tests/test-init-remote
|
||||||
tests/test-mem
|
tests/test-mem
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
AM_CPPFLAGS = -I$(top_srcdir)/include
|
AM_CPPFLAGS = -I$(top_srcdir)/include
|
||||||
|
|
||||||
EXTRA_DIST = run-ia64-test-dyn1 run-ptrace-mapper run-ptrace-misc \
|
EXTRA_DIST = run-ia64-test-dyn1 run-ptrace-mapper run-ptrace-misc \
|
||||||
run-check-namespace check-namespace.sh.in Gtest-nomalloc.c \
|
run-check-namespace run-coredump-unwind \
|
||||||
Gtest-nocalloc.c
|
check-namespace.sh.in Gtest-nomalloc.c Gtest-nocalloc.c
|
||||||
|
|
||||||
MAINTAINERCLEANFILES = Makefile.in
|
MAINTAINERCLEANFILES = Makefile.in
|
||||||
|
|
||||||
|
@ -45,9 +45,8 @@ endif #ARCH_IA64
|
||||||
Gtest-trace Ltest-trace \
|
Gtest-trace Ltest-trace \
|
||||||
test-async-sig test-flush-cache test-init-remote \
|
test-async-sig test-flush-cache test-init-remote \
|
||||||
test-mem test-setjmp test-ptrace \
|
test-mem test-setjmp test-ptrace \
|
||||||
Ltest-nomalloc Ltest-nocalloc rs-race \
|
Ltest-nomalloc Ltest-nocalloc rs-race
|
||||||
test-coredump-unwind
|
noinst_PROGRAMS_cdep = forker crasher mapper test-ptrace-misc \
|
||||||
noinst_PROGRAMS_cdep = forker mapper test-ptrace-misc \
|
|
||||||
Gperf-simple Lperf-simple
|
Gperf-simple Lperf-simple
|
||||||
|
|
||||||
if HAVE_BACKTRACE
|
if HAVE_BACKTRACE
|
||||||
|
@ -58,6 +57,11 @@ if SUPPORT_CXX_EXCEPTIONS
|
||||||
check_PROGRAMS_cdep += Ltest-cxx-exceptions
|
check_PROGRAMS_cdep += Ltest-cxx-exceptions
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if OS_LINUX
|
||||||
|
check_SCRIPTS_cdep += run-coredump-unwind
|
||||||
|
noinst_PROGRAMS_cdep += test-coredump-unwind
|
||||||
|
endif
|
||||||
|
|
||||||
perf: perf-startup Gperf-simple Lperf-simple Lperf-trace
|
perf: perf-startup Gperf-simple Lperf-simple Lperf-trace
|
||||||
@echo "########## Basic performance of generic libunwind:"
|
@echo "########## Basic performance of generic libunwind:"
|
||||||
@./Gperf-simple
|
@./Gperf-simple
|
||||||
|
@ -111,6 +115,10 @@ Ltest_nocalloc_SOURCES = Ltest-nocalloc.c
|
||||||
Gtest_trace_SOURCES = Gtest-trace.c ident.c
|
Gtest_trace_SOURCES = Gtest-trace.c ident.c
|
||||||
Ltest_trace_SOURCES = Ltest-trace.c ident.c
|
Ltest_trace_SOURCES = Ltest-trace.c ident.c
|
||||||
|
|
||||||
|
# prevent function inlining
|
||||||
|
crasher: crasher.c
|
||||||
|
$(CC) -O0 -o crasher crasher.c
|
||||||
|
|
||||||
LIBUNWIND = $(top_builddir)/src/libunwind-$(arch).la
|
LIBUNWIND = $(top_builddir)/src/libunwind-$(arch).la
|
||||||
LIBUNWIND_ptrace = $(top_builddir)/src/libunwind-ptrace.a
|
LIBUNWIND_ptrace = $(top_builddir)/src/libunwind-ptrace.a
|
||||||
LIBUNWIND_coredump = $(top_builddir)/src/libunwind-coredump.a
|
LIBUNWIND_coredump = $(top_builddir)/src/libunwind-coredump.a
|
||||||
|
|
63
tests/crasher.c
Normal file
63
tests/crasher.c
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/* This program should crash and produce coredump */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
void a(void) __attribute__((noinline));
|
||||||
|
void b(int x) __attribute__((noinline));
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
void write_maps(char *fname)
|
||||||
|
{
|
||||||
|
char buf[512], path[128];
|
||||||
|
char exec;
|
||||||
|
uintmax_t addr;
|
||||||
|
FILE *maps = fopen("/proc/self/maps", "r");
|
||||||
|
FILE *out = fopen(fname, "w");
|
||||||
|
|
||||||
|
if (!maps || !out)
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
while (fgets(buf, sizeof(buf), maps))
|
||||||
|
{
|
||||||
|
if (sscanf(buf, "%jx-%*jx %*c%*c%c%*c %*x %*s %*d /%126[^\n]", &addr, &exec, path+1) != 3)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (exec != 'x')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
path[0] = '/';
|
||||||
|
fprintf(out, "0x%jx:%s ", addr, path);
|
||||||
|
}
|
||||||
|
fprintf(out, "\n");
|
||||||
|
|
||||||
|
fclose(out);
|
||||||
|
fclose(maps);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error Port me
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void a(void)
|
||||||
|
{
|
||||||
|
*(int *)NULL = 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
void b(int x)
|
||||||
|
{
|
||||||
|
if (x)
|
||||||
|
a();
|
||||||
|
else
|
||||||
|
b(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc > 1)
|
||||||
|
write_maps(argv[1]);
|
||||||
|
b(0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
20
tests/run-coredump-unwind
Executable file
20
tests/run-coredump-unwind
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
TESTDIR=`pwd`
|
||||||
|
TEMPDIR=`mktemp -d`
|
||||||
|
|
||||||
|
# create core dump
|
||||||
|
(
|
||||||
|
cd $TEMPDIR
|
||||||
|
ulimit -c 10000
|
||||||
|
$TESTDIR/crasher $TEMPDIR/backing_files
|
||||||
|
) 2>/dev/null
|
||||||
|
COREFILE=$TEMPDIR/core*
|
||||||
|
|
||||||
|
# fail if any command fails
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# magic option -testcase enables checking for the specific contents of the stack
|
||||||
|
./test-coredump-unwind $COREFILE -testcase `cat $TEMPDIR/backing_files`
|
||||||
|
|
||||||
|
rm -r -- $TEMPDIR
|
|
@ -10,11 +10,13 @@
|
||||||
* -oexample-core-unwind
|
* -oexample-core-unwind
|
||||||
*
|
*
|
||||||
* Run:
|
* Run:
|
||||||
* objdump -sx COREDUMP
|
|
||||||
* eu-unstrip -n --core COREDUMP
|
* eu-unstrip -n --core COREDUMP
|
||||||
* figure out which segments in COREDUMP correspond to which mapped executable files
|
* figure out which virtual addresses in COREDUMP correspond to which mapped executable files
|
||||||
* (binary and libraries), then supply them like this:
|
* (binary and libraries), then supply them like this:
|
||||||
* ./example-core-unwind COREDUMP 3:/bin/crashed_program 6:/lib/libc.so.6 [...]
|
* ./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
|
#undef _GNU_SOURCE
|
||||||
|
@ -207,11 +209,11 @@ void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
|
||||||
|
|
||||||
uc = ucontext;
|
uc = ucontext;
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
#ifdef TARGET_X86
|
#ifdef UNW_TARGET_X86
|
||||||
ip = uc->uc_mcontext.gregs[REG_EIP];
|
ip = uc->uc_mcontext.gregs[REG_EIP];
|
||||||
#elif defined(TARGET_X86_64)
|
#elif defined(UNW_TARGET_X86_64)
|
||||||
ip = uc->uc_mcontext.gregs[REG_RIP];
|
ip = uc->uc_mcontext.gregs[REG_RIP];
|
||||||
#elif defined(TARGET_ARM)
|
#elif defined(UNW_TARGET_ARM)
|
||||||
ip = uc->uc_mcontext.arm_ip;
|
ip = uc->uc_mcontext.arm_ip;
|
||||||
#endif
|
#endif
|
||||||
#elif defined(__FreeBSD__)
|
#elif defined(__FreeBSD__)
|
||||||
|
@ -266,6 +268,11 @@ main(int argc, char **argv)
|
||||||
unw_cursor_t c;
|
unw_cursor_t c;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
#define TEST_FRAMES 4
|
||||||
|
int testcase = 0;
|
||||||
|
int test_cur = 0;
|
||||||
|
long test_start_ips[TEST_FRAMES];
|
||||||
|
|
||||||
install_sigsegv_handler();
|
install_sigsegv_handler();
|
||||||
|
|
||||||
const char *progname = strrchr(argv[0], '/');
|
const char *progname = strrchr(argv[0], '/');
|
||||||
|
@ -275,7 +282,7 @@ main(int argc, char **argv)
|
||||||
progname = argv[0];
|
progname = argv[0];
|
||||||
|
|
||||||
if (!argv[1])
|
if (!argv[1])
|
||||||
error_msg_and_die("Usage: %s COREDUMP [SEGMENT_NO:BINARY_FILE]...", progname);
|
error_msg_and_die("Usage: %s COREDUMP [VADDR:BINARY_FILE]...", progname);
|
||||||
|
|
||||||
msg_prefix = progname;
|
msg_prefix = progname;
|
||||||
|
|
||||||
|
@ -291,16 +298,23 @@ main(int argc, char **argv)
|
||||||
error_msg_and_die("unw_init_remote() failed: ret=%d\n", ret);
|
error_msg_and_die("unw_init_remote() failed: ret=%d\n", ret);
|
||||||
|
|
||||||
argv += 2;
|
argv += 2;
|
||||||
|
|
||||||
|
/* Enable checks for the crasher test program? */
|
||||||
|
if (*argv && !strcmp(*argv, "-testcase"))
|
||||||
|
{
|
||||||
|
testcase = 1;
|
||||||
|
logmode = LOGMODE_NONE;
|
||||||
|
argv++;
|
||||||
|
}
|
||||||
|
|
||||||
while (*argv)
|
while (*argv)
|
||||||
{
|
{
|
||||||
char *colon = strchr(*argv, ':');
|
char *colon;
|
||||||
if (!colon)
|
long vaddr = strtol(*argv, &colon, 16);
|
||||||
|
if (*colon != ':')
|
||||||
error_msg_and_die("Bad format: '%s'", *argv);
|
error_msg_and_die("Bad format: '%s'", *argv);
|
||||||
*colon = '\0';
|
if (_UCD_add_backing_file_at_vaddr(ui, vaddr, colon + 1) < 0)
|
||||||
unsigned n = atoi(*argv);
|
error_msg_and_die("Can't add backing file '%s'", colon + 1);
|
||||||
*colon = ':';
|
|
||||||
if (_UCD_add_backing_file_at_segment(ui, n, colon + 1) < 0)
|
|
||||||
error_msg_and_die("Can't add backing file '%s'", *argv);
|
|
||||||
argv++;
|
argv++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,11 +329,19 @@ main(int argc, char **argv)
|
||||||
ret = unw_get_proc_info(&c, &pi);
|
ret = unw_get_proc_info(&c, &pi);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
error_msg_and_die("unw_get_proc_info(ip=0x%lx) failed: ret=%d\n", (long) ip, ret);
|
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",
|
printf("\tip=0x%08lx proc=%08lx-%08lx handler=0x%08lx lsda=0x%08lx\n",
|
||||||
(long) ip,
|
(long) ip,
|
||||||
(long) pi.start_ip, (long) pi.end_ip,
|
(long) pi.start_ip, (long) pi.end_ip,
|
||||||
(long) pi.handler, (long) pi.lsda);
|
(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");
|
log("step");
|
||||||
ret = unw_step(&c);
|
ret = unw_step(&c);
|
||||||
log("step done:%d", ret);
|
log("step done:%d", ret);
|
||||||
|
@ -330,6 +352,20 @@ main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
log("stepping ended");
|
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);
|
_UCD_destroy(ui);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in a new issue