A sample debugging session with MINIMON

Basic and Machine Language

Moderator: Moderators

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: Sun Jun 06, 2021 2:37 pm I already linked to 'relocate.prg' in an earlier post in the thread, but nonetheless replicated the link in my latest post.
Thanks. I managed to find it by reading this thread from the top, but it's good that it's now in the relevant post.

This must mean that the release of MINIMON is on the horizon, right? After all, why revisit a solution without a problem by way of documentation without a product? :D
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:This must mean that the release of MINIMON is on the horizon, right? [...]
PM sent. :)
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 »

On a related note, I finally found the implementation of the SizeOf routine I originally had in mind when I wrote this, which is again different from chysn's or Jim Butterfield's method.
Mike wrote: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.
This is the routine used in C'mon by Bruce Clark, mirrored in Ed Spittles' (BigEd of 6502.org) github repository (link):

Code: Select all

; Return instruction length - 1 (note that BRK is considered to be a 2 byte
; instruction and returns 1)
;
GETLEN LDY #1
       CMP #$20  ; if opcode = $20, then length = 3
       BEQ GETL3
       AND #$DF
       CMP #$40
       BEQ GETL1 ; if (opcode & $DF) = $40, then length = 1
       AND #$1F
       CMP #$19
       BEQ GETL3 ; if (opcode & $1F) = $19, then length = 3
       AND #$0D
       CMP #$08
       BNE GETL2 ; if (opcode & $0D) = $08, then length = 1
GETL1  DEY
GETL2  CMP #$0C
       BCC GETL4 ; if (opcode & $0D) >= $0C, then length = 3
GETL3  INY
GETL4  RTS
This one doesn't (need to) reload the accumulator between the checks, but the individual checks are somewhat more complicated. With LDY #2 at the start, it would return the instruction lengths 1 to 3 as in the other routines featured in the thread here.

With RTS excluded, this one weighs in at 30 bytes, which is more than chysn's 28 bytes, so there ...
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 »

Oh, thanks for tracking that down!

I think it's fascinating that there's one set of bit patterns that treats BRK as a 1-byte instruction and a totally different set of patterns that treats BRK as a 2-byte instruction, while maintaining the other relationships! I assume that if I had considered BRK to be a 2-byte instruction, I would have found that pattern instead.

Likewise, Brian Clark surely would have found the 1-byte set of patterns. I've got a lot of old 6502 books, and they all list BRK as 1 byte. So what was it about C'mon that required BRK to be treated as 2 bytes?

I think the answer here is that the instruction length is needed as part of a single-stepping system in C'mon. Since BRK pushes PC+2, the 2-byte assumption helps manage C'mon's "knapsack." I plan to study this code more, since I'm interested in the single stepping.
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 »

A few days ago, I used MINIMON to "train" the game Get More Diamonds, i.e. provide the player with infinite lives. You normally start with 3 lives. Even though the game puts extra lives into the levels (displayed as hearts), one would rather want to concentrate on the key-door puzzles. ;)

"Game enhancing" tools on other platforms/consoles use a particular technique to determine the address where a game stores the number of lives - either to provide a much higher number or alternatively, to eliminate the instruction(s) that decrement that number. With the game "frozen" right after start, one enters the current number of lives. The tool stores a list of addresses, which gets narrowed down by resuming the game, losing one life and scanning that address list for one life less (and repeat those steps, in case still more than one candidate was left). On a platform with limited RAM resources however, that technique is not feasible.

GMD uses a reduced screen size, 16 by 16 characters. The first text line displays a score, the number of lives and some other data. We assume the number of lives is stored somewhere as byte, probably in the zeropage. We start the analysis by loading the game, starting up MINIMON and loading the transient ASCII display tool - nothing wrong with getting an overview first:

Image

BASIC memory runs from $1201..$7FFF with a RAM expansion of +24K or more, and GMD requires +35K on its "package". We change the redisplayed register dump so PC=$02A3 (start of the ASCII display routine) and AC=$12 (display page $12xx) ...

Image

... and run the tool with the G command:

Image

We are very lucky here - right at the start there is a dead giveaway: the addresses $1221..$1230 contain the "blue print" of the score display!

It is quite probable, that this blue print is accessed with an indexed address mode with either $1221 or $1220 as base address. The command H 1200 7FFF 21 12 returns with 3 hits, and we disassemble from those addresses, minus 1 to account for the opcode byte:

Image

The first disassembly already shows the loop that (upon game start) writes the status line to the screen. Note the number of lives is provided for display at screen address $1008. The next step thus is to hunt for a store instruction to $1008. The pattern to search for is determined by a "test assembly" of STA $1008 to a convenient place, like $033C. This results in $8D $08 $10. We get only one hit with the H command, and disassemble a few instructions before that, which then exposes where the number of lives is actually stored - note the LDA $3F instruction at address $3275:

Image

The next search pattern is for DEC $3F (we follow up with BRK to clean up the "test assembly" behind the DEC instruction), these are the two bytes $C6 $3F. Upon executing that H command we again only get one hit at $3338 ...

Image

... which upon disassembly shows a typical "check for 0 lives left" test. :mrgreen:

Only thing left to do: replace the DEC $3F instruction with 2 NOPs, return to BASIC and ask BASIC where the equivalent POKEs are supposed to go, and with what value.

Image

Thus the two POKEs POKE13112,234:POKE13113,234 provide infinite lives in the game. Enter these POKEs after LOAD and before RUN.
User avatar
orion70
VICtalian
Posts: 4341
Joined: Thu Feb 02, 2006 4:45 am
Location: Piacenza, Italy
Occupation: Biologist

Re: A sample debugging session with MINIMON

Post by orion70 »

Excellent Mike, very instructive! If you go on like that, chances are that a complete donkey like me will end up learning something :mrgreen:
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 »

Well, GMD gave me a good opportunity to showcase the utility of MINIMON for analysing 3rd party code of a game.

The exact way to get infinite lives will surely look different for other games, but it will likely involve the search for the memory address where the value is held, and then eliminate the instruction(s) that decrement the number of lives. In a nutshell, this is what I do with GMD as example in the post above.

...

For completeness, here's the code of the ASCII dump utility. It uses the breakpoint facility of MINIMON, self-increments the page number and places a G command into the keyboard buffer, so repeated presses of [RETURN] quickly page memory (download):

Code: Select all

.02A1 BRK
.02A2 NOP
.02A3 LDY #$00     ; G 02A3 to start the utility
.02A5 STY $FB
.02A7 STA $FC      ; AC contains high byte of memory page to display
.02A9 LDA #$0D
.02AB JSR $FFD2    ; start dump with ASCII 13 (^= carriage return)
.02AE LDA $FC
.02B0 JSR $02EA    ; output high ...
.02B3 TYA
.02B4 JSR $02EA    ; ... and low byte in hexadecimal
.02B7 LDA #$3A
.02B9 JSR $FFD2    ; delimit address with a colon (ASCII 58)
.02BC LDX #$10
.02BE LDA ($FB),Y  ; load byte from memory
.02C0 INY
.02C1 PHA
.02C2 AND #$7F     ; is byte
.02C4 CMP #$20     ; printable?
.02C6 PLA
.02C7 BCS $02CB    ; yes
.02C9 LDA #$2E     ; no - replace by full stop (ASCII 46)
.02CB JSR $FFD2    ; print character in accumulator
.02CE DEX
.02CF BNE $02BE    ; until 16 characters per line 
.02D1 LDA #$0D
.02D3 JSR $FFD2    ; advance to next line
.02D6 CPY #$00
.02D8 BNE $02AE    ; until 256 bytes displayed
.02DA LDA #$47
.02DC STA $0277    ; place "G" into keyboard buffer
.02DF LDA #$01
.02E1 STA $C6      ; one outstanding keypress
.02E3 INC $FC
.02E5 LDA $FC      ; advance page
.02E7 JMP $02A1    ; and return to MINIMON. [RETURN] shows next page.

.02EA PHA          ; save AC to stack for later use
.02EB LSR          ; extract
.02EC LSR          ; :
.02ED LSR          ; :
.02EE LSR          ; upper hexadecimal digit
.02EF JSR $02F5    ; print digit
.02F2 PLA          ; retrieve original AC from stack and ...
.02F3 AND #$0F     ; ... extract lower hexadecimal digit
.02F5 CMP #$0A     ; is it 0..9?
.02F7 BCC $02FB    ; yes
.02F9 ADC #$06     ; no, adjust for A..F
.02FB ADC #$30     ; add ASCII base value of "0"
.02FD JMP $FFD2    ; print character in accumulator
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 »

One of the obvious applications of a monitor is the creation of a backup copy. Here, I take "Las Vegas 20" by Anirog as example. The game comes along on tape, with forced autostart and with a fast tape loader, of course the STOP key and STOP/RESTORE are also blocked.

So then, let's roll up the sleeves and play hardball: instead of having LOAD start through into the fast tape loader, we just load in the header, with SYS 63407, and then start MINIMON with SYS 38912:

Image

The fast tape routine now already resides in the tape buffer, behind the file name. As we inspect the tape buffer at $033C, we see this:

Image

The $03 byte in $033C signifies that the following start and end addresses always determine where the payload is being loaded to, even if the secondary address is 0! That means, even a simple LOAD + [RETURN] forces the autostart. The payload is supposed to load to $0300 (inclusive) up to $0334 (exclusive). That address range contains the BASIC and KERNAL vectors, this way the fast tape routine takes over the computer. We don't want this to happen, for this reason we instead load the payload to $1300 by changing the high bytes at $033E and $0340 to $13:

Image

Actually, for later we also need to know the execution start address of the fast tape loader. With SYS 62980, we load the payload to $1300 ...

Image

... and inspect it there:

Image

Wow! The fast tape loader just makes short work of the BASIC and KERNAL vectors! Basically, most of them are overwritten with $0351 - you can't make the execution start address more obvious.

As a side note: the KERNAL tape routines run under interrupt control. Here, the values in $0314/$0315 are kept 'life' for the load action - normally, during SAVE, they contain other values! The payload thus had been written to tape separately from the header, in a similar fashion we both loaded separately.

Now for a sharp look into the fast tape loader itself: if we just start it with G 0351 we would have gained nothing, as it then still would start through to the game:

Image

The sub-routine at $03AF initializes the fast tape loader, and then each call of JSR $03D8 loads a single byte from tape. The first four bytes are start and end address in memory, the start address is stored as (running) pointer to $C1/$C2, the end address goes to $2D/$2E ... is this a BASIC program going to be loaded?

Continuing the disassembly:

Image

Indeed. The instructions at $0397..$03A0 contain a variant of the standard idiom to run a BASIC program from machine code (re-link BASIC text, CLR, set TXTPTR, re-enter interpreter loop).

JSR $FD52 at $0386 and JSR $E45B at $038C restore the KERNAL and BASIC vectors, respectively. Before those, JSR $FDF9 at $0383 takes care of the VIA I/O registers. So, everything's in order again. Though, the fast tape loader then again manipulates $0328 (STOP vector) and $0302 (BASIC warm start vector) - we don't want this and use F 038F 0396 EA to remove the offending instructions.

As a check, we also fill BASIC memory with the value $2A (F 1203 7FFF 2A) and, as a last preparation, the fast tape loader may return to MINIMON by placing a JMP $9800 at $03A0. :mrgreen:

Image

G 0351 and drum roll ...

:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:

... and here's MINIMON back in charge, with the game in memory. Let's take a look at $2B..$2E for BASIC start and "end of program" (a.k.a. "start of variables"):

Image

$2D/$2E point to $47F2 and the memory dump shows a $2A at $47F2 after two zeroes in $47F0 and $47F1, that's looking good. Back to BASIC, ...

Image

... using SAVE for the backup copy. With LIST, we wave the BASIC stub a quick "Hello!" and with RUN we allow ourself a nice game of Slot Machine. :)
User avatar
beamrider
Vic 20 Scientist
Posts: 1452
Joined: Sun Oct 17, 2010 2:28 pm
Location: UK

Re: A sample debugging session with MINIMON

Post by beamrider »

Thanks Mike - I'll work through that myself later...
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 »

Here is a more application-related use of the relocate tool: porting a game from +3K RAM expansion to +8K.

The main issue with +3K games is they require RAM present at $0400..$0FFF, which you will not get from your average +8K RAM expansion (or +16K/+24K/+32K, for that matter). So unless you are one of the lucky ones to own a full RAM expansion (+35K), you either have to swap your bigger expansion with the small +3K RAM expansion - or produce a version of the game that runs with any of the bigger RAM expansions instead. :wink:

Misfit's "Santa's Sadistic Sleigh Service" serves as an ideal example here.

A sharp look into the game with MINIMON reveals that the code part is fully contained in the range $0400..$1A5E. Then follows a copy of the title screen, and finally the user defined characters at $1C00. A more detailed analysis also is re-assuring: there are no critical hidden pointers present (in tables or in form of immediate operands containing address parts).

We will relocate the code part from $0400 to $2400, but leave the data (including the character set) where it is.

First thing - with a full RAM expansion - is to restrict RAM as if only a +3K RAM expansion was present. Otherwise, the screen at $1000 would overwrite part of the game program code as we work on it:

Image

The VIC-20 resets into "6655 BYTES FREE" and we load in the game as usual, enter the monitor and as first action we clear the upper part of RAM with the command "F 2000 7FFF 00". You find the relocate program in an earlier post in the thread here. It is loaded as transient tool with the L command.

Image

The T command transfers the code part from $0400 to $2400.

Image

Next thing then is to set up all pointers for the relocate tool: the relocation begins ($55) at $2400, the limit ($57) is at $3A5F, the range of addresses to relocate starts ($59) with $0400 and ends ($5B) at $1A5F - all these addresses are offset ($5D) by $2000.

Image

And ... GO!

As intermediate step, we now make a "raw" memory dump from $1A60..$3A5F, saving along the data part, a copy of the current screen (we'll take care of that) and the relocated code part and then, we reset the VIC-20. This reverts the computer to the full RAM configuration, with 28159 BYTES FREE being shown.

Image

Within the monitor, we now re-init the entire BASIC memory with all zeroes to ensure we will not save along any 'debris' with the final result. Then we prepare a new BASIC stub, so the game still can be started with RUN. LOAD"RAW.PRG",8,1 outside the monitor correctly sets the pointer to the start of variables in 45/46 (a.k.a. "end of program") to the desired value for the SAVE command later.

Image

We assemble a little bit of extra code that is necessary to move the screen to $1E00 before the main game code is entered at $240D. This fixes a small omission in the game ($9002 is not properly initialised). As penultimate step, we clear out the unnecessary dump of the screen memory at $1E00 with "F 1E00 1FFF 00". X returns to BASIC.

Image

That is all. SAVE "SSSS-8K.PRG",8 saves the +8K version of the game to disk.

Producing a crunched version of the game executable left as an exercise to the reader. :wink:
Post Reply