Stack traces on Linux/x86

While reading stackoverflow I recalled a little snippet I wrote 4 years ago.

This snippet allows the programmer to get a stack trace in C environment on Linux/x86 whenever a memory access problem happens. x86_64 is not supported yet.


/* */
#include <stdio.h>
#include <stdlib.h>

#include <signal.h>

#include <execinfo.h>

#include <unistd.h>

#define __USE_GNU
#include <ucontext.h>

typedef void signal_specific_handler(int signum, siginfo_t *pinfo, void *arg) ;

struct sigaction_pack
{
 struct sigaction         current ;
 struct sigaction         original ;
 signal_specific_handler *pspecific ;
} ;

/* we are instrumenting the first 32 signals. We are NOT instrumenting the real-time signals */
static struct sigaction_pack g_pbs_signal_actions[32] ;
static size_t max_pbs_size = 32 ;

static void* g_a_activation_record_list[64] ;
static size_t max_activation_record_count = 64 ;
static size_t activation_record_count ;

/* This is a global variable set at program start time.  It marks the  highest used stack address.  */
extern void *__libc_stack_end;

/*------------------------------------------------------------------------------
 *
 *   A short refresh on the x86 assembly
 *   -----------------------------------
 *
 *    %eip -> current instruction
 *
 *    %esp -> top of the stack (the used element)
 *
 *   This is the stack layout we see with every stack frame.
 *
 *           +-----------------+
 *   %esp -> | local variable  |
 *           | local variable  |
 *           +-----------------+
 *   %ebp -> | %ebp last frame |-+
 *           |                 | |
 *           | return address  | |
 *           |(old instruction | |
 *           |   pointer )     | |
 *           +-----------------+ |
 *           | parameters 1..  | |
 *      +----| parameters 2... |--+
 *      |    | parameters---   |
 *      |    +-----------------+
 *      |    | local variable  |
 *      |    | local variable  |
 *      |    +-----------------+
 *      + -> | %ebp last frame----....
 *           |                 |
 *           | return address  |
 *           +-----------------+
 *
 *
 */

/**
 * Activation record layout
 * Used to parse stack contents while unwinding the stack.
 */
struct activation_record_layout
{
 struct activation_record_layout * next;
 void *return_address;
};

/**
 * Unwind the stack at any location.
 * This function works like the backtrace function provided by the GNU libc,
 * with the only difference that this function doesn't unwind _current_ stack.
 * Instead, it takes values of the relevant registers (EIP, EBP and ESP) as
 * parameters.
 * Thus, this function is suited for being called from a signal handler.
 */
int
rhyno_backtrace (
   void **activation_frame_array,
   int    max_size,
   void  *eip,
   void  *ebp,
   void  *esp)
{
 /* used to parse the stack */
 struct activation_record_layout *current;

 /* keeps track on visited stack trace */
 int count = 0;

 /* save the EIP into the frames list */
 activation_frame_array[count++] = eip ;

 /* Initialize the frame list with EBP */
 current = (struct activation_record_layout *) ebp;

 while (count < max_size)
 {
 /* If we reach past stack pointer, or end of stack segment,
 * we have problem (possible memory overrun.)
 */
 if ((void *) current < esp || (void *) current > __libc_stack_end)
 break;

 /* Save the return address of frame i into the frame list */
 activation_frame_array[count++] = current->return_address;

 /* Advance the frame list pointer */
 current = current->next;
 }

 /* How many frames we had collected */
 return count;
}

void sa_segv_specific_hanlder(int signum, siginfo_t *pinfo, void *arg)
{
 /*
 Example for SEGV...
 */
 printf("  Segmentation violation: Reason: %d: %s\n",
 pinfo->si_code,
 pinfo->si_code == SEGV_MAPERR ?
 "Address not mapped" : pinfo->si_code == SEGV_ACCERR ?
 "Invalid permissions" : "?????") ;
 printf("    Offending address: %p\n", pinfo->si_addr ) ;
}

void sa_bluescreen_handler(int signum, siginfo_t *pinfo, void *arg)
{

 /*
 siginfo_t {
 int      si_signo;  / * Signal number * /
 int      si_errno;  / * An errno value * /
 int      si_code;   / * Signal code * /
 pid_t    si_pid;    / * Sending process ID * /
 uid_t    si_uid;    / * Real user ID of sending process * /
 int      si_status; / * Exit value or signal * /
 clock_t  si_utime;  / * User time consumed * /
 clock_t  si_stime;  / * System time consumed * /
 sigval_t si_value;  / * Signal value * /
 int      si_int;    / * POSIX.1b signal * /
 void *   si_ptr;    / * POSIX.1b signal * /
 void *   si_addr;   / * Memory location which caused fault * /
 int      si_band;   / * Band event * /
 int      si_fd;     / * File descriptor * /
 }
 */
 printf("Got deadly signal %d, following information (%p) is available:\n",
 signum,pinfo ) ;

 printf(" Signal %d (code %d, errno %d) send by process %d (uid %d)\n",
 pinfo->si_signo,
 pinfo->si_code,
 pinfo->si_errno,
 pinfo->si_pid,
 pinfo->si_uid ) ;

 /* Call message specific handler.
 For example, the segmentation fault handler will
 print the offending address
 */
 if (g_pbs_signal_actions[signum].pspecific != NULL)
 {
 (*g_pbs_signal_actions[signum].pspecific)(signum,pinfo,arg) ;
 }

 if ( arg == NULL )
 {
 printf("No register/stack information is available\n") ;
 }
 else
 {
 printf("Important CPU Registers:\n" ) ;
 printf("CS:  %012p DS:  %012p  SS:  %012p \n",
 ((ucontext_t*)(arg))->uc_mcontext.gregs[REG_CS],
 ((ucontext_t*)(arg))->uc_mcontext.gregs[REG_DS],
 ((ucontext_t*)(arg))->uc_mcontext.gregs[REG_SS]
 ) ;

 printf("EIP: %012p EBP: %012p  ESP: %012p\n",
 ((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EIP],
 ((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EBP],
 ((ucontext_t*)(arg))->uc_mcontext.gregs[REG_ESP]
 ) ;

 printf("\n\nStack Trace:\n") ;

 /*------------------------------------------------------------------------------
 * Collect activation records in format similar to that of
 * glibc's `backtrace', but use the foreign stack.
 */
 activation_record_count = rhyno_backtrace(
 g_a_activation_record_list,
 max_activation_record_count,
 (void*)(((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EIP]),
 (void*)(((ucontext_t*)(arg))->uc_mcontext.gregs[REG_EBP]),
 (void*)(((ucontext_t*)(arg))->uc_mcontext.gregs[REG_ESP])
 ) ;

 /*------------------------------------------------------------------------------
 * Use internal glibc reporting mechanism to convert
 * activation records to symbolic names (requires -rdynamic linker flag)
 */
 backtrace_symbols_fd(g_a_activation_record_list,activation_record_count,1) ;

 } /* register information */

 /* propagate the signal to the original handler */
 if ( g_pbs_signal_actions[signum].original &&
 (g_pbs_signal_actions[signum].original.sa_sigaction != NULL ))
 {
 printf("\n\nPropagating the signal %d to the original handler\n",
 signum) ;
 g_pbs_signal_actions[signum].original.sa_sigaction(signum,pinfo,arg)  ;
 }
 else
 {
 printf("No original handler is registered for singal %d, continuing\n",
 signum ) ;
 }
}

int register_signal_handlers()
{
 int signum ;
 int result = -1 ;

 for ( signum = 0 ; signum < max_pbs_size ; ++signum)
 {
 /* zero the signal action */
 memset(&g_pbs_signal_actions[signum].current,0,sizeof (struct sigaction)) ;

 /* register the generic handler */
 g_pbs_signal_actions[signum].current.sa_sigaction = sa_bluescreen_handler ;

 /* turn on the flag that tells the runtime to invoke the extended signal handler,
 see man sigaction for details */
 g_pbs_signal_actions[signum].current.sa_flags |= SA_SIGINFO ;

 /* Register the signal handler, store the original signal handler for propagation */
 result = sigaction(signum,&g_pbs_signal_actions[signum].current,
 &g_pbs_signal_actions[signum].original) ;
 /* Report result */
 printf ("-- Registered signal handler for signal %d, result %d, handler %p original handler %p \n", signum,result,
 g_pbs_signal_actions[signum].current.sa_sigaction,
 g_pbs_signal_actions[signum].original.sa_sigaction) ;
 }

 g_pbs_signal_actions[SIGSEGV].pspecific = sa_segv_specific_hanlder ;

 return result ;
}

/* Function prototype. Here we will do THINGS */
int malicious_code(int) ;

int
main (int argc, char* argv[], char* envp[])
{
 int sleep_interval = 300 ;
 /* Very important - register the handlers */
 if ( argc > 1 )
 register_signal_handlers() ;

 sleep(sleep_interval) ;
 malicious_code(10) ;

 return 0 ;
}

int malicious_code(int arg)
{
 if ( ! arg )
 {
 int * garbage_pointer = (int*)441231324;
 return *((int*)(garbage_pointer)) ;
 }
 else
 {
 return malicious_code(--arg) ;
 }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s