Use of Debuggers
A Brief Introduction to GDB, including its use within
emacs
Prepared by Professor Daniel Spiegel
Kutztown University
A debugger is a program that is used to run other
programs. In a debugger, a program may be executed, debugged, or a combination
of both. Further, debuggers provide commands in order that data may be
examined.
There are many operations available in most debuggers.
This document will only cover a subset of the functionality of debuggers. The
gdb debugger available to Kutztown students will be described.
You can find a detailed (and long) description of this
debugger online:
GNU project gdb: http://www.gnu.org/manual/gdb-5.1.1/html_mono/gdb.html
The gnu gdb documentation site page has a link to a
detailed manual for its use within emacs (gdb in emacs is covered shortly).
Features of Debuggers:
¨
Check
the order of statement execution in a program.
¨
Evaluate
a variable, parameter, or (possibly) an expression at a particular point during
program execution.
¨
Determine
where execution was occurring in a program when a program crashes, even if the
crash was in a library.
¨
Stop
a program’s execution at a particular statement
Debugger Operations:
The following are basic operations of all debuggers:
Setting a Breakpoint
This operation is used to
cause a program that is executing in the debugger to suspend itself, permitting
debugging. In many integrated development environments (IDEs), a program can
also be suspended and enter debugging mode via a command. Visual C++ allows
this via a break command that suspends immediately and also by a run
to cursor command that stops the program on the line the cursor is on.
Resume Execution
This operation restarts a program that is in debugging mode. The program will execute until it terminates, or encounters a breakpoint or other suspend directive.
Execute a statement
¨ Step over – Execute the present statement. If the statement is a function call, the function will be executed; the debugger will stop at the statement after the function call.
¨ Step into – Execute the
present statement. If the statement is a function call, the function will be
debugged; the debugger will stop at the first statement inside the function.
Note that if you perform a step into on a library function call such as cout
<<, the debugger may attempt to step into the file containing the library
function. In Visual C++, this will load a file, usually full of assembly
language statements; in gdb, you may have trouble, even if you use finish to
get out. Use of step into with function calls should be limited to functions
you wrote.
Note: Step into and Step over are indistinguishable if the present statement is not a function call.
¨ Step out – This operation resumes
execution until the function the program is executing terminates; the debugger
will stop at the statement after the function call. This function is available
in both Visual C++ and gdb.
Watching Variables
At any time during a debugging session, you may examine the value of a variable via a watch. In IDEs, watches can be set up in a window. In gdb, the value of a variable and/or expression can be output.
Enabling and Starting GDB:
To enable gdb to be able to display the statements of
your program, you must use the –g flag during compilation and linking of all
files comprising your program. The –g flag causes the compiler and linker to
maintain variable names and untranslated C++ statements, in order that
variables can be examined and C++ statements can be displayed and followed.
To start a stand-alone gdb session, simply type gdb
<executable> at the system prompt. For example, to debug a.out,
issue the command
Unix Prompt >gdb a.out
gdb will start and load a.out.
Alternatively, gdb can be started by simply issuing
the command gdb at the Unix prompt. To then load an executable,
issue a file command. For example:
(gdb)file a.out
To start a gdb debugging session in emacs:
¨
Start
emacs without specifying a file
o
Unix
Prompt >emacs
¨
Issue
the command to execute a program on the emacs command line
o
ESC-x (then hit return)
¨
Start
gdb
o
gdb
(it will follow the M-x, which remains on the emacs command line)
¨
You
will then be prompted by the phrase run like this? Add the file
to debug (or use the file command after gdb starts)
The gdb environment will appear inside the single
emacs window.
GDB commands:
When GDB starts, the loaded
executable is not running; there are many commands that can be issued to
prepare debugging. A glossary of commands can be displayed by issuing the
command
(gdb)help
There will be a list of command
areas, each of which has its own help list. For example,
(gdb)help running
will list commands available for
running a program. If you issue a help command followed by and actual command,
the help information for that command will be displayed.
Commands can often be issued
without typing the entire command. Here are some commonly used commands; many
of them can be invoked using only the first letter:
(gdb) quit
– exit the debugger
(gdb) file –
load an executable file
(gdb) break
line-number/function name -- Set a break-point on a line/at start of function
(gdb) run
<args> -- start running the program; if there are command-line arguments,
put them after the run invocation
(gdb) cont
-- continue running, after a break
(gdb) next
-- Next program line (step over function calls)
(gdb) step
-- Step into function calls.
(gdb) finish -
Step out of the present function
(gdb) print
expression -- Show value of a variable or expression
(gdb) list
– List 10 lines of the program being debugged. The sixth line is the preset
statement. Subsequent, consecutive entry of list will list the next 10 lines.
(gdb) where
– obtain a backtrace showing all function calls before the current statement
(gdb) up
– Move to the function that called the present function. Useful if your program
crashes in a library function; use up to get to the last function call in your
program
(gdb) down
– Reverses the action of up
(gdb) delete
– Removes breakpoint by number (see example following). If no number, all
deleted.
(gdb) kill –
Terminates the program.
Notes on selected gdb commands:
Break: To set a breakpoint on a line, issue the break
command with a line number. For example, to set a breakpoint on line 43, issue b
43 . To set a breakpoint at the
start of a function, issue the break command with the function name. For
example, to suspend execution and enter debugging mode at the start of function
foo, issue b foo . To
debug from the start of the program, issue b main
In projects
with multiple files, a breakpoint can be set in an associated file using <file
name>:line number
A
breakpoint can be set at the beginning of an object’s function using <class>::<function>
Note:
In IDEs a breakpoint is set explicitly within the program code in a window.
Print: The
print command has great functionality. You can enter just about any valid
expression, and it will be evaluated an printed. You can print the values of
pointers, dereference a pointer, and print entire arrays and objects. See the
emacs example on the next page for sample uses of the print command.
To
debug in emacs, start gdb within emacs as noted earlier. Then, set a
breakpoint. Start your program, and when the program reaches the breakpoint,
emacs will split into two windows. One window will contain the gdb command
environment, and the other will contain the program code, with a text arrow (=>)
indicating the present statement.
You
may then issue gdb commands in the original emacs window while viewing the
present line and surrounding code in the 2nd window.
To
exit emacs, enter ctl-x followed by ctl-c. If a program is loaded
at the time, you may be prompted before exiting.
Demonstration
of gdb within emacs:
Debugging
using gdb within emacs will be demonstrated using the ArrayADT example from
class. This example uses the files Array.h, Array.cpp, and ArrayDebug.cpp.
Also, on a Unix system such as acad.kutztown.edu, you would want to obtain the
file makefile, which establishes compilation rules for this program.
These files are available on acad in subdirectory
/export/home/public/spiegel/cis136/Debug/ArrayADT . On the web, they are
available at URL http://faculty.kutztown.edu/spiegel/Debugging/Files/.
For more information on makefiles, see the makefile primer at http://faculty.kutztown.edu/spiegel/Makefile/Makefile.htm.
The
contents of ArrayDebug.cpp are in the box at left, along with the output obtained from its execution. This
file includes breakpoint comments that aren’t in the actual file. There are a
number of functions. main() is declared last, mainly to save room by
eliminating the function prototypes.
These
functions provide a variety of argument types/return values to permit
demonstration of debugger and language features.
To begin,
make sure that compilation occurred with the –g flag (if compiling with g++).
The makefile handles this (Do you know what a makefile is/does? If not, be sure
to read the makefile primer on this site). Now, start the debugger inside emacs
as shown previously. The name of the executable produced by the makefile is debug.
This demo will use the putty telnet client, in which will encounter the
screen seen below (using putty is not required...
Screen 1
was obtained by starting emacs, hitting ESC, then X, then gdb,
and return. When prompted Run like this? type debug
(or whatever name your executable has) and hit return again.
To prepare
to run the program, first set up some breakpoints. The lines in bold in the
program in Figure 1 will be breakpoints. To set them up, the following commands
are issued:
OK,
now the program can be started. Use the run command to start (need only
enter r as on top of window).
The program
stops at the first breakpoint, the first line of main() in Screen 3. At
this point, nothing is defined, as we haven’t yet completed the first
executable statement.
Issue the next
command (Screen 4) to construct the Array object named one. Note
that the location arrow skips over the declaration of integer n, since
that statement is not executable (wait! what makes the declaration of one
executable?). Following this, issue the print command (p one) to
see the value of that object. Note that the value of data member ptr is
a memory address (the 0x prefix means the value is in hexadecimal form,
or base 16). The value of class variable arrayCount is printed, it is a
data member of the class (and thus all its objects). Now, enter the continue
command to restart the program; it will stop at the next breakpoint.
The program
executes the first two output statements in main(), and the output of
Arrays one and two is printed (Screen 5a). The values can be
easily confirmed by briefly inspecting ArrayDebug.cpp in concert with
knowledge that an Array object is constructed with all elements equal to 0.
But, the
program doesn’t stop where you might expect. It actually stops in a different
file! While previous action all occurred in ArrayDebug.cpp, the next
breakpoint reached is a call of the destructor.
It is
certainly of interest to determine how this came to be. We know that the
program hasn’t yet reached breakpoint 3, so some object left scope before that
point. To find out where, use the up command.
The program
is now executing in ArrayDebug.cpp, at the call of add1(one). If you look at the declaration of this
function, you can see its argument is not a reference argument. This is the
cause of the destructor call. A value argument that is an object must be copied
from the actual argument, and this means the copy constructor must be executed
(this will be demonstrated shortly). When the function returns, this copy
leaves scope and must be destroyed. To explore this further, we will use some
stepping during the later execution of add3(), which also has a value
argument.
Again, issue the continue command to restart the
program. It will stop when it reaches the next breakpoint. The program will stop
at the call of add2 in main() (bottom of the visible window in Screen
6). Let’s step into the function so we can see something else. But first, print
the actual argument and its address
(p two and p &two) , then issue the step command to
debug the function (issuing next would have executed it, rather than entering it and stopping at
the first line), and print the
formal argument’s address (p &a) after entering the function.
Finally, print the actual argument from inside the function, using scope resolution
by issuing the command p &(main::two). You can see that the formal
and actual argument have the same memory address, as would be expected.
This leaves
us at the first line of the function, as shown at left in Screen 6. Let’s
examine the alteration of the second of the elements of Array a (index
1).
We must
wait for i to be 1. To get there, (Screen 7) first issue the next
command to step over (execute, not debug) the call of getSize() in the for
statement. Now, make i display each time by issuing display i. Now,
issue the next command until i displays as 1 and the body of the
for loop is to be executed (location indicator is on the assignment to a[i] in
the body of the loop). Now, print a[i]. To do this, you must print the proper
data member. Issue p a.ptr[i]. To prepare to see the effect, print
main::two.ptr[i],
Now, issue next,
then repeat the previous two prints.
Issue the continue
command until the program terminates. Remove all breakpoints by issuing the
command d (this deletes all breakpoints; you will be prompted). Type undisplay to reverse the effect
of any display commands.
Let’s see
where the copy constructor gets called. Issue the command to break at a call of
the copy constructor by typing b 'Array::Array(Ar and then pressing the
TAB key. gdb will auto-complete the command for you; just press return. Now,
run the program using the r command. For variety, you could have instead
issued: b Array.cpp:30, which will stop the program at line 30 of that
file (first line of the copy constructor); run the program again.
Your
program will suspend with the arrow indicating the open curly brace ( { ) on
line 29 of Array.cpp (not shown), inside the copy constructor. To determine
what event caused this call to the copy constructor to occur, issue the command
up.
Screen 8. Call of copy constructor before add1()
executes
The call that caused this is the assignment of Array two to
Array one. This is due to the return statement in the assignment (=)
operator in Array, and is a bit complicated. Let’s look at a more
straight-forward call of the copy constructor.
Issue the c
command to continue execution. Your program should again stop at line 29 of
Array.cpp. Now, issue the up command. The location arrow indicates the
call of add1() as shown in Screen 8. The formal parameter of add1()
is passed by value (why would it be a compiler error if it was pass by
reference?) . We know this means that the actual parameter is evaluated and copied
to the corresponding formal parameter. How is an object copied? Using the copy
constructor!
Let’s
confirm this. Issue the down command. This puts you back into the copy
constructor. Let’s see what’s being copied. Issue the command p &init.
This will produce a hexidecimal number (it can contain letters between a
and f, which are hexidecimal digits). This is the memory address of the
object being copied.
Now, issue
the command p this. This produces another hexidecimal number, which is
the memory address of the object being constructed. Next, issue the command p
&(main::one). This produces the memory address of object one.
Which value does it match? It should match the memory location of parameter init.
If our
theory is correct, the memory location of this should match the formal
parameter of the called function add1(). Let’s confirm this. Issue the s
command until the copy constructor completes (12 times). The location indicator
should now be on line 17 of ArrayDebug.cpp, the first line of add1().
Issue the command p &a. Its memory location is the same as that of this,
the self-reference of the just constructed object, and that proves that in
order to pass an object by value, it must be copied, requiring use of the copy
constructor.
For further
exploration of language features, there is a debugger exercise located in URL: http://faculty.kutztown.edu/spiegel/Debugging/Exercise/
Detecting the Location
of Unix run-time errors
The gnu debugger may also be utilized to determine
the exact location in a program where a segmentation fault, bus error, or other
fatal run-time error has occurred. When a program crashes due to an unspecified
error, start gdb inside emacs, loading the executable (don’t forget to be sure
the program is compiled using the –g option). Then, simply run the program and
it will report the error. At this point, the emacs window will not have split.
To find the statement that caused the error, issue the up command
repeatedly until the window splits, revealing the last statement executed.
To provide
a simple demonstration, there is a file crash.cpp (along with a
makefile) on acad, in the subdirectory /export/home/public/spiegel/cis136/Debug/ArrayADT/crash
. On the web, the files are available at URL http://faculty.kutztown.edu/spiegel/cis136/Debug/crash/
. This program crashes on a division by zero. The program appears in the box.
Clearly, the program will crash inside function b().To
try this out, build the executable (again named debug), start gdb in emacs, and
issue the run command. When the program crashes, issue the up
command to see the cause. Note that it is often necessary to issue the up command multiple times before the window splits and shows the last line
executed in your program. Most run-time errors occur in library functions
called by other library functions, which are in turn called from your program.
In the example at left, the executable debug was started. It crashed upon
attempting to execute the division by 0 in the return statement where the
location indicator is pointing. This line was displayed following the issuance
of the up command in the gdb window.
You can also obtain the trail of function calls leading up to a fatal
error using the backtrace command. This displays all function calls that occurred prior to
execution of the line that (often indirectly) caused the fatal error. Note that
the indicated lines can be examined by issuing the up command. For example, to see the
call in main() that led to the run-time error, you would issue the up command
two more times (after the first issuance of up, the location indicator would be
on the call of b() inside function a().
For further information on
use of gdb, please consult the gnu project’s gdb manual at the URL given
previously.