3.8 The Program Debugger

Debugging programs written at the assembly language level presents a challenge to the programmer, in part because of the effort required to insert temporary diagnostics.

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 bugs and logic flaws through careful design and reasoning is a laudable goal for any programmer. Nevertheless, few final programs are developed without passing through an awkward debugging phase to fix the inevitable errors. Learning to use at least the simplest capabilities of a debugger can greatly increase productivity.

The command-line debuggers provide interactive environments that will allow you to experiment with your programs as you develop them. We shall illustrate how to use the debugger for obtaining simple formatted output.

3.8.1 Capabilities of Debugger Programs

A 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:

  • State examination and modification. You can examine variables and change their values at will. This can save development time, since you can try a simple modification without editing the source file.

  • Execution control. You can restrain your program from runaway execution by stepping along, one or a few statements at a time. You can modify the flow of execution by skipping instructions, by branching to a different point in the program, or by calling procedures.

  • Breakpoints and watchpoints. You can define a breakpoint at a specific statement where execution should be paused in order to allow you to examine registers or memory. You can define a watchpoint for a variable in order to pause automatically whenever that variable is modified.

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 Gr0 … Gr127 and Fr0 … Fr127. 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 user-defined symbols that begin with the letters r, g, or f.

An address ADDR can be specified numerically, by the name of a general register containing the address, or by the C-style &SYMB where SYMB is a symbolic address. The address ADDR can also be specified as an expression containing any of these.

The next two sections illustrate some gdb and adb debugger commands. Study most carefully the one that corresponds to the facilities to which you have access.

3.8.2 Running SQUARES using gdb (Linux® and HP-UX®)

A debugger is most useful when it has access to the symbolic names used in a program. The gcc assembler and linker include basic symbolic information in the executable file by default. A good strategy is to use break commands that ask the debugger to pause when execution reaches certain labeled points in the program.

Table 3-6. Selected Debugger Commands for Itanium Programming Environments

gdb

adb

Action taken

Examining and modifying memory locations

x/ngf &SYMBOL

ADDR/njF

Displays n double-precision values

x/ngx &SYMBOL

ADDR/njx

Displays n quad word values (hexadecimal)

x/ngd &SYMBOL

ADDR/njd

Displays n quad word values (decimal)

x/nwx &SYMBOL

ADDR/ngx

Displays n double word values (hexadecimal)

x/nwd &SYMBOL

ADDR/ngd

Displays n double word values (decimal)

x/nhx &SYMBOL

ADDR/nex

Displays n word values (hexadecimal)

x/nhd &SYMBOL

ADDR/ned

Displays n word values (decimal)

x/nbx &SYMBOL

ADDR/nbx

Displays n byte values (hexadecimal)

x/nbd &SYMBOL

ADDR/nbd

Displays n byte values (decimal)

x/ni &SYMBOL

ADDR/ni

Interprets as n (gdb) or 3 n (adb) instructions

x/nc &SYMBOL

ADDR/nc

Shows as a string of n characters

x/s &SYMBOL

ADDR/s

Shows a null-terminated string

set {long} &SYMBOL=EXP

Changes a stored quad word value

Examining and modifying variables and registers

print/x EXP

 

Prints evaluated expression (hexadecimal)

print/d EXP

 

Prints evaluated expression (decimal)

set REG=EXP

 

Changes a register value

 

ra or r or f

Prints all (ra) or general (r) or floating (f) registers

Flow control

run

:r

Starts execution of the program

stepi n or si n

,n:s

Executes only n machine instructions

continue

:c

Resumes execution to the next breakpoint

quit

q

Exits from the debugger

Breakpoints and watchpoints

break LABEL

LABEL:b

Puts a breakpoint at the labeled line

watch VAR

 

Sets a watchpoint for modification of the variable

delete n

LABEL:d

Removes a breakpoint or watchpoint

A successful assembling, linking, and gdb debugging session for the SQUARES program would proceed as follows (spaces and tabs have been adjusted for a clearer presentation):

 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 $1 = 0x1 (gdb) p/x sq2 $2 = 0x4 (gdb) p/x sq3 $3 = 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 methods, the print command, abbreviated as p. We see the expected answers for the squares of the first three integers.

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 cells declared in the SQUARES program.

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 colon. A breakpoint is set at the line marked with the label done. The program is then allowed to run until execution reaches the label done. The printed results are clearly correct for an algorithm that computes a table of squares of the first few integers.

3.8.4 Examples of Debugger Commands

We 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 example

We 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 operate much more slowly with watchpoints than with breakpoints.

Single-step example

We could have used single-stepping to follow the progress of SQUARES after noting that register r20 successively takes on the values of the computed squares:

 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 human-readable mnemonic form is tantamount to being able to disassemble, or reverse-engineer, the program. The gdb debugger has a useful synonym for print/i that takes two labels (or related expressions) to specify the start and end points for display of program instructions:

 (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 differ from environment to environment, and depend upon particular capabilities of the hardware and operating system. We have introduced enough material to demonstrate how to obtain printed output without taking on program segmentation, the use of system routines, or other complications at this stage.

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.



ItaniumR Architecture for Programmers. Understanding 64-Bit Processors and EPIC Principles
ItaniumR Architecture for Programmers. Understanding 64-Bit Processors and EPIC Principles
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net