Debugging with GDB on Intel x86.


Intel x86 assembly


Lets say we have the following source code:
void func()
{
        /* do nothing */
}

int main()
{
        func();
        return 0;
}

We compile it with GCC (including debugging symbols): gcc -g test.c

We then run GDB: gdb a.out


Typing "disas main" gives us the main function in assembly.

(gdb) disas main
Dump of assembler code for function main:
0x80483a8 <main>:       push   %ebp
0x80483a9 <main+1>:     mov    %esp,%ebp
0x80483ab <main+3>:     call   0x80483a0 <func>
0x80483b0 <main+8>:     xor    %eax,%eax
0x80483b2 <main+10>:    jmp    0x80483b4 <main+12>
0x80483b4 <main+12>:    leave
0x80483b5 <main+13>:    ret
End of assembler dump.

As you can see, it calls "func" which is our function "func".

(gdb) disas func
Dump of assembler code for function func:
0x80483a0 <func>:       push   %ebp
0x80483a1 <func+1>:     mov    %esp,%ebp
0x80483a3 <func+3>:     leave
0x80483a4 <func+4>:     ret
End of assembler dump.

This function does nothing, so nothing much is done. However, this explains some of the opcodes.

push %ebp
mov %esp, %ebp

This will occur at the beginning of every function. Register ebp is sometimes referred to as the frame pointer. The purpose is to allocate stack for the function so that the function can allocate variables. You can see code like this: sub $0x64, %ebp when you allocate some variables.

func was changed:

void func()
{
        char buf[100];
        /* do nothing */
}

(gdb) disas func
Dump of assembler code for function func:
0x80483a0 <func>:       push   %ebp
0x80483a1 <func+1>:     mov    %esp,%ebp
0x80483a3 <func+3>:     sub    $0x64,%esp
0x80483a6 <func+6>:     leave
0x80483a7 <func+7>:     ret
End of assembler dump.

Obviously, LEAVE and RET are opcodes produced at the end of the function. LEAVE restores the stack so that it will automatically deallocate stack variables, and RET returns to the previous caller (resets instruction pointer).


Breakpoints

Say you wanted to stop just when func() is called. You would set a breakpoint. To do this under GDB:

(gdb) break func
Breakpoint 1 at 0x80483a6: file test.c, line 5.

Then to run the program up to the breakpoint:

(gdb) run
Starting program: /home/test/a.out
Breakpoint 1, func () at test.c:5
5       }
(gdb)


Core dumps

int main()
{
        char *string=0x324789234;

        printf(string);

        return 0;
}

./a.out
Segmentation fault (core dumped)


To see what happened:

$ gdb a.out core
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0  _IO_vfprintf (s=0x40108980,
    format=0x24789234 <Address 0x24789234 out of bounds>, ap=0xbffffb94)
    at printf-parse.h:95
95      printf-parse.h: No such file or directory.
(gdb)


This doesn't reveal that much, in fact, it goes too far into detail and is telling us something happened inside GLIBC.

What we do is a backtrace -- that is tracing all the function calls up to the library call.

(gdb) bt
#0  _IO_vfprintf (s=0x40108980,
    format=0x24789234 <Address 0x24789234 out of bounds>, ap=0xbffffb94)
    at printf-parse.h:95
#1  0x40068070 in printf (format=0x24789234 <Address 0x24789234 out of bounds>)
    at printf.c:31
#2  0x80483e6 in main () at test.c:5


Ah ha!

test.c line 5! It is just a printf(string)... which is innocuous by itself. But the problem is that string was initialised with garbage values!


EOF
[That's all there is for now.]




1