A sample debugging session with MINIMON

Basic and Machine Language

Moderator: Moderators

User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

A sample debugging session with MINIMON

Post by Mike »

Hi!

I had a nice PM exchange with chysn and as a result I'd like to showcase the debugging capabilites of MINIMON in this thread here. Actually, the routine in question works as expected, but I want to take a look at the intermediary values it calculates, in BASIC, in floating point. Here's the annotated source:

Code: Select all

.Sqrt
 JSR $DC1B             ; round FAC#1
 JSR $DC2B             ; check sign of FAC#1
 BEQ Sqrt_03           ; =0? Then end immediately! (SQR(0)=0)
 BPL Sqrt_00           ; continue, if positive, else
 JMP $D248             ; flag ?ILLEGAL QUANTITY ERROR
.Sqrt_00
 LDA $61
 STA $FB               ; remember exponent byte of argument
 AND #$01
 ORA #$80
 STA $61               ; reduce argument X to range [0.5,2.0[
 LDA #$04
 STA $FC
 LDX #Sqrt_04 MOD 256
 LDY #Sqrt_04 DIV 256
 JSR $DBD4             ; store FAC#1 (X: reduced argument) to Sqrt_04
 LDA #$BC
 LDY #$D9
 BNE Sqrt_02           ; start with Y=1 as first approximation,
.Sqrt_01               ; and loop 4 times, calculating Y=(Y+X/Y)/2.0 each turn
 LDX #Sqrt_05 MOD 256
 LDY #Sqrt_05 DIV 256
 JSR $DBD4             ; store FAC#1 (Y: intermediate value) to Sqrt_05
 LDA #Sqrt_04 MOD 256
 LDY #Sqrt_04 DIV 256
 JSR $DB0F             ; FAC#1 = X/Y
 LDA #Sqrt_05 MOD 256
 LDY #Sqrt_05 DIV 256
.Sqrt_02
 JSR $D867             ; FAC#1 = Y+X/Y (=1+X on first entry)
 DEC $61               ; divide FAC#1 by 2.0
 DEC $FC
 BNE Sqrt_01
 LDA $FB               ; calculate exponent of result
 LSR A
 ADC #$40
 STA $61
.Sqrt_03
 RTS
.Sqrt_04
 EQUB 0:EQUB 0:EQUB 0:EQUB 0:EQUB 0
.Sqrt_05
 EQUB 0:EQUB 0:EQUB 0:EQUB 0:EQUB 0
So, the mission is to follow what is stored in Y := Sqrt_05 with each of the loop iterations.

First, the batch assembly. You see this in action with the movie linked to in the OP of the WIP: MINIMON thread. The input of MINIMON is temporarily redirected to come from a SEQ file:

Image

When the batch assembly has completed, a small helper routine in $0140 restores the default I/O, closes the file and returns to BASIC:

Image

We take a deeper look at the assembled code to search for the equivalent of the line "JSR $DBD4 ; store FAC#1 (Y: intermediate value) to Sqrt_05" ...

Image

... and find it at $02CD:

Image

The intermediate value Y goes to $02F1. We now instrument the code, so we can take a look at the stored floating point value at $02F1 on each loop iteration. A copy of JSR $DBD4 is placed somewhere else as begin of a "knapsack", and followed by a BRK/NOP/RTS sequence. That knapsack in turn is called by a JSR that takes the place of the original JSR $DBD4 at $02CB:

Image

After exiting to BASIC, we do a PRINTUSR(2) to calculate the square root of 2. The print command is preceded by a row of colons, as MINIMON also uses the BASIC line input buffer for its own command line, and we don't want the whole thing to end with a ?SYNTAX error.

During execution, the routine reduces the argument X into the range [0.5,2.0[, so it actually internally calculates the square root of 0.5 and corrects the exponent of the result at the end.

Image

The breakpoint has been called the first time and we see "$80 $40 $00 $00 $00" is stored at $02F1. With G and M, we display the next intermediary values until the routine has finished its calculation and hands control back to the BASIC interpreter:

Image

... with the PRINT result neatly appended to the last G command. :wink:

The three intermediary floating point values correspond to the annotated decimal fractions.

>02F1 80 40 00 00 00 ^= 0.75
>02F1 80 35 55 55 55 ^= 0.708333333
>02F1 80 35 05 05 05 ^= 0.707107843

After those three iterations, the next value in FAC #1 is already exact within the floating point accuracy and after correction of the exponent, the result is printed as 1.41421356.

Cheers,

Michael
Vic20-Ian
Vic 20 Scientist
Posts: 1214
Joined: Sun Aug 24, 2008 1:58 pm

Re: A sample debugging session with MINIMON

Post by Vic20-Ian »

Could you use this to review the text code for level names in Star Defence?

Every 5th level and multiples the name is Yllabian Dogfight

Every 10th wave is something else e.g. Firebomber Showdown, Dynamo Recharge.

Every 5th wave is a wave of white ships but every 10th wave is an empty wave announced and then absent :-(

I always wondered if this game has a bug and the 10th multiple waves are broken or if they were never coded or the game was released with them missing.

This tool will be great for some digital archaeology like this:

https://www.youtube.com/watch?v=QJCwsi6O5xU
Vic20-Ian

The best things in life are Vic-20

Upgrade all new gadgets and mobiles to 3583 Bytes Free today! Ready
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample debugging session with MINIMON

Post by Mike »

Vic20-Ian wrote:Could you use this to review the text code for level names in Star Defence?

Every 5th level and multiples the name is Yllabian Dogfight

Every 10th wave is something else e.g. Firebomber Showdown, Dynamo Recharge.
These names appear in (inverse) screen codes around address $4700:

Image

I use the F command to prepare the colour RAM and then, the T command to copy the game code to the text screen, pagewise. When my first search of ASCII encoded strings came up empty, this was the next thing I tried. :)
Every 5th wave is a wave of white ships but every 10th wave is an empty wave announced and then absent :-(

I always wondered if this game has a bug and the 10th multiple waves are broken or if they were never coded or the game was released with them missing.
Maybe the missing ships are in background colour? That can surely be sorted out but needs more time than just 5 minutes. ;)

Greetings,

Michael
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: A sample debugging session with MINIMON

Post by chysn »

1) What does the instrument and the JSR to it look like when you want to break after a one- or two-byte instruction?

2) Any particular reason for a BRK/NOP/RTS sequence instead of BRK/RTS/RTS? I’m thinking in the direction of automated instrument generation, where it would be easier to pad out the maximum instrument size with one value ($60).
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
Vic20-Ian
Vic 20 Scientist
Posts: 1214
Joined: Sun Aug 24, 2008 1:58 pm

Re: A sample debugging session with MINIMON

Post by Vic20-Ian »

Every 5th wave is a wave of white ships but every 10th wave is an empty wave announced and then absent :-(

I always wondered if this game has a bug and the 10th multiple waves are broken or if they were never coded or the game was released with them missing.
Maybe the missing ships are in background colour? That can surely be sorted out but needs more time than just 5 minutes. ;)

Greetings,

Michael
Thank you Mike. This is the list I am looking for.

When you play the game (I recommend the trained game to get to Level 10) the level 10 start is similarly announced to Wave 5 but no ships appear and the wave ends and goes to wave 11 with normal stargate wave. Wave 15 is white ships Yllabian dogfight and wave 20 no wave.

It will be great to dig into this and find either a bug and whole new gameplay or that it was intentional.

It has been an unresolved question since 1985 I think ;-)

Can Minimon be used now with MegaCart or FE3 or do I need the cartridge version?
Vic20-Ian

The best things in life are Vic-20

Upgrade all new gadgets and mobiles to 3583 Bytes Free today! Ready
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample debugging session with MINIMON

Post by Mike »

chysn wrote:1) What does the instrument and the JSR to it look like when you want to break after a one- or two-byte instruction?
You place a group of one 1-byte and one 2-byte or of three 1-byte instructions into the knapsack and put the breakpoint before the group, between two of the instructions or at the end of the group. Two 2-byte instructions would be replaced by either JSR/NOP or NOP/JSR. Branches and instructions involving the stack need special handling, but that can also be done.

You have to be wary though, that none of the moved away instructions is itself a branch target or is referenced in some other way, for example by self-modifying code.
2) Any particular reason for a BRK/NOP/RTS sequence instead of BRK/RTS/RTS? I’m thinking in the direction of automated instrument generation, where it would be easier to pad out the maximum instrument size with one value ($60).
When the 6502 executes a BRK instruction, it puts its address + 2 on the stack. Some monitors 'correct' this address by subtracting 1 (not MINIMON though). The BRK/NOP sequence allows the breakpoint to work on both types of monitors: non-correcting monitors restart the program code after the NOP, correcting monitors additionally execute the harmless NOP.

The RTS instruction is not part of the breakpoint sequence itself, it just returns from the JSR'd-to knapsack.
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample debugging session with MINIMON

Post by Mike »

Vic20-Ian wrote:Can Minimon be used now with MegaCart or FE3 or do I need the cartridge version?
I already replied to this in the hardware thread:
Mike wrote:FE3 on its own is not able to run a software image of the MINIMON firmware. The MINIMON firmware resides in $9800..$9FFF, where FE3 puts its own registers to access the FE3 hardware, and the FE3 cartridge cannot overlay that address range with RAM.

It is entirely possible though to operate FE3 as slave with the MINIMON cartridge.
That answer also applies in full to Mega-Cart. Furthermore, when majikeyric put in his request:
majikeyric wrote:Count me in for [...] a software image for the Ultimem :)
... my response was:
Mike wrote:[...] If you own the hardware, you can always create a software image for yourself with the S command, but I won't do a software-only release.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: A sample debugging session with MINIMON

Post by chysn »

I think I’d need an actual debugging problem to solve to understand how this might be better than regular breakpoints.

I appreciate that you run all the code and that you can thus do complete iterations. But if I can’t find a problem with one breakpoint on the first iteration, that tells me that my breakpoint is in the wrong place, not that I need to check another iteration at the same place.

I’m starting my new game, so I’ll keep this in mind the next time I’m debugging something, to get an idea of what kinds of problems it’s good for.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample debugging session with MINIMON

Post by Mike »

This is not a question of good or better, the code instrumentation method I showcased in the OP highlights a way to insert breakpoints into code that has already been built, and is not easy to re-build (because it might not be ones own code and the source is not readily available).

In self-written code, you can place breakpoints as you assemble it (from a symbolic assembler, or manually in the monitor of your choice), without the need to use a knapsack; inserting the BRK/NOP sequence between instructions suffices. Just using BRK on its own is not recommended for the aforementioned reasons, some monitors 'vet' the stacked return PC, others (including MINIMON) do not.

Breakpoints retain the current program counter, processor status, the A, X and Y registers, stack pointer and stack contents between SP and stack end. You can 'walk' the user program as it executes with a breakpoint inserted at a relevant position. In the monitor, you can peek or change registers, memory or instructions, and then continue execution with the G command.

What breakpoints are *not* is: being a termination of the program execution or - alternatively - just taking snapshots of the registers (without actually invoking the monitor command prompt), the last stored snapshot then to be inspected after program termination. Seeing breakpoints only this way means severely discounting their merits.
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: A sample debugging session with MINIMON

Post by chysn »

I’ll try it out when I get home from my holiday travels. It seems a straightforward task to automate the knapsack creation process and installation into the code, and the restoration when the system is no longer needed. I’ve already got the “dumb” version written on paper, which handles the 3-byte case. The “smart” version will need to figure out what to do based on addressing modes of the surrounding instructions.

Solving it is a lot like playing that childhood game where you take between one and three sticks, trying to force your opponent to take the last stick.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: A sample debugging session with MINIMON

Post by chysn »

I'm working on an automated system for generating knapsacks, and it seems like it's better to put the BRK up front in all cases. The generator requires only a knowledge of instruction length (which it already has access to as part of an assembler package), and no back-tracking is necessary.

I was on my way to typing out the whole algorithm, but I'll do that later in case I find I need to change something along the way. But the general idea is to start the knapsack with BRK,NOP, and then add at least three bytes (and at most five bytes) from the breakpoint. At the end, JMP back to the breakpoint plus the number of bytes copied (3, 4, or 5).

In my code, I'll use JMP/JMP instead of JSR/RTS. This allows the knapsack to freely use stack-based instructions (including RTS itself). When you're done with the knapsack, the system always replaces three bytes. (Update: Changed this to pad with NOPs)

The generator throws an error if it must knapsack a relative branch instruction, which would crash the system.

For the example in the original post, the knapsack would look like this:

Code: Select all

.033C BRK
.033D NOP
.033E LDA #$EC
.0340 LDY #$02
.0342 JMP $02D4 ; Return to main code ($02D0 + 4)
And the main code is updated to this:

Code: Select all

.02CD JSR $DBD4
.02D0 JMP $033C ; Enter knapsack
.02D3 NOP
.02D4 JSR $DB0F ; Return point
The functionality is identical, but it's easier (for me) to generate this kind of knapsack automatically, because there's no need to "back in" the code, or try to decide if the BRK needs to go between instructions.

Note: Incidentally, the reason I'm spending the time writing a generator is because I have found this technique valuable. The ability to pick up where you left off has proven to be more powerful than I expected. It really saved me some time over the weekend.

Other Note: When I'm done, I'll share my code and it can be used with MINIMON. You'll just need to add a subroutine that takes an opcode in A and returns the number of bytes the instruction takes in X, and sets the Carry flag if it's a relative branch instruction.
Last edited by chysn on Mon Jul 13, 2020 8:06 pm, edited 2 times in total.
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: A sample debugging session with MINIMON

Post by chysn »

Here's the knapsack generator tool. It should be easy to integrate into any system; the SizeOf subroutine will need to be replaced (see the comments), and the entry to Main might need to be modified.

I did wind up padding the breakpoint code with NOPs. While technically optional, this guarantees that the disassembly remains readable.

Code: Select all

*=$a000

; BASIC Routines
UND_ERROR   = $c8e3             ; ?UNDEF'D STATEMENT ERROR

; Knapsack Generator
KNAPSACK    = $033c             ; Knapsack storage (10 bytes)
BREAKPT     = KNAPSACK+10       ; Breakpoint address (2 bytes)
KNAPSIZE    = BREAKPT+2         ; Knapsack size (1 byte)
EFADDR      = $a6               ; Temp zeropage pointer to breakpoint address

; To use this code for another system--
; Replace SizeOf with a routine that takes an opcode in A and returns
; its size in X, and clears Carry if the opcode is unfound or a relative
; branch instruction
Lookup      = $7012             ; Opcode lookup routine

; Main routine entry point
; * If setting a breakpoint, its address is in EFADDR vector
; * If clearing a breakpoint, the Carry flag is clear
Main:       bcs NewKnap         ; A legal address has been provided in $a6/$a7
restore:    lda BREAKPT         ; Otherwise, restore the breakpoint to the
            sta EFADDR          ;   original code by copying the
            lda BREAKPT+1       ;   bytes in the knapsack back to the code
            sta EFADDR+1        ;   ,,
            ldy KNAPSIZE        ; Get knapsack code size (3-5, or 0)
            beq restore_r       ; Don't restore if KNAPSIZE isn't set
            dey
-loop       lda KNAPSACK+2,y    ; Move between 3 and 5 bytes back
            sta (EFADDR),y      ;   to their original locations
            dey                 ;   ,,
            bpl loop            ;   ,,
            lda #$00            ; Reset the knapsack size so that doing it again
            sta KNAPSIZE        ;   doesn't mess up the original code
restore_r:  rts                 ;   ,,

; New Knapsack
; Generate a new knapsack at the effective address
NewKnap:    lda KNAPSIZE        ; Don't install a knapsack if there's already
            bne knap_r          ;   one installed
            ldy #$00            ; (BRK)
            sty KNAPSACK        ; ,,
            lda #$ea            ; (NOP)
            sta KNAPSACK+1      ; ,,
next_inst:  tya                 ; Preserve Y against SizeOf
            pha                 ; ,,
            lda (EFADDR),y      ; A = Opcode of the breakpoint instruction
            jsr SizeOf          ; X = Size of instruction (1-3)
            pla
            tay
            bcs xfer            ; Error if branch or unknown instruction
            jmp UND_ERROR       ; ?UNDEF'D STATEMENT ERROR     
xfer:       lda (EFADDR),y      ; Move X bytes starting at Y index
            sta KNAPSACK+2,y    ; Y is a running count of knapsacked bytes
            iny                 ; ,,
            dex                 ; ,,
            bne xfer            ; ,,
            cpy #$03            ; If at least three bytes have been knapsacked
            bcc next_inst       ;   we're done
            lda EFADDR          ; Stash pointer in breakpoint storage for
            sta BREAKPT         ;   later restoration
            lda EFADDR+1        ;   ,,
            sta BREAKPT+1       ;   ,,
            sty KNAPSIZE        ; Save knapsack size for later
            lda #$ea            ; (NOP)
-loop:      cpy #$03            ; Pad code with more than three bytes with
            beq add_kjmp        ;   NOPs after the first three
            dey                 ;   ,,
            sta (EFADDR),y      ;   ,,
            bne loop            ;   ,,
add_kjmp:   ldy #$00
            lda #$4c            ; (JMP) This is the JMP to the knapsack
            sta (EFADDR),y      ; 
            lda #<KNAPSACK      ; Store knapsack JMP low byte
            iny                 ; ,,
            sta (EFADDR),y      ; ,,
            lda #>KNAPSACK      ; Store knapsack JMP high byte
            iny                 ; ,,
            sta (EFADDR),y      ; ,,
            lda KNAPSIZE        ; Calculate the return jump point (original
            tay                 ;   address + Y)
            clc                 ;   ,,
            adc EFADDR          ;   ,,
            sta KNAPSACK+3,y    ; Store return JMP low byte
            lda #$00
            adc EFADDR+1 
            sta KNAPSACK+4,y    ; Store return JMP high byte
            lda #$4c            ; (JMP) This is the JMP to the return point        
            sta KNAPSACK+2,y    ; ,,
knap_r:     rts 
    
; Size Of Instruction
; Given an opcode in A, return instruction size in X and set Carry flag
; Carry clear indicates an error (unknown or relative branch instruction)        
SizeOf:     jsr Lookup          ; Addressing mode is in Accumulator
            bcc size_r          ; Return with Carry clear to indicate error
            lsr                 ; Shift addressing mode to the right to get
            lsr                 ;   an index
            lsr                 ;   ,,
            lsr                 ;   ,,
            tax                 ; Use that index to get the size from the
            lda AddrSize,x      ;   table
            tax                 ; X is the return value
            cpx #$01            ; Carry set = success, clear = failure
size_r:     rts            
     

; Size by addressing mode high nybble
AddrSize:   .byte 0,3,2,2,3,3,3,2,2,2,2,1,0,1
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample debugging session with MINIMON

Post by Mike »

chysn wrote:Here's the knapsack generator tool.
Thank you for this! I'll surely adapt this tool for use with MINIMON.

Regarding the SizeOf subroutine: IIRC, there exists a compact code snippet which derives the instruction length directly from the opcode byte, and which is not bigger than about 2 dozen instructions. Even if the check for branch instructions is added, that routine could be an alternative to hardcoding the address of the instruction decode routine in wAx or MINIMON. As a useful side effect, this makes the knapsack generator stand-alone.

Greetings,

Michael
User avatar
chysn
Vic 20 Scientist
Posts: 1205
Joined: Tue Oct 22, 2019 12:36 pm
Website: http://www.beigemaze.com
Location: Michigan, USA
Occupation: Software Dev Manager

Re: A sample debugging session with MINIMON

Post by chysn »

Mike wrote: Thu Jul 23, 2020 3:07 pm Regarding the SizeOf subroutine: IIRC, there exists a compact code snippet which derives the instruction length directly from the opcode byte, and which is not bigger than about 2 dozen instructions. Even if the check for branch instructions is added, that routine could be an alternative to hardcoding the address of the instruction decode routine in wAx or MINIMON. As a useful side effect, this makes the knapsack generator stand-alone.
Ooh!

Update: I wasn't able to find that after a few minutes of Googling, but I've got a lot of 6502 books I can dive into.

Update 2: I think I see the pattern. I'm going to give this a shot... JSR is a weird duck.

Update 3: Blah, I failed at the two dozen bytes part of the challenge, coming in at 40-ish bytes. I might need to stare at JSR, BRK, RTS, and RTI a little harder.

Code: Select all

SizeOf:     cmp #$20            ; JSR
            beq three
            cmp #$00            ; BRK
            beq one
            cmp #$60            ; RTS
            beq one
            cmp #$40            ; RTI
            beq one
            tay
            and #%00001000
            beq two
            tya
            and #%00000101
            beq one
            tya
            and #%00010000
            bne three
            tya
            and #%00000100
            bne three
two:        ldx #2 
            .byte $3c
three:      ldx #3
            .byte $3c
one:        ldx #1
            rts           
My methodology involved converting all the opcodes into binary and sorting them by size, and then just looking at them for a while, looking down the columns. Some patterns, like the ????10?0 of 1-byte instructions were easy to spot. The two-byte instructions are a real mixed bag, and are sort of the leftovers. I may be able to gain more insight with more looking. Maybe some beer and then more looking? But for tonight, I'll have to be satisfied with this.

I should mention the rock-star role of BBEdit in this little project. It's got a regular expression search utility that highlights matches in real time. So I put in something like ^....10.0 and watch things light up. It would have been hard to do without it.

Like you said, it won't take much more work to find the relative branches and clear Carry, and then it'll be a standalone system! That's why I used ldx/top instead of ldx/rts, so I can dive straight into the relative branch code later.

Update 4: BRK, RTS, and RTI are organically mistaken for two-byte instructions. But two-byte instructions with $?0 always have either bit 4 or bit 7 set, while BRK, RTS, and RTI do not. I can use this, but I really need to spend some time with the wife.

Update 5: Determining whether it's a branch instruction is a matter of checking the opcode for $?0, and then 4 x ASL to set Carry.

Update 6: Here's a standalone knapsack generator using the standalone SizeOf.

Code: Select all

; Code Instrumention Generator
; Create a breakpoint at the specified address, and a code knapsack
; at $02f0. If there's already a breakpoint set, clear it and restore
; the code.
;
; In preparation for using this, fill $02f0-$02ff with $00
;
; (Originally assembled with xa)

*=$033c

; BASIC Routine
UND_ERROR   = $c8e3             ; UNDEF'D STATEMENT ERROR

; Knapsack Generator
EFADDR      = $07               ; Temp zeropage pointer to breakpoint address
KNAPSACK    = $02f0             ; Knapsack storage (10 bytes)
BREAKPT     = KNAPSACK+10       ; Breakpoint address (2 bytes)
KNAPSIZE    = BREAKPT+2         ; Knapsack size (1 byte)

; Main routine entry point
; If no breakpoint is set, set one by setting X as the low byte
; and A as the high byte.
;
; If a breakpoint is already set, then clear it by restoring the knapsack code
Main:       ldy KNAPSIZE        ; If a breakpoint is not already set, then
            beq NewKnap         ;   create it with the address at Y/X
restore:    lda BREAKPT         ; Otherwise, restore the breakpoint to the
            sta EFADDR          ;   original code by copying the
            lda BREAKPT+1       ;   bytes in the knapsack back to the code
            sta EFADDR+1        ;   ,,
            dey
-loop       lda KNAPSACK+2,y    ; Move between 3 and 5 bytes back
            sta (EFADDR),y      ;   to their original locations
            dey                 ;   ,,
            bpl loop            ;   ,,
            lda #$00            ; Reset the knapsack size so that doing it again
            sta KNAPSIZE        ;   doesn't mess up the original code
restore_r:  rts                 ;   ,,

; New Knapsack
; Generate a new knapsack at the address specified by X (low byte) and
; A (high byte)
NewKnap:    stx EFADDR          ; Save low byte of breakpoint
            sta EFADDR+1        ; Save high byte of breakpoint
            ldy #$00            ; (BRK) (also set Y to 0 for index)
            sty KNAPSACK        ; ,,
            lda #$ea            ; (NOP)
            sta KNAPSACK+1      ; ,,
next_inst:  tya                 ; Preserve Y against SizeOf
            pha                 ; ,,
            lda (EFADDR),y      ; A = Opcode of the breakpoint instruction
            jsr SizeOf          ; X = Size of instruction (1-3)
            pla
            tay
            bcc xfer            ; Error if relative branch instruction
            jmp UND_ERROR        ; ?UNDEF'D STATEMENT ERROR     
xfer:       lda (EFADDR),y      ; Move X bytes starting at Y index
            sta KNAPSACK+2,y    ; Y is a running count of knapsacked bytes
            iny                 ; ,,
            dex                 ; ,,
            bne xfer            ; ,,
            cpy #$03            ; If at least three bytes have been knapsacked
            bcc next_inst       ;   we're done
            lda EFADDR          ; Stash pointer in breakpoint storage for
            sta BREAKPT         ;   later restoration
            lda EFADDR+1        ;   ,,
            sta BREAKPT+1       ;   ,,
            sty KNAPSIZE        ; Save knapsack size for later
            lda #$ea            ; (NOP)
-loop:      cpy #$03            ; Pad code with more than three bytes with
            beq add_kjmp        ;   NOPs after the first three
            dey                 ;   ,,
            sta (EFADDR),y      ;   ,,
            bne loop            ;   ,,
add_kjmp:   ldy #$00
            lda #$4c            ; (JMP) This is the JMP to the knapsack
            sta (EFADDR),y      ; 
            lda #<KNAPSACK      ; Store knapsack JMP low byte
            iny                 ; ,,
            sta (EFADDR),y      ; ,,
            lda #>KNAPSACK      ; Store knapsack JMP high byte
            iny                 ; ,,
            sta (EFADDR),y      ; ,,
            lda KNAPSIZE        ; Calculate the return jump point (original
            tay                 ;   address + Y)
            clc                 ;   ,,
            adc EFADDR          ;   ,,
            sta KNAPSACK+3,y    ; Store return JMP low byte
            lda #$00
            adc EFADDR+1 
            sta KNAPSACK+4,y    ; Store return JMP high byte
            lda #$4c            ; (JMP) This is the JMP to the return point        
            sta KNAPSACK+2,y    ; ,,
knap_r:     rts 
    
; Size Of Instruction
; Given an opcode in A, return instruction size in X and set Carry flag
; Carry set indicates a relative branch instruction       
SizeOf:     cmp #$20            ; JSR
            beq three           ;   is three bytes
            tay                 ; Save opcode in Y for repeated testing
            and #%00001000      ; This bit pattern usually means two bytes
            beq two_cand        ; ,,
            tya                 ; The pattern ?????010 always means one byte
            and #%00000101      ;   when the two-byte test above fails
            beq one             ;   ,,
            tya                 ; If the previous tests fail, this pattern
            and #%00010000      ;   always indiates three bytes. Really testing
            bne three           ;   for ???11???
            tya                 ; As does this one. Really testing for
            and #%00000100      ;   ????11??
            bne three           ;   ,,
two_cand:   tya                 ; Of the two-byte instructions that match
            and #%00001111      ;   ????0000, all of them have either bit 4 or
            bne two             ;   bit 7 set. If that's not the case, then it's
            tya                 ;   actually a one-byte instruction like BRK,
            and #%10010000      ;   RTS, or RTI
            beq one             ;   ,,
two:        ldx #2              ; Set to two bytes and fall through with TOP
            .byte $3c           ; ,, (skip word)
three:      ldx #3              ; Set to three bytes and fall through with TOP
            .byte $3c           ; ,, (skip word)
one:        ldx #1              ; Set to one byte and fall through with TOP
            clc                 ; Clear carry indicates success
            tya 
            and #%00001111      ; If the instruction low nybble is 0, it's a
            bne size_r          ;   branch instruction if and only if bit 4
            tya                 ;   is set. 4xASL so sets Carry, and indicates
            asl                 ;   an instruction that can't be knapsacked
            asl                 ;   ,,
            asl                 ;   ,,
            asl                 ;   ,,
size_r:     rts
VIC-20 Projects: wAx Assembler, TRBo: Turtle RescueBot, Helix Colony, Sub Med, Trolley Problem, Dungeon of Dance, ZEPTOPOLIS, MIDI KERNAL, The Archivist, Ed for Prophet-5

WIP: MIDIcast BASIC extension

he/him/his
User avatar
Mike
Herr VC
Posts: 4841
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Re: A sample debugging session with MINIMON

Post by Mike »

The SizeOf routine I have seen once greatly resembles your version. There it also was noted, that JSR was an oddball regarding the instruction length (i.e. not fitting in the pattern). BRK actually was handled as a two byte instruction.

The tail of your routine could probably be shortened like thus:

Code: Select all

.SizeOf
 LDX #$03
 CMP #$20
 BEQ three
[...]
.one
 DEX
.two
 DEX
.three
 RTS
;)
Post Reply