A sample debugging session with MINIMON

Basic and Machine Language

Moderator: Moderators

User avatar
Mike
Herr VC
Posts: 4849
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: 4849
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: 4849
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: 4849
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: 4849
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: 4849
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
;)
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 »

That’s a great idea, but I’ll have to rearrange the flow a bit. I’d like to shave off enough bytes (7, I think) to fit both the generator and knapsack data in the cassette buffer.
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 »

Mike wrote: Fri Jul 24, 2020 12:55 am The tail of your routine could probably be shorted like thus:
Your technique provided enough of a savings so that I could fit the knapsack AND generator in the cassette buffer!

I also realized that I could combine the two three-byte tests into one, and two two-byte tests into one, so it's even shorter.

Code: Select all

; Code Instrumention Generator
;
; Create a breakpoint at the specified address, and a code knapsack.
; If there's already a breakpoint set, clear it and restore
; the code.
;
; (Originally assembled with xa)

*=$033c

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

; Knapsack Generator
;KNAPSACK   = A 13-byte region after the generator code
EFADDR      = $07               ; Temp zeropage pointer to breakpoint address
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
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)
            bne inst_ok         ; Error if relative branch instruction
            jmp UND_ERROR       ; ?UNDEF'D STATEMENT ERROR     
inst_ok:    pla
            tay
-loop:      lda (EFADDR),y      ; Move X bytes starting at Y index
            sta KNAPSACK+2,y    ; ,,
            iny                 ; ,, (Y is a running count of knapsacked bytes)
            dex                 ; ,,
            bne loop            ; ,,
            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    ; ,,
            rts
    
; Size Of Instruction
; Given an opcode in A, return instruction size in X
; Set Z if the instruction is a branch   
SizeOf:     ldx #3              ; X is number of bytes in instruction
            cmp #$20            ; JSR...
            beq three           ;   "THERE... ARE... THREE... BYTES!" --Picard
            tay                 ; Save opcode in Y for repeated testing
            and #%00001000      ; ????0??? 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 #%00010100      ;   always indiates three bytes. Really testing
            bne three           ;   for ???11??? and ????11??
two_cand:   tya                 ; Of the two-byte instructions that match
            and #%10011111      ;   ????0000, all of them have either bit 4 or
            bne two             ;   bit 7 set. If that's not the case, it's 1
one:        dex
two:        dex
three:      tya                 ; If the instruction low nybble is 0, and bit 4
            and #$1f            ;   is set, it's a branch, so set Z
            cmp #$10            ;   ,,
size_r:     rts

KNAPSACK:   .byte $00,$ea,0,0,0,0,0,0,0,0,0,0,0 ; BRK,NOP,etc.
Here's the test data generated from wAx's instruction table. A length of 0 here indicates that it's not an official 6502 instruction*. This can be used to verify that SizeOf's results are correct.

Code: Select all

00000000: 01 02 00 00 00 02 02 00 01 02 01 00 00 03 03 00  ................
00000010: 02 02 00 00 00 02 02 00 01 03 00 00 00 03 03 00  ................
00000020: 03 02 00 00 02 02 02 00 01 02 01 00 03 03 03 00  ................
00000030: 02 02 00 00 00 02 02 00 01 03 00 00 00 03 03 00  ................
00000040: 01 02 00 00 00 02 02 00 01 02 01 00 03 03 03 00  ................
00000050: 02 02 00 00 00 02 02 00 01 03 00 00 00 03 03 00  ................
00000060: 01 02 00 00 00 02 02 00 01 02 01 00 03 03 03 00  ................
00000070: 02 02 00 00 00 02 02 00 01 03 00 00 00 03 03 00  ................
00000080: 00 02 00 00 02 02 02 00 01 00 01 00 03 03 03 00  ................
00000090: 02 02 00 00 02 02 02 00 01 03 01 00 00 03 00 00  ................
000000a0: 02 02 02 00 02 02 02 00 01 02 01 00 03 03 03 00  ................
000000b0: 02 02 00 00 02 02 02 00 01 03 01 00 03 03 03 00  ................
000000c0: 02 02 00 00 02 02 02 00 01 02 01 00 03 03 03 00  ................
000000d0: 02 02 00 00 00 02 02 00 01 03 00 00 00 03 03 00  ................
000000e0: 02 02 00 00 02 02 02 00 01 02 01 00 03 03 03 00  ................
000000f0: 02 02 00 00 00 02 02 00 01 03 00 00 00 03 03 00  ................
* Nobody will be surprised that the illegal opcodes do not follow the patterns I found for official opcodes. Okay, I was a tiny bit surprised.
Last edited by chysn on Sat Jul 25, 2020 3:41 pm, edited 13 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
Post Reply