BreakAid v0.01 ======== by Rick Murray Prcis ------ There are generally three ways of debugging applications within RISC OS. The first is to use DDT, the interactive debugger. This presupposes you have the Desktop Development Environment, build a "debug" version of the application (containing special debug data), and then step into the program and set up breakpoints etc. For complicated problems, this approach may be invaluable, however it is rather tedious. DDT also suffers from two fundamental problems inherent to how it works. The first is that the "debug" code has all of the optimisations disabled. This is unlikely to make much difference if you're stepping through the program in lines of C code, however should you try to compare the assembler you will find it can often be quite a bit different to the normal compiled version. Sometimes this may be important in how a bug shows itself. Secondly, DDT provides a "sanitised" environment, which means that a piece of code that expects a variable to be initialised as zero (but isn't) will work perfectly within DDT but fail in real life. The second method is to use the built-in Debugger module. While this allows for setting up proper breakpoints and showing memory contents and registers, it is remarkably basic - you cannot, for instance, change the contents of a register or the flags, which seems like a bizarre omission to me. The third method is the one favoured since the dawn of affordable computing. You simply insert commands to output something to the screen. In RISC OS, this can be achieved with a simple SWI call that corrupts no registers - SWI (256 plus the ASCII code), for instance: SWI 256 + 'A' SWI 256 + 'B' SWI 256 + 'C' And then you just run the bugged code and see how much gets printed before it goes wrong. Keep playing around like that and you'll soon pinpoint the exact line that is failing. I've been programming since the BBC Micro back in the mid eighties and I feel that this method is underrated! Problem is, while outputting stuff to the screen can narrow things down, it can't tell you what is actually going on - it requires inserted code to show the values of registers, for instance, and being able to change them is non-trivial. Enter a fourth option. BreakAid. What BreakAid does ------------------ BreakAid leverages the barely-used OS "breakpoint" system. This is the SWI "OS_BreakPt" which has no purpose other than to call the "breakpoint handler". This is not a breakpoint in the same sense as the Debugger module or DDT, you cannot specify instructions to break at, you must explicitly call the OS_BreakPt SWI when you want the trap to be taken. And when this SWI is called? BreakAid will kick into action and take over from your program, offering you a dump of the registers followed by a menu of choices. Unlike the Debugger, you cannot modify the executing program (or memory) but you can see what is there - either by disassembling around PC (the current instruction) or by looking at a dump of memory. More importantly, you can see what the registers are. You can modify the registers, and you can modify the flags. It is also possible to alter the program execution by modifying the value in R15. To give you an example, your program causes an "abort on data transfer". You've tracked it down to an LDM instruction. Nothing in the code looks untoward, however with the aid of BreakAid you can clearly see that the base register for the LDM is &90549439 (2,421,462,073). Well, not only is that not a word aligned address, it is also a value that is WAY out of range. So then you need to work back to see what's going wrong in calculating the base address. Using BreakAid in an assembler program -------------------------------------- To use BreakAid in an assembler program, insert these two lines at the part you wish to examine: SWI &79A40 ; XBreakAid_Attach SWIVC &17 ; OS_BreakPt The first SWI will tell BreakAid to "attach" itself as the BreakPt handler. This is necessary as RISC OS has a habit of resetting this to the system breakpoint handler ALL the time. However since we are calling the X form of the SWI, we can double up to test if the BreakAid module is even loaded. If it is not, an error will be returned (V set), so we can then call the OS_BreakPt SWI only if BreakAid is available. Using BreakAid in a C program ----------------------------- C programs can also be looked at with BreakAid. If you are using the DDE, you can use the following to invoke BreakAid: __asm { SWI 0x79A40, {}, {PSR}, {} // XBreakAid_Attach SWIVC 0x17, {}, {}, {} // OS_BreakPt } For other compilers (such as gcc) you will need to refer to the documentation to see if it is possible to inline assembler. It is, obviously, harder to poke around a C program. Using BreakAid in a BASIC program --------------------------------- Don't. No, seriously. Don't. BASIC is a stack-based interpreter, which also happens to transfer values passed into a SYS call to R0-R9. And if you don't provide any values? It'll set them to zero. You could, alternatively, try to sidestep that with a short bit of assembler to call the two SWIs given above, using the CALL command. And you'll see that the registers bear utterly no resemblance to anything (when I tried, most of them were set to '1'). So, technically BreakAid can be used with BASIC. However, in reality there's no point doing so. BreakAid's SWIs --------------- BreakAid_Attach &59A40 This SWI will ensure that BreakAid is the current BreakPt handler. You should call this prior to calling OS_BreakPt. It is safe to call this multiple times. BreakAid_Remove &59A41 This SWI restores the previous BreakPt handler. It should be called when your application exits. Caveats ------- 1. BreakAid is currently intended for NON multitasking programs. This is because it outputs to the VDU, which might have odd effects when used within the Desktop world. [this will be looked at soon] 2. BreakAid is only intended for examining USR mode code. It cannot be used in SVC mode for three reasons: a. Since SWIs themselves run in SVC mode, R14 *will* be trashed. b. The OS_BreakPt code in RISC OS flattens the SVC stack. In English, that means R13 is reset to its default position and anything stacked is thus discarded. c. A bug in RISC OS (fixed in June 2016, a mere 14 years after the release of the Iyonix!) meant that calling OS_BreakPt in SVC mode would cause an exception. 3. BreakAid is intended for use with application code. Some of the functionality restricts addresses in the range &8000-appslot. Using BreakAid -------------- When your application calls OS_BreakPt, your program will be paused, the current registers will be displayed, and a menu will appear: Breakpoint at &0000801C: R0 = &00008049 R1 = &00408000 R2 = &203C7BE4 R3 = &00000068 R4 = &00008000 R5 = &3004575C R6 = &00000000 R7 = &00000068 R8 = &0000000A R9 = &300482F0 R10 = &FA208000 R11 = &FA207FA0 R12 = &80000000 R13 = &00407FF0 R14 = &FC04D78C PC = &0000801C Flags: nzCv (&20000110) [C]ontinue, [Q]uit, [D]ump registers, change [R]egister, sys[I]nfo change [F]lags, display [M]emory, display [S]tack, PC dis[A]ssembly Choice? Continue -------- This will restore the state of your program and jump to the instruction following the call to OS_BreakPt. Quit ---- This will call OS_Exit to immediately terminate your program. Note that this is not a tidy exit, so if there are files currently open, they will remain open. Dump registers -------------- This dumps the registers. Useful to confirm changes. R0 = &00008049 R1 = &00408000 R2 = &203C7BE4 R3 = &00000068 R4 = &00008000 R5 = &3004575C R6 = &00000000 R7 = &00000068 R8 = &0000000A R9 = &300482F0 R10 = &FA208000 R11 = &FA207FA0 R12 = &80000000 R13 = &00407FF0 R14 = &FC04D78C PC = &0000801C Flags: nzCv (&20000110) Change register --------------- This prompts you for a register to change, and then what to change it to. Note at all registers are referenced by number, so SP=13, LR=14, and PC=15. Here is an example of resetting the application's stack pointer by discarding the four stacked values: Change R13 R13 is &00407FF0, or 4227056. Change it to (prefix hex with &) : &408000 Register R13 changed to &00408000, or 4227072. And if we then dump the registers, we can see that R13 has been altered: R0 = &00008049 R1 = &00408000 R2 = &203C7BE4 R3 = &00000068 R4 = &00008000 R5 = &3004575C R6 = &00000000 R7 = &00000068 R8 = &0000000A R9 = &300482F0 R10 = &FA208000 R11 = &FA207FA0 R12 = &80000000 R13 = &00408000 R14 = &FC04D78C PC = &0000801C Flags: nzCv (&20000110) SysInfo ------- Provides some basic information on the system: System memory 219 Mbytes total, 185 Mbytes free. Application memory 4 Mbytes Processor CPUID &410FB767, alignment exceptions Vectors Low Running in a TaskWindow. The last line, obviously, only appears when using the TaskWindow. Change flags ------------ This permits you to alter the decision-making processor flags - N, Z, C, and V. You are told the current state of the flags, then you are asked to press S to specify Set or C to specify Clear for each flag in turn. Like this: Flags: Negative : Clear Zero : Clear Carry : Set oVerflow : Clear Press [S]et or [C]lear, or [Return] to leave as-is. Negative ? Clear Zero ? Set Carry ? Clear oVerflow ? Set Flags are now: Negative : Clear Zero : Set Carry : Clear oVerflow : Set Display memory -------------- This is akin to the *Memory command in the Debugger. Provide an address to see what's there: Address (prefix hex with &) ? &8000 Address : 3 2 1 0 7 6 5 4 B A 9 8 F E D C : ASCII Data 00008000 : EF000010 E1A0D001 E92D000F EF079A40 : ....Р..-@. 00008010 : E28F0018 EF000002 EF000003 EF000017 : ........... 00008020 : E28F0024 EF000002 EF000003 EF000011 : $.......... 00008030 : 756F6241 6F742074 6C616320 7262206C : About to call br 00008040 : 706B6165 746E696F 00000000 75746552 : eakpoint....Retu 00008050 : 64656E72 6F726620 7262206D 706B6165 : rned from breakp 00008060 : 746E696F 00000000 E3A00000 E3530000 : oint........S 00008070 : D1A0F00E E48C0004 E2533004 EAFFFFFB : ....0S Display stack ------------- This will walk backwards up the stack displaying each stored value in turn: 00407FF0 : 201F66D4 00407FF4 : 00408000 00407FF8 : 203C7BE4 00407FFC : 00000068 ** END ** It will display up to eight entries, and stop if it reaches the end of application space (which is the normal way that a stack is set up in a simple assembler application). PC disassembly -------------- This will attempt to disassemble ten instructions either side of the current Program Counter, however it is constrained to not disassemble below &8000 (as memory there doesn't exist on a high vector machine). It uses the disassembler built into the Debugger module, the entire output is designed to look the same. The current location of PC is indicated by "->". 00008000 : ... : EF000010 : SWI OS_GetEnv 00008004 : .Р : E1A0D001 : MOV R13,R1 00008008 : ..- : E92D000F : STMDB R13!,{R0-R3} 0000800C : @. : EF079A40 : SWI XBreakAid_Attach 00008010 : .. : E28F0018 : ADR R0,&00008030 00008014 : ... : EF000002 : SWI OS_Write0 00008018 : ... : EF000003 : SWI OS_NewLine 0000801C-> ... : EF000017 : SWI OS_BreakPt 00008020 : $. : E28F0024 : ADR R0,&0000804C 00008024 : ... : EF000002 : SWI OS_Write0 00008028 : ... : EF000003 : SWI OS_NewLine 0000802C : ... : EF000011 : SWI OS_Exit 00008030 : Abou : 756F6241 : STRVCB R6,[PC,#-577]! ; *** PC writeback 00008034 : t to : 6F742074 : SWIVS &742074 00008038 : cal : 6C616320 : STCVSL CP3,C6,[R1],#-128 0000803C : l br : 7262206C : RSBVC R2,R2,#&6C ; ="l" 00008040 : eakp : 706B6165 : RSBVC R6,R11,R5,ROR #2 00008044 : oint : 746E696F : STRVCBT R6,[R14],#-2415 00008048 : .... : 00000000 : ANDEQ R0,R0,R0 0000804C : Retu : 75746552 : LDRVCB R6,[R4,#-1362]! 00008050 : rned : 64656E72 : STRVSBT R6,[R5],#-3698 Test program ------------ The information was taken from the following test program written directly in Zap (!). &8000 SWI OS_GetEnv 8004 MOV R13, R1 8008 STMFD R13!,{R0-R3} 800C ADR R0, &8030 8010 SWI OS_Write0 8014 SWI OS_NewLine 8018 SWI XBreakAid_Attach 801C SWIVC OS_BreakPt 8020 ADR R0, &804C 8024 SWI OS_Write0 8028 SWI OS_NewLine 802C SWI OS_Exit 8030 DCS "About to call breakpoint" 8048 DCD 0 804C DCS "Returned from breakpoint" 8064 DCD 0 A very simple program demonstrating BreakAid in use. Support ------- BreakAid is available from: http://www.heyrick.co.uk/software/breakaid/ No "official" support is offered, however if you have a problem or a request you can contact me at: heyrick 1973 -at- yahoo -dot- co -dot- uk However, before contacting me please check to ensure that you are using the latest version of BreakAid. Thanks, credits, etc -------------------- Thanks to Castle and ROOL for the fact that RISC OS still exists today. Thanks to Jeffrey for identifying and fixing the OS_BreakPt bug. https://www.riscosopen.org/viewer/revisions/logs?ident=1470681362-525659.html Thanks to Tank for fixing Zap to work on newer machines. Thanks to the Raspberry Pi Foundation for a RISC OS machine that costs less than a meal out. Thanks to CJE Micro's for the Pi itself, plus the RTC. Thanks to Mystery Benefactor for the DDE. ;-) Thanks to Colin Ferris for putting the idea in my head in the first place. https://www.riscosopen.org/forum/forums/4/topics/6366 Thanks to Tetley for the brain fuel. Lots and lots of. Thanks to Kalafina and Nightwish for the soundtrack. Thanks to Mom, 'cos she'd be upset if I didn't. Finally, thanks to the many people who have come and gone and have provided me with help, ideas, or things to think about.