3.8 The Program Debugger
Debugging programs written at the assembly language level
Figure 3-4 Output from the nm command for SQUARES
L> nm bin/squares
0000000000000000 a *ABS*
60000000000007e0 ? _DYNAMIC
6000000000000798 ? _GLOBAL_OFFSET_TABLE_
6000000000000930 G _IO_stdin_used
6000000000000780 ? __CTOR_END__
6000000000000778 ? __CTOR_LIST__
6000000000000790 ? __DTOR_END__
6000000000000788 ? __DTOR_LIST__
6000000000000960 A __bss_start
6000000000000750 D __data_start
40000000000005c0 t __do_global_ctors_aux
40000000000004a0 t __do_global_dtors_aux
6000000000000960 B __dso_handle
w __gmon_start__
6000000000000940 G __ia64_app_header
6000000000000758 D __libc_ia64_register_backing_store_base
U __libc_start_main@@GLIBC_2.2
6000000000000960 A _edata
6000000000000968 A _end
4000000000000640 ? _fini
40000000000002b0 ? _init
4000000000000400 T _start
6000000000000750 W data_start
4000000000000590 t done
6000000000000938 g dtor_ptr
4000000000000520 t first
4000000000000520 T main
6000000000000760 d sq1
6000000000000768 d sq2
6000000000000770 d sq3
L>
Debugging aids are available with most programming environments; we shall use only the simplest such tools in command-line mode, the adb debugger for HP-UX and the gdb debugger for Linux and HP-UX. We present a subset of commands and techniques for exploration of our sample programs.
Preventing
The command-line
3.8.1 Capabilities of Debugger ProgramsA debugger allows you to execute your program interactively through the control capabilities of the debugger program itself. A typical debugger includes facilities such as the following:
Specific debugger commands are listed in Table 3-6. Both debuggers offer considerable built-in assistance through an interactive help command.
Usually the commands for a debugger may be abbreviated—e.g., simply
p
for
print
(
gdb
). The debugger may use different naming conventions for the hardware registers Gr
… Gr
127
and Fr
… Fr
127
. For instance, the
gdb
debugger expects
$r2
and
$f2
(Linux) or
$gr2
and
$fr2
(HP-UX), respectively, in contrast to
r2
and
f2
in the assembly language source file. The leading
$
character avoids conflict with
An address
ADDR
can be specified
The
3.8.2 Running SQUARES using gdb (Linux and HP-UX )
A debugger is most useful when it has access to the symbolic
Table 3-6. Selected Debugger Commands for Itanium Programming Environments
A successful assembling, linking, and
gdb
debugging session for the SQUARES program would proceed as
L> gcc -Wall -O0 -o bin/squares squares.s L> gdb bin/squares [messages deleted here] (gdb) break done Breakpoint 1 at 0x4000000000000590 (gdb) run Starting program: /home/user/bin/squares Breakpoint 1, 0x4000000000000590 in done () (gdb) p/x sq1 = 0x1 (gdb) p/x sq2 = 0x4 (gdb) p/x sq3 = 0x9 (gdb) q The program is running. Exit anyway? (y or n) y L> The debugger is instructed to pause execution at done . The run command initiates debugger-controlled execution of the program. When the instruction pointer reaches the address corresponding to done , the debugger returns to its interactive prompt, (gdb) .
The three squares should have been stored in memory. Now we can issue additional debugger commands to assess how the program has worked. Here we chose one of the simplest
The print command of gdb can print only the value of one evaluated expression at a time. If we had a lot of computed values, we could examine contiguous information units using the x command instead: (gdb) x/3g &sq1 0x6000...0760 <sq1>: 0x0000...0001 0x0000....0004 0x6000...0770 <sq3>: 0x0000...0009 (gdb)
We have used zero suppression (
...
) to make this example fit the book margins. Notice that the debugger agrees with the
nm
command on the addresses corresponding to the storage
3.8.3 Running SQUARES using adb (HP-UX)A successful assembling, linking, and adb debugging session for the SQUARES program would proceed as follows (spaces and tabs have been adjusted for a clearer presentation):
H> cc_bundled +DD64 -o bin/squares squares.s
H> adb bin/squares
adb> done:b
adb> :r
Process 735 Thread 120552 Execed
Breakpoint 1 set at address 0x4000000000000ad0
main + 0x70:
> adds r8=0,r0
nop.f 0
nop.b 0;;
Hit Breakpoint 1 at address 0x4000000000000ad0
adb> sq1/jx
sq1:
0x1
adb> sq2/jx
sq2:
0x4
adb> sq3/jx
sq3:
0x9
adb> q
H>
Note that some commands to
adb
begin with or contain a
3.8.4 Examples of Debugger CommandsWe present here some further illustrations of useful gdb commands. These scarcely scratch the surface of what can be accomplished using any reasonably versatile debugger. Watchpoint exampleWe could have used a watchpoint to determine when the SQUARES program has stored a value for sq3 . Here are suitable command gdb sequences:
L> gdb bin/squares
[messages deleted here]
(gdb) watch sq3
Hardware watchpoint 1: {<data variable, no debug info>} ...
(gdb) run
Starting program: /home/user/bin/squares
Hardware watchpoint 1: {<data variable, no debug info>} ...
Hardware watchpoint 1: {<data variable, no debug info>} ...
Hardware watchpoint 1: {<data variable, no debug info>} ...
Old value = 0
New value = 9
0x4000000000000581 in main ()
(gdb)
The debugger executes the program instructions, and pauses when the specified variable
sq3
has been modified. Although seemingly trivial here, watchpoints can be invaluable with a bigger program when you may have developed a psychological "blind spot" and cannot see how, when, or where a quantity is modified. Note, however, that the debugger may
Single-step example
We could have used
L> gdb bin/squares [messages deleted here] (gdb) display/i $pc (gdb) display/x $r20 (gdb) break main Breakpoint 1 at 0x4000000000000520 (gdb) break done Breakpoint 2 at 0x4000000000000590 (gdb) run Starting program: /home/user/bin/squares Breakpoint 1, 0x4000000000000520 in main () 2: /x $r20 = 0x2000000000044000 1: x/i $ip 0x4000...0520 <main>: [MMI] mov r21=1;; (gdb) stepi 0x4000000000000521 in main () 2: /x $r20 = 0x2000000000044000 1: x/i $ip 0x4000...0521 <main+1>: mov r22=2 (gdb) stepi 0x4000000000000522 in main () 2: /x $r20 = 0x2000000000044000 1: x/i $ip 0x4000...0522 <main+2>: nop.i 0x0;; (gdb) stepi 0x4000000000000530 in main () 2: /x $r20 = 0x2000000000044000 1: x/i $ip 0x4000...0530 <main+16>: [MMI] mov r20=1;; (gdb) stepi 0x4000000000000531 in main () 2: /x $r20 = 0x1 1: x/i $ip 0x4000...0531 <main+17>: addl r14=-56,r1 (gdb) stepi 0x4000000000000532 in main () 2: /x $r20 = 0x1 1: x/i $ip 0x4000...0532 <main+18>: nop.i 0x0;; (gdb) stepi 0x4000000000000540 in main () 2: /x $r20 = 0x1 1: x/i $ip 0x4000...0540 <main+32>: [MMI] st8 [r14]=r20;; (gdb) stepi 0x4000000000000541 in main () 2: /x $r20 = 0x1 1: x/i $ip 0x4000...0541 <main+33>: add r21=r22,r21 (gdb) stepi 0x4000000000000542 in main () 2: /x $r20 = 0x1 1: x/i $ip 0x4000...0542 <main+34>: nop.i 0x0;; (gdb) stepi 0x4000000000000550 in main () 2: /x $r20 = 0x1 1: x/i $ip 0x4000...0550 <main+48>: [MMI] add r20=r21,r20;; (gdb) stepi 0x4000000000000551 in main () 2: /x $r20 = 0x4 1: x/i $ip 0x4000...0551 <main+49>: addl r14=-48,r1 (gdb) cont Continuing. Breakpoint 2, 0x4000000000000590 in done () 2: /x $r20 = 0x9 1: x/i $ip 0x4000...0590 <done>: [MMI] mov r8=r0;; (gdb) Here we have asked the debugger to step through the program from main onwards and print out both the current value in register r20 and the mnemonic form of the next instruction indicated by the instruction pointer. When we saw r20 change from 1 to 4, we then ceased single-stepping and allowed the program to continue until the next interrupting event, which was the breakpoint set at done . We do not need to retype the stepi command each time because gdb responds to pressing the return key on an empty line by repeating the most recent command. Program disassembly
The capability of a debugger's
print
command to interpret stored instructions back into
(gdb) disas main done Dump of assembler code from 0x4000...0520 to 0x4000...0590: 0x4000000000000520 <main>: [MMI] mov r21=1;; 0x4000000000000521 <main+1>: mov r22=2 0x4000000000000522 <main+2>: nop.i 0x0;; 0x4000000000000530 <main+16>: [MMI] mov r20=1;; 0x4000000000000531 <main+17>: addl r14=-56,r1 0x4000000000000532 <main+18>: nop.i 0x0;; 0x4000000000000540 <main+32>: [MMI] st8 [r14]=r20;; 0x4000000000000541 <main+33>: add r21=r22,r21 0x4000000000000542 <main+34>: nop.i 0x0;; 0x4000000000000550 <main+48>: [MMI] add r20=r21,r20;; 0x4000000000000551 <main+49>: addl r14=-48,r1 0x4000000000000552 <main+50>: nop.i 0x0;; 0x4000000000000560 <main+64>: [MMI] st8 [r14]=r20;; 0x4000000000000561 <main+65>: add r21=r22,r21 0x4000000000000562 <main+66>: nop.i 0x0;; 0x4000000000000570 <main+80>: [MMI] add r20=r21,r20;; 0x4000000000000571 <main+81>: addl r14=-40,r1 0x4000000000000572 <main+82>: nop.i 0x0;; 0x4000000000000580 <main+96>: [MFB] st8 [r14]=r20 0x4000000000000581 <main+97>: nop.f 0x0 0x4000000000000582 <main+98>: nop.b 0x0;; End of assembler dump. (gdb) This information is equivalent to a segment of an assembly listing (Figure 3-2) for the instructions from main up to, but not including, done . The notations [MMI] and [MFB] refer to the templates for instruction bundles. We will discuss those later in this book. Other capabilities
Symbolic debuggers usually offer many additional capabilities, but these tend to
The capabilities and features of the adb debugger furnished with a basic installation of HP-UX are more limited than those of gdb . In particular, it lacks a way to inspect the contents of just one or a select few registers. To have used the ra , r , and f commands for showing register contents in examples like those in this section would have produced voluminous output ill-suited for presentation in a book. |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||