blob: ea3f925180f9edc336781412c5b03060a9d9df9c [file] [log] [blame]
/* -----------------------------------------------------------------------------
* signal.c
*
* WAD signal handler.
*
* Author(s) : David Beazley (beazley@cs.uchicago.edu)
*
* Copyright (C) 2000. The University of Chicago.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* See the file COPYING for a complete copy of the LGPL.
* ----------------------------------------------------------------------------- */
#include "wad.h"
static char cvs[] = "$Header$";
extern void wad_stab_debug();
/* For some odd reason, certain linux distributions do not seem to define the
register constants in a way that is easily accessible to us. This is a hack */
#ifdef WAD_LINUX
#ifndef ESP
#define ESP 7
#endif
#ifndef EBP
#define EBP 6
#endif
#ifndef EIP
#define EIP 14
#endif
#ifndef ESI
#define ESI 5
#endif
#ifndef EDI
#define EDI 4
#endif
#ifndef EBX
#define EBX 8
#endif
#endif
/* Signal handling stack */
#define STACK_SIZE 4*SIGSTKSZ
char wad_sig_stack[STACK_SIZE];
/* This variable is set if the signal handler thinks that the
heap has overflowed */
int wad_heap_overflow = 0;
static void (*sig_callback)(int signo, WadFrame *data, char *ret) = 0;
void wad_set_callback(void (*s)(int,WadFrame *,char *ret)) {
sig_callback = s;
}
/* This bit of nastiness is used to make a non-local return from the
signal handler to a configurable location on the call stack. In a nutshell,
this works by repeatedly calling "restore" to roll back the
register windows and stack pointer. Then we fake a return value and
return to the caller as if the function had actually completed
normally. */
int wad_nlr_levels = 0;
static volatile int *volatile nlr_p = &wad_nlr_levels;
long wad_nlr_value = 0;
void (*wad_nlr_func)(void) = 0;
/* Set the return value from another module */
void wad_set_return_value(long value) {
wad_nlr_value = value;
}
/* Set the return function */
void wad_set_return_func(void(*f)(void)) {
wad_nlr_func = f;
}
#ifdef WAD_SOLARIS
static void nonlocalret() {
long a;
a = wad_nlr_value;
/* We never call this procedure as a function. This code
causes an immediate return if someone does this */
asm("jmp %i7 + 8");
asm("restore");
/* This is the real entry point */
/* asm(".globl _returnsignal");*/
asm(".type _returnsignal,2");
asm("_returnsignal:");
while (*nlr_p > 0) {
(*nlr_p)--;
asm("restore");
}
asm("sethi %hi(wad_nlr_value), %o0");
asm("or %o0, %lo(wad_nlr_value), %o0");
asm("ld [%o0], %i0");
/* If there is a non-local return function. We're going to go ahead
and transfer control to it */
if (wad_nlr_func)
(*wad_nlr_func)();
asm("jmp %i7 + 8");
asm("restore");
asm(".size _returnsignal,(.-_returnsignal)");
}
#endif
#ifdef WAD_LINUX
/* Saved values of the machine registers */
long wad_saved_esi = 0;
long wad_saved_edi = 0;
long wad_saved_ebx = 0;
static void nonlocalret() {
asm("_returnsignal:");
while (*nlr_p > 0) {
(*nlr_p)--;
asm("leave");
}
if (wad_nlr_func)
(*wad_nlr_func)();
/* Restore the registers */
asm("movl wad_saved_esi, %esi");
asm("movl wad_saved_edi, %edi");
asm("movl wad_saved_ebx, %ebx");
asm("movl wad_nlr_value, %eax");
asm("leave");
asm("ret");
}
/* This function uses a heuristic to restore the callee-save registers on i386.
According to the Linux Assembly HOWTO, the %esi, %edi, %ebx, and %ebp registers
are callee-saved. All others are caller saved. To restore the callee-save
registers, we use the fact that the C compiler saves the callee-save registers
(if any) at the beginning of function execution. Therefore, we can scan the
instructions at the start of each function in the stack trace to try and find
where they are.
The following heuristic is used:
1. Each function starts with a preamble like this which saves the %ebp
register:
55 89 e5 ---> push %ebp
mov %esp, %ebp
2. Next, space is allocated for local variables, using one of two schemes:
83 ec xx ---> Less than 256 bytes of local storage
^^^
length
81 ec xx xx xx xx --> More than 256 bytes of local storage
^^^^^^^^^^^
length
3. After this, a collection of 1-byte stack push op codes might appear
56 = pushl %esi
57 = pushl %edi
53 = pushl %ebx
Based on the size of local variable storage and the order in which
the %esi, %edi, and %ebx registers are pushed on the stack, we can
determine where in memory the registers are saved and restore them to
their proper values.
*/
void wad_restore_i386_registers(WadFrame *f, int nlevels) {
WadFrame *lastf = f;
int localsize = 0;
unsigned char *pc;
unsigned long *saved;
int i, j;
int pci;
for (i = 0; i <= nlevels; i++, f=f->next) {
/* This gets the starting instruction for the stack frame */
pc = (unsigned char *) f->sym_base;
/* printf("pc = %x, base = %x, %s\n", f->pc, f->sym_base, SYMBOL(f)); */
if (!pc) continue;
/* Look for the standard prologue 0x55 0x89 0xe5 */
if ((pc[0] == 0x55) && (pc[1] == 0x89) && (pc[2] == 0xe5)) {
/* Determine the size */
pci = 3;
if ((pc[3] == 0x83) && (pc[4] == 0xec)) {
/* printf("8-bit size\n");*/
localsize = (int) pc[5];
pci = 6;
}
if ((pc[3] == 0x81) && (pc[4] == 0xec)) {
/* printf("32-bit size\n"); */
localsize = (int) *((long *) (pc+5));
pci = 10;
}
saved = (long *) (f->fp - localsize - sizeof(long));
/* printf("saved = %x, fp = %x\n", saved, f->fp);
printf("localsize = %d\n", localsize);
*/
for (j = 0; j < 3; j++, saved--, pci++) {
if (pc[pci] == 0x57) {
wad_saved_edi = *saved;
/* printf("restored edi = %x\n", wad_saved_edi); */
}
else if (pc[pci] == 0x56) {
wad_saved_esi = *saved;
/* printf("restored esi = %x\n", wad_saved_esi); */
}
else if (pc[pci] == 0x53) {
wad_saved_ebx = *saved;
/* printf("restored ebx = %x\n", wad_saved_ebx); */
}
else break;
}
}
}
}
#endif
void wad_signalhandler(int sig, siginfo_t *si, void *vcontext) {
greg_t *pc;
greg_t *npc;
greg_t *sp;
greg_t *fp;
#ifdef WAD_LINUX
greg_t *esi;
greg_t *edi;
greg_t *ebx;
#endif
unsigned long addr;
ucontext_t *context;
unsigned long p_sp; /* process stack pointer */
unsigned long p_pc; /* Process program counter */
unsigned long p_fp; /* Process frame pointer */
int nlevels = 0;
int found = 0;
void _returnsignal();
WadFrame *frame, *origframe;
char *framedata;
char *retname = 0;
unsigned long current_brk;
/* Reset all of the signals while running WAD */
wad_signal_clear();
wad_nlr_func = 0;
context = (ucontext_t *) vcontext;
wad_printf("WAD: Collecting debugging information...\n");
/* Read the segments */
if (wad_segment_read() < 0) {
wad_printf("WAD: Unable to read segment map\n");
return;
}
if (wad_debug_mode & DEBUG_SIGNAL) {
wad_printf("WAD: siginfo = %x, context = %x\n", si, vcontext);
}
current_brk = (long) sbrk(0);
/* Get some information about the current context */
#ifdef WAD_SOLARIS
pc = &((context->uc_mcontext).gregs[REG_PC]);
npc = &((context->uc_mcontext).gregs[REG_nPC]);
sp = &((context->uc_mcontext).gregs[REG_SP]);
#endif
#ifdef WAD_LINUX
sp = &((context->uc_mcontext).gregs[ESP]); /* Top of stack */
fp = &((context->uc_mcontext).gregs[EBP]); /* Stack base - frame pointer */
pc = &((context->uc_mcontext).gregs[EIP]); /* Current instruction */
esi = &((context->uc_mcontext).gregs[ESI]);
edi = &((context->uc_mcontext).gregs[EDI]);
ebx = &((context->uc_mcontext).gregs[EBX]);
wad_saved_esi = (unsigned long) (*esi);
wad_saved_edi = (unsigned long) (*edi);
wad_saved_ebx = (unsigned long) (*ebx);
/* printf("esi = %x, edi = %x, ebx = %x\n", wad_saved_esi, wad_saved_edi, wad_saved_ebx); */
/* printf("&sp = %x, &pc = %x\n", sp, pc); */
#endif
/* Get some information out of the signal handler stack */
addr = (unsigned long) si->si_addr;
/* See if this might be a stack overflow */
p_pc = (unsigned long) (*pc);
p_sp = (unsigned long) (*sp);
#ifdef WAD_LINUX
p_fp = (unsigned long) (*fp);
#endif
#ifdef WAD_SOLARIS
p_fp = (unsigned long) *(((long *) p_sp) + 14);
#endif
if (wad_debug_mode & DEBUG_SIGNAL) {
wad_printf("fault at address %x, pc = %x, sp = %x, fp = %x\n", addr, p_pc, p_sp, p_fp);
}
frame = wad_stack_trace(p_pc, p_sp, p_fp);
if (!frame) {
/* We're really hosed. Not possible to generate a stack trace */
wad_printf("WAD: Unable to generate stack trace.\n");
wad_printf("WAD: Maybe the call stack has been corrupted by buffer overflow.\n");
wad_signal_clear();
return;
}
{
WadFrame *f = frame;
while (f) {
wad_find_object(f);
wad_find_symbol(f);
f = f->next;
}
f = frame;
while (f) {
wad_find_debug(f);
wad_build_vars(f);
f = f->next;
}
}
wad_heap_overflow = 0;
if (sig == SIGSEGV) {
if (addr >= current_brk) wad_heap_overflow = 1;
}
wad_stack_debug(frame);
/* Generate debugging strings */
wad_debug_make_strings(frame);
wad_stab_debug();
/* Walk the exception frames and try to find a return point */
origframe = frame;
while (frame) {
WadReturnFunc *wr = wad_check_return(frame->sym_name);
if (wr) {
found = 1;
wad_nlr_value = wr->value;
retname = wr->name;
}
if (found) {
frame->last = 1; /* Cut off top of the stack trace */
break;
}
frame = frame->next;
nlevels++;
}
if (found) {
wad_nlr_levels = nlevels - 1;
#ifdef WAD_LINUX
wad_restore_i386_registers(origframe, wad_nlr_levels);
#endif
} else {
wad_nlr_levels = -1;
}
wad_string_debug();
wad_memory_debug();
/* Before we do anything with callbacks, we are going
to attempt to dump a wad-core */
{
int fd;
static int already = 0;
fd = open("wadtrace",O_WRONLY | O_CREAT | (already*O_APPEND) | ((already==0)*O_TRUNC),0666);
if (fd > 0) {
wad_dump_trace(fd,sig,origframe,retname);
close(fd);
already=1;
}
}
if (sig_callback) {
(*sig_callback)(sig,origframe,retname);
} else {
/* No signal handler defined. Go invoke the default */
wad_default_callback(sig, origframe,retname);
}
if (wad_debug_mode & DEBUG_HOLD) while(1);
/* If we found a function to which we should return, we jump to
an alternative piece of code that unwinds the stack and
initiates a non-local return. */
if (wad_nlr_levels >= 0) {
*(pc) = (greg_t) _returnsignal;
#ifdef WAD_SOLARIS
*(npc) = *(pc) + 4;
#endif
if (!(wad_debug_mode & DEBUG_ONESHOT)) {
wad_signal_init();
}
return;
}
exit(1);
}
/* -----------------------------------------------------------------------------
* wad_signal_init()
*
* Resets the signal handler.
* ----------------------------------------------------------------------------- */
void wad_signal_init() {
struct sigaction newvec;
static stack_t sigstk;
static int initstack = 0;
if (wad_debug_mode & DEBUG_INIT) {
wad_printf("WAD: Initializing signal handler.\n");
}
/* This is buggy in Linux and threads. disabled by default */
#ifndef WAD_LINUX
if (!initstack) {
/* Set up an alternative stack */
sigstk.ss_sp = (char *) wad_sig_stack;
sigstk.ss_size = STACK_SIZE;
sigstk.ss_flags = 0;
if (!(wad_debug_mode & DEBUG_NOSTACK)) {
if (sigaltstack(&sigstk, (stack_t*)0) < 0) {
perror("sigaltstack");
}
}
initstack=1;
}
#endif
sigemptyset(&newvec.sa_mask);
sigaddset(&newvec.sa_mask, SIGSEGV);
sigaddset(&newvec.sa_mask, SIGBUS);
sigaddset(&newvec.sa_mask, SIGABRT);
sigaddset(&newvec.sa_mask, SIGILL);
sigaddset(&newvec.sa_mask, SIGFPE);
newvec.sa_flags = SA_SIGINFO;
if (wad_debug_mode & DEBUG_ONESHOT) {
newvec.sa_flags |= SA_RESETHAND;
}
#ifndef WAD_LINUX
if (!(wad_debug_mode & DEBUG_NOSTACK)) {
newvec.sa_flags |= SA_ONSTACK;
}
#endif
newvec.sa_sigaction = ((void (*)(int,siginfo_t *, void *)) wad_signalhandler);
if (sigaction(SIGSEGV, &newvec, NULL) < 0) goto werror;
if (sigaction(SIGBUS, &newvec, NULL) < 0) goto werror;
if (sigaction(SIGABRT, &newvec, NULL) < 0) goto werror;
if (sigaction(SIGFPE, &newvec, NULL) < 0) goto werror;
if (sigaction(SIGILL, &newvec, NULL) < 0) goto werror;
return;
werror:
wad_printf("WAD: Couldn't install signal handler!\n");
}
/* -----------------------------------------------------------------------------
* clear signals
* ----------------------------------------------------------------------------- */
void wad_signal_clear() {
signal(SIGSEGV, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGABRT, SIG_DFL);
}