C H A P T E R  6

Debugging

OpenBoot provides debugging tools that include a Forth language decompiler, a machine language disassembler, register display commands, a symbolic debugger, breakpoint commands, a Forth source-level debugger, a high-level language patching facility, and exception tracing. This chapter describes the capabilities specified by IEEE Standard 1275-1994 .


Using the Forth Language Decompiler

The built-in Forth language decompiler can be used to recreate the source code for any previously-defined Forth word. The command:

ok see old-name

displays a listing of the source for old-name (without the source comments, of course).

A companion to see is (see) which is used to decompile the Forth word whose execution token is taken from the stack. For example:

ok ' old-name (see)

(see) produces a listing in a format identical to see .

ok see see
: see   
   ' ['] (see) catch if    
      drop 
   then  
; 
ok see (see)
defer (see) is 
: (f0018a44)   
   40 rmargin ! dup dup (f00189c4) dup (f0018944) (f0018980) (f0018658) 
   ??cr 
; 
ok f0018a44 (see)
: (f0018a44)   
   40 rmargin ! dup dup (f00189c4) dup (f0018944) (f0018980) (f0018658) 
   ??cr 
; 

The preceding listing shows that:

For words implemented in Forth assembler language, see displays a Forth assembler listing. For example, decompiling dup displays:

ok see dup
code dup 
f0008c98     sub         %g7, 8, %g7
f0008c9c     stx         %g4, [%g0 + %g7]
f0008ca0     ld          [%g5], %l0
f0008ca4     jmp         %l0, %g2, %g0
f0008ca8     add         %g5, 4, %g5


Using the Disassembler

The built-in disassembler translates the contents of memory into equivalent assembly language.

TABLE 6-1 lists commands that disassemble memory into equivalent opcodes.

TABLE 6-1 Disassembler Commands

Command

Stack Diagram

Description

+dis

( -- )

Continues disassembling where the last disassembly left off.

dis

( addr -- )

Begins disassembling at the specified address.


dis begins to disassemble the data content of any desired location. The system pauses when:

Disassembly can then be stopped or the +dis command can be used to continue disassembling at the location where the last disassembly stopped.

Memory addresses are normally shown in hexadecimal. However, if a symbol table is present, memory addresses are displayed symbolically whenever possible.


Displaying Registers

You can enter the user interface from the middle of an executing program as a result of a program crash, a user abort, or an encountered breakpoint. (Breakpoints are discussed in Breakpoints .) In all these cases, the user interface automatically saves all the CPU data register values in a buffer area. These values can then be inspected or altered for debugging purposes.

SPARC Registers

TABLE 6-2 lists the SPARC register commands.

TABLE 6-2 SPARC Register Commands

Command

Stack Diagram

Description

%g0 through %g7

( -- value )

Returns the value in the specified global register.

%i0 through %i7

( -- value )

Returns the value in the specified input register.

%l0 through %l7

( -- value )

Returns the value in the specified local register.

%o0 through %o7

( -- value )

Returns the value in the specified output register.

%pc %npc %y

( -- value )

Returns the value in the specified register.

%f0 through %f31

( -- value )

Returns the value in the specified floating point register.

.fregisters

( -- )

Displays the values in %f0 through %f31 .

.locals

( -- )

Displays the values in the i , l and o registers.

.registers

( -- )

Displays values in processor registers.

.window

( window# -- )

Same as w .locals ; displays the desired window.

ctrace

( -- )

Displays the return stack showing C subroutines.

set-pc

( new-value -- )

Sets %pc to new-value , and sets %npc to
( new-value +4).

to regname

( new-value -- )

Changes the value stored in any of the above registers.
Use in the form: new-value to regname.

w

( window# -- )

Sets the current window for displaying %i x, %l x, or %o x.


TABLE 6-3 SPARC V9 Register Commands

Command

Stack Diagram

Description

%fprs

%asi

%pstate

%tl-c

%pil

%tstate

%tt

%tba

%cwp

%cansave

%canrestore

%otherwin

%wstate

%cleanwin

( -- value )

Returns the value in the specified register.

.pstate

( -- )

Formatted display of the processor state register.

.ver

( -- )

Formatted display of the version register.

.ccr

( -- )

Formatted display of the %ccr register.

.trap-registers

( -- )

Displays trap-related registers.


The values of all of these registers are saved and can be altered with to . After the values have been inspected and/or modified, program execution can be continued with the go command. The saved (and possibly modified) register values are copied back into the CPU, and execution resumes at the location specified by the saved program counter.

If you change %pc with to , you should also change %npc . (It is easier to use set-pc , which changes both registers automatically.)

On SPARC V9 systems, if N is the current window, N-1 specifies the window for the caller, N-2 specifies the callers's caller, etc.


Breakpoints

The user interface provides a breakpoint capability to assist in the development and debugging of stand-alone programs. (Programs that run over the operating system generally do not use this OpenBoot feature, but use other debuggers designed to run with the operating system.) The breakpoint feature lets you stop the program under test at desired points. After program execution has stopped, registers or memory can be inspected or changed, and new breakpoints can be set or cleared. You can resume program execution with the go command.

TABLE 6-4 lists the breakpoint commands that control and monitor program execution.

TABLE 6-4 Breakpoint Commands

Command

Stack Diagram

Description

+bp

( addr -- )

Adds a breakpoint at the specified address.

-bp

( addr -- )

Removes the breakpoint at the specified address.

--bp

( -- )

Removes the most-recently-set breakpoint.

.bp

( -- )

Displays all currently set breakpoints.

.breakpoint

( -- )

Performs a specified action when a breakpoint occurs. This word can be altered to perform any desired action. For example, to display registers at every breakpoint, type: ['] .registers to .breakpoint . The default behavior is .instruction . To perform multiple behaviors, create a single definition which calls all desired behaviors, then load that word into .breakpoint .

.instruction

( -- )

Displays the address, opcode for the last-encountered breakpoint.

.step

( -- )

Performs a specified action when a single step occurs (see .breakpoint ).

bpoff

( -- )

Removes all breakpoints.

finish-loop

( -- )

Executes until the end of this loop.

go

( -- )

Continues from a breakpoint. This can be used to go to an arbitrary address by setting up the processor's program counter before issuing go .

gos

( n -- )

Executes go n times.

hop

( -- )

(Like the step command.) Treats a subroutine call as a single instruction.

hops

( n -- )

Executes hop n times.

return

( -- )

Executes until the end of this subroutine.

returnl

( -- )

Executes until the end of this leaf subroutine.

skip

( -- )

Skips (does not execute) the current instruction.

step

( -- )

Single-steps one instruction.

steps

( n -- )

Executes step n times.

till

( addr -- )

Executes until the given address is encountered. Equivalent to +bp go .


To debug a program using breakpoints, use the following procedure.

1. Load the test program into memory.

2. See Chapter 5 for more information. The register values are initialized automatically.

3. (Optional) Disassemble the downloaded program to verify a properly-loaded file.

4. Begin single-stepping the test program using the step command.

5. You can also set a breakpoint, then execute (for example, using the commands addr +bp and go ) or perform other variations.


Forth Source-Level Debugger

The Forth source-level Debugger allows single-stepping and tracing of Forth programs. Each step represents the execution of one Forth word.

The debugger commands are shown in TABLE 6-5 .

TABLE 6-5 Forth Source-level Debugger Commands

Command

Description

c

"Continue". Switches from stepping to tracing, thus tracing the remainder of the execution of the word being debugged.

d

"Down a level". Marks for debugging the word whose name was just displayed, then executes it.

u

"Up a level". Un-marks the word being debugged, marks its caller for debugging, and finishes executing the word that was previously being debugged.

f

Starts a subordinate Forth interpreter with which Forth commands can be executed normally. When that interpreter is terminated (with resume ), control returns to the debugger at the place where the f command was executed.

g

"Go." Turns off the debugger and continues execution.

q

"Quit". Aborts the execution of the word being debugged and all its callers and returns to the command interpreter.

s

"see". Decompiles the word being debugged.

$

Displays the address,len on top of the stack as a text string.

h

"Help". Displays symbolic debugger documentation.

?

"Short Help". Displays brief symbolic debugger documentation.

debug name

Marks the specified Forth word for debugging. Enters the Forth Source-level Debugger on all subsequent attempts to execute name . After executing debug , the execution speed of the system might decrease until debugging is turned off with debug-off . (Do not debug basic Forth words such as ".".)

(debug

Like debug except that (debug takes an execution token from the stack instead of a name from the input stream.

debug-off

Turns off the Forth Source-level Debugger so that no word is being debugged.

resume

Exits from a subordinate interpreter, and goes back to the stepper (See the f command in this table).

stepping

Sets "step mode" for the Forth Source-level Debugger, allowing the interactive, step-by-step execution of the word being debugged. Step mode is the default.

tracing

Sets "trace mode" for the Forth Source-level Debugger. Tracing enables the execution of the word being debugged, while showing the name and stack contents for each word called by that word.

<space-bar>

Executes the word just displayed and proceeds to the next word.


Every Forth word is defined as a series of one or more words that could be called "component" words. While debugging a specified word, the debugger displays information about the contents of the stack while executing each of the word's "component" words. Immediately before executing each component word, the debugger displays the contents of the stack and the name of the component word that is about to be executed.

In trace mode, that component word is then executed, and the process continues with the next component word.

In step mode (the default), the user controls the debugger's execution behavior. Before the execution of each component word, the user is prompted for one of the keystrokes specified in TABLE 6-5 .


Using patch and (patch)

OpenBoot provides the ability to change the definition of a previously compiled Forth word using high-level Forth language. While the changes will typically be made in the appropriate source code, the patch facility provides a means of quickly correcting errors uncovered during debugging.

patch reads the input stream for the following information:

For example, consider the following example in which the word test is replaced with the number 555 :

ok : patch-me  test 0 do  i . cr  loop ;
ok patch 555 test patch-me
ok see patch-me
: patch-me
	h# 555 0 do
		i . cr
	loop
;

When using patch , some care must be taken to select the right word to replace. This is especially true if the word you are replacing is used several times within the target word and the occurrence of the word that you want to replace is not the first occurrence within the target word. In such a case, some subterfuge is required.

ok : patch-me2  dup dup dup ( This third dup should be drop) ;
ok : xx dup ;
ok patch xx dup patch-me2
ok patch xx dup patch-me2
ok patch drop dup patch-me2
ok see patch-me2
: patch-me2
	xx xx drop
;

Another use for patch is the case where the word to be patched contains some functionality that needs to be completely discarded. In this case, the word exit should be patched over the first word whose functionality is to be eliminated. For example, consider a word whose definition is:

ok : foo good bad unneeded ;

In this example, the functionality of bad is incorrect and the functionality of unneeded should be discarded. A first attempt to patch foo might be:

ok : right this that exit ;
ok patch right bad foo 

on the expectation that the use of exit in the word right would prevent the execution of unneeded . Unfortunately, exit terminates the execution of the word which contains it, in this case right . The correct way to patch foo is:

ok : right this that ;
ok patch right bad foo 
ok patch exit unneeded foo 

(patch) is similar to patch except that (patch) obtains its arguments from the stack. The stack diagram for (patch) is:

( new-n1 num1? old-n2 num2? xt -- )

where:

For example, consider the following example in which we reverse the affect of our first patch example by replacing the number 555 with test :

ok see patch-me
: patch-me
	h# 555 0 do
		i . cr
	loop
;
ok ['] test false 555 true ['] patch-me (patch)
ok see patch-me
: patch-me
	test 0 do
		i . cr
	loop
;


Using ftrace

The ftrace command shows the sequence of Forth words that were being executed at the time of the last exception. An example of ftrace follows.

ok : test1 1 ! ; 
ok : test2 1 test1 ; 
ok test2 
Memory address not aligned
ok ftrace 
! 		 Called from test1 	at ffeacc5c
test1 		 Called from test2 	at ffeacc6a
(ffe8b574) 	Called from (interpret 	at ffe8b6f8
execute 			Called from catch 	at ffe8a8ba
	ffefeff0
	0	
	ffefebdc
catch 			 Called from (fload) 	at ffe8ced8
	0
(fload) 			Called from interact 	at ffe8cf74
execute 	Called from catch 	at ffe8a8ba
	ffefefd4
	0		
	ffefebdc
catch 		Called from (quit 	at ffe8cf98

In this example, test2 calls test1 , which tries to store a value to an unaligned address. This results in the exception: Memory address not aligned .

The first line of ftrace output shows the last command that caused the exception to occur. The next lines show locations from which the subsequent commands were being called.

The last few lines are usually the same in any ftrace output, because that is the calling sequence in effect when the Forth interpreter interprets a word from the input stream.