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.
Here are the relevant commands you'll need (in gdb
), up front:
set detach-on-fork
: this command tells gdb
to not "detach" from the
child process after forking — i.e., gdb
will instead pause the child
process and allow you to switch to it laterinferior
ID: this command switches to an "inferior" (gdb
calls any
program it executes an inferior) with the given IDinfo inferiors
: this lists information (inferior ID, PID, program) on each
inferior currently being debugged by the active gdb
detach inferior
ID: detaches from the inferior identified by ID, allowing it
to run to completion normallyConsider 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");
}
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.
gdb
on multiple inferiorsNext, 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.
Here's the full gdb
transcript, for easy reference:
$ 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<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.