Initial crash triaging support for DeepState harnesses
Adds initial support for emitting out a backtrace after a forked DeepState test throws some type of signal using --verbose_crash_trace. The output for a buffer overflow/memory corruption example looks as so:
$ ./a.out --verbose_crash_trace --input_test_file input/test
WARNING: No test specified, defaulting to first test defined (Test_Crashing)
TRACE: Initialized test input buffer with data from `input/test`
TRACE: Running: Test_Crashing from test.cpp(6)
INFO: Test crashed with: Bus error
TRACE: ./a.out() [0x409a8c]
TRACE: /lib/x86_64-linux-gnu/libc.so.6(+0x41100) [0x7f3f2ff1e100]
TRACE: /lib/x86_64-linux-gnu/libc.so.6(waitpid+0x17) [0x7f3f2ffbb3b7]
TRACE: ./a.out() [0x408bf1]
TRACE: ./a.out() [0x409d83]
TRACE: ./a.out(main+0x460) [0x409600]
TRACE: /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f3f2ff0109b]
TRACE: ./a.out(_start+0x2a) [0x40438a]
This is made possible by handling the SIGCHLD signal, and getting information from the siginfo_t * struct regarding the child's termination.
TODO
- [ ] Stack trace into remote child
- [ ] Resolve symbol names
- [ ] Validate platform-independent compatibility for
execinfo - [ ] Emit line number from backtrace addresses
Stack unwinding and backtrace becomes tricky and a little intrusive / not super security conscious when involving forked processes during execution, since it involves using ptrace, most likely through libunwind's interface. libunwind doesn't work well as a static library, so we stick with the de facto glibc backtrace functionality, and extend it to support backtracing local single-process test runs (--fork=0)