18 Machine-Level Debugging

The Ladebug debugger lets you debug your programs at the machine- code level as well as at the source-code level. Using debugger commands, you can examine and edit values in memory, print the values of all machine registers, and step through program execution one machine instruction at a time.

Only those users familiar with machine-language programming and executable-file-code structure will find low-level debugging useful.

18.1 Examining Memory Addresses

You can examine the value contained at an address in memory as follows:

18.1.1 Using the <examine address> Command

The <examine address> command has two main syntaxes. The following syntax prints a range of addresses by specifying the beginning and end of the range:

start_address, end_address / mode

If a symbol precedes the slash (/) in an address expression, you may need to enclose the expression in parentheses. For example:

(ladebug) ($pc), ($pc+12) / i

The following command syntax prints a range of addresses by specifying the beginning address and the total number of memory locations display:

start_address / count mode

You can enter memory addresses in decimal or in hexadecimal by preceding the number with 0x. The mode variable determines how the values are displayed. Table 18-1 lists the valid modes.

Table 18-1 Valid Memory Display Modes

Mode  Description 
d   Print a short word in decimal 
u   Print a short word in unsigned decimal 
D   Print a long word in decimal 
U   Print a long word in unsigned decimal 
o   Print a short word in octal 
O   Print a long word in octal 
x   Print a short word in hexadecimal 
X   Print a long word in hexadecimal 
b   Print a byte in hexadecimal 
c   Print a byte as a character 
s   Print a string of characters (a C-style string that ends in null) 
f   Print a single-precision real number 
g   Print a double-precision real number 
i   Disassemble machine instructions 

Example 18-1 shows how to disassemble a range of memory.

Example 18-1 Disassembling Values Contained in a Range of Addresses

(ladebug) 0x120001180, 0x120001185 / i
 [main:4, 0x120001180]  addq    zero, 0x1, t0

 [main:4, 0x120001184]  stl     t0, 24(sp)

(ladebug) 0x120001180 / 2 i
 [main:4, 0x120001180]  addq    zero, 0x1, t0

 [main:4, 0x120001184]  stl     t0, 24(sp)
(ladebug)

In this example, the same range of addresses was accessed using start_address command in both the end_address syntax and the / count syntax.

18.1.2 Using Pointer Arithmetic

You can use C and C++ pointer-type conversions to display the contents of a single address in decimal. Using the print command, the syntax is as follows:

print *(int *)(address)

Using the same pointer arithmetic, you can use the assign command to alter the contents of a single address. Use the following syntax:

assign *(int *)(address) = value

Example 18-2 shows how to use pointer arithmetic to examine and change the contents of a single address.

Example 18-2 Using Pointer Arithmetic to Display and Change Values in Memory

(ladebug) print *(int*)(0x10000000)
4198916

(ladebug) assign *(int*)(0x10000000) = 4194744

(ladebug) print *(int*)(0x10000000)
4194744
(ladebug)

18.2 Examining Machine-Level Registers

The printregs command prints the values of all machine-level registers. The registers displayed by the debugger are machine dependent. The values are in decimal or hexadecimal, depending on the value of the $hexints variable (the default is 0, decimal). The register aliases are shown; for example, $r1 [$t0] .

Example 18-3 shows Digital UNIX Alpha machine-level registers.

Example 18-3 Printing Machine Registers on the Digital UNIX Alpha Platform

(ladebug) printregs
$r0  [$v0]  = 10                        $r1  [$t0]  = 1
$r2  [$t1]  = 4831844048                $r3  [$t2]  = 5368719424
$r4  [$t3]  = 0                         $r5  [$t4]  = 0
$r6  [$t5]  = 4396972783304             $r7  [$t6]  = 2
$r8  [$t7]  = 10                        $r9  [$s0]  = 337129856
$r10 [$s1]  = 337127744                 $r11 [$s2]  = 4396973344608
$r12 [$s3]  = 0                         $r13 [$s4]  = 5368847640
$r14 [$s5]  = 5368753616                $r15 [$s6]  = 20
$r16 [$a0]  = 1                         $r17 [$a1]  = 4831835496
$r18 [$a2]  = 4831835512                $r19 [$a3]  = 4831835848
$r20 [$a4]  = 4396981193976             $r21 [$a5]  = 5
$r22 [$t8]  = 9                         $r23 [$t9]  = 9
$r24 [$t10] = 4831842472                $r25 [$t11] = 1648
$r26 [$ra]  = 4831842828                $r27 [$t12] = 4831842912
$r28 [$at]  = 4396981208928             $r29 [$gp]  = 5368742064
$r30 [$sp]  = 4831835408                $r31 [$zero]= 4831842928
$f0         = 0.1                       $f1         = 0
$f2         = 0                         $f3         = 0
$f4         = 0                         $f5         = 0
$f6         = 0                         $f7         = 0
$f8         = 0                         $f9         = 0
$f10        = 0                         $f11        = 0
$f12        = 0                         $f13        = 0
$f14        = 2.035550460865936e-320    $f15        = 4120
$f16        = 0                         $f17        = 0
$f18        = 0                         $f19        = 0
$f20        = 0                         $f21        = 0
$f22        = 0                         $f23        = 0
$f24        = 0                         $f25        = 0
$f26        = 0                         $f27        = 0
$f28        = 0                         $f29        = 0
$f30        = 0                         $f31        = 0
$pc         = 0x120001270
(ladebug)

18.3 Stepping at the Machine Level

The stepi and nexti commands let you step through program execution incrementally, like the step and next commands described in Chapter 9. The stepi and nexti commands execute one machine instruction at a time, as opposed to one line of source code. Example 18-4 shows stepping at the machine-instruction level.

Example 18-4 Stepping Through Program Execution One Machine Instruction at a Time

(ladebug) stop in main
[#1: stop in main ]

(ladebug) run
[1] stopped at [main:4 0x120001180]
      4     for (i=1 ; i<3 ; i++) {

(ladebug) stepi
stopped at [main:4 0x120001184] stl     t0, 24(sp)
(ladebug) <Return>
stopped at [main:5 0x120001188] ldl     a0, 24(sp)

(ladebug) <Return>
stopped at [main:5 0x12000118c] ldq     t12, -32664(gp)

(ladebug) <Return>
stopped at [main:5 0x120001190] bsr     ra,

(ladebug) <Return>
stopped at [factorial:12 0x120001210]   ldah    gp, 8192(t12)

(ladebug)

At the machine-instruction level, you can step into, rather than over, a function's prolog. While within a function prolog, you may find that the stack trace, variable scope, and parameter list are not correct. Stepping out of the prolog and into the actual function updates the stack trace and variable information kept by the debugger.

Single-stepping through function prologs that initialize large local variables is slow. As a workaround, use the next command.