Chapter 7. Debugging Machine Language Code

This chapter explains how to debug machine language code; it includes the following topics:

Examining and Changing Register Values

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

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

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

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.

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

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

Same as X but used 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” 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).

Syntax of the Stopi Command

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


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.


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.


Continuing Execution after a Machine-Level Breakpoint

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.

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

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

Stepping through Machine Code

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.