CSC 101 Lab Notes Weeks 9 and 10
Using a Symbolic Debugger
This lab focuses on tools for program debugging. The major debugging tool we will use is named "gdb" -- the Gnu debugger. We will also see how gdb can be used inside the Emacs editor to provide a convenient debugging environment.
Gdb allows the programmer to perform the following general functions:
The exercises that follow illustrate the gdb commands that perform these functions. You should read through the exercises and then perform them by running gdb and executing the commands that are illustrated.
NOTE: In order for gdb to run a program properly, the program must be compiled with the -g argument, in addition to any other arguments that the compiler requires. E.g., the program in the first exercise below was compiled as follows:
CC -g lab9-ex1.cpp -o lab9-ex1
Consider the following program, from the file lab9-ex1.cpp:
////
//
// This is the example used in Exercise 1 of Lab Notes Week 9.
//
// Author: Gene Fisher (gfisher@calpoly.edu)
// Created: 24may99
// Modified: 24may99
//
////
#include "Boolean.h"
const int LOOP_LIMIT = 5; // Upper limit of loop iterations
int main() {
Boolean continue_loop = TRUE; // Loop sentinel variable
int i; // Loop counter
//
// Execute loop while continue_loop is true.
//
for (i = 0; continue_loop = TRUE; i++) {
if (i > LOOP_LIMIT) {
continue_loop = FALSE;
}
}
}
The problem with the program is that it has an infinite loop. Here is how gdb
is used to run the program and inspect program variables to determine the cause
of the problem.
Command/Message Description
--------------------------------------------+--------------------------------------------
polylog1> gdb lab9-ex1 |At the UNIX prompt, run the executable
|program lab9-ex1 under gdb control.
|
GDB is free software ... |Gdb starts with an opening message, then
|outputs its command prompt "(gdb) ".
|
(gdb) run |Enter the gdb run command to start the
|program. The program will execute exactly
|as it would if run directly from UNIX,
|except now gdb will be in control of the
|program.
|
Starting program: ... |Gdb responds to the run command by
|outputting a message with the name of the
|program file that it's running.
|
^C^C |Since the program is in an infinite loop,
|you must type control-C a couple times to
|stop it. This is the same way to stop an
|infinite loop program at the UNIX command
|level. When the program is stopped from
|inside gdb, we will be able to see better
|what is going on.
|
Program received ... Interrupt. |Gdb responds to the control-C interrupt by
0x10810 in main () at lab9-ex1.cpp:24 |outputting an informational message. The
24 if (i > LOOP_LIMIT) { |first line of the message says that the
|program stopped because you interrupted it
|with control-C. The second line of the
|message says where the program was stopped
|(you can ignore the "0x10810" number --
|it's the actual computer memory address
|where the program stopped). The third
|line of the message is a copy of program
|source line where the program stopped. In
|this case, it's in the body of the
|infinite loop.
|
(gdb) print i |Use the print command to examine the value
|of program variables. Any legal
|expression can given to the print command.
|Here we print the value of variable i.
|
$1 = 24850033 |Gdb responds to the print command by
|outputting the value of i. It's clear
|from this value that i is way to big,
|since according to the intended program
|logic, the loop should have quit at 5.
|Using this information, we inspect the
|program code, and deduce that the bug is
|due to the mistyped loop condition
| continue_loop = TRUE
|which should be
| continue_loop == TRUE
|
|(Note that the "$1 =" part of the output
|is a numbering scheme used by gdb to list
|all of its outputs. You can safely ignore
|the "$" numbers.)
|
(gdb) quit |To exit gdb, type the quit command.
The program is running. |
Quit anyway (and kill it)? (y or n) y |
polylog1> |
Consider the following program
////
//
// This is the example used in Exercise 2 of Lab Notes Week 9.
//
// Author: Gene Fisher (gfisher@calpoly.edu)
// Created: 24may99
// Modified: 24may99
//
////
#include "Boolean.h"
const int LOOP_LIMIT = 5; // Upper limit of loop iterations
const int ARRAY_SIZE = 100; // Size of array.
int main() {
int a[ARRAY_SIZE]; // Array variable
Boolean continue_loop = TRUE; // Loop sentinel variable
int i; // Loop counter
//
// Execute loop while continue_loop is true. For each loop iteration,
// store 10 times the value of the loop counter i in the ith array element.
//
for (i = 0; continue_loop = TRUE; i++) {
//
// Signal loop exit when i exceeds limit.
//
if (i > LOOP_LIMIT) {
continue_loop = FALSE;
}
//
// Store i*10 in the next array element.
//
a[i] = i*10;
}
}
This program has the same logic bug as the program in Exercise 1. Here, since an array is involved, the infinite loop is terminated when the program tries to write too far past the end of the array declared to have 100 elements. Here is how gdb is used to debug the program:
Command/Message Description
--------------------------------------------+--------------------------------------------
polylog1> gdb lab9-ex2 |At the UNIX prompt, run the executable
|program lab9-ex2 under gdb control.
|
GDB is free software ... |Gdb starts with an opening message, then
|outputs its command prompt "(gdb) ".
|
(gdb) run |Enter the gdb run command to start the
|program. The program will execute exactly
|as it would if run directly from UNIX,
|except now gdb will be in control of the
|program.
|
Starting program: ... |Gdb responds to the run command by
Program received ... Segmentation fault. |outputting a message with the name of the
0x10844 in main () at lab9-ex2.cpp:38 |program file that it's running. Shortly
38 a[i] = i*10; |after execution begins, gdb prints the
|"Segmentation fault" message, because the
|program has attempted to write into the
|array beyond the segment of computer
|memory the program has been allotted. As
|when the program was interrupted with
|control-C, gdb prints out the source line
|of the program where the segmentation
|fault interrupt occurred.
|
(gdb) print i |Again, use the print command to inspect
|the value of i.
|
$1 = 646 |In this program, i has only reached a
|value of 646 before the Segmentation fault
|occurred. The fix to the bug in this
|program is the same as it is in Exercise
|1.
Suppose when debugging the program in exercise 2 we want to stop program
execution before the Segmentation fault occurs. Gdb allows a program
breakpoint to be set for such purposes. Here is an example that
continues the debugging session begun in Exercise 2:
Command/Message Description
--------------------------------------------+--------------------------------------------
(gdb) break 38 |The gdb break command is used to set a
|breakpoint at a selected line of the
|program. Any number of breakpoints can be
|set at different points in the program.
|When the program reaches a line at which a
|breakpoint has been set, gdb interrupts
|the program temporarily so that program
|values can be investigated.
|
Breakpoint 1 at ... |Gdb responds to the break command by
|outputting a message indicating exactly
|where the breakpoint has been set.
|
(gdb) run |Execute the run command to start the
|program over again.
|
The program ... started already. |Gdb responds to this second run command by
Start it from the beginning? (y or n) y |indicating that the program has already
|been started and is technically still
|running (even though it had a Segmentation
|fault). Type y to proceed with execution.
|
Breakpoint 1, main () at lab9-ex2.cpp:38 |When the program reaches line 38, gdb
38 a[i] = i*10; |takes control and prints a message
|announcing that a breakpoint has been
|reached, including the line on which the
|breakpoint occurred. At this point, any
|legal gdb command can be executed.
|
(gdb) print i |Use the print command to examine the value
|of i.
|
$2 = 0 |Gdb prints the value of i as 0.
|
(gdb) cont |The gdb cont command is used to continue
|execution after a breakpoint.
|
Continuing. |Gdb responds to cont with a brief message.
Breakpoint 1, main () at lab9-ex2.cpp:38 |Then, since the breakpoint is in a loop,
38 a[i] = i*10; |the program comes back to line 38 and
|stops again.
|
(gdb) print i |Issue a series of commands to print i,
$3 = 1 |a[0] and a[1]. Note that since we have
(gdb) print a[0] |stopped on line 38, this line has not yet
$4 = 0 |been executed, so the value of a[1] has
(gdb) print a[1] |not yet been set. We can use the gdb next
$5 = 0 |command to execute just one line.
|
(gdb) next |The next command tells gdb to execute a
|single line and stop again. In this case
|line 38 is executed.
|
39 } |Gdb responds to the next command by
|printing the source line of the program
|where it stops. Note that line 39 has not
|yet been executed.
|
(gdb) print a[i] |Use print to inspect the value of a[i],
$6 = 10 |which is now 10 as expected. Note how the
|value of i is used in the print command.
|Any legal C++ expression can be printed.
|
(gdb) delete 1 |Use the delete command to remove a
|breakpoint. Note that breakpoints are
|deleted by number. You can list all
|currently set breakpoints using the info
|break gdb command.
|
(gdb) cont |When execution is continued, the same
Continuing. |segmentation fault as before occurs, since
... Segmentation fault. |the program still has the bug in it.
... |
|
(gdb) quit |Use the quit command to leave gdb.
Gdb can be run inside Emacs using a few basic commands. The commands are:
Command Description
-----------------------------------------------------
M-x gdb Run gdb inside emacs. The display
screen is split into two windows, with
gdb running in one window. All normal
gdb commands can be typed in the gdb
window, just as they would typed if gdb
were running directly under UNIX.
C-x o Move the emacs cursor to the other
window. This allows you to switch
between the gdb window and whatever
other window you have.
C-x space Here "space" means type the space key.
This command is used by moving the
cursor to the line of C++ source program
in non-gdb display window. When C-x
space is typed at that line, a
breakpoint is automatically set in the
gdb window.
The main advantage of running gdb inside emacs is that whenever an program interrupt or breakpoint occurs, emacs automatically opens the source file where the interrupt or break happened, moves the cursor to the affected line of the program, and points to the line with an indicator "=>".
To experiment with gdb in Emacs, you can rerun Exercises 1 through 3 from inside Emacs.