Stepping with gdb

lets say we have an example program called test.c that looks like this;

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

char *
malloc_bad(void) {
    return NULL;
}

int
main(int argc, char **argv) {
    char *ptr;

    ptr = malloc_bad();
    printf("%s\n", ptr);

    return 1;
}

we want to compile it with the -g flag to include the debugging symbols.

to compile it;

make test CFLAGS=-g

now if we run it, it'll cause a null pointer dereference.

./test
Segmentation fault

now we're gonna wanna debug it, using gdb

gdb ./test
[...]
Reading symbols from test...done.
(gdb)

the first thing we're doing is pausing the program flow, we do this by adding a breakpoint.

(gdb) b main

this will cause gdb to pause the execution when the program gets to the main function.

(gdb) r
Starting program: /usr/src/gdbtest/test

Breakpoint 1, main (argc=1, argv=0x7fffffffc9e8) at test.c:13
13              ptr = malloc_bad();
(gdb)

as we can see, the program stopped execution at line 13, which is the line where ptr gets assigned. if we want to step in to the malloc_bad function, we can issue step or s for short.

(gdb) s
malloc_bad () at test.c:5
(gdb) # from here on out we can press enter to issue the same command as last
(gdb) 
0x00005555555546b1      5       malloc_bad(void) {
(gdb) 
6               return NULL;
(gdb) 
7       }
(gdb) 
0x00005555555546ba      7       }
(gdb) 
0x00005555555546cf in main (argc=1, argv=0x7fffffffc9e8) at test.c:13
13              ptr = malloc_bad();
(gdb) 
14              printf("%s\n", ptr);
(gdb) # now we're gonna use the command next to avoid stepping into printf's inner workings
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
[...] 0x000deadbeef in strlen () from /lib/your/libc.so.6
(gdb) # so obviously this program had a segfault, better check the backtrace
(gdb) bt
#0  0x00007ffff7aba646 in strlen () from /lib/your/libc.so.6
#1  0x00007ffff7aa2f72 in puts () from /lib/your/libc.so.6
#2  0x00005555555546df in main (argc=1, argv=0x7fffffffc9e8)at test.c:14
(gdb) q
A debugging session is active.

        Inferior 1 [process 4387] will be killed.

    Quit anyway? (y or n)

okay so, in this simple example, the problem is obvious, but the same logic applies to other larger programs. you can step through until you find a problem, or you can run it, check the backtrace, and set the breakpoint at the first function where the problem occurs.

brotip; to have a more visual experience, add the -tui flag when invoking gdb.


here are the most useful commands when stepping.

command description
r[un] run the program
s[tep] single step, entering lower functions
n[ext] single step, without entering lower functions
b[reak] set a breakpoint (e.g. b main, or b 142)
c[ontinue] continue execution until next breakpoint
p[rint] <var> print a varible (e.g. p argc, or p (char*)argv[0])

see also; Debugging with GDB