GDB Inferior Tutorial

While working on the shell and I/O labs you'll likely find yourself wanting a way to peek into the behavior of a child process post fork — i.e., using a debugger to trace the execution of the child process instead of (or in addition to) the parent.

There is a screencast that covers how to do this, but this guide will serve as an abbreviated reference for the necessary commands.

Command reference

Here are the relevant commands you'll need (in gdb), up front:

An example

The program

Consider the following program, which I'll use to illustrate multi-inferior debugging:

#include <stdio.h>
#include <unistd.h>

int main () {
    pid_t pid = fork();
    printf("Hello!\n");
}

Compilation (with debug information)

To aid in debugging, I'm going to compile the program with debug information turned on (the -g flag). Given that the program is stored in the file "demo.c", here's the command I use to compile it:

gcc -g demo.c -o demo

And now I have an executable binary residing in the file "demo".

Note that for all labs, the Makefile you're provided with will automatically compile your program(s) for you with the -g flag when you run the command make. You will not need to run gcc directly, like I do here.

Using gdb on multiple inferiors

Next, I'll start gdb up on the executable, and set a breakpoint at the printf statement (note that I use the -q flag to gdb to tell it to be "quiet" in its operation — i.e., not print out copyright information and other messages):

$ gdb -q ./demo        
Reading symbols from /home/lee/tmp/demo...done.
(gdb) list
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main () {
5           pid_t pid = fork();
6           printf("Hello!\n");
7       }
(gdb) break 6
Breakpoint 1 at 0x400514: file demo.c, line 6.

Next, I'll go ahead and run the executable in gdb, but only after I tell it to not detach from any child processes that might be created:

(gdb) set detach-on-fork off
(gdb) run
Starting program: /home/lee/tmp/demo

[New process 27941]

Breakpoint 1, main () at demo.c:6
6           printf("Hello!\n");

Note that, due to the fork system call in the program at line 5 (just before the breakpoint), a new process is created. gdb tells me about this with the line [New process 27941].

I can now list information on all inferiors in gdb as follows:

(gdb) info inferiors
  Num  Description       Executable        
  2    process 27941     /home/lee/tmp/demo

# 1    process 27938     /home/lee/tmp/demo<a id="sec-3" name="sec-3"></a>

The asterisk (*) tells me that gdb is still currently inspecting the parent process (I know it's the parent, because the PID printed above for the new process is for the other inferior).

I can switch to the child process now with the inferior command, and continue running it (which it will do until it hits the breakpoint I set earlier):

(gdb) inferior 2
[Switching to inferior 2 [process 27941] (/home/lee/tmp/demo)]
[Switching to thread 2 (process 27941)] 
#0  0x000000310acac49d in fork () from /lib64/libc.so.6
(gdb) c
Continuing.

Breakpoint 1, main () at demo.c:6
6           printf("Hello!\n");

I can now step over the next line (the printf), then let it run to completion (by detaching from it):

(gdb) n
Hello!
7       }
(gdb) detach inferior 2
Detaching from program: /home/lee/tmp/demo, process 27941
(gdb) info inferiors 
  Num  Description       Executable

# 2    <null>            /home/lee/tmp/demo<a id="sec-4" name="sec-4"></a>

  1    process 27938     /home/lee/tmp/demo

Note that listing inferiors now shows a <null> for the former child process's slot.

At this point I can resume execution of the parent by switching to it; I'll just let it run to completion with c (continue).

(gdb) inferior 1
[Switching to inferior 1 [process 27938] (/home/lee/tmp/demo)]
[Switching to thread 1 (process 27938)] 
#0  main () at demo.c:6
6           printf("Hello!\n");
(gdb) c
Continuing.
Hello!

Program exited with code 07.

The full transcript

Here's the full gdb transcript, for easy reference:

$ gdb -q ./demo        
Reading symbols from /home/lee/tmp/demo&#x2026;done.

(gdb) list
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main () {
5           pid<sub>t</sub> pid = fork();
6           printf("Hello!\n");
7       }

(gdb) break 6
Breakpoint 1 at 0x400514: file demo.c, line 6.

(gdb) set detach-on-fork off

(gdb) run
Starting program: /home/lee/tmp/demo

[New process 27941]

Breakpoint 1, main () at demo.c:6
6           printf("Hello!\n");

(gdb) info inferiors
  Num  Description       Executable        
  2    process 27941     /home/lee/tmp/demo

# 1    process 27938     /home/lee/tmp/demo<a id="sec-6" name="sec-6"></a>

(gdb) inferior 2
[Switching to inferior 2 [process 27941] (/home/lee/tmp/demo)]
[Switching to thread 2 (process 27941)] 
\\#0  0x000000310acac49d in fork () from /lib64/libc.so.6

(gdb) c
Continuing.

Breakpoint 1, main () at demo.c:6
6           printf("Hello!\n");

(gdb) n
Hello!
7       }

(gdb) detach inferior 2
Detaching from program: /home/lee/tmp/demo, process 27941
(gdb) info inferiors 
  Num  Description       Executable

# 2    <null>            /home/lee/tmp/demo<a id="sec-7" name="sec-7"></a>

1    process 27938     /home/lee/tmp/demo

(gdb) inferior 1
[Switching to inferior 1 [process 27938] (/home/lee/tmp/demo)]
[Switching to thread 1 (process 27938)] 
\\#0  main () at demo.c:6
6           printf("Hello!\n");

(gdb) c
Continuing.
Hello!

Program exited with code 07.