mailto: blog -at- heyrick -dot- eu

Custom breakpoint handler code

A few days ago, somebody on the ROOL forum asked if there was a way to interrupt an executing program to display what the register values were at that point. As it turns out, RISC OS does have a SWI that would help here - OS_BreakPt. Upon executing that SWI, the complete user mode state of the processor will be saved and the breakpoint handler invoked.
The standard system breakpoint handler says, simply, "Stopped at break point at &xxxxxxxx" and then quits the program.

What would be far nicer is to have a simple breakpoint handler that would show the register contents, and then ask if you want to continue running the program, or quit it.

Now, my normal inclination to a request such as that would be to say "Google is your friend". I mean, RISC OS is more than a quarter of a century old, there will be some examples of using OS_BreakPt right? Right?

Well, in the future, this page will serve as an example. ☺

 

Before we begin

Please note carefully the following:
  • This only works for USER mode programs.
  • This has been written using facilities available to the 32 bit processors (CPSR, SPSR, etc). It probably won't run on older 26 bit machines.
  • This cannot easily be used from BASIC. How to do this at the end of the document, along with reasons why there's not much point in doing so.

 

Some wrapper code

This little program is some generic wrapper code. This represents "your program", and it indicates the three necessary parts:
  • Call debug_init to set up the breakpoint handler.
  • Call debug_finish before you quit to restore the original OS breakpoint handler.
  • Call the OS_BreakPt SWI whenever you want to stop and see the registers at that point.

Note also that the program uses literals for the SWI numbers, so this will build without any reference to headers or the like.

; TestCode
; ========
;
; Stupid test program to test custom OS_BreakPt handler
;
; Rick Murray, 2016/08/04
;

        AREA |My$Code|, CODE, A32bit

        IMPORT debug_init      ; in the other code file ;-)
        IMPORT debug_finish

        ENTRY

        SWI    &10             ; OS_GetEnv
        MOV    R13, R1         ; set stack pointer

        BL     debug_init      ; set up breakpoint handler code

        ; set some registers so they can be seen in the debug dump
        MOV    R0, #&00
        MOV    R1, #&11
        MOV    R2, #&22
        MOV    R3, #&33
        MOV    R4, #&44
        MOV    R5, #&55
        MOV    R6, #&66
        MOV    R7, #&77
        ; only bother with R0-R7

        ; Okay, let's blow it up
        SWI    &17             ; invoke breakpoint

        ; Do it again - to see that the registers are the same
        SWI    &17             ; invoke breakpoint

        ; Output something
        ADR    R0, my_message
        SWI    &2              ; OS_Write0
        SWI    &3              ; OS_NewLine

        BL     debug_finish    ; undo debug stuff

        SWI    &11             ; OS_Exit
        ; *** EXIT ***

my_message
        =      "Hasta la vista, BABY.", 0
        ALIGN

        END

If you are somebody who prefers to work using C code, then here is an example C program:

#include <stdio.h>

extern void debug_init(void);
extern void debug_finish(void);

int main(void)
{
   int  a = 1;
   int  b = 2;
   int  c = 0;
   int  d = 0;
   
   debug_init(); // set up breakpoint handler
   
   c = a * b;
   d = c << b;
   
   printf("The values of a-d are %d, %d, %d, %d.\n", a, b, c, d);
   
   // invoke breakpoint
   
   {
      SWI 0x17, {}, {}, {} // OS_BreakPt, uses and corrupts nothing
   }

   printf("The values of a-d are %d, %d, %d, %d.\n", a, b, c, d);
   
   // invoke breakpoint
   __asm
   {
      SWI 0x17, {}, {}, {} // OS_BreakPt, uses and corrupts nothing
   }

   printf"(Done.\n");
   debug_finish();

   return 0;   
}

 

What happens?

Running the assembler test program:
*BreakTest
Breakpoint at &000080AC
R0  = &00000000 (0)
R1  = &00000011 (17)
R2  = &00000022 (34)
R3  = &00000033 (51)
R4  = &00000044 (68)
R5  = &00000055 (85)
R6  = &00000066 (102)
R7  = &00000077 (119)
R8  = &0000000A (10)
R9  = &30047BB8 (805600184)
R10 = &FA208000 (4196433920)
R11 = &FA207FA0 (4196433824)
R12 = &80000000 (2147483648)
R13 = &00408000 (4227072)
R14 = &0000808C (32908)
Flags: nzCv
[C]ontinue or [Q]uit?
Breakpoint at &000080B0
R0  = &00000000 (0)
R1  = &00000011 (17)
R2  = &00000022 (34)
R3  = &00000033 (51)
R4  = &00000044 (68)
R5  = &00000055 (85)
R6  = &00000066 (102)
R7  = &00000077 (119)
R8  = &0000000A (10)
R9  = &30047BB8 (805600184)
R10 = &FA208000 (4196433920)
R11 = &FA207FA0 (4196433824)
R12 = &80000000 (2147483648)
R13 = &00408000 (4227072)
R14 = &0000808C (32908)
Flags: nzCv
[C]ontinue or [Q]uit?
Hasta la vista, BABY.
*

Running the C test program:

*BreakTestC
The values of a-d are 1, 2, 2, 8.
Breakpoint at &000080D4
R0  = &00000022 (34)
R1  = &00000000 (0)
R2  = &00452402 (4531202)
R3  = &00000000 (0)
R4  = &00000001 (1)
R5  = &00000002 (2)
R6  = &00000002 (2)
R7  = &00000008 (8)
R8  = &0000940C (37900)
R9  = &0000963C (38460)
R10 = &00009EF4 (40692)
R11 = &0000AC48 (44104)
R12 = &0000AB6C (43884)
R13 = &0000AC2C (44076)
R14 = &FC1732B8 (4229378744)
Flags: nZCv
[C]ontinue or [Q]uit?
The values of a-d are 1, 2, 2, 8.
Breakpoint at &000080F8
R0  = &00000022 (34)
R1  = &00000000 (0)
R2  = &00452402 (4531202)
R3  = &00000000 (0)
R4  = &00000001 (1)
R5  = &00000002 (2)
R6  = &00000002 (2)
R7  = &00000008 (8)
R8  = &0000940C (37900)
R9  = &0000963C (38460)
R10 = &00009EF4 (40692)
R11 = &0000AC48 (44104)
R12 = &0000AB6C (43884)
R13 = &0000AC2C (44076)
R14 = &FC1732B8 (4229378744)
Flags: nZCv
[C]ontinue or [Q]uit?
Done.
*

 

The breakpoint handler - annotated

Here is an annotated version of the breakpoint handler code.

First up is the usual program header blurb.

; DebugTest
; =========
;
; Test using OS_BreakPt for debugging.
;
; Rick Murray, 2016/08/04
;
; Open Source software - this code has been released under the EUPL v1.1 (only).
; https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11
;
; Special note: The EUPL licence applies ONLY to this specific code. It may therefore be
;               used in and with software under other licences[*] without requiring any
;               modification to the licence of these other parts.
;
;               * - EUPL code can be converted to GPL v2 to work around the lack of
;                   compatibility and openness of the GPL.
;                   It is not, however, compatible with the even-less-open GPL v3.
;
The special note is important. I am choosing to release my code as EUPL, however unlike certain other licences (<cough>GPL</cough>), this does not mean everything it touches will be tainted by this licence. You can use this in commercial software. You can even use this with GPL v2 software as the EUPL permits that version of the code (not every version regardless of what FSF thinks) to be 'relicenced' as GPL in order to allow it to inter-operate with restrictive intentionally incompatible licences such as the GPL. This is what open source is supposed to be about.
Note that the EUPL is not directly compatible with the GPLv3. GNU helpfully provide a workaround by using a third licence as an intermediary. However I'm inclined to say that if you feel the need to go to such lengths, you really ought to stop and ask yourself exactly what your definition of open source is and whether you want your "freedom" to be open and inclusive, or closed and restrictive.

Okay, end of soapbox rant. ☺
But it is worth taking the time to say that, because had I decided to, say, licence this as GPL, it would be a huge red flag to anybody wishing to include this code within their own projects...

A standard AREA definition, and then a declaration of the two functions that we EXPORT to be available to other parts of the source code:

        AREA |My$Code|, CODE, A32bit

        EXPORT debug_init      ; our host program needs this
        EXPORT debug_finish

Now for the debug_init function. What we are doing here is to use the SWI OS_BreakCtrl to read the address of the register save block and the current breakpoint handler, and to install our own breakpoint handler in its place.
The register save block is 17*4 words (68 bytes), which represents R0-R15 in order, and then the CPSR following. However in order to make things simpler, we just use the OS-supplied register save block.
Technically, the OS_BreakCtrl SWI has been deprecated (since forever). One is supposed to use OS_ChangeEnvironment instead; however I note in the RISC OS source code that RISC OS itself uses OS_BreakCtrl. ☺

debug_init
        ; set up debug stuff
        MOV    R0, #0          ; use existing register save block
        ADR    R1, debug_break ; our handler
        SWI    &20018          ; XOS_BreakCtrl (deprecated! ;-) )
        ADR    R2, debug_saveblock
        STR    R0, [R2, #0]    ; remember where the save block is
        STR    R1, [R2, #4]    ; remember the previous control routine
        MOV    PC, R14

The inverse of the above is to undo the custom breakpoint handler and restore the default one. This is performed by calling the same SWI and giving it the values that we remembered from before:

debug_finish
        ; revert to previous
        ADR    R2, debug_saveblock
        LDR    R0, [R2, #0]    ; retrieve original saveblock
        LDR    R1, [R2, #4]    ; retrieve original control routine
        SWI    &20018          ; XOS_BreakCtrl
        MOV    PC, R14

Here follows some workspace. Two words for remembering the register save block address and previous handler; and three words for translating numeric values to printable numbers:

debug_saveblock
        DCD    0               ; to remember saveblock address
        DCD    0               ; to remember control routine

debug_wordbuffer
        DCD    0               ; space for 11 characters plus terminull
        DCD    0
        DCD    0

Here starts the breakpoint handler. We are entered in SVC mode (so we have a private R14 to play with). We do not return, so all registers (save R13) are trashable. We exit either by calling OS_Exit (to quit the application) or by restoring state from the register save block.
In the function, we treat R0-R3 as working registers (akin to APCS), R4 is the register save block pointer, and R5 is a counter for stepping through the registers.

debug_break
        ; Called upon a breakpoint
        ; USER mode registers are saved at debug_saveblock address,
        ; and we are entered in SVC mode.

        ;   R0-R3 : Scratch
        ;   R4    : Register save pointer
        ;   R5    : Counter

        ; Pick up address of saved registers
        ADR    R4, debug_saveblock
        LDR    R4, [R4]

Now that we have a pointer to our saved registers, the first thing to do is pick up the saved value of PC so we can report where the breakpoint occurred. This bit of code does that:

        ; First, report where the break point happened
        ADR    R0, brk_msg
        SWI    &2              ; OS_Write0
        LDR    R0, [R4, #(15*4)] ; R15 aka PC
        ADR    R1, debug_wordbuffer
        MOV    R2, #11
        SWI    &D4             ; OS_ConvertHex8
        SWI    &2              ; OS_Write0
        SWI    &3              ; OS_NewLine

As you can see, we have some help from the SWI OS_ConvertHex8. This means that we have so far printed out the message "Breakpoint at &xxxxxxxx".

The next task to do is step through all of the registers, printing out their contents in both hex and denary. To begin, however, we need to nicely output the register number:

        ; Now dump the registers
        MOV    R5, #0          ; Start from R0

debug_dumploop
        ; Output "Rxx = &"
        SWI    256 + 'R'       ; Output "R"
        ADD    R0, R5, #48     ; R0 is now a number
        CMP    R5, #10         ; Register >= 10?
        SUBGE  R0, R0, #10     ; ...bring it back to 0-9 range
        SWIGE  256 + '1'       ; ...and prefix a '1'
        SWI    &0              ; OS_WriteC
        CMP    R5, #10
        SWILT  256 + ' '       ; Add a space if register < 10
        ADR    R0, reg_msg     ; " = &"
        SWI    &2              ; OS_Write0

The complication here is twofold. Firstly, we output the register number by writing out the character for the number. There is no "10" or "11" etc, so we need to instead output a "1" followed by the second digit as appropriate. Secondly, we need to insert an additional space character if the register only has one digit, so everything lines up nicely. It's two lines of code, no excuse for amateurish results.

Now write the register in hex, this is just like we did above for PC:

        ; Read the register and output it, in the form:
        ;   xxxxxxxx (yyyyyy)
        ;   [hex]     [denary]
        MOV    R1, R5, LSL#2   ; R1 = counter, shifted to word offset
        LDR    R3, [R4, R1]    ; R3 = savedregs + counter offset
        MOV    R0, R3
        ADR    R1, debug_wordbuffer
        MOV    R2, #11
        SWI    &D4             ; OS_ConvertHex8
        SWI    &2              ; OS_Write0

Output a space, an open bracket, the value in denary (using a slightly different SWI), then a closing bracket. I had thought of using a string, but ultimately the simplest option was to just output those characters directly.
Of note here is the use of OS_ConvertCardinal. This treats the register value as unsigned, so will never result in a negative number.

        SWI    256 + ' '
        SWI    256 + '('
        MOV    R0, R3          ; pick up the register value again
        ADR    R1, debug_wordbuffer
        MOV    R2, #11
        SWI    &D8             ; OS_ConvertCardinal4
        SWI    &2              ; OS_Write0
        SWI    256 + ')'
        SWI    &3              ; OS_NewLine

Now loop around until al of the registers (R0-R14) have been displayed.

        ADD    R5, R5, #1      ; next register
        CMP    R5, #15         ; done beyond R14?
        BLT    debug_dumploop  ; if not, go around again

The final part of our output is to pick up the saved CPSR and report on the status of the flags. The flags reported are Negative, Zero, Carry, and oVerflow (bits 31 to 28 respectively).
The test is performed using the TST instruction. It might seem odd that an EQ result afterwards means NO match, but if you consider the instruction itself performs an AND comparison between the specified register and the value we are checking for. Therefore zero (EQ) means the AND result was zero (no match) whereas non-zero (NE) means there was a matching bit. We set the character to output (in R0) to the uppercase letter. If the comparison is zero (no match), 32 is added to force the character to be its lower case form. Thus, upper case means the flag is set, lower case means it is unset.

        ; Now we want to read the stored CPSR and report the flags
        ADR    R0, flags_msg
        SWI    &2              ; OS_Write0
        LDR    R1, [R4, #(16*4)]
        TST    R1, #(1<<31)    ; Test N bit
        MOV    R0, #'N'
        ADDEQ  R0, R0, #32           ; if unset, make lower case
        SWI    &0                    ; OS_WriteC
        TST    R1, #(1<<30)    ; Test Z bit
        MOV    R0, #'Z'
        ADDEQ  R0, R0, #32
        SWI    &0
        TST    R1, #(1<<29)    ; Test C bit
        MOV    R0, #'C'
        ADDEQ  R0, R0, #32
        SWI    &0
        TST    R1, #(1<<28)    ; Test V bit
        MOV    R0, #'V'
        ADDEQ  R0, R0, #32
        SWI    &0
        SWI    &3              ; OS_NewLine

We don't bother to report the processor mode. As I said, this stuff is only intended to work in USR32 mode, so that is just assumed to be the case.
Other flags you might want to add: Q (sticky overflow) bit 27, IRQ disabled bit 7, FIQ disabled bit 6. There are others on later ARM processors, refer to a datasheet for specifics.
The version of this program that I made available this morning did the following:

        TST    R0, #(1<<28)    ; Test V bit
        SWIEQ  256 + 'v'
        SWINE  256 + 'V'
However it is not possible to rely upon flags being preserved across SWI calls. The code worked fine in testing, but this may be luck more than anything else. The method given now will work.

Now the output has been done, ask the user if they want to continue running the program, or quit. We say the options are [Q] and [C] but in reality we only continue if the user replies with 'C' or 'c' - anything else is assumed to be Quit.

        ; If we're here, R0-R14 have been dumped. Ask the user what
        ; to do.
        ADR    R0, abort_retry_ignore
        SWI    &2              ; OS_Write0
        SWI    &3              ; OS_NewLine
        SWI    &4              ; OS_ReadC

        ; 'C' means continue, anything else means exit.
        CMP    R0, #'C'        ; Was it a 'C'?
        CMPNE  R0, #'c'        ; Or was it a 'c'?
        BNE    debug_exit      ; If not, EXIT.

As we will have branched to quit, the following is the continuation code. You will note the use of some things (LDM with ^ and MOVS PC) that are not supposed to be used in 32 bit code. These instructions do, in fact, exist, but their behaviour is different to the older versions.
The first task is to pick up the user mode status register (CPSR) and write it to the SVC mode's saved PSR (SPSR).
Next, we point our private copy of R14 to point to the saved register block, and we then load R0-R14 into the user mode register block (hence the ^).
Then we load the saved copy of PC into R14, and add four (so it will point to the instruction following the one that raised the breakpoint).
Finally, we use MOVS to push R14 into PC and resume execution of the program. The use of MOVS in this manner works because we have a private SPSR, and the instruction thus copies the SPSR into the CPSR at the same time. This behaves like the MOVS of old in restoring execution and processor state/mode.
After this, our program resumes unaware that anything has happened.

        ; If we're here, the user wants to resume with the program
        LDR    R0, [R4, #(16*4)] ; pick up the CPSR
        MSR    SPSR_cxsf, R0   ; store it in SPSR_svc

        MOV    R14, R4         ; Set R14_svc to point to register save block
        LDMIA  R14, {R0-R14}^  ; Restore all USER MODE registers
        MOV    R0, R0          ; objasm whinges

        LDR    R14, [R14, #(15*4)] ; R14_svc is now USER MODE PC
        ADD    R14, R14, #4    ; Point to the *following* instruction!

        MOVS   PC, R14         ; MOVS here will copy SPSR to CPSR
        ; we're back to our program now...

The exit clause is here. We force USR32 mode (no longer in SVC mode), undo the breakpoint handler, and then call OS_Exit to terminate the application.

debug_exit
        ; We come here to EXIT the program

        MSR    CPSR_c, #16     ; drop to USER32 mode (from SVC)

        BL     debug_finish    ; restore previous breakpoint handler

        SWI    &11             ; OS_Exit

        ; ** Done **

Some strings finish our code.

brk_msg
        =      "Breakpoint at &", 0
        ALIGN

reg_msg
        =      " = &", 0
        ALIGN

flags_msg
        =      "Flags: ", 0
        ALIGN

abort_retry_ignore
        =      "[C]ontinue or [Q]uit?", 0
        ALIGN

        END

 

The breakpoint handler - complete

Here's a non-annotated version:
; DebugTest
; =========
;
; Test using OS_BreakPt for debugging.
;
; Rick Murray, 2016/08/04
;
; Open Source software - this code has been released under the EUPL v1.1 (only).
; https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11
;
; Special note: The EUPL licence applies ONLY to this specific code. It may therefore be
;               used in and with software under other licences[*] without requiring any
;               modification to the licence of these other parts.
;
;               * - EUPL code can be converted to GPL v2 to work around the lack of
;                   compatibility and openness of the GPL.
;                   It is not, however, compatible with the even-less-open GPL v3.
;

        AREA |My$Code|, CODE, A32bit

        EXPORT debug_init      ; our host program needs this
        EXPORT debug_finish

debug_init
        ; set up debug stuff
        MOV    R0, #0          ; use existing register save block
        ADR    R1, debug_break ; our handler
        SWI    &20018          ; XOS_BreakCtrl (deprecated! ;-) )
        ADR    R2, debug_saveblock
        STR    R0, [R2, #0]    ; remember where the save block is
        STR    R1, [R2, #4]    ; remember the previous control routine
        MOV    PC, R14

debug_finish
        ; revert to previous
        ADR    R2, debug_saveblock
        LDR    R0, [R2, #0]    ; retrieve original saveblock
        LDR    R1, [R2, #4]    ; retrieve original control routine
        SWI    &20018          ; XOS_BreakCtrl
        MOV    PC, R14

debug_saveblock
        DCD    0               ; to remember saveblock address
        DCD    0               ; to remember control routine

debug_wordbuffer
        DCD    0               ; space for 8 characters plus terminull
        DCD    0
        DCD    0

debug_break
        ; Called upon a breakpoint
        ; USER mode registers are saved at debug_saveblock address,
        ; and we are entered in SVC mode.

        ;   R0-R3 : Scratch
        ;   R4    : Register save pointer
        ;   R5    : Counter

        ; Pick up address of saved registers
        ADR    R4, debug_saveblock
        LDR    R4, [R4]

        ; First, report where the break point happened
        ADR    R0, brk_msg
        SWI    &2              ; OS_Write0
        LDR    R0, [R4, #(15*4)] ; R15 aka PC
        ADR    R1, debug_wordbuffer
        MOV    R2, #11
        SWI    &D4             ; OS_ConvertHex8
        SWI    &2              ; OS_Write0
        SWI    &3              ; OS_NewLine

        ; Now dump the registers
        MOV    R5, #0          ; Start from R0

debug_dumploop
        ; Output "Rxx = &"
        SWI    256 + 'R'       ; Output "R"
        ADD    R0, R5, #48     ; R0 is now a number
        CMP    R5, #10         ; Register >= 10?
        SUBGE  R0, R0, #10     ; ...bring it back to 0-9 range
        SWIGE  256 + '1'       ; ...and prefix a '1'
        SWI    &0              ; OS_WriteC
        CMP    R5, #10
        SWILT  256 + ' '       ; Add a space if register < 10
        ADR    R0, reg_msg     ; " = &"
        SWI    &2              ; OS_Write0

        ; Read the register and output it, in the form:
        ;   xxxxxxxx (yyyyyy)
        ;   [hex]     [denary]
        MOV    R1, R5, LSL#2   ; R1 = counter, shifted to word offset
        LDR    R3, [R4, R1]    ; R3 = savedregs + counter offset
        MOV    R0, R3
        ADR    R1, debug_wordbuffer
        MOV    R2, #11
        SWI    &D4             ; OS_ConvertHex8
        SWI    &2              ; OS_Write0
        SWI    256 + ' '
        SWI    256 + '('
        MOV    R0, R3          ; pick up the register value again
        ADR    R1, debug_wordbuffer
        MOV    R2, #11
        SWI    &D8             ; OS_ConvertCardinal4
        SWI    &2              ; OS_Write0
        SWI    256 + ')'
        SWI    &3              ; OS_NewLine

        ADD    R5, R5, #1      ; next register
        CMP    R5, #15         ; done beyond R14?
        BLT    debug_dumploop  ; if not, go around again

        ; Now we want to read the stored CPSR and report the flags
        ADR    R0, flags_msg
        SWI    &2              ; OS_Write0
        LDR    R1, [R4, #(16*4)]
        TST    R1, #(1<<31)    ; Test N bit
        MOV    R0, #'N'
        ADDEQ  R0, R0, #32           ; if unset, make lower case
        SWI    &0                    ; OS_WriteC
        TST    R1, #(1<<30)    ; Test Z bit
        MOV    R0, #'Z'
        ADDEQ  R0, R0, #32
        SWI    &0
        TST    R1, #(1<<29)    ; Test C bit
        MOV    R0, #'C'
        ADDEQ  R0, R0, #32
        SWI    &0
        TST    R1, #(1<<28)    ; Test V bit
        MOV    R0, #'V'
        ADDEQ  R0, R0, #32
        SWI    &0
        SWI    &3              ; OS_NewLine

        ; If we're here, R0-R14 have been dumped. Ask the user what
        ; to do.
        ADR    R0, abort_retry_ignore
        SWI    &2              ; OS_Write0
        SWI    &3              ; OS_NewLine
        SWI    &4              ; OS_ReadC

        ; 'C' means continue, anything else means exit.
        CMP    R0, #'C'        ; Was it a 'C'?
        CMPNE  R0, #'c'        ; Or was it a 'c'?
        BNE    debug_exit      ; If not, EXIT.

        ; If we're here, the user wants to resume with the program
        LDR    R0, [R4, #(16*4)] ; pick up the CPSR
        MSR    SPSR_cxsf, R0   ; store it in SPSR_svc

        MOV    R14, R4         ; Set R14_svc to point to register save block
        LDMIA  R14, {R0-R14}^  ; Restore all USER MODE registers
        MOV    R0, R0          ; objasm whinges

        LDR    R14, [R14, #(15*4)] ; R14_svc is now USER MODE PC
        ADD    R14, R14, #4    ; Point to the *following* instruction!

        MOVS   PC, R14         ; MOVS here will copy SPSR to CPSR
        ; we're back to our program now...


debug_exit
        ; We come here to EXIT the program

        MSR    CPSR_c, #16     ; drop to USER32 mode (from SVC)

        BL     debug_finish    ; restore previous breakpoint handler

        SWI    &11             ; OS_Exit

        ; ** Done **

brk_msg
        =      "Breakpoint at &", 0
        ALIGN

reg_msg
        =      " = &", 0
        ALIGN

flags_msg
        =      "Flags: ", 0
        ALIGN

abort_retry_ignore
        =      "[C]ontinue or [Q]uit?", 0
        ALIGN

        END

 

Using this from BASIC

For those rare times you may find yourself doing register-level debugging in BASIC...

Modify the breakpoint handler code to begin as follows:

        AREA |My$Code|, CODE, A32bit

        EXPORT debug_init      ; our host program needs this
        EXPORT debug_finish

        ENTRY
        B      debug_throw
        B      debug_init
        B      debug_finish

debug_throw
        ; Make OS_BreakPt become
        SWI     &17             ; OS_BreakPt
        MOV     PC, R14

debug_init

The two EXPORTs are not needed, you can remove them if you want. It doesn't matter.

Now assemble this as an object file, and drag that object file to link with the Binary option chosen. Save the data file offered.

Now, from BASIC...

REM >BreakTestB
REM
REM Using the breakpoint handler in BASIC (OMG!)
REM
REM Rick Murray, 2016/08/05
REM

REM *NOTE* Assumes "debugcode" is in the CSD.

DIM code% 511
SYS "OS_File", 16, "debugcode", code%, 0 : REM *LOAD it

REM Set up the breakpoint handler
CALL code%+4

a% = 1
b% = 2
c% = a% * b%
PRINT a%, b%, c%
CALL code%

PRINT a%, b%, c%
SYS "OS_BreakPt"

REM Remove the breakpoint handler
CALL code%+8

PRINT "Done."
END

Of course, calling SYS in BASIC will likely leave you with most of the registers as zero (as BASIC copies over the registers you pass on entry). Even trying to sidestep this to CALL some assembler to call the SWI won't have much effect, as BASIC doesn't work like a regular program and its environment isn't one that poking around at register level would be much use for.
You can see in these examples. The first register dump was the CALL version. This, by rights, should leave most of the registers as-is, yet R0-R7 are all '1'. The SYS version follows, and you can see it has set R0-R9 to zero (as nothing was specified, so BASIC assumes zero).
Frankly, asides from working with assembler code, you won't get much use out of the breakpoint code in BASIC. But, hey, somebody was bound to ask, right? ☺

*BreakTestB
         1         2         2
Breakpoint at &000090AC
R0  = &00000001 (1)
R1  = &00000001 (1)
R2  = &00000001 (1)
R3  = &00000001 (1)
R4  = &00000001 (1)
R5  = &00000001 (1)
R6  = &00000001 (1)
R7  = &00000001 (1)
R8  = &00008700 (34560)
R9  = &00407FD8 (4227032)
R10 = &00000000 (0)
R11 = &00008100 (33024)
R12 = &00009014 (36884)
R13 = &00407FC0 (4227008)
R14 = &FC1AD4A8 (4229616808)
Flags: nzCv
[C]ontinue or [Q]uit?
         1         2         2
Breakpoint at &000086D4
R0  = &00000000 (0)
R1  = &00000000 (0)
R2  = &00000000 (0)
R3  = &00000000 (0)
R4  = &00000000 (0)
R5  = &00000000 (0)
R6  = &00000000 (0)
R7  = &00000000 (0)
R8  = &00000000 (0)
R9  = &00000000 (0)
R10 = &0000000D (13)
R11 = &000086D4 (34516)
R12 = &0000903B (36923)
R13 = &00407FD0 (4227024)
R14 = &FC1AD2B8 (4229616312)
Flags: nZCv
[C]ontinue or [Q]uit?
Done.
*

 

This is, incidently, the beginnings of how a debugger works. A debugger would likely save a copy of the program code somewhere for its own private use. Then certain instructions (branches, memory access, etc) would be replaced by breakpoint calls.
When the breakpoint is encountered, the value of PC allows the debugger to look to see what instruction was supposed to have been executed. This instruction could then be faked in a sanitised fashion (such as trapping errant memory accesses), and then execution would resume until the next breakpoint, at which point the whole process starts all over again.

 

 

Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
 
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

David Pilling, 12th August 2016, 01:25
Hmm my comment vanished - I wondered if you could take an open source memory manager and create a better OS_Heap module. I once suggested to Pace how to improve C malloc - something like merging neighbouring free'd blocks. Which they may have done. Lots of interesting things one can do like adding guard bytes to block and see if they are corrupted. 
, 30th April 2018, 19:41
Bonjour, 
Thanks for all your tutor. I am testing a basic program with assembler code. I use your debuger code, nice, to trace the registers before the call to swi. 
OMAPxxx.Castle.RiscOs.sources.HwSupport.buffers.test.Basher 
I am trying to test buffer but this example is 26 bits. 
Note: I have tested your basic programme: call code% works whith a%, b% , c% all caps.

Add a comment (v0.11) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 40161 backwards.
Your comment
French flagSpanish flagJapanese flag
Calendar
«   August 2016   »
MonTueWedThuFriSatSun
2467
89111214
15161718192021
232425262728
293031    

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index

Geekery
 
Alphabetical:

Search

Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.

Etc...

Last read at 14:42 on 2024/12/09.

QR code


Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0

 

© 2016 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.

 

Have you noticed the watermarks on pictures?
Next entry - 2016/08/10
Return to top of page