This chapter explains how to debug machine language code; it includes the following topics:
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, $ta0... $ta3 | Temporaries (32 bit) Arguments (64 bit) |
$r12... $r15 | $t4... $t7, $t0... $t3 $ta0... $ta3 | Temporaries (32 bit) Temporaries (64 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 $d0, $d2, ... $d31 |
| Double precision floating point registers (32 bit) (64 bit) |
$f0, $f2, ... $f30 $f0, $f1, ... $f31 |
| Single precision floating point registers (32 bit) (64 bit) |
For registers with alternate names, the dbx $regstyle variable 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 $octints and $hexints variables. By default, dbx prints register values in decimal. You can set the output base to octal by setting the dbx $octints variable to a nonzero value. You can set the output base to hexadecimal by setting the dbx $hexints variable 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 commands:
(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 |
This assigns the value of expression to register. You must precede the name of the register with a dollar sign ($).
Example 7-1. assign command and register values
For example:
(dbx) assign $f0 = 3.14159 3.1415899999999999 (dbx) assign $t3 = 0x5a 0x5a |
By default, the assign register 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: prints the contents of the specified address, or disassembles the code for the instruction at the specified address. Repeat for a total of count addresses in increasing address. In other words, it works like an examine forward command. Format codes are listed in Table 7-2.
address ? count format: prints the contents of the specified address or, disassembles the code for the instruction at the specified address. Repeat for a total of count addresses in decreasing address. In other words, it works like an examine backward command. The format codes are listed in Table 7-2.
address / count L value mask: examines count 32-bit words in increasing addresses; prints those 32-bit words which, when ORed with mask, equals value. This command searches memory for specific patterns.
./: repeats the previous examine command with increasing address.
.?: repeats the previous examine command with decreasing address.
Table 7-2. Memory Display Format Codes
Format Code | Displays Memory in the Format |
---|---|
i | Print machine instructions (disassemble) |
d | |
D | |
dd | |
o | Print a 16-bit word in octal |
O | |
oo | Print a 64-bit word in octal |
x | Print a 16-bit word in hexadecimal |
X | |
xx | Print a 64-bit word in hexadecimal |
v | Print a 16-bit word in unsigned decimal |
V | Print a 32-bit word in unsigned decimal |
vv | Print a 64-bit word in unsigned decimal |
L | Same as X but used with val mask |
b | |
c | Print a byte as character |
s | Print a string of characters that ends in a null byte |
f | Print a single-precision real number |
g | Print a double-precision real number |
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” in Chapter 6, 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” in Chapter 6).
The following list describes the syntax of the stopi command:
stopi at: sets an unconditional breakpoint at the current instruction.
stopi at address: sets an unconditional breakpoint at address.
stopi in procedure: sets an unconditional breakpoint to stop execution upon entering procedure.
stopi [expression|variable]: inspects the value before executing each machine instruction and stops if the value has changed.
If expression is of type pointer, look at the data pointed to and watch until it changes. If the expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
stopi [expression|variable] at address: inspects the value when the program is at the given address and stops if the value has changed (for machine-level debugging).
If expression is of type pointer, look at the data pointed to and watch until it changes. If expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
stopi [expression|variable] in procedure: inspects the value at every machine instruction within procedure and stops if the value has changed.
If expression is of type pointer, look at the data pointed to and watch until it changes. If the expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
stopi if expression: evaluates expression before executing each instruction and stops if the expression is true. Note that execution is very slow if you choose this type of conditional breakpoint.
stopi at address if expression: evaluates expression at the given address and stops if the expression is true.
stopi in procedure if expression: evaluates expression at every instruction within a given procedure and stops if the expression is true.
stopi [expression1|variable] if expression2: tests both conditions before executing each machine instruction. Stops if both conditions are true.
If expression1 is of type pointer, look at the data pointed to and watch until it changes. If expression1 is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
stopi [expression1|variable] at address if expression2: tests both conditions at the given address (for machine-level debugging). Stops if both conditions are true.
If expression1 is of type pointer, look at the data pointed to and watch until it changes. If expression1 is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
stopi [expression1|variable] in procedure if expression2: tests the expression each time that the given variable changes within the given procedure.
If expression1 is of type pointer, look at the data pointed to and watch until it changes. If expression1 is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
Example 7-2. Linking with DSOs and stopi command
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 instruction. 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 the version of these commands that has in in it to ensure that the function breakpoint is hit.
The conti command continues execution of assembly code after a breakpoint has been hit. 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” in Chapter 6.
The syntax of the conti command is as follows:
conti [signal]: continues execution with the current instruction.
conti [signal] [at|to] address: sets a temporary breakpoint at the specified address, then resumes execution with the current instruction. When your program reaches the breakpoint at address, dbx stops your program and deletes the temporary breakpoint.
conti [signal] in procedure: sets a temporary breakpoint to stop execution upon entering the specified procedure, then resumes execution with the current instruction. When your program reaches the breakpoint in procedure, dbx stops your program and deletes the temporary breakpoint.
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 of which are described in “Managing Breakpoints, Traces, and Conditional Commands” in Chapter 6).
The following list describes the options and arguments to the tracei command:
tracei [expression|variable]: whenever the specified variable changes, dbx prints the old and new values of that variable (for machine-level debugging). Note that execution is very slow if you choose this type of trace.
If expression is of type pointer, look at the data pointed to and watch until it changes. If expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
tracei procedure: this command is equivalent to entering the trace procedure command. dbx prints the values of the parameters passed to the specified procedure whenever your program calls it. Upon return, dbx prints the return value.
tracei [expression|variable] at address: prints the value of variable whenever your program reaches the specified address (for machine-level debugging).
If expression is of type pointer, look at the data pointed to and watch until it changes. If expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
tracei [expression|variable] in procedure: whenever variable changes within the procedure that you specify, dbx prints the old and new values of that variable (for machine-level debugging).
If expression is of type pointer, look at the data pointed to and watch until it changes. If expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
tracei [expression1|variable] at address if expression2: p rints the value of variable whenever your program reaches the specified address and the given expression is true (for machine-level debugging).
If expression1 is of type pointer, look at the data pointed to and watch until it changes. If expression1 is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
tracei [expression1|variable] in procedure if expression2: whenever the variable changes within the procedure that you specify, dbx prints the old and new values of that variable, if the given expression is true (for machine-level debugging).
If expression1 is of type pointer, look at the data pointed to and watch until it changes. If expression1 is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
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” in Chapter 6. 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.
wheni if expression command-list: evaluates expression before executing each machine instruction. If expression is true, executes the command list.
wheni at address if expression command-list: evaluates expression at the given address. If expression is true, executes the command list.
wheni variable at address if expression command-list: tests both conditions at the given address. If the conditions are true, executes the command list (for machine-level debugging) .
If expression is of type pointer, look at the data pointed to and watch until it changes. If expression is not of type pointer, look at the 32 bits at that address (assume the expression evaluates to an address).
wheni variable in procedure if expression command-list: tests both conditions at every machine instruction within a procedure. If both conditions are true, executes the command list.
The 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:
nexti [integer] |
stepi [n] |
nexti [integer]: executes the specified number of machine instructions, stepping over procedures. If you do not provide an argument, nexti executes one instruction. If nexti encounters any breakpoints, even in procedures that it steps over, it immediately stops execution.
stepi (without arguments): single steps one machine instruction, stepping into procedures (as called by jal and jalr). If stepi encounters any breakpoints, it immediately stops execution.
stepi [n]: executes the specified number of machine instructions, stepping into procedures (as called by jal and jalr).
The value of the dbx $stepintoall variable affects the stepi and nexti commands just as it does the step and next commands. See “Stepping through Your Program” in Chapter 6, for more information.
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 runtime linker, rld(1), which becomes part of your program at run time.