Skip to content

Instantly share code, notes, and snippets.

@jvranish
Last active April 25, 2024 15:51
Show Gist options
  • Star 63 You must be signed in to star a gist
  • Fork 16 You must be signed in to fork a gist
  • Save jvranish/4441299 to your computer and use it in GitHub Desktop.
Save jvranish/4441299 to your computer and use it in GitHub Desktop.
An example of catching exceptions and printing stack traces in C on Windows, Linux and OS X
/* 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 &lt; 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);
}
@jvranish
Copy link
Author

jvranish commented Feb 2, 2015

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.

@willthink
Copy link

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.

@Forty-Bot
Copy link

@willthink

addr2line is a linux command. Try installing cygwin?

@munsuri
Copy link

munsuri commented Dec 20, 2019

Great example, thanks for sharing. Tho while trying it out on Mac OS 10.14, the stack overflow isn't caught.

@gullradriel
Copy link

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 :-)

@MNGanesan
Copy link

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 ?

@davidhcefx
Copy link

@MNGanesan No, Cygwin doesn't implement <execinfo.h>, nor does MinGW.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment