A debugger is software that runs your code and examines any problems it finds. GNU Debugger (GBD) is one of the most popular debuggers, and in this article, I examine GDB's step
command and related commands for several common use cases. Step is a widely used command, but there are a few lesser-known things about it that might be confusing. Also, there are ways to step into a function without actually using the step
command itself such as using the less-known advance
command.
No debugging symbols
Consider a simple example program:
#include <stdio.h>
int num() {
return 2;
}
void bar(int i)
{
printf("i = %d\n", i);
}
int main()
{
bar(num());
return 0;
}
If you compile without the debugging symbols first, set a breakpoint on bar
and then try to step within it. The GDB gives an error message about no line number information:
gcc exmp.c -o exmp
gdb ./exmp
(gdb) b bar
Breakpoint 1 at 0x401135
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, 0x0000000000401135 in bar ()
(gdb) step
Single stepping until exit from function bar,
which has no line number information.
i = 2
0x0000000000401168 in main ()
Stepi
It is still possible to step inside a function that has no line number information, but the stepi
command should be used instead. Stepi executes just one instruction at a time. When using GDB's stepi
command, it's often useful to first do display/i $pc
. This causes the program counter value and corresponding machine instruction to be displayed after each step:
(gdb) b bar
Breakpoint 1 at 0x401135
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, 0x0000000000401135 in bar ()
(gdb) display/i $pc
1: x/i $pc
=> 0x401135 <bar+4>: sub $0x10,%rsp
In the above display
command, the i
stands for machine instructions and $pc
is the program counter register.
It can be useful to use info registers and print some register contents:
(gdb) info registers
rax 0x2 2
rbx 0x7fffffffdbc8 140737488346056
rcx 0x403e18 4210200
(gdb) print $rax
$1 = 2
(gdb) stepi
0x0000000000401139 in bar ()
1: x/i $pc
=> 0x401139 <bar+8>: mov %edi,-0x4(%rbp)
Complicated function call
After recompiling the example program with debugging symbols, you can set the breakpoint on the bar
call in main using its line number and then try to step into bar
again:
gcc -g exmp.c -o exmp
gdb ./exmp
(gdb) b exmp.c:14
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
Now, step into bar()
:
(gdb) step
num () at exmp.c:4
4 return 2;
The arguments for a function call need to be processed before the actual function call, so num()
is expected to execute before bar()
is called. But how do you step into the bar
, as desired? You need to use the finish
command and step
again:
(gdb) finish
Run till exit from #0 num () at exmp.c:4
0x0000000000401161 in main () at exmp.c:14
14 bar(num());
Value returned is $1 = 2
(gdb) step
bar (i=2) at exmp.c:9
9 printf("i = %d\n", i);
Tbreak
The tbreak
command sets a temporary breakpoint. It's useful for situations where you don't want to set a permanent breakpoint. For example, if you want to step into a complicated function call like f(g(h()), i(j()), ...)
, you need a long sequence of step/finish/step
to step into f
. Setting a temporary breakpoint and then using continue can help to avoid using such sequences.
To demonstrate this, you need to set the breakpoint to the bar
call in main
as before. Then set the temporary breakpoint on bar
. As a temporary breakpoint, it is automatically removed after being hit:
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) tbreak bar
Temporary breakpoint 2 at 0x40113c: file exmp.c, line 9.
After hitting the breakpoint on the call to bar
and setting a temporary breakpoint on bar
, you just need to continue to end up in bar
.
(gdb) continue
Continuing.
Temporary breakpoint 2, bar (i=2) at exmp.c:9
9 printf("i = %d\n", i);
Disable command
Alternatively, you could set a normal breakpoint on bar
, continue, and then disable this second breakpoint when it's no longer needed. This way you can achieve the same results as with tbreak
with one extra command:
(gdb) b exmp.c:14
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) b bar
Breakpoint 2 at 0x40113c: file exmp.c, line 9.
(gdb) c
Continuing.
Breakpoint 2, bar (i=2) at exmp.c:9
9 printf("i = %d\n", i);
(gdb) disable 2
As you can see, the info breakpoints
command displays n
under Enb
, which means it’s disabled. But you can enable it later if it’s needed again.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000401157 in main at exmp.c:14
breakpoint already hit 1 time
2 breakpoint keep n 0x000000000040113c in bar at exmp.c:9
breakpoint already hit 1 time
(gdb) enable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040116a in main at exmp.c:19
breakpoint already hit 1 time
2 breakpoint keep y 0x0000000000401158 in bar at exmp.c:14
breakpoint already hit 1 time
Advance location
Another option you can use is an advance
command. Instead of tbreak bar ; continue
, you can do advance bar
. This command continues running the program up to the given location.
The other cool thing about advance
is that if you don't reach the location you try to advance to, GDB will stop after the current frame's function finishes. Thus, execution of the program is constrained:
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) advance bar
bar (i=2) at exmp.c:9
9 printf("i = %d\n", i);
Skipping a function
Yet another way to step into the bar,
avoiding num
, is using the skip
command:
(gdb) b exmp.c:14
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) skip num
Function num will be skipped when stepping.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) step
bar (i=2) at exmp.c:9
9 printf("i = %d\n", i);
To know which functions are currently skipped, use info skip
. The num
function is marked as enabled to be skipped by y
:
(gdb) info skip
Num Enb Glob File RE Function
1 y n <none> n num
If skip
is not needed anymore, it can be disabled (and re-enabled later) or deleted altogether. You can add another skip
and disable the first one and then delete them all. To disable a certain skip
, its number has to be specified; if it's not specified, each skip
is disabled. It works the same for enabling or deleting a skip
:
(gdb) skip bar
(gdb) skip disable 1
(gdb) info skip
Num Enb Glob File RE Function
1 n n <none> n num
2 y n <none> n bar
(gdb) skip delete
(gdb) info skip
Not skipping any files or functions.
GDB step command
GDB's step
command is a useful tool for debugging your application. There are several ways to step into even complicated functions, so give these GDB techniques a try the next time you're troubleshooting your code.
Comments are closed.