Skip to content

Instantly share code, notes, and snippets.

@JCash
Last active July 16, 2020 18:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JCash/c879c8a7a415f367e58793d9b6b10dbd to your computer and use it in GitHub Desktop.
Save JCash/c879c8a7a415f367e58793d9b6b10dbd to your computer and use it in GitHub Desktop.
How to print the correct, offending callstack
#include <string.h>
#include <signal.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <cxxabi.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
// https://fossies.org/linux/ruby/vm_dump.c
int backtrace(void **trace, int size)
{
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip;
int n = 0;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
while (unw_step(&cursor) > 0) {
unw_get_reg(&cursor, UNW_REG_IP, &ip);
trace[n++] = (void *)ip;
char buf[256] = {};
unw_get_proc_name(&cursor, buf, sizeof(buf), &ip);
printf("%2d: %s\n", n, buf);
#if defined(__APPLE__)
if (strncmp("_sigtramp", buf, sizeof("_sigtramp")) == 0) {
goto darwin_sigtramp;
}
#endif
}
return n;
#if defined(__APPLE__)
darwin_sigtramp:
/* darwin's bundled libunwind doesn't support signal trampoline */
{
ucontext_t *uctx;
/* get _sigtramp's ucontext_t and set values to cursor
* http://www.opensource.apple.com/source/Libc/Libc-825.25/i386/sys/_sigtramp.s
* http://www.opensource.apple.com/source/libunwind/libunwind-35.1/src/unw_getcontext.s
*/
unw_get_reg(&cursor, UNW_X86_64_RBX, &ip);
uctx = (ucontext_t *)ip;
# if __DARWIN_UNIX03
# define MCTX_SS_REG(reg) __ss.__##reg
# else
# define MCTX_SS_REG(reg) ss.reg
# endif
unw_set_reg(&cursor, UNW_X86_64_RAX, uctx->uc_mcontext->MCTX_SS_REG(rax));
unw_set_reg(&cursor, UNW_X86_64_RBX, uctx->uc_mcontext->MCTX_SS_REG(rbx));
unw_set_reg(&cursor, UNW_X86_64_RCX, uctx->uc_mcontext->MCTX_SS_REG(rcx));
unw_set_reg(&cursor, UNW_X86_64_RDX, uctx->uc_mcontext->MCTX_SS_REG(rdx));
unw_set_reg(&cursor, UNW_X86_64_RDI, uctx->uc_mcontext->MCTX_SS_REG(rdi));
unw_set_reg(&cursor, UNW_X86_64_RSI, uctx->uc_mcontext->MCTX_SS_REG(rsi));
unw_set_reg(&cursor, UNW_X86_64_RBP, uctx->uc_mcontext->MCTX_SS_REG(rbp));
unw_set_reg(&cursor, UNW_X86_64_RSP, 8+(uctx->uc_mcontext->MCTX_SS_REG(rsp)));
unw_set_reg(&cursor, UNW_X86_64_R8, uctx->uc_mcontext->MCTX_SS_REG(r8));
unw_set_reg(&cursor, UNW_X86_64_R9, uctx->uc_mcontext->MCTX_SS_REG(r9));
unw_set_reg(&cursor, UNW_X86_64_R10, uctx->uc_mcontext->MCTX_SS_REG(r10));
unw_set_reg(&cursor, UNW_X86_64_R11, uctx->uc_mcontext->MCTX_SS_REG(r11));
unw_set_reg(&cursor, UNW_X86_64_R12, uctx->uc_mcontext->MCTX_SS_REG(r12));
unw_set_reg(&cursor, UNW_X86_64_R13, uctx->uc_mcontext->MCTX_SS_REG(r13));
unw_set_reg(&cursor, UNW_X86_64_R14, uctx->uc_mcontext->MCTX_SS_REG(r14));
unw_set_reg(&cursor, UNW_X86_64_R15, uctx->uc_mcontext->MCTX_SS_REG(r15));
ip = uctx->uc_mcontext->MCTX_SS_REG(rip);
/* There are 4 cases for SEGV:
* (1) called invalid address
* (2) read or write invalid address
* (3) received signal
*
* Detail:
* (1) called invalid address
* In this case, saved ip is invalid address.
* It needs to just save the address for the information,
* skip the frame, and restore the frame calling the
* invalid address from %rsp.
* The problem is how to check whether the ip is valid or not.
* This code uses mincore(2) and assume the address's page is
* incore/referenced or not reflects the problem.
* Note that High Sierra's mincore(2) may return -128.
* (2) read or write invalid address
* saved ip is valid. just restart backtracing.
* (3) received signal in user space
* Same as (2).
* (4) received signal in kernel
* In this case saved ip points just after syscall, but registers are
* already overwritten by kernel. To fix register consistency,
* skip libc's kernel wrapper.
* To detect this case, just previous two bytes of ip is "\x0f\x05",
* syscall instruction of x86_64.
*/
char vec[1];
int r = mincore((const void *)ip, 1, vec);
if (r || vec[0] <= 0 || memcmp((const char *)ip-2, "\x0f\x05", 2) == 0) {
// if segv is caused by invalid call or signal received in syscall
// the frame is invalid; skip
trace[n++] = (void *)ip;
ip = *(unw_word_t*)uctx->uc_mcontext->MCTX_SS_REG(rsp);
}
trace[n++] = (void *)ip;
unw_set_reg(&cursor, UNW_REG_IP, ip);
}
while (unw_step(&cursor) > 0) {
unw_get_reg(&cursor, UNW_REG_IP, &ip);
trace[n++] = (void *)ip;
char buf[256] = {};
unw_get_proc_name(&cursor, buf, sizeof(buf), &ip);
printf("%2d: %s\n", n, buf); // // doesn't get here
}
return n;
#endif // __APPLE__
}
void cause_segfault()
{
// produces a well behaved callstack from cause_segfault() to main()
// int * p = (int*)0x12345678;
// *p = 0;
// Doesn't produce a proper callstack. Stops at "_sigtramp"
printf("STR: %s\n", (char*)1);
}
int fac(int n)
{
if ( n == 0 ) {
cause_segfault();
return 1;
} else {
return n*fac(n-1);
}
}
// This array contains the default behavior for each signal.
static const int SIGNAL_MAX = 64;
static struct sigaction sigdfl[SIGNAL_MAX];
static void Handler(const int signum, siginfo_t *const si, void *const sc)
{
sigaction(signum, &sigdfl[signum], NULL); // to avoid infinite recursion
void* callstack[64];
backtrace(callstack, 64);
}
void InstallOnSignal(int signum)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = Handler;
sa.sa_flags = SA_SIGINFO;
// The current (default) behavior is stored in sigdfl.
sigaction(signum, &sa, &sigdfl[signum]);
}
void InstallHandler()
{
InstallOnSignal(SIGSEGV);
InstallOnSignal(SIGBUS);
InstallOnSignal(SIGTRAP);
InstallOnSignal(SIGILL);
InstallOnSignal(SIGABRT);
}
int main()
{
InstallHandler();
fac(10);
return 0;
}

Currently, I cannot seem to generate a proper callstack using libunwind, from within a signal handler. This is what happens (OSX Catalina 10.15.3)

$ clang++ -g -fPIC backtrace_darwin.cpp -o backtrace_darwin 
$ ./backtrace_darwin                                                   [master] 19:55:44
 1: _ZL7HandleriP9__siginfoPv
 2: _sigtramp
darwin_sigtramp
[1]    81838 segmentation fault  ./backtrace_darwin

clang:

Apple clang version 11.0.3 (clang-1103.0.32.62)
Target: x86_64-apple-darwin19.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
CXXFLAGS := \
-O0 -g -Wall \
-march=native -mtune=native \
-I/usr/local/opt/libunwind-headers/include
ifeq ($(shell uname), Linux)
LDFLAGS := -lunwind
endif
TARGETS := \
backtrace
all: $(TARGETS)
run: all
./backtrace
clean:
rm -rf $(TARGETS) *.dSYM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment