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.

