Published on April 3, 2012 by Andrei Petre
Tagged: gdb, debugging, CLI, .gdbinit, backtrace, breakpoint

The GNU Debugger Command (GDB) is a very useful debugging tool, widely used in the C environment.

Workflow

GDB can be run in two distinct ways:

  • using the gdb command
  • using a core generated file, usually from a serious error

Let’s have a look at the former one on a simple program:

int random() {
	int r = 4;
	return r;
}

int main() {
	char *no_addr = 0;
	*no_addr = random();
	return 0;
}

The -g compiler option is used to add debugging information to the executable (here a.out) for use by GDB. We’ll run it again using gdb, because the above code gives us a segmentation fault:

$ gcc -Wall -g random.c
$ gdb a.out
[...]
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x080483c5 in main () at random.c:7
7		*no_addr = random();

So this helps us a lot, it even shows us the line causing the problem. Now we’ll create a core file to show how the latter one works, too. Note that # at the beginning of the line specifies that commands are run as root:

# ulimit -c 4      # set core file size to 4 blocks
# ./a.out
Segmentation fault (core dumped)
# gdb ./a.out core
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0  0x080483cd in main () at random.c:7
7		*no_addr = random();

Useful commands

Let’s see a common workflow, while using GDB:

$ gdb a.out				# run with gdb debugger
(gdb) break main			# set up breakpoint at main() function
Breakpoint 1 at 0x80483bc: file random.c, line 6.
					# this suspends the program
					# can also receive file name (break random.c:3)
					# or address (break *0x080483c5)
(gdb) run				# .. just run the thing
Starting program: /home/andrei/a.out 

Breakpoint 1, main () at random.c:6	# it stops at first breakpoint
6		char *no_addr = 0;
(gdb) next				# execute next line, doesn't enter functions
7		*no_addr = random();
(gdb) step				# like next, but enters functions
random () at random.c:2
2		int r = 4;

(gdb) next
3	return r;
(gdb) print r				# print values in decimal
$1 = 4
(gdb) print /x				# hexa
$2 = 0x4
(gdb) print /o				# octal
$3 = 04
(gdb) print &r
$4 = (int *) 0xbfffef5c
(gdb) list				# list source code
1    int random() {
2        int r = 4;
3        return r;
4    }
5    int main() {
6        char *no_addr = 0;
7        int r = random();
8        *no_addr = r;
9        return 0;
10    }

(gdb) break 8 				# add breakpoint at line 8
Breakpoint 2 at 0x80483cb: file random.c, line 8.
(gdb) continue 				# continue to next breakpoint
Continuing.

Breakpoint 2, main () at random.c:8
8		*no_addr = r;
(gdb) next

Program received signal SIGSEGV, Segmentation fault.
0x080483d3 in main () at random.c:8
8		*no_addr = r;
(gdb) backtrace 			# print stack backtrace; show trace of where you are
					# which functions you're in
#0  0x080483d3 in main () at random.c:8
(gdb) quit

Now, some other thing you may find useful is to have the value of an expression get printed frequently (automatically, of course). You can do that with display expression. Take this sample code:

int main() {
	int i, j = 0;
	for (i = 0; i < 10; i++)
		j += i * 10;
	return 0;
}

And run it in gdb:

(gdb) break main
Breakpoint 1 at 0x804839a: file random2.c, line 2.
(gdb) run
Starting program: /home/andrei/a.out 

Breakpoint 1, main () at random2.c:2
2		int i, j = 0;
(gdb) next
3		for (i = 0; i < 10; i++)
(gdb) next
4			j += i * 10;
(gdb) display i
1: i = 0
(gdb) display j
2: j = 0
(gdb) break 4 if i == 8
Breakpoint 2 at 0x80483aa: file random2.c, line 4.
(gdb) continue
Continuing.

Breakpoint 2, main () at random2.c:4
4			j += i * 10;
2: j = 280
1: i = 8

This way you can see how your variables’ value change. To delete a display, use the number associated with it:

(gdb) delete display 2
(gdb) next
4			j += i * 10;
1: i = 3

One last trick worth mentioning in this initial GDB tutorial is setting up your ~/.gdbinit file. When GDB starts up, it looks for a file in the current user’s home directory called .gdbinit; this file is used for simple configuration commands. The format is the following:

define <command>
<code>
end
document <command>
<help text>
end

A simple example of .gdbinit:

andrei@sherlock:~$ cat .gdbinit
define cls
shell clear
end
document cls
Clears the screen with a simple command.
end

define bpl
info breakpoints
end
document bpl
List breakpoints
end

Now you can use cls to clear the screen in gdb, or you can find what breakpoints you’ve set:

(gdb) bpl
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x0804839a in main at random2.c:2
2       breakpoint     keep y   0x080483aa in main at random2.c:4

You can also use .gdbinit inside your project’s directory to include commands used only for this project. It will be read when starting gdb in that directory and it overwrites the settings in ~/.gdbinit. You can add into it a few commands to be run when the gdb starts: commands like setting up the breakpoints and the values used with display commands.

Using the previous source code, we add the following .gdbinit file in the same directory:

b main
r
disp i
disp j
disp /x i
disp

Now, we can run gdb:

$ gdb -q ./a.out
Reading symbols from /tmp/a.out...done.
Breakpoint 1 at 0x804839a: file 1.c, line 5.

Breakpoint 1, main () at 1.c:5
5		int i, j = 0;
3: /x i = 0x0 2: j = 134513616
1: i = 0
(gdb) n
6		for (i = 0; i < 10; i++)
3: /x i = 0x0
2: j = 0
1: i = 0
(gdb) q

Observe the last disp in the .gdbinit file, used to display all expressions defined up to that point.

If you want to disable reading the .gdbinit files, pass a -n flag to gdb just like we passed -q above to strip the header with version info.

Final notes. CGDB is a curses front-end to GDB and is more friendly and coloured than GDB. Also, try this in GDB (I know this from Andrada Georgescu):

(gdb) b main
Breakpoint 1 at 0x804839a: file random2.c, line 2.
(gdb) r
Starting program: /home/andrei/a.out 
Breakpoint 1, main () at random2.c:2
2		int i, j = 0;
(gdb) -					# add dash and enter

comments powered by Disqus