Debuggers
Introduction
Debugging means locating (and then removing) bugs, i.e.,
faults, in programs. In the entire process of program development errors may
occur at various stages and efforts to detect and remove them may also be
made at various stages. However, the word debugging is usually in context of
errors that manifest while running the program during testing or during
actual use. The most common steps taken in debugging are to examine the
flow of control during execution of the program, examine values of variables
at different points in the program, examine the values of parameters passed
to functions and values returned by the functions, examine the function call
sequence, etc. In the absence of other mechanisms, one usually inserts
statements in the program at various carefully chosen points, that prints
values of significant variables or parameters, or some message that
indicates the flow of control (or function call sequence). When such a
modified version of the program is run, the information output by the extra
statements gives clue to the errors.
Using print statements for debugging a program is often not adequate
or convenient. For example, the programmer may want to change the values of
certain variables (or parameters) after observing the execution of the
program till some point. For a large program it may be difficult to go back
to the source program, make the necessary changes (maybe temporarily) and
rerun the program. Again, if such print statements are placed inside
loops, it will produce output everytime the loop is executed though the
programmer may be interested in only certain iterations of the loop. To
overcome several such drawbacks of debugging by inserting extra statements in
the program, there are a kind of tool called debugger that helps in
debugging programs by giving the programmer some control over the execution
of the program and some means of examining and modifying different program
variables during runtime.
Basic Operations Supported by a Debugger
A debugger provides an interactive interface to the programmer to control the
execution of the program and observe the proceedings. The program (executable
file) to be debugged is provided as an input to the debugger. The basic
operations supported by a debugger are -
- Breakpoints - Setting breakpoints at various positions in the
program. The breakpoints are points in the program at which the programmer
wishes to suspend normal execution of the program and perform other tasks.
- Examining values of different memory locations - When the execution of a
program is suspended, the contents of specified memory locations can be
examined. This includes local variables (usually on the stack), function
parameters, and global (extern) variables.
- Examining the contents of the program stack - The contents of the program
stack reveals information related to the function call sequence that is
active at that moment.
- Depositing values in different memory locations - While the execution of
the debugged program is not underway (yet to start or suspended at a
breakpoint), the programmer can deposit any value in the memory locations
corresponding to the program variables, parameters to subroutines, and
processor registers.
- Testing assertions - The programmer may specify relations involving
program values, that must hold at certain positions in the program during
execution. eg., after an assignment of the form a = b - c , b
must be larger than a (provided c is positive).
- Detecting conditions - Suspend execution of the program whenever any user
defined condition involving the program variables and/or parameters is met.
Use of Source Program Symbols
Most debuggers allow the user to refer to the program information in terms of
symbols of source program, viz., variable names, subroutine names, parameter
names, field names of composite data structures (records), source program
line numbers (for specifying breakpoints), etc. Since an executable program
usually do not contain the mappings from source program symbols to target
program addresses, hence to be useful to a debugger, the compiler must
include such mappings in the executable program as additional information
(say debugger information). Most compilers support some
invocation-option for this purpose (eg. in Unix/Linux, the cc option
-g). Format of this information created by a compiler must be
understandable by a debugger.
Principle of Operation
The principle of operation of a debugger can be understood by considering a
simple view - from the specially compiled executable program the
debugger reads the debugger information into its own data structures. The
interactive features of the debugger is in the form of a module (that can be
invoked as a function call or through a software interrupt). The
interactive interface is invoked by the debugger once at the beginning. The
user can specify breakpoints through the interface and then tell the debugger
to start execution of the program to be debugged. The program will continue
till the first breakpoint is encountered. At that point the control is
transfered to the interactive interface. The programmer can carry out various
kinds of operations that are supported in that interface.
Setting of breakpoints
To make the user program stop at specified points, the debugger inserts
certain statement at those points that would transfer control to the
interactive interface module. These statements might be in the form of
function call instruction or software interrupt instruction. Usually in
programs compiled specifically for debugging, the compiler inserts NOP
instructions after the translation of each statement of the source
program. So the debugger can simply replace a NOP instruction by the function
call or interrupt instruction. In some cases where NOP instructions are not
there, the debugger replaces valid instructions of the program to insert its
own function call (or interrupt) instruction, but takes care to have the
original instructions executed whenever execution proceeds through that
point.