The debugger |
The first thing to remember, is save often. Save, save, and save again.
There are two types of debugging. The simplest way to trace a program is to have print statements. You can make use of the SWI 256 + ASCII to trace your way through a program. Here is a lame and trivial example:
STMFD R13!, {R0-R2, R14} SWI 256 + ASC("A") MOV R1, #1 SWI 256 + ASC("B") MOV R2, #2 SWI 256 + ASC("C") MUL R0, R1, R2 SWI 256 + ASC("D") LDMFD R13!, {R0-R2, PC}Here, we are simply multiplying two registers. When run, it will print
ABCD
to the
screen. Applied to a larger program, this method can help you follow code.
You can also drop in SWI 256 + 7
if you would rather a beep, but use this sparingly
as a beep in a stuck loop is annoying - to say the least!
However, sooner or later you'll be wanting to use the debugger built into RISC OS.
You are supposed to officially enter the debugger using the *Debug
command, but as
far as I can ascertain this just outputs 'Debug' before the * prompt. All the debugger commands
can be called from any OS_CLI handler (the command line, OSCLI, SYS OS_CLI, etc...).
The debugger accepts two types of parameter. You can either pass a memory address (a hex number,
with or without the ampersand), or a register name, like R12
. Often two addresses
may be given, the first address being the start and the second address being the end. The end
address may be absolute, or an offset. The offset is denoted by a +
before the
value.
You can use a negative offset, but it doesn't quite work correctly.
Here is an example of a debug session. This is the 'MemTest' program (try looking on arcade.demon.co.uk's filesearch?), and I want to get at it just as it starts...
*Load %.MemTest 8000 *MemoryI 8000 +A00 [...snip...] 0000806C : .... : 00000000 : ANDEQ R0,R0,R0 00008070 : ... : EF000010 : SWI OS_GetEnv 00008074 : . : E1A0C001 : MOV R12,R1 00008078 : . : E1A0A00C : MOV R10,R12 0000807C : .. : EB0000E6 : BL &0000841C [...snip...] *BreakSet 8078 *Go 8000 Application Space Memory Test: Version 1.00 made on 1st February 1988. Testing memory from &8000 to &Stopped at break point set at &00008078 Register dump (stored at &021A046C) is: R0 = 02102AB4 R1 = 000A8000 R2 = 02379FA4 R3 = 03806358 R4 = 021A0564 R5 = 0000000D R6 = 000000FF R7 = 00008000 R8 = 00000000 R9 = 00000000 R10 = 00000067 R11 = 021A0564 R12 = 000A8000 R13 = 0237A534 R14 = 83814024 R15 = 00008078 Mode USR flags set: nzcvif Debug*Memory R13 -40 Address : 7 6 5 4 B A 9 8 F E D C 3 2 1 0 : ASCII Data 0237A4F4 : 00000000 021633FF 02379F94 0237A334 : ....3..7.47. 0237A504 : 00000000 0237A5A0 01C01832 01C02000 : .... 7.2... . 0237A514 : 0215C964 00000000 00000001 239E7A80 : d..........z# 0237A524 : 00000000 00000000 00000000 639E7B80 : ............{cWhat we have done here is to set a 'breakpoint' at a chosen address. What happens is when a breakpoint is reached, the debugger halts the program, dumps the registers, and returns control to you. Here, you can examine/alter memory, set more breakpoints, and continue as you see fit.
You also see the benefit of a negative offset, you can examine stacked data. But, the deliberate error here is that RISC OS stacks are 'fully descending', they grow backwards from the top. Your data, however, may be stacked upwards. So you have some flexibility to play around, but notice that the offsets don't quite match up. We have asked for R13-&40 to R13. R13 is &237A534 and we got &237A4F4 (correct) to &237A524 (16 bytes off). Even so, it is useful to be able to go both directions.
*BreakSet <address|register>
*BreakSet 8078 *
*BreakList
*BreakList Address Old data 00008078 E1A0A00C *
*BreakClr [<address|register>]
*BreakClr Clear all breakpoints? [Y/N] All breakpoints cleared. *
*Continue
*Continue Continue from breakpoint set at &00008078 Execute out of line? [Y/N] <- program continues ->
*Memory [B] <address|register> [<address|register>]
*Memory 8000 Address : 3 2 1 0 7 6 5 4 B A 9 8 F E D C : ASCII Data 00008000 : EF000001 6C707041 74616369 206E6F69 : ...Application 00008010 : 63617053 654D2065 79726F6D 73655420 : Space Memory Tes 00008020 : 56203A74 69737265 31206E6F 2030302E : t: Version 1.00 00008030 : 6564616D 206E6F20 20747331 72626546 : made on 1st Febr 00008040 : 79726175 38393120 0D0A2E38 65540D0A : uary 1988.....Te 00008050 : 6E697473 656D2067 79726F6D 6F726620 : sting memory fro *
*MemoryI <address|register> [<address|register>]
*MemoryI 8000 +10 00008000 : EF000001 : ... : SWI "OS_WriteS" 00008004 : 6C707041 : Appl : LDCVSTL CP0,C7,[R0],#-&104 ; =260 00008008 : 74616369 : icat : STRVCBT R6,[R1],#-&369 ; =873 0000800C : 206E6F69 : ion : RSBCS R6,R14,R9,ROR #30 *
*MemoryA [B] <address|register> [<address|register>]
*MemoryA 8000 + 00008000 : ... : EF000001 : SWI "OS_WriteS" Enter new value: EF000011 + 00008000 : ... : EF000011 : SWI "OS_Exit" Enter new value: + 00008004 : Appl : 6C707041 : LDCVSTL CP0,C7,[R0],#-&104 ; =260 Enter new value: Escape *
The *Memory commands can take a third parameter. The regular form is:
*MemoryI <address> [[+|-]<length or second address>The extended form is:
*MemoryI <address> [[+]<offset>] [[+|-]<length or second address>
What this means is that instead of disassembling from 'address' to 'address + length' (or the second address), it will disassemble from 'address + offset' to '(address + offset) + length'. This comes into it's own when playing around in modules.
And finally...
*InitStore
*ShowRegs
*ShowRegs Register dump (stored at &0219D094) is: R0 = 02102B04 R1 = 000A8000 R2 = 02340F34 R3 = 03806358 R4 = 0234154C R5 = 0000000D R6 = 000000FF R7 = 00008000 R8 = 00000000 R9 = 00000000 R10 = 00000067 R11 = 0234154C R12 = 000A8000 R13 = 023414C4 R14 = 00000000 R15 = 00008078 Mode USR, flags set: nzcvif *
*Help DisassemblerFlags ==> Help on keyword DisassemblerFlags *DisassemblerFlags allows you to set various MemoryI and Debugger_Disassemble features. Available switches: -FDwithR13 use FD with R13, eg. STMDB R13 -> STMFD R13 -APCS use APCS-R register set -LFMstack use stack notation with LFM & SFM where possible -LFS use LFS and SFS in preference to LFM & SFM -QuoteSWIs put quotes around SWI names -UseDCD use DCD instead of 'Undefined instruction' -UseVDU use VDU x instead of SWI OS_WriteI+x -ANDEQasDCD use DCD instead of 'ANDEQ' and similar -UseADRL use ADRL (ADRX) instead of ADR + ADD/SUB (+ ADD/SUB) -UseADRW use ADRW, LDRW, STRW for R12m & [R12,#m] -LongMul append L to UMUL, SMUL, UMLA, SMLA -UseLDRL use LDRL instead of ADR/ADD/SUB + LDR -UseNOP use NOP for MOV R0,R0 and BRK for DCD &x6000010 -OldPSR use old CPSR/SPSR names -Wide disassemble for wide display -HSLO use HS and LO instead of CS and CC -Shift append comments of the form x<] *
To see this in action, we'll look at a bit of the DOSmount module, loaded at &8000
for our purposes (obviously inactive!).
A 'normal' disassembly would look something like:
*MemoryI 8078 +30 00008078 < E1A0A00C : . : MOV R10,R12 0000807C : E3A00006 : .. : MOV R0,#6 00008080 : E3A03F81 : ? : MOV R3,#&0204 ; =516 00008084 : EF02001E : ... : SWI XOS_Module 00008088 : 624F0038 : 8.Ob : ADRVS R0,&00008058 0000808C : 748C2000 : . t : STRVC R2,[R12],#0 00008090 : E8BD900E : . : LDMIA R13!,{R1-R3,R12,PC} 00008094 : E92D5000 : .P- : STMDB R13!,{R12,R14} 00008098 : E3A00007 : .. : MOV R0,#7 0000809C : E1A0200C : . : MOV R2,R12 000080A0 : EF02001E : ... : SWI XOS_Module 000080A4 : E8FD9000 : . : LDMIA R13!,{R12,PC}^ *
We can make it nicer by adding 'FD' style stacking and quoted SWIs with the command
*DisassemblerFlags -FDwithR13 1 -QuoteSWIs 1
, so then we will see:
*MemoryI 8078 +30 00008078 < E1A0A00C : . : MOV R10,R12 0000807C : E3A00006 : .. : MOV R0,#6 00008080 : E3A03F81 : ? : MOV R3,#&0204 ; =516 00008084 : EF02001E : ... : SWI "XOS_Module" 00008088 : 624F0038 : 8.Ob : ADRVS R0,&00008058 0000808C : 748C2000 : . t : STRVC R2,[R12],#0 00008090 : E8BD900E : . : LDMFD R13!,{R1-R3,R12,PC} 00008094 : E92D5000 : .P- : STMFD R13!,{R12,R14} 00008098 : E3A00007 : .. : MOV R0,#7 0000809C : E1A0200C : . : MOV R2,R12 000080A0 : EF02001E : ... : SWI "XOS_Module" 000080A4 : E8FD9000 : . : LDMFD R13!,{R12,PC}^ *
The final example variation is the -APCS option, which would give us:
*MemoryI 8078 +30 00008078 < E1A0A00C : . : MOV sl,ip 0000807C : E3A00006 : .. : MOV a1,#6 00008080 : E3A03F81 : ? : MOV a4,#&0204 ; =516 00008084 : EF02001E : ... : SWI "XOS_Module" 00008088 : 624F0038 : 8.Ob : ADRVS a1,&00008058 0000808C : 748C2000 : . t : STRVC a3,[ip],#0 00008090 : E8BD900E : . : LDMIA sp!,{a2-a4,ip,pc} 00008094 : E92D5000 : .P- : STMDB sp!,{ip,lr} 00008098 : E3A00007 : .. : MOV a1,#7 0000809C : E1A0200C : . : MOV a3,ip 000080A0 : EF02001E : ... : SWI "XOS_Module" 000080A4 : E8FD9000 : . : LDMIA sp!,{ip,pc}^ *
The debugger is incomplete - it only deals with USR mode, SWIs are assumed to return to the following address, there's no support for FP instructions, the text can sometimes overflow (you can see this in the example), but all in all it is a very good start. As an extra thing, Paul is releasing the source code, so you can either make your own extensions/corrections, or you can simply see how it is that debuggers work.
Paul's debugger differs quite a lot from Darren Salt's. Darren's, and Acorn's own, is a non-interactive debugger. You can play with memory, and you get breakpoints. That is expected of all debuggers. Paul's goes a stage further and allows you to single-step. There are no OSCLI commands. The interactive debugger is an 'environment', while the standard debugger is a 'tool'.
Click to download Debug v1.00, and as a bonus you'll get a code profiler (also with source).
If you use this software, please let Paul know what you think.
But, before we leave DDT, a caveat: Ever had this?
Your program acts like a baby, it screams and howls and messes itself in hard to tidy-up ways
while running 'native', but behaves perfectly under DDT?
This is usually a side effect of the 'sanitised' environment provided by the debugger. All memory
is cleared, etc. So you may have a variable with a quick check to abort if it is zero, but sadly
you forgot to initialise the variable before use, so the variable is validly non-zero, but it
points at who-knows-where?