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.
* 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.