Crazy Commodore Tricks - Part Two
10 years ago
General
Today we're going to learn about another interesting Commodore trick that depends on another minor exploit. We're going to learn a way to make a program automatically run once it's loaded. There's a bit of technical know-how in here, but I'll try to break it down into easy steps.
First you remember from a previous post that when programs are saved, it includes the memory address to start loading from. For Basic programs, this is always the start of Basic memory. For Machine Language programs, it could be anywhere there's enough memory to contain the program. The loading routine starts at the address specified and increments the address for each successive byte until it hits the end of file marker.
Second we need to learn about how memory works in the Commodore 64. The Commodore 64 really did have 64K of RAM, despite the boot screen saying there was 39K-and-change free. The problem here is that the Commodore 64 can only address a total of 64K of memory space. If all of that was devoted to RAM, there'd be no addresses left for kernal ROM routines. No I/O routines, no keyboard reading, no screen painting... nothing but RAM.
If there really is 64K of RAM, but there's also ROM for the kernal, how does it all co-exist? The answer is bank switching. The address space is divided into 4K blocks that can be switched between ROM and RAM. Some of it, like Basic memory, is always RAM. But other parts, like kernal I/O or the Basic Interpreter are blocks of ROM that sit "on top" of RAM. When you try to read those addresses, the result is whichever bank (ROM or RAM) is currently switched in.
But a funny thing happens if you try to write to a bank that has ROM switched in: because you can't write to ROM, the RAM "underneath" it is written. Of course if you try to read back what you wrote, you'll get the ROM information unless you switch it out for the RAM.
Armed with those two bits of information, let's get to how this can be used to make a program that automatically runs when loaded. The program to auto-run is put in memory and a utility is executed to save the program to disk with a customized loading address and some extra code.
When the program is loaded, the loading address is way up high in memory, in RAM that is underneath the kernal ROM. First some utility and bootstrapping code is loaded, and then the code from the actual program is loaded after that. The load address is calculated so that the end of the program lands at address $FFFD, two addresses short of the end of memory. At this point there are four bytes left to load.
The first two go into locations $FFFE and $FFFF. These two addresses in memory are special: they're the interrupt vector. Every 1/60 of a second, the code pointed to by this vector is executed. Normally it's housekeeping stuff: flash the cursor, read the keyboard, things like that. These locations are written to point to the bootstrapping code that's been loaded. But nothing happens yet, because all this is being written into the RAM hiding under the ROM. The system is still reading the ROM and executing the standard interrupt.
So we just wrote the last two locations in memory, but we still have two bytes in the file that need to be written. Now what happens? What happens is the bug/exploit/flaw that makes this whole crazy thing work. Computer geeks know what happens when you have a register maxed out and add one more to it: it "wraps around" back to its minimum. And the same thing happens here. When the load routine tries to add one to $FFFF, it gets $0000. The last two bytes are loaded into the first two addresses in memory.
And the first to addresses in memory are also special: they control the bank switching. The last two bytes of the file are loaded into the bank switching registers and they swap out that high-memory ROM, revealing the RAM underneath where all the code was just loaded. And it also reveals the altered interrupt vector.
And within the next 1/60th of a second, the system checks the interrupt vector and runs the code at that location. Which is now... the bootstrap code. The bootstrap code copies some utility code into a safe location in RAM that's normally available and then copies the program into the location where it originally resided. After this, it jumps to the utility code. This code resets the bank switching back to normal (restoring kernal routines and the factory interrupt) and executes the program.
An automatic execution, thanks to the hijack of the interrupt vector. But that hijack wouldn't really be possible without that subtle flaw in the memory loading routine that lets a load wrap around from the top of memory to the bottom.
This is another development that trickled over into commercial programs. In the latter part of the Commodore 64's life cycle, it wasn't uncommon to find software that not only ran automatically after being loaded, but also loaded up a fast load routine for the disk drive.
Notice it was never said that the loaded code is erased out of the RAM under kernal ROM. It isn't. It's still sitting there, hidden away. Which has some interesting side effects. Once the auto-run program exits, the high ROM can be manually switched out and the program will immediately run again because the altered interrupt vector is exposed and the bootstrap code executes.
But it can be even crazier if the auto-run program happens to be written in Basic. It's possible to type NEW to clear out Basic memory, and verify the memory is clear with a LIST command. Nothing up my sleeve... manually switch out the high ROM... and not only does the Basic program immediately run, it's back in memory again! Resurrected from the dead? No, only copied back into Basic memory from the high RAM when the bootstrap code runs... but it's a startling effect!
First you remember from a previous post that when programs are saved, it includes the memory address to start loading from. For Basic programs, this is always the start of Basic memory. For Machine Language programs, it could be anywhere there's enough memory to contain the program. The loading routine starts at the address specified and increments the address for each successive byte until it hits the end of file marker.
Second we need to learn about how memory works in the Commodore 64. The Commodore 64 really did have 64K of RAM, despite the boot screen saying there was 39K-and-change free. The problem here is that the Commodore 64 can only address a total of 64K of memory space. If all of that was devoted to RAM, there'd be no addresses left for kernal ROM routines. No I/O routines, no keyboard reading, no screen painting... nothing but RAM.
If there really is 64K of RAM, but there's also ROM for the kernal, how does it all co-exist? The answer is bank switching. The address space is divided into 4K blocks that can be switched between ROM and RAM. Some of it, like Basic memory, is always RAM. But other parts, like kernal I/O or the Basic Interpreter are blocks of ROM that sit "on top" of RAM. When you try to read those addresses, the result is whichever bank (ROM or RAM) is currently switched in.
But a funny thing happens if you try to write to a bank that has ROM switched in: because you can't write to ROM, the RAM "underneath" it is written. Of course if you try to read back what you wrote, you'll get the ROM information unless you switch it out for the RAM.
Armed with those two bits of information, let's get to how this can be used to make a program that automatically runs when loaded. The program to auto-run is put in memory and a utility is executed to save the program to disk with a customized loading address and some extra code.
When the program is loaded, the loading address is way up high in memory, in RAM that is underneath the kernal ROM. First some utility and bootstrapping code is loaded, and then the code from the actual program is loaded after that. The load address is calculated so that the end of the program lands at address $FFFD, two addresses short of the end of memory. At this point there are four bytes left to load.
The first two go into locations $FFFE and $FFFF. These two addresses in memory are special: they're the interrupt vector. Every 1/60 of a second, the code pointed to by this vector is executed. Normally it's housekeeping stuff: flash the cursor, read the keyboard, things like that. These locations are written to point to the bootstrapping code that's been loaded. But nothing happens yet, because all this is being written into the RAM hiding under the ROM. The system is still reading the ROM and executing the standard interrupt.
So we just wrote the last two locations in memory, but we still have two bytes in the file that need to be written. Now what happens? What happens is the bug/exploit/flaw that makes this whole crazy thing work. Computer geeks know what happens when you have a register maxed out and add one more to it: it "wraps around" back to its minimum. And the same thing happens here. When the load routine tries to add one to $FFFF, it gets $0000. The last two bytes are loaded into the first two addresses in memory.
And the first to addresses in memory are also special: they control the bank switching. The last two bytes of the file are loaded into the bank switching registers and they swap out that high-memory ROM, revealing the RAM underneath where all the code was just loaded. And it also reveals the altered interrupt vector.
And within the next 1/60th of a second, the system checks the interrupt vector and runs the code at that location. Which is now... the bootstrap code. The bootstrap code copies some utility code into a safe location in RAM that's normally available and then copies the program into the location where it originally resided. After this, it jumps to the utility code. This code resets the bank switching back to normal (restoring kernal routines and the factory interrupt) and executes the program.
An automatic execution, thanks to the hijack of the interrupt vector. But that hijack wouldn't really be possible without that subtle flaw in the memory loading routine that lets a load wrap around from the top of memory to the bottom.
This is another development that trickled over into commercial programs. In the latter part of the Commodore 64's life cycle, it wasn't uncommon to find software that not only ran automatically after being loaded, but also loaded up a fast load routine for the disk drive.
Notice it was never said that the loaded code is erased out of the RAM under kernal ROM. It isn't. It's still sitting there, hidden away. Which has some interesting side effects. Once the auto-run program exits, the high ROM can be manually switched out and the program will immediately run again because the altered interrupt vector is exposed and the bootstrap code executes.
But it can be even crazier if the auto-run program happens to be written in Basic. It's possible to type NEW to clear out Basic memory, and verify the memory is clear with a LIST command. Nothing up my sleeve... manually switch out the high ROM... and not only does the Basic program immediately run, it's back in memory again! Resurrected from the dead? No, only copied back into Basic memory from the high RAM when the bootstrap code runs... but it's a startling effect!
Chiaroscuro
~chiaroscuro
Mmmm, the wonders of what those C-64s did..
FA+
