GDB is a free command-line debugger for UNIX. It supports several languages, and has an extensive list of features. Unfortunately, debugging from the command-line can be difficult for new users. This has led to the development of many graphical front ends, such as gf.
Let's first compile a simple C program.
1 2 3 4 5 6 | #include <stdio.h> int main(int argc, char **argv) { printf("Hello, world!\n"); return 0; } |
Compile this with:
1 | gcc hello.c -o hello -g |
This'll produce our executable, hello
.
Now we can debug the program.
Let's start GDB and tell it to read our executable:
1 | gdb hello |
You should see it says "Reading symbols from hello...done.". This means it successfully loaded in the executable.
If you now type "run" and press enter, you should see your program print out the message "Hello, world!".
If your program requires command line arguments, use
1 | gdb --args myprog arg1 arg2 |
otherwise gdb will interpret the extra arguments as being for itself.
Now let's try to inspect what is happening in our program.
We shall set a breakpoint at the main
function. This means our program will be paused when the main function starts.
1 | break main |
The break command takes several different types of argument. You can pass a function name, a line number (within the current file), or a line number in the format "sourcefile:linenumber" to break in another file.
You can also break directly on a memory address by prefixing it with an asterisk, i.e. "b *0x400000" or "b *main+20".
Now trying running your program. You should this text:
1 2 | Breakpoint 1, main (argc=1, argv=0x7fffffffe948) at hello.c:4 4 printf("Hello, world!\n"); |
This means we're just about to execute the line that prints out our message - but we haven't done it yet.
We're also shown the arguments that were passed to the main
function, the breakpoint's number, and the source file we're in.
To see the surrounding code, try the "list" command.
To run a line of code, we can use the "next" command.
1 | next
|
This can be abbreviated to "n". Also, in GDB, the last command can be repeated by typing nothing and just pressing enter.
After using the next
command thrice, you'll be greeted with the following cryptic message.
1 | 0x00007ffff7a41f4a in __libc_start_main () from /usr/lib/libc.so.6 |
This means we've left the main
function and are in libc territory.
To exit the paused state, we can use the continue command.
1 | continue
|
This can abbreviated to "c".
This'll continue the program until another breakpoint is hit, or it exits. In our case the program exits.
To exit GDB, use the "quit" command.
To add a new breakpoint only when a certain condition is met, add an "if" statement after the break line, e.g. "break myfunc if myarg == 2". In this example myfunc is assumed to take a parameter named myarg, but variables referenced in the if statement can be anything in scope, not just arguments.
To add a condition to a breakpoint that was already set up, use "cond" followed by the breakpoint number and condition, e.g:
(gdb) b myfunc Breakpoint 1 at 0x400000 (gdb) cond 1 somevar == 4
To look at how GDB interacts with subroutines in our program, let's expand it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h> int AnotherFunction(int a, int b) { int c = a + b; return c; } int main(int argc, char **argv) { printf("Hello, world!\n"); int x = AnotherFunction(3, 5); printf("x = %d\n", x); return 0; } |
Compile this as before, and get it running in GDB, breaking at the main
function.
Now we can look at a new command, "step" (shortened to "s"). When the step command is used, the debugger will go inside any subroutine that is being called, rather than going over like the next command would.
Use the next command to go past the first print statement, and then use the step command to go into the other function we made. You should see this:
1 2 | AnotherFunction (a=3, b=5) at hello.c:4 4 int c = a + b; |
We are now inside another function. We are also shown the parameters that were passed to it.
We can also see the current stack trace, i.e. the list of functions that were called to get here. This requires the use of the "bt" command.
1 2 | #0 AnotherFunction (a=3, b=5) at hello.c:4 #1 0x00005555555546de in main (argc=1, argv=0x7fffffffe968) at hello.c:11 |
Once you've used the next command to go past the line that calculates the variable "c", how can you see what its value is?
Here's where the print command comes in handy. Type "p" and then the expression. So in this case, type "p c", and GDB will display the value 8.
To print the value of a register, prefix the registers name with a dollar symbol, e.g. "p $eax".
If you're stepping through your program and want to always see the value of a variable as it changes, you can use the "display" command, and the variable will automatically be re-printed after other commands you make such as step/next.
Use "undisplay <number>" to stop a variable being printed.
To examine a piece of memory, use the "x" command. There are various formats you can choose for how the memory should be interpreted, as well as the amount to be displayed.
These options use the format "x/<count><width><type>": e.g. "x/20wx 0x400000" will print 20 words/32-bit values (w) in hexadecimal (x) starting at the address 0x400000.
The <width> can be one of:
b - 1 byte / 8 bits h - 2 bytes / 16 bits w - 4 bytes / 32 bits g - 8 bytes / 64 bits
and <type> can be:
d - signed decimal u - unsigned decimal x - hex f - float c - char s - string t - binary i - instruction (disassembly)
You can also pass variables to "x" instead of a raw addresses. The variable will always be interpreted as a pointer even if it isn't: i.e. using x on an int holding a value 0x12345 will try and display memory starting from address 0x12345.
These slash formatting options can also be used with print/p but are perhaps less useful since p already prints based on the type of its argument.
gdb has several layout modes that allow you to see the source code / dissassembly / registers along with the normal command window.
To view the source along with the command window in a split view, type "layout src" (keyboard shorcut: <CTRL + x CTRL + a>). After doing this the up/down keys will scroll the source instead of page through command history, to fix that type "focus cmd".
Similarly, to view assembly with the command window type "layout asm", and to view both source and assembly, use "layout split".
After using "layout src" or "layout asm", you can get a panel showing the current register state by using "layout regs". It will highlight the registers that change as you step through your program.
Whenever you print / examine something in gdb, it'll print a line like:
$1 = 1234
That '$1' is an automatic variable that you can refer to in later commands such as 'p $1 + 2'
'$' on its own refers to the last thing that was printed, and '$$' the second last. These can be useful e.g. when printing out linked lists by using p "$->next".
That's all we'll cover in this simple tutorial. To exit GDB, type "quit" or "q".
At startup GDB will read a file in your home folder called .gdbinit
. This can contain custom commands, for example:
define con target remote :1234 end
You can store temporary values in "convenience variables". They are prefixed with $
, so be careful not to overlap with system registers. For example:
define ldcx set $new_rip = context->rip set $new_rsp = context->rsp set $new_rbp = context->rbp set $rip = $new_rip set $rsp = $new_rsp set $rbp = $new_rbp set $rip = $new_rip end
This command loads the new values of the registers RIP, RSP and RBP from a structure (that will be inaccessible after any of the registers are modified), and then sets their values all in one go. I set the RIP register twice because it likes to be set both before and after changing the stack.
file <executable>
run / r
attach <pid>
target remote <ip>:<port>
The IP address can be ommitted if it's on the local machine.
c
jump <location>
<location>
can be a line number, function, etc.
p <expression>
In the expression you can use $
to refer to the last printed value.
b <function_name>
b <line_number>
b <file_name>:<line_number>
b <where> if <expression>
s
finish / fin
n
watch *<address>
bt
bt frame <number> up down
info threads
info threads thread <number>
dir <path>
set disassembly-flavor intel
list
layout src (Source code) layout asm (Disassembly) layout split (Both) focus cmd (Return the focus back to the command prompt)
layout regs
set <variable or $register> = <expression>
info locals info args
info variables
ptype /o <struct>