7 pro tips for using the GDB step command

There are several ways to step into even complicated functions, so give these GDB techniques a try next time you're troubleshooting your code.
1 reader likes this.
Bug tracking magnifying glass on computer screen

Pixabay, testbytes, CC0

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.

What to read next
Tags

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.