Chapter 7. Debugging Machine Language Code

This chapter explains how to debug machine language code by:

Examining and Changing Register Values

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 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").

Printing Register Values

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

Changing Register Values

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 


Assigns the value of expression to register. You must precede the name of the register with a dollar sign ($).

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.

Examining Memory and Disassembling Code

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, an "examine forward" command. The 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, 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

print a 16-bit word in signed decimal

D

print a 32-bit word in signed decimal

dd

print a 64-bit word in signed decimal

o

print a 16-bit word in octal

O

print a 32-bit word in octal

oo

print a 64-bit word in octal

x

print a 16-bit word in hexadecimal

X

print a 32-bit word in hexadecimal

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

like X but use with val mask

b

print a byte in octal

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

Setting Machine-Level Breakpoints

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").

Syntax of the stopi Command

The syntax of the stopi command is:

stopi at 

Sets an unconditional breakpoint at the current instruction.

stopi at address 


Sets an unconditional breakpoint at the specified address.

stopi in procedure 


Sets an unconditional breakpoint to stop execution upon entering the specified procedure.

stopi [expression|variable]  


Inspects the value before executing each machine instruction and stops if the value has changed.

If the 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 the 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] in procedure  


Inspects the value at every machine instruction within a given procedure and stops if the value has changed.

If the 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 the 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 the expression at the given address and stops if the expression is true.

stopi in procedure if expression 


Evaluates the 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).


Note: When you stop execution because of a machine-level breakpoint set by one of the stopi in commands, a where command at the point of stop may yield an incorrect stack trace. This is because the stack for the function is not completely set up until several machine instructions have been executed. dbx attempts to account for this, but is sometimes unsuccessful.


Linking With DSOs

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.

Continuing Execution After a Machine-Level Breakpoint

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:

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.

See also "Linking With DSOs" for a description on using the conti in and conti at commands with DSOs.

Tracing Execution at the Machine Level

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:

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 the 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).

tracei procedure 


This command is equivalent to entering traceprocedure. 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 the variable whenever your program reaches the specified address. (For machine-level debugging.)

If the 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).

tracei [expression|variable] in procedure  


Whenever the variable changes within the procedure that you specify, dbx prints the old and new values of that variable. (For machine-level debugging.)

If the 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).

tracei [expression1|variable] at address if expression2  


Prints the value of the 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).

See also "Linking With DSOs" for a description on using the tracei in and tracei at commands with DSOs.

Writing Conditional Commands at the Machine Level

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.

wheni if expression {command-list} 


Evaluates the expression before executing each machine instruction. If the expression is true, executes the command list.

wheni at address if expression {command-list} 


Evaluates the expression at the given address. If the expression is true, executes the command list.

wheni in procedure if expression {command-list} 


Evaluates the expression in the given procedure. If the 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 the 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).

wheni variable in procedure if expression {command-list} 


Tests both conditions at every machine instruction within a given procedure. If they are true, executes the command list.

See also "Linking With DSOs" for a description on using the wheni in and wheni at commands with DSOs.

Stepping Through Machine Code

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:

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 

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 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.