Tutorial 3 (Sep 26): gdb (GNU Project Debugger)
- gdb (GNU Project Debugger)
We will be using this file [gdbex.s](1770 bytes) to demonstrate gdb during class.
To download from terminal, run this command:
|gdb myprogram||open your program in gdb (compile first with gcc)||gcc myprogram.s -o myprogram
|r||run your program|
|b <line number>||set a breakpoint at <line number>||b 15|
|b <label name>||set a breakpoint at <label name>||b main
|c||continue execution after a breakpoint|
|si||execute next instruction, after breaking
(step into function calls like printf)
|ni||execute next instruction, after breaking
(do not step into function calls)
|info breakpoints||list all your breakpoints|
|clear <label name>||delete a breakpoint at <label name>||clear main
|delete||delete all your breakpoints|
|x||show (examine) address of current instruction|
|x/i||show (examine) address and content of current instruction|
|display/i $pc||always show the current instruction to be executed (like always having x/i on)
(use this, then run your program, it will show every time your program breaks)
|print the value of the register specified
/d : Signed decimal
/x : Hexadecimal
/t : Binary
|info registers||print the value of all registers|
|kill||stop execution of current running program|
gdb (GNU Project Debugger): the debugger is a programmer's best friend
gcc usuaully outputs error messages, with line numbers, when it encounters an error in your code. The problem is, some errors are syntactically correct, but logically flawed. For example, your arithmetic operations may be wrong, or you might be storing your values in the wrong registers due to typos. These errors can be very difficult to catch without a debugger, since your program will execute just fine, until it prints out the wrong output.
- Compile your program like usual:
gcc myprogram.s -o myprogram
- Start your program in gdb:
- To run the program, just enter:
Just running your program in gdb doesn't help much. We want to be able to pause the program during its execution, for a couple reasons:
- If the program execution reaches a breakpoint, we know that the program at least runs up to that point. This is useful if you are looking for which line is crashing your program.
- If you are tracing your program, you need to know the values stored in your registers. To do that, you first reach a breakpoint, before using the display or p commands (below) to check your registers.
You can set a breakpoint on a line number, or on a label. For example:
(gdb) break main // set a break point at "main" label
Breakpoint 1 at 0x4005f0
(gdb) break test // set a break point at "test" label
Breakpoint 2 at 0x400610
(gdb) info breakpoints // list all my breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004005f0 <main+20>
2 breakpoint keep y 0x0000000000400610 <test>
To continue after stopping at a breakpoint, enter
Setting too many breakpoints can be tedious, and it'll get messy. Instead of setting a breakpoint on every line, we can continue executing the program one line at a time from a breakpoint, using si or ni.
si: execute the next instruction, step into functions (for now, use this one)
ni: execute the next instruction, if it reaches a function call, proceed until the function returns (when you start writing functions, you might need this one to see what's happening inside each function call)
- If you want to check whether your arithmetic operations are correct, you might want to set a breakpoint immediately before and after your operations.
- At each breakpoint, use one of the p or info registers commands to check the value of your registers.
- If you discover an error, you may want to run your program again. Upon reaching the first breakpoint, step through each line with si or ni, checking the registers along the way.