-
-
Save jvranish/4441299 to your computer and use it in GitHub Desktop.
| /* compile with: | |
| on linux: gcc -g stack_traces.c | |
| on OS X: gcc -g -fno-pie stack_traces.c | |
| on windows: gcc -g stack_traces.c -limagehlp | |
| */ | |
| #include <signal.h> | |
| #include <stdio.h> | |
| #include <assert.h> | |
| #include <stdlib.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdbool.h> | |
| #include <errno.h> | |
| #ifdef _WIN32 | |
| #include <windows.h> | |
| #include <imagehlp.h> | |
| #else | |
| #include <err.h> | |
| #include <execinfo.h> | |
| #endif | |
| // void almost_c99_signal_handler(int sig) | |
| // { | |
| // switch(sig) | |
| // { | |
| // case SIGABRT: | |
| // fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr); | |
| // break; | |
| // case SIGFPE: | |
| // fputs("Caught SIGFPE: arithmetic exception, such as divide by zero\n", stderr); | |
| // break; | |
| // case SIGILL: | |
| // fputs("Caught SIGILL: illegal instruction\n", stderr); | |
| // break; | |
| // case SIGINT: | |
| // fputs("Caught SIGINT: interactive attention signal, probably a ctrl+c\n", stderr); | |
| // break; | |
| // case SIGSEGV: | |
| // fputs("Caught SIGSEGV: segfault\n", stderr); | |
| // break; | |
| // case SIGTERM: | |
| // default: | |
| // fputs("Caught SIGTERM: a termination request was sent to the program\n", stderr); | |
| // break; | |
| // } | |
| // _Exit(1); | |
| // } | |
| // void set_signal_handler() | |
| // { | |
| // signal(SIGABRT, almost_c99_signal_handler); | |
| // signal(SIGFPE, almost_c99_signal_handler); | |
| // signal(SIGILL, almost_c99_signal_handler); | |
| // signal(SIGINT, almost_c99_signal_handler); | |
| // signal(SIGSEGV, almost_c99_signal_handler); | |
| // signal(SIGTERM, almost_c99_signal_handler); | |
| // } | |
| static char const * icky_global_program_name; | |
| /* Resolve symbol name and source location given the path to the executable | |
| and an address */ | |
| int addr2line(char const * const program_name, void const * const addr) | |
| { | |
| char addr2line_cmd[512] = {0}; | |
| /* have addr2line map the address to the relent line in the code */ | |
| #ifdef __APPLE__ | |
| /* apple does things differently... */ | |
| sprintf(addr2line_cmd,"atos -o %.256s %p", program_name, addr); | |
| #else | |
| sprintf(addr2line_cmd,"addr2line -f -p -e %.256s %p", program_name, addr); | |
| #endif | |
| return system(addr2line_cmd); | |
| } | |
| #ifdef _WIN32 | |
| void windows_print_stacktrace(CONTEXT* context) | |
| { | |
| SymInitialize(GetCurrentProcess(), 0, true); | |
| STACKFRAME frame = { 0 }; | |
| /* setup initial stack frame */ | |
| frame.AddrPC.Offset = context->Eip; | |
| frame.AddrPC.Mode = AddrModeFlat; | |
| frame.AddrStack.Offset = context->Esp; | |
| frame.AddrStack.Mode = AddrModeFlat; | |
| frame.AddrFrame.Offset = context->Ebp; | |
| frame.AddrFrame.Mode = AddrModeFlat; | |
| while (StackWalk(IMAGE_FILE_MACHINE_I386 , | |
| GetCurrentProcess(), | |
| GetCurrentThread(), | |
| &frame, | |
| context, | |
| 0, | |
| SymFunctionTableAccess, | |
| SymGetModuleBase, | |
| 0 ) ) | |
| { | |
| addr2line(icky_global_program_name, (void*)frame.AddrPC.Offset); | |
| } | |
| SymCleanup( GetCurrentProcess() ); | |
| } | |
| LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS * ExceptionInfo) | |
| { | |
| switch(ExceptionInfo->ExceptionRecord->ExceptionCode) | |
| { | |
| case EXCEPTION_ACCESS_VIOLATION: | |
| fputs("Error: EXCEPTION_ACCESS_VIOLATION\n", stderr); | |
| break; | |
| case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: | |
| fputs("Error: EXCEPTION_ARRAY_BOUNDS_EXCEEDED\n", stderr); | |
| break; | |
| case EXCEPTION_BREAKPOINT: | |
| fputs("Error: EXCEPTION_BREAKPOINT\n", stderr); | |
| break; | |
| case EXCEPTION_DATATYPE_MISALIGNMENT: | |
| fputs("Error: EXCEPTION_DATATYPE_MISALIGNMENT\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_DENORMAL_OPERAND: | |
| fputs("Error: EXCEPTION_FLT_DENORMAL_OPERAND\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_DIVIDE_BY_ZERO: | |
| fputs("Error: EXCEPTION_FLT_DIVIDE_BY_ZERO\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_INEXACT_RESULT: | |
| fputs("Error: EXCEPTION_FLT_INEXACT_RESULT\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_INVALID_OPERATION: | |
| fputs("Error: EXCEPTION_FLT_INVALID_OPERATION\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_OVERFLOW: | |
| fputs("Error: EXCEPTION_FLT_OVERFLOW\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_STACK_CHECK: | |
| fputs("Error: EXCEPTION_FLT_STACK_CHECK\n", stderr); | |
| break; | |
| case EXCEPTION_FLT_UNDERFLOW: | |
| fputs("Error: EXCEPTION_FLT_UNDERFLOW\n", stderr); | |
| break; | |
| case EXCEPTION_ILLEGAL_INSTRUCTION: | |
| fputs("Error: EXCEPTION_ILLEGAL_INSTRUCTION\n", stderr); | |
| break; | |
| case EXCEPTION_IN_PAGE_ERROR: | |
| fputs("Error: EXCEPTION_IN_PAGE_ERROR\n", stderr); | |
| break; | |
| case EXCEPTION_INT_DIVIDE_BY_ZERO: | |
| fputs("Error: EXCEPTION_INT_DIVIDE_BY_ZERO\n", stderr); | |
| break; | |
| case EXCEPTION_INT_OVERFLOW: | |
| fputs("Error: EXCEPTION_INT_OVERFLOW\n", stderr); | |
| break; | |
| case EXCEPTION_INVALID_DISPOSITION: | |
| fputs("Error: EXCEPTION_INVALID_DISPOSITION\n", stderr); | |
| break; | |
| case EXCEPTION_NONCONTINUABLE_EXCEPTION: | |
| fputs("Error: EXCEPTION_NONCONTINUABLE_EXCEPTION\n", stderr); | |
| break; | |
| case EXCEPTION_PRIV_INSTRUCTION: | |
| fputs("Error: EXCEPTION_PRIV_INSTRUCTION\n", stderr); | |
| break; | |
| case EXCEPTION_SINGLE_STEP: | |
| fputs("Error: EXCEPTION_SINGLE_STEP\n", stderr); | |
| break; | |
| case EXCEPTION_STACK_OVERFLOW: | |
| fputs("Error: EXCEPTION_STACK_OVERFLOW\n", stderr); | |
| break; | |
| default: | |
| fputs("Error: Unrecognized Exception\n", stderr); | |
| break; | |
| } | |
| fflush(stderr); | |
| /* If this is a stack overflow then we can't walk the stack, so just show | |
| where the error happened */ | |
| if (EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode) | |
| { | |
| windows_print_stacktrace(ExceptionInfo->ContextRecord); | |
| } | |
| else | |
| { | |
| addr2line(icky_global_program_name, (void*)ExceptionInfo->ContextRecord->Eip); | |
| } | |
| return EXCEPTION_EXECUTE_HANDLER; | |
| } | |
| void set_signal_handler() | |
| { | |
| SetUnhandledExceptionFilter(windows_exception_handler); | |
| } | |
| #else | |
| #define MAX_STACK_FRAMES 64 | |
| static void *stack_traces[MAX_STACK_FRAMES]; | |
| void posix_print_stack_trace() | |
| { | |
| int i, trace_size = 0; | |
| char **messages = (char **)NULL; | |
| trace_size = backtrace(stack_traces, MAX_STACK_FRAMES); | |
| messages = backtrace_symbols(stack_traces, trace_size); | |
| /* skip the first couple stack frames (as they are this function and | |
| our handler) and also skip the last frame as it's (always?) junk. */ | |
| // for (i = 3; i < (trace_size - 1); ++i) | |
| for (i = 0; i < trace_size; ++i) // we'll use this for now so you can see what's going on | |
| { | |
| if (addr2line(icky_global_program_name, stack_traces[i]) != 0) | |
| { | |
| printf(" error determining line # for: %s\n", messages[i]); | |
| } | |
| } | |
| if (messages) { free(messages); } | |
| } | |
| void posix_signal_handler(int sig, siginfo_t *siginfo, void *context) | |
| { | |
| (void)context; | |
| switch(sig) | |
| { | |
| case SIGSEGV: | |
| fputs("Caught SIGSEGV: Segmentation Fault\n", stderr); | |
| break; | |
| case SIGINT: | |
| fputs("Caught SIGINT: Interactive attention signal, (usually ctrl+c)\n", stderr); | |
| break; | |
| case SIGFPE: | |
| switch(siginfo->si_code) | |
| { | |
| case FPE_INTDIV: | |
| fputs("Caught SIGFPE: (integer divide by zero)\n", stderr); | |
| break; | |
| case FPE_INTOVF: | |
| fputs("Caught SIGFPE: (integer overflow)\n", stderr); | |
| break; | |
| case FPE_FLTDIV: | |
| fputs("Caught SIGFPE: (floating-point divide by zero)\n", stderr); | |
| break; | |
| case FPE_FLTOVF: | |
| fputs("Caught SIGFPE: (floating-point overflow)\n", stderr); | |
| break; | |
| case FPE_FLTUND: | |
| fputs("Caught SIGFPE: (floating-point underflow)\n", stderr); | |
| break; | |
| case FPE_FLTRES: | |
| fputs("Caught SIGFPE: (floating-point inexact result)\n", stderr); | |
| break; | |
| case FPE_FLTINV: | |
| fputs("Caught SIGFPE: (floating-point invalid operation)\n", stderr); | |
| break; | |
| case FPE_FLTSUB: | |
| fputs("Caught SIGFPE: (subscript out of range)\n", stderr); | |
| break; | |
| default: | |
| fputs("Caught SIGFPE: Arithmetic Exception\n", stderr); | |
| break; | |
| } | |
| case SIGILL: | |
| switch(siginfo->si_code) | |
| { | |
| case ILL_ILLOPC: | |
| fputs("Caught SIGILL: (illegal opcode)\n", stderr); | |
| break; | |
| case ILL_ILLOPN: | |
| fputs("Caught SIGILL: (illegal operand)\n", stderr); | |
| break; | |
| case ILL_ILLADR: | |
| fputs("Caught SIGILL: (illegal addressing mode)\n", stderr); | |
| break; | |
| case ILL_ILLTRP: | |
| fputs("Caught SIGILL: (illegal trap)\n", stderr); | |
| break; | |
| case ILL_PRVOPC: | |
| fputs("Caught SIGILL: (privileged opcode)\n", stderr); | |
| break; | |
| case ILL_PRVREG: | |
| fputs("Caught SIGILL: (privileged register)\n", stderr); | |
| break; | |
| case ILL_COPROC: | |
| fputs("Caught SIGILL: (coprocessor error)\n", stderr); | |
| break; | |
| case ILL_BADSTK: | |
| fputs("Caught SIGILL: (internal stack error)\n", stderr); | |
| break; | |
| default: | |
| fputs("Caught SIGILL: Illegal Instruction\n", stderr); | |
| break; | |
| } | |
| break; | |
| case SIGTERM: | |
| fputs("Caught SIGTERM: a termination request was sent to the program\n", stderr); | |
| break; | |
| case SIGABRT: | |
| fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr); | |
| break; | |
| default: | |
| break; | |
| } | |
| posix_print_stack_trace(); | |
| _Exit(1); | |
| } | |
| static uint8_t alternate_stack[SIGSTKSZ]; | |
| void set_signal_handler() | |
| { | |
| /* setup alternate stack */ | |
| { | |
| stack_t ss = {}; | |
| /* malloc is usually used here, I'm not 100% sure my static allocation | |
| is valid but it seems to work just fine. */ | |
| ss.ss_sp = (void*)alternate_stack; | |
| ss.ss_size = SIGSTKSZ; | |
| ss.ss_flags = 0; | |
| if (sigaltstack(&ss, NULL) != 0) { err(1, "sigaltstack"); } | |
| } | |
| /* register our signal handlers */ | |
| { | |
| struct sigaction sig_action = {}; | |
| sig_action.sa_sigaction = posix_signal_handler; | |
| sigemptyset(&sig_action.sa_mask); | |
| #ifdef __APPLE__ | |
| /* for some reason we backtrace() doesn't work on osx | |
| when we use an alternate stack */ | |
| sig_action.sa_flags = SA_SIGINFO; | |
| #else | |
| sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK; | |
| #endif | |
| if (sigaction(SIGSEGV, &sig_action, NULL) != 0) { err(1, "sigaction"); } | |
| if (sigaction(SIGFPE, &sig_action, NULL) != 0) { err(1, "sigaction"); } | |
| if (sigaction(SIGINT, &sig_action, NULL) != 0) { err(1, "sigaction"); } | |
| if (sigaction(SIGILL, &sig_action, NULL) != 0) { err(1, "sigaction"); } | |
| if (sigaction(SIGTERM, &sig_action, NULL) != 0) { err(1, "sigaction"); } | |
| if (sigaction(SIGABRT, &sig_action, NULL) != 0) { err(1, "sigaction"); } | |
| } | |
| } | |
| #endif | |
| int divide_by_zero(); | |
| void cause_segfault(); | |
| void stack_overflow(); | |
| void infinite_loop(); | |
| void illegal_instruction(); | |
| void cause_calamity(); | |
| static char const * icky_global_program_name; | |
| int main(int argc, char * argv[]) | |
| { | |
| (void)argc; | |
| /* store off program path so we can use it later */ | |
| icky_global_program_name = argv[0]; | |
| set_signal_handler(); | |
| cause_calamity(); | |
| puts("OMG! Nothing bad happend!"); | |
| return 0; | |
| } | |
| void cause_calamity() | |
| { | |
| /* uncomment one of the following error conditions to cause a calamity of | |
| your choosing! */ | |
| // (void)divide_by_zero(); | |
| cause_segfault(); | |
| // assert(false); | |
| // infinite_loop(); | |
| // illegal_instruction(); | |
| // stack_overflow(); | |
| } | |
| int divide_by_zero() | |
| { | |
| int a = 1; | |
| int b = 0; | |
| return a / b; | |
| } | |
| void cause_segfault() | |
| { | |
| int * p = (int*)0x12345678; | |
| *p = 0; | |
| } | |
| void stack_overflow(); | |
| void stack_overflow() | |
| { | |
| int foo[1000]; | |
| (void)foo; | |
| stack_overflow(); | |
| } | |
| /* break out with ctrl+c to test SIGINT handling */ | |
| void infinite_loop() | |
| { | |
| while(1) {}; | |
| } | |
| void illegal_instruction() | |
| { | |
| /* I couldn't find an easy way to cause this one, so I'm cheating */ | |
| raise(SIGILL); | |
| } | |
How do you get addr2line work on Windows?
I'm getting
'addr2line' is not recognized as an internal or external command,
operable program or batch file.
addr2line is a linux command. Try installing cygwin?
Great example, thanks for sharing. Tho while trying it out on Mac OS 10.14, the stack overflow isn't caught.
Great share, thanks for it !
I cannibalized it to make a simple wrapper.
@munsuri : In the code, the SA_ONSTACK cause some crashes on OSX. I had the same problem on Redhat, and the cause was that the alternate stack was NOT big enough.
Changing the size of static uint8_t alternate_stack[SIGSTKSZ]; to static uint8_t alternate_stack[65536]; gave me better result.
If you see your signal caught, and it's getting a sigseg just after, it's likely that the alt stack size is too low.
Anyway thanks for the share :-)
Where is following function defined ?
messages = backtrace_symbols(stack_traces, trace_size);
Does CYGWIN has CONTEXT structure defined ? If yes what is the header file ?
@MNGanesan No, Cygwin doesn't implement <execinfo.h>, nor does MinGW.
@jvranish I got these errors compiling on Linux:
stack_traces.c: In function ‘posix_print_stack_trace’:
stack_traces.c:216:20: error: ‘lt’ undeclared (first use in this function)
216 | for (i = 0; i < trace_size; ++i) // we'll use this for now so you can see what's going on
| ^~
stack_traces.c:216:20: note: each undeclared identifier is reported only once for each function it appears in
stack_traces.c:216:34: error: expected ‘)’ before ‘;’ token
216 | for (i = 0; i < trace_size; ++i) // we'll use this for now so you can see what's going on
Since someone asked:
I put this code in the public domain. As such you can use it without restrictions, but I provide no warranty as to its usefulness or fitness for any purpose.