This chapter explains how to debug machine language code by:
Using dbx, you can examine and change the hardware registers during execution of your program. Table 7-1 lists the machine form of the register names and the alternate software names as defined in the include file regdef.h.
Table 7-1. Hardware Registers and Aliases
Register | Software Name | Description |
---|---|---|
$r0 | $zero | Always 0 |
$r1 | $at | Reserved for assembler |
$r2... $r3 | $v0... $v1 | Expression evaluations, function return values, static links |
$r4... $r7 | $a0... $a3 | Arguments |
$r8... $r11 | $t0... $t7 $a4... $a7, | Temporaries (32 bit) Arguments (64 bit) |
$r12... $r15 | $t4... $t7, | Temporaries (32 bit) |
$r16... $r23 | $s0... $s7 | Saved across procedure calls |
$r24... $r25 | $t8... $t9 | Temporaries |
$r26... $r27 | $k0... $k1 | Reserved for kernel |
$r28 | $gp | Global pointer |
$r29 | $sp | Stack pointer |
$r30 | $s8 | Saved across procedure calls |
$r31 | $ra | Return address |
$mmhi |
| Most significant multiply/divide result register |
$mmlo |
| Least significant multiply/divide result register |
$fcsr |
| Floating point control and status register |
$feir |
| Floating point exception instruction register |
$cause |
| Exception cause register |
$d0, $d2, ... $d30 |
| Double precision floating point registers |
$f0, $f2, ... $f30 |
| Single precision floating point registers |
For registers with alternate names, the dbx variable $regstyle controls which name is displayed when you disassemble code (as described in "Examining Memory and Disassembling Code"). If $regstyle is set to 0, then dbx uses the alternate form of the register name (for example, "zero" instead of "r0," and "t1" instead of "r9"); if $regstyle is anything other than 0, the machine names are used ("r0" through "r31").
Use the printregs command to print the values stored in all registers.
The base in which the register values are displayed depends on the values of the dbx variables $octints and $hexints. By default, dbx prints the register values in decimal. You can set the output base to octal by setting the dbx variable $octints to a nonzero value. You can set the output base to hexadecimal by setting the dbx variable $hexints to a nonzero value. If you set both $octints and $hexints to nonzero values, $hexints takes precedence.
To examine the register values in hexadecimal, enter the following:
(dbx) set $hexints = 1 (dbx) printregs r0/zero=0x0 r1/at=0x19050 r2/v0=0x8 r3/v1=0x100120e0 r4/a0=0x4 r5/a1=0xffffffad78 r6/a2=0xffffffad88 r7/a3=0x0 r8/a4=0x10 r9/a5=0x20 r10/a6=0x0 r11/a7=0xfbd5990 r12/t0=0x0 r13/t1=0x0 r14/t2=0x65 r15/t3=0x0 r16/s0=0x1 r17/s1=0xffffffad78 r18/s2=0xffffffad88 r19/s3=0xffffffaf70 r20/s4=0x0 r21/s5=0x0 r22/s6=0x0 r23/s7=0x0 r24/t8=0x0 r25/t9=0x10001034 r26/k0=0x0 r27/k1=0x20 r28/gp=0x1001a084 r29/sp=0xffffffaca0 r30/s8=0x0 r31/ra=0x1000110c mdhi=0x0 mdlo=0xe0 cause=0x24 pc=0x10001050 fpcsr=0x0 f0=0.0000000e+00 f1=0.0000000e+00 f2=0.0000000e+00 f3=0.0000000e+00 f4=0.0000000e+00 f5=0.0000000e+00 f6=0.0000000e+00 f7=0.0000000e+00 f8=0.0000000e+00 f9=0.0000000e+00 f10=0.0000000e+00 f11=0.0000000e+00 f12=0.0000000e+00 f13=0.0000000e+00 f14=0.0000000e+00 f15=0.0000000e+00 f16=0.0000000e+00 f17=0.0000000e+00 f18=0.0000000e+00 f19=0.0000000e+00 f20=0.0000000e+00 f21=0.0000000e+00 f22=0.0000000e+00 f23=0.0000000e+00 f24=0.0000000e+00 f25=0.0000000e+00 f26=0.0000000e+00 f27=0.0000000e+00 f28=0.0000000e+00 f29=0.0000000e+00 f30=0.0000000e+00 f31=0.0000000e+00 d0=0.000000000000000e+00 d1=0.000000000000000e+00 d2=0.000000000000000e+00 d3=0.000000000000000e+00 d4=0.000000000000000e+00 d5=0.000000000000000e+00 d6=0.000000000000000e+00 d7=0.000000000000000e+00 d8=0.000000000000000e+00 d9=0.000000000000000e+00 d10=0.000000000000000e+00 d11=0.000000000000000e+00 d12=0.000000000000000e+00 d13=0.000000000000000e+00 d14=0.000000000000000e+00 d15=0.000000000000000e+00 d16=0.000000000000000e+00 d17=0.000000000000000e+00 d18=0.000000000000000e+00 d19=0.000000000000000e+00 d20=0.000000000000000e+00 d21=0.000000000000000e+00 d22=0.000000000000000e+00 d23=0.000000000000000e+00 d24=0.000000000000000e+00 d25=0.000000000000000e+00 d26=0.000000000000000e+00 d27=0.000000000000000e+00 d28=0.000000000000000e+00 d29=0.000000000000000e+00 d30=0.000000000000000e+00 d31=0.000000000000000e+00 |
(Note that there are twice as many floating point registers with 64-bit programs.) You can also use the value of a single register in an expression by typing the name of the register preceded by a dollar sign ($).
For example, to print the current value of the program counter (the pc register), enter:
(dbx) printx $pc 0x10001050 |
In the same way you change the values of program variables, you can use the assign command to change the value of registers:
assign register = expression |
|
For example:
(dbx) assign $f0 = 3.14159 3.1415899999999999 (dbx) assign $t3 = 0x5a 0x5a |
By default, the assignregister command changes the register value in the current activation level, which is a typical operation. To force the hardware register to be updated regardless of the current activation level, use the $ set $framereg command.
The listregions command shows all memory regions, along with their sizes, in use by your program. This overview can be particularly useful if you want to know to what piece of your program a given data address corresponds. Since listregions shows the sizes of the memory regions, it allows you to easily determine the sizes of the data and stack regions of your program.
The forward slash (/) and question mark (?) commands allow you to examine the contents of memory. Depending on the format you specify, you can display the values as numbers, characters, or disassembled machine code. Note that all common forms of address are supported. Some unusual expressions may not be accepted unless enclosed in parentheses, as in (address)/count format.
The commands for examining memory have the following syntax:
address / count format |
| |||||||||||||||||||||||||||||||||||||||||
address ? count format |
| |||||||||||||||||||||||||||||||||||||||||
address / count L value mask |
| |||||||||||||||||||||||||||||||||||||||||
./ | Repeats the previous examine command with increasing address. | |||||||||||||||||||||||||||||||||||||||||
.? | Repeats the previous examine command with decreasing address. Table 7-2. Memory Display Format Codes
|
For example, to display 10 disassembled machine instructions starting at the current address of the program counter, enter:
(dbx) $pc/10i *[main:26, 0x400290] sw zero,28(sp) [main:27, 0x400294] sw zero,24(sp) [main:29, 0x400298] lw t1,28(sp) [main:29, 0x40029c] lw t2,32(sp) [main:29, 0x4002a0] nop [main:29, 0x4002a4] slt at,t1,t2 [main:29, 0x4002a8] beq at,zero,0x4002ec [main:29, 0x4002ac] nop [main:31, 0x4002b0] lw t3,28(sp) [main:31, 0x4002b4] nop |
To disassemble another 10 lines, enter:
(dbx) ./ [main:31, 0x4002b8] addiu t4,t3,1 [main:31, 0x4002bc] sw t4,28(sp) [main:32, 0x4002c0] lw t5,24(sp) [main:32, 0x4002c4] lw t6,28(sp) [main:32, 0x4002c8] nop [main:32, 0x4002cc] addu t7,t5,t6 [main:32, 0x4002d0] sw t7,24(sp) [main:33, 0x4002d4] lw t8,28(sp) [main:33, 0x4002d8] lw t9,32(sp) [main:33, 0x4002dc] nop |
To examine ten 32-bit words starting at address 0x7fffc754, and print those whose least significant byte is hexadecimal 0x19, enter:
(dbx) 0x7fffc754 / 10 L 0x19 0xff 7fffc758: 00000019 |
Consider a single-precision floating point array named array. You can examine the six consecutive elements, beginning with the fifth element, by entering:
(dbx) &array[4] / 6f 7fffc748: 0.2500000 0.2000000 0.1666667 0.1428571 7fffc758: 0.1250000 0.1111111 |
dbx allows you to set breakpoints while debugging machine code just as you can while debugging source code. You set breakpoints at the machine code level using the stopi command.
The conditional and unconditional versions of the stopi commands work in the same way as the stop command described in "Setting Breakpoints" with these exceptions:
The stopi command checks its breakpoint conditions on a machine-instruction level instead of a source-code level.
The stopi at command requires an address rather than a line number.
Each breakpoint is assigned a number when you create it. Use this number to reference the breakpoint in the various commands provided for manipulating breakpoints (for example, disable, enable, and delete, all described in "Managing Breakpoints, Traces, and Conditional Commands").
The syntax of the stopi command is:
If you link with a DSO, be careful when you use the stopi at command. For example, suppose you enter:
dbx()stopi at functionx
The breakpoint at functionx is hit only if the gp_prolog instruction is executed. (gp_prolog is a short sequence of instructions at the beginning of the routine.)
To avoid this problem, use the stopi in command:
dbx()stopi in functionx
If you really want to use stopi at, a safe alternative is to disassemble functionx and put the breakpoint after the gp_prolog. For more information on gp_prolog, see the MIPSpro Assembly Language Programmer's Guide.
The tracei at, wheni at, and conti at commands described in the following sections also follow this pattern. Use their "in" versions to ensure that the function breakpoint is hit.
The conti command continues executing assembly code after a breakpoint. As with the cont command, if your program stops because dbx catches a signal intended for your program, then dbx sends that signal to your program when you continue execution. You can also explicitly send a signal to your program when you continue execution. Signal processing and sending signals to your program is discussed in "Using Signal Processing".
The syntax of the conti command is:
See also "Linking With DSOs" for a description on using the conti in and conti at commands with DSOs.
The tracei command allows you to observe the progress of your program while debugging machine code, just as you can with the trace command while debugging source code. The tracei command traces in units of machine instructions instead of in lines of code.
Each trace is assigned a number when you create it. Use this number to reference the breakpoint in the various commands provided for manipulating breakpoints (for example, disable, enable, and delete, all described in "Managing Breakpoints, Traces, and Conditional Commands").
The syntax of the tracei command is:
See also "Linking With DSOs" for a description on using the tracei in and tracei at commands with DSOs.
Use the wheni command to write conditional commands for use in debugging machine code. The wheni command works in the same way as the when command described in "Writing Conditional Commands". The command list is a list of dbx commands, separated by semicolons. When the specified conditions are met, the command list is executed. If one of the commands in the list is stop (with no operands), then the process stops when the command list is executed.
See also "Linking With DSOs" for a description on using the wheni in and wheni at commands with DSOs.
The stepi and nexti commands allow you to step through machine code in much the same way you can with the step and next commands while debugging source code. The stepi and nexti commands step in units of machine instructions instead of in lines of code.
The formats of the nexti and stepi commands are:
The value of the dbx variable $stepintoall affects the stepi and nexti commands just as it does the step and next commands. See the section "Stepping Through Your Program" in Chapter 6 for a discussion.
If your program has DSOs, set the environment variable LD_BIND_NOW to 1 before you run your program. This forces complete run-time linking when your program starts. Otherwise, you could accidentally step into the run-time linker, rld(1), which becomes part of your program at run time.