GDB – The GNU Debugger

The GNU Debugger (GDB) is a utility which works with an application and allows a user to analyze what a piece of software is doing whilst it executes. Specifically GDB allows a user to:

  • start a program and specify command line options for that program on a local machine, or to start it on a remote machine and connect to that machine via a second.
  • stop the execution of a program when any one or more of a number of criteria are satisfied.
  • examine the status of a stopped program in terms of memory/register content.
  • to change the content of both memory and registers of a stopped program.

In order to demonstrate some of the features gdb’s features we will be working with the following example code.

#include<stdio.h>

int call_02(int var) {
  int *foo = NULL;

  *foo = var * 2;
  return *foo;
}

int call_01(int var) {
  int temp;
  temp = var + 1;
  try {
    call_02(temp);
  } catch(…) {
    temp = temp + 3;
  }
  return temp;
}

int main(void) {
  int var1 = 0;
  int var2 = 2;
 
  var2 = call_01(var1);
  printf("%d\n",var2);
  return 0;
}

which can be compiled using the command

$ g++ -g main.c

Starting to Debug

Starting the debug process going is as simple as entering the command followed by the location of the executable you want to debug. If you need to run with arguments then the command is slightly more involved as shown below.

$ gdb --args <executable> <arg1> <arg2> <arg3> ...

An alternative to providing the arguments from the command line is to start gdb without arguments and supply then once gdb is running.

$ gdb <executable>
(gdb) r <arg1> <arg2> <arg3> ...

Executing Code

There are a number of ways in which you may want to execute code. At the most basic level you may want to execute a single machine instruction “stepi”. At a source code level you may want to step through a single line of source using the “step” command (abbreviated to s). In both cases a numeric argument can be appended which will be the number of times to step. To step through code but not enter any functions there is the “next” command (abbreviated n) which again can take a numeric argument. The command “nexti” provides the same functionality at the machine code level. If the user wants to exit the current function then there is the “finish” command (abbreviated as “fin”), whilst to exit a loop there is the “until” command (abbreviated as “u”) which executes until the source after the current line is reached or the specified location if an appropriate argument is given.

To execute code from the current position until completion or the next breakpoint there is the command “continue” (abbreviated c), and to restart execution from the beginning the user enters “run” (abbreviated r).

Halting Execution

Execution of the code can be halted at specific locations in the code dependent on a given criteria (breakpoints) or when a specified location is read or altered (watchpoints). It is also possible to halt an event such as the throwing of an exception (catchpoint).

Breakpoints

Breakpoints are locations within the code at which the execution may be halted and control returned to gdb according to the specified criteria. When a breakpoint is hit gdb will return the location of that breakpoint together with the number of times it has been hit. It is possible to list the current breakpoints (active and inactive) using the command

(gdb) i b 

Adding Breakpoints

Breakpoints are specified busing the ‘b’ command followed by a location and an optional condition. The location can be specified as either a function, an offset from the current position, a line number in either the current or another specified file or an address. If the break command is used without and arguments then a breakpoint is added at the next instruction to be executed in the current stack frame. Commands are generally of the form shown below

(gdb) b <location> if <condition> 

where location is of the form

(gdb) b <function>
(gdb) b <file:function>
(gdb) b <line number>
(gdb) b <file:line number>
(gdb) b <+offset>
(gdb) b <-offset>
(gdb) b *<address>
(gdb) b

Conditions are boolean expressions in the appropriate programming language e.g.

(gdb) b <function> if foofoo == -1
(gdb) b <file:line number> if *pntr != 0

Conditions can be modified at any time using the condition command followed by the breakpoint number and the new condition.

Deleting/ Disabling Breakpoints

Breakpoints can be deleted, disabled, or re-enabled on a singlar or multiple basis. Where a specific breakpoint is to be manipulated it must be identified by its number as indicated when listing breakpoints. Breakpoints 1, 3, and 4 could be deleted, disabled, and re-enabled as shown below.

(gdb) delete 1 3 4
(gdb) disable 1 3 4
(gdb) enable 1 3 4

If the numbers are omitted following any of these commands then all breakpoints will be deleted, disabled, or re-enabled.

Watchpoint

Watchpoints are triggered when memory at a given address is manipulated in some way. A break on read/write access to a memory location can be requested using the command below

(gdb) watch *<address>

Alternatively, it is also possible to request a break when a given value is written into a location.

(gdb) watch *<address> == <value>

Catchpoint

Setting catchpoints causes the debugger to stop execution as a result of a program event. Typically events are either throw or catch although other events are available under HP-UX. In the example below we have set a catchpoint on the throw event. This is triggered in call_02 when there is an attempt to use a NULL pointer. The output shows that the event was triggered by line 7 of the code.

(gdb) catch throw
Catchpoint 1 (throw)
(gdb) r
Starting program: a.out 
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555166 in call_02 (var=1) at main.c:7
7      *foo = var * 2;
(gdb) bt
0  0x0000555555555166 in call_02 (var=1) at main.c:7
1  0x0000555555555192 in call_01 (var=0) at main.c:18
2  0x00005555555551bb in main () at main.c:32

Reading/Writing Data

Working with Variables

The variables local to a region can be viewed using the command info locals as shown below. Alternatively you could also request the display of specific variables and the state the way in which they are to be displayed.

(gdb) b main.c:32
Breakpoint 1 at 0x11b1: file main.c, line 32.
(gdb) r
Starting program: a.out 
Breakpoint 1, main () at main.c:32
32      var2 = call_01(var1);
(gdb) info locals
var1 = 0
var2 = 2

Variables can be modified using gdb. The example below shows how the value of var2 is modified to -1 and displayed using the “print” command (abbreviated to p). The example also shows that it is possible to cast this variable and display it in both decimal and hex formats.

(gdb) set var2=-1
(gdb) p var2
$6 = -1
(gdb) p (unsigned int) var2
$7 = 4294967295
(gdb) p/x (unsigned int) var2
$8 = 0xffffff
SpecifierDescription
xPrint the binary representation in hexadecimal
dPrint the binary representation in decimal
uPrint the binary representation as an unsigned decimal
oPrint the binary representation in octal
tPrint the value in binary
aPrint an absolute address and as an offset from the nearest preceding symbol
cPrint the numerical value and the character representation. If the value of the character is greater than the 7-bit ASCII value the character representation is replaced with an octal value.
fPrint the value in floating point representation
sRepresent the data as a string
zPrint in hexadecimal format but with leading zeros
iPrint the address and assembler for the given location (this format is only applicable for use with the x command).

Working with Memory

Displaying the contents of memory is achieved with the ‘x’ command. The command can be used to display multiple words of data in a specified format and of a specified size. The command is entered in the format shown below

x/<number of words to display><size of a word><style of display>

The size of a word is requested according to the character in the table below

ModifierDescription
bbyte (8-bit value)
hhalfward (16-bit value)
wword (32-bit value)
ggiant word (64-bit value)

Whilst the style of display is specified according to the characters already described in the previous section.

Dumping / Restoring From File

If there is a need to extract the contents of memory to file this can be achieved using the dump/append commands. Commands are entered as show below, where the format specifier is used as described in the following table.

(gdb) dump <format> memory <filename> <start address> <end address>
(gdb) restore <filename> <format> <bias> <start> <end>
FormatDescription
binaryData is stored in raw binary format
ihexData is stored in Intel hex format
srecData is stored in motorola s-record format
tekhexData is stored in Tektronix hex format
verilogData is stored in Verilog hex format

Navigating the Stack

When a breakpoint is hit such as in the catchpoint example earlier you may want to examine local variables from previous stack frames. Using the up and down commands allows you to move between stack frames. Both up and down commands can be followed by a value expressing the number of stack frames to step through.