Viewing signal numbers in gdb

Tags:

Recently someone asked me if there was a way, with gdb, to find out for which signal a particular signal handler was called. The catch was that there were no debugging symbols, but the binary was not stripped either. That way I could set breakpoints and do some general debugging on the flow of the program but not view variable values directly. So the backtrace does not show the signal number that was passed into the signal handler and I would have to do some magic to get at that value. So here's a little demo to explain what I did:

So I had a program that looked like this:

#include <signal.h>
#include <stdio.h>

void foo(void)
{
    printf ("that's all folks!\n");
}

void handler(int sig)
{
    foo();
}

int main()
{
    signal(SIGIO, handler);
    while(1);
}

I built the program, started it under gdb and set a break point at foo(). I then ran it and from another terminal, did a killall -IO a.out. here's what I got:

(gdb) bt
#0  0x00000000004004dc in foo ()
#1  0x00000000004004f8 in handler ()
#2  <signal handler called>
#3  0x000000000040050d in main ()

Notice that the argument value for the handler() function is not shown, which is our problem. This is because there is no way that gdb would know anything about the arguments without any debug symbols, i.e. the number of arguments, the size of each argument, etc. But we do. A signal handler has only one argument and it is an integer. It is the signal number.

To go any further, we need to check out the stack frame of handler(). As you can see in the stacktrace above, it is frame 1, so here's what we get:

(gdb) info frame 1
Stack frame at 0x7fff84277c30:
rip = 0x4004f8 in handler; saved rip 0x301e2301b0
called by frame at 0x7fff84277c38, caller of frame at 0x7fff84277c10
Arglist at 0x7fff84277c20, args:
Locals at 0x7fff84277c20, Previous frame's sp is 0x7fff84277c30
Saved registers:
  rbp at 0x7fff84277c20, rip at 0x7fff84277c28

So we know now that the arglist starts at 0x7fff84277c20, so the signal number argument should be in this region. Since it is a 32 bit value, we do some basic math and go down the stack by 4 bytes to see what word is stored at 0x7fff84277c1c:

(gdb) x/1 0x7fff84277c1c
0x7fff84277c1c:    0x0000001d

Do some more college math and convert 0x0000001d to decimal and we get 29. Lookup signum.h (search for it in /usr/include) and you'll see that it is SIGIO, the signal I sent in my kill. W00t!!

One may use this method to also get local variables of that function. It is just that we will have to do some guessing to find out the sequence of the locals in the stack if one does not have access to the source code. It should not be that hard though. This may also be one of the bases for those try to use buffer overflows to overwrite some value in the stack and hence modify the flow of a program. I'm yet to figure out how one could overwrite stuff far enough to drop from the program right into a shell. Learning computer science bit by bit now after wasting 6+ years of formal schooling in it :)

Comments are closed.