3.4. Advanced GDB Features

This section presents advanced GDB features, some of which may make sense only after reading the Operating Systems chapter.

3.4.1. GDB and make

GDB accepts the make command to rebuild an executable during a debugging session, and if the build is successful, it will run the newly built program (when issued the run command to run it from the beginning).

(gdb) make
(gdb) run

Building from within GDB is convenient to a user who has set many breakpoints and has fixed one bug but wants to continue the debugging session. In this case, rather than quitting GDB, recompiling, re-starting GDB with the new executable, and resetting all the breakpoints, a GDB user can run make and start debugging the new version of the program with all the breakpoints still set. Keep in mind, however, that modifying C source and recompiling by running make from within GDB may result in breakpoints not being at the same logical location in the new version of the program as in the old version; adding or removing lines of C source code can result in breakpoints no longer being at the same logical part of the program in the new version of the source code. When this problem occurs, either exit GDB and restart the GDB session on the new executable, or use disable or delete to disable or delete old breakpoints and then break to set new breakpoints at the correct location in the newly compiled version of the program.

3.4.2. Attaching GDB to a running process

GDB supports debugging a program that is already running (vs. starting a program to run from within a GDB session), by attaching GDB to a running process. To do this, the user needs to get the process ID (PID) value.

  1. Get the process’s PID using the ps command in a bash shell:

    # ps to get process's PID (lists all processes started in current shell):
    $ ps
    
    # list all processes pipe through grep for just those named a.out:
    $ ps -A | grep a.out
       PID TTY          TIME CMD
       12345 pts/3     00:00:00 a.out
  2. Start GDB and attach it to the specific running process (with PID 12345):

    # gdb <executable> <pid>
    $ gdb a.out 12345
    (gdb)
    
    # OR alternative syntax: gdb attach <pid>  <executable>
    $ gdb attach 12345 a.out
    (gdb)

Attaching GDB to a process pauses it, and the user can issue GDB commands before continuing its execution.

Alternatively, a program can explicitly pause itself to wait for debugging by calling kill(getpid(), SIGSTOP) (as in the attach_example.c example). When the program pauses at this point, a programmer can attach GDB to the process to debug it.

Regardless of how a program pauses, after GDB attaches and the user enters some GDB commands, the program’s execution continues from its attach point using cont. If cont doesn’t work, GDB may need to explicitly send the process a SIGCONT signal in order to continue its execution:

(gdb) signal SIGCONT

3.4.3. Following a process on a fork

When GDB debugs a program that calls the fork() function to create a new child process, GDB can be set to follow (to debug) either the parent process or the child process, leaving the execution of the other process unaffected by GDB. By default, GDB follows the parent after a call to fork(). To set GDB to follow the child process after a call to fork(), use the set follow-fork-mode command:

(gdb) set follow-fork-mode child     Set gdb to follow child on fork

(gdb) set follow-fork-mode parent    Set gdb to follow parent on fork
(gdb) show follow-fork-mode          Display gdb's follow mode

Setting breakpoints at fork() calls in the program is useful when the user wants to change this behavior during a GDB session.

The attach_example.c example shows one way to "follow" both processes on fork: GDB follows the parent process after the fork, and the child sends itself a SIGSTOP signal to explicitly pause after the fork, allowing the programmer to attach a second GDB process to the child before it continues.

3.4.4. Signal control

The GDB process can send signals to the target process it is debugging and can handle signals received by the target process.

GDB can send signals to the process it debugs using the signal command:

(gdb) signal SIGCONT
(gdb) signal SIGALARM
...

Sometimes a user would like GDB to perform some action when a signal is receive by the debugged process. For example, if a program tries to access memory with a misaligned memory address for the type it is accessing, it receives a SIGBUS signal and usually exits. The default behavior of GDB on a SIGBUS is also to let the process exit. If, however, you want GDB to examine program state when it receives a SIGBUS, you can specify that GDB handle the SIGBUS signal differently using the handle command (the info command shows additional information about how GDB handles signals received by the process during debugging):

(gdb) handle SIGBUS stop    # if program gets a SIGBUS, gdb gets control

(gdb) info signal           # list info on all signals
(gdb) info SIGALRM          # list info just for the SIGALRM signal

3.4.5. DDD settings and bug fixes

Running DDD creates a .ddd directory in your home directory, which it uses to store its settings so that users don’t need to reset all their preferences from scratch on each invocation. Some examples of saved settings include sizes of sub-windows, menu display options, and enabling windows to view register values and assembly code.

Sometimes DDD hangs on start-up with a "Waiting until GDB ready" message. This often indicates an error in its saved settings files. The easiest way to fix this is remove the .ddd directory (you will lose all your saved settings and need to reset them when it starts up again):

$ rm -rf ~/.ddd  # Be careful when entering this command!
$ ddd ./a.out