The primary impact of this is that you type the arguments to a command first. All commands are documented in the summary and in the online help with a stack-effect comment. Briefly, these comments take the form of ( arg1 arg2 -- result ), meaning that the command expects to find two arguments on the stack and returns one result on the stack. For example, the disassemble command is documented as:
dis ( addr count -- )"dis" expects to find a start address and a count of instructions to disassemble on the stack as its arguments. It is used:
pc @ 10 dis"pc @" puts the contents of the program counter on the stack, "10" puts a literal 10 on the stack, and "dis" invokes the disassembly. If you do not specify enough arguments for a command you will be rewarded with a "stack underflow" message. Not to worry - just hit the enter key and you will be back at your prompt and can try again. More examples of stack-effect comments can be found in the FORTH intro.
If you are familiar with FORTH, you will quickly appreciate the power of a fully scriptable debugger.
If you do want to work in hexadecimal, just type the command "hex". Also, regardless of your defined base, numbers entered with a preceeding 0x will be interpreted as hex.
The 32-bit registers available to you are: d0..d7, a0..a6, fp (an alias for a6), usp, ssp, and pc. There is one 16-bit register: sr. If you are new to m68k assembly, there is a short introduction to the registers below.
As an example, you can view the contents of the d0 register with the command "d0 @ ." (do not include the quotes) or "d0 ?" (? is defined to mean "@ .". "d0" puts the address of the d0 variable on the stack, "@" (fetch) gets the contents of that variable, and "." prints it to the screen. (The exception is the status register, sr, which you fetch with the word "w@" because it is a 16-bit register.)
Similarly, you can set the contents of the d0 register. You can set d0 to 0 (zero) with the command "0 d0 !" (again, no quotes). As before, "d0" puts the address of the d0 variable on the stack, but "!" (store) sets the contents of that variable. (To set the value of the status register use the word "w!".)
One more example is in order. The debuffer can run scripts that inspect, act on, or modify the values of registers:
: check0 d0 @ 0= if ." Routine _foo returned FALSE." 1 d0 ! then ;This defines a word (check-foo) that inspects the value of d0 and, if it is 0, prints a message and sets the value to 1. This type of thing will come up again in the section on breakpoints, as it can be used to do fun things like setting watchpoints and conditional breakpoints.
That's all well and good, you say, but it seems like an awful lot of keystrokes for a really common operation like printing the value of a register. It is. The debuffer defines a number of softwords to make common operations easier to do. In the example above, you would really just type ".d0" (print d0) to see the contents of the d0 register.
The softwords for examining the registers are: .d0, .d1, .d2, .d3, .d4, .d5, .d6, .d7, .a0, .a1, .a2, .a3, .a4, .a5, .a6, .fp, .usp, .ssp, .pc, and .sr. Notice that you don't have to remember that sr is a special case (16-bits). There is also a word .regs that dumps all the registers. Since these words are all defined in FORTH you can change them without recompiling the application - even at runtime - to customize their behavior however you would like. For instance, you might want to reformat the output of .regs - it's your choice.
So, let's say we want to see the contents of the double-word eight bytes above the frame pointer. (This would be an argument passed to a subroutine. Learn more about the emulator stack below.) We can do this as follows:
fp @ 8 + ?long"fp @" fetches the value of the frame pointer (the a6 register), "8 +" adds eight, and "?long" prints the 32-bit value at that location. ?byte, ?word, and ?long are all softwords.
Similarly, there are three primitive words for modifying the contents of memory locations in the emulator: !byte, !word, and !long. Each of these takes a value and an address as arguments.
-1 fp @ 4 - !longThis sets the value of a 32-bit local variable to -1.
You can examine blocks of memory easily with the commands: da (dump ascii), db (dump bytes), dd (dump double-words), and dw (dump words). Each of these takes an address and a count as its arguments and produces an ascii or hexadecimal dump of the memory. For example:
a0 @ 20 db a0 @ 20 daEach of these commands displays the twenty bytes pointed to by the a0 register. You might choose da over db if you believe that a0 points to a string and want to see it in a readable format. "a0 @ 20 dw" dumps the twenty words (forty bytes) pointed to by a0.
da, dd, and dw are all defined in FORTH so you can customize their display quite easily. db is not currently defined in FORTH but that's only because it was easier in C++. You are still free to redefine db in FORTH and your definition will take precedence over the built-in.
The command "dis" also takes as its arguments an address and a count, but instead of a raw dump dis produces a listing of the next count instructions starting at the given address. So "pc @ 20 dis" disassembles the next 20 instructions starting with the current program counter.
"bt" (backtrace, not to be confused with all those other "b*" commands in the next section) displays a complete call history by walking the stack. bt is implemented entirely in FORTH. bt uses a debuffer primitive "ln" (list name) that prints the routine name associated with the address specified as its argument.
fp @ 4 + @long bpThis gets the caller's return address off the stack and uses it to set a breakpoint. (For this example, you could also use the predefined command "out" discussed in the next section.)
At this time, the emulator supports a maximum of five user-defined breakpoints. The debuffer will not let you try to define more than that. You can see what breakpoints you have set with the command "bl", which takes no arguments. bl lists each breakpoint with its breakpoint id in the first column. The breakpoint id is used as the argument to the commands "bc" (clear breakpoint), "bd" (disable breakpoint), and "be" (enable breakpoint).
bp takes as an optional argument a text string that can contain any command or sequence of commands you wish to execute when that breakpoint is hit. Continuing our earlier example:
fp @ 4 + @long bp .d0This command will break on exit from the current routine and print its return value. We could use this feature to set a conditional breakpoint and only break if that return value is 0.
: break0 @ if go then ; fp @ 4 + @long bp d0 break0go instructs the debuffer to resume execution. Alternatively, we could set a watchpoint that displayed the value of d0 without stopping.
fp @ 4 + @long bp .d0 goOr, we could ensure that the routine always returns 1.
fp @ 4 + @long bp 1 d0 ! go
Softwords
Many softwords are described elsewhere in this document. This section is intended to cover those miscellaneous words that are not documented elsewhere.
Any CPU will have some number of internal memory locations. Whereas memory outside the CPU is identified by an address, the memory in most CPUs is identified by name. These named memory locations are collectively called registers.
The contents of some registers are not arbitrary. That is to say, the contents of these registers are interpreted in specific ways either by the hardware, the operating system, or the programming language, so that these registers have some meaning.
The Motorola 68328 DragonBall microprocessor at the heart of your Palm computer has the following registers: d0, d1, d2, d3, d4, d5, d6, d7, a0, a1, a2, a3, a4, a5, a6, usp, ssp, pc, and sr. It interprets these registers in the following ways:
Registers d0..d7 are collectively data registers while registers a0..a6 are address registers. Some operations are allowed on data registers that are not allowed on address registers, and vice-versa.
Register usp is the user stack pointer. It contains the address of the top of the stack when the CPU is running in user mode. It does not seem to be used by PalmOS.
Register ssp is the system, or supervisor, stack pointer. It contains the address of the top of the stack when the CPU is not in user mode, which seems to be all the time under PalmOS.
Register pc is the program counter It contains the address of the next instruction to be executed by the CPU.
Register sr is the status register. The individual bits within the status register are assigned meanings and indicate (what else?) status information. Specifically:
Some of the data and address registers have conventional meanings under PalmOS that are not enforced by the hardware:
It is important to note that these last few are only conventions
and are not always followed. For instance, optimized routines may not
setup an explicit stack frame, or one programming language might choose
to always return values on the stack.
The Stack
The call stack is not to be confused with the argument stack in FORTH. FORTH is a stack-based language and maintains its own separate stack. You can, however, gain access to the call stack from within FORTH.
Routines frequently use a stack frame to provide easy access to arguments, return values, and local variables. In a stack frame, the register a6 is used to point to the base of the frame. The 32-bit value at this location is conventionally (and conveniently!) the old value of the a6 register. In other words, the first value in my stack frame is a pointer to my caller's stack frame. The next value, at "a6 @ 4 +" is the return address. This points to the next instruction to be executed in my caller's routine, upon completion of my routine. Above this, at "a6 @ 8 +" and higher, are the arguments to my routine.
At negative offsets from my stack frame are my local variables. So I might use "a6 @ 4 -" to get to one local variable, and "a6 @ 8 -" to get to another.