skip navigation

Debugging on the ATARI 2600 and Hacking Pitfall!


In this episode we demonstrate the debugging capability of the Stella emulator to trace yoru code and even hack other games.

July 19, 2023

Original Link:


Your support is greatly appreciated!

Welcome to 8-Blit, a channel covering all the tips, tricks, and everything in between for programming your own games for the Atari 2600 from 1977. In this episode, we’re going to hack Pitfall to give us unlimited lives, but first we’ll dive into a crucial programming skill, particularly for Atari 2600 programming: debugging. That’s all coming up on this episode of 8Blit!

If you were the type of person who looked past the images on the screen and imagined playing within the world illustrated on the cover art, dreaming of one day making your own games for the Atari 2600, then you’ve found the right channel. We explore the technology and history of arguably one of the most iconic game consoles ever made. It’s a flashback to when things were simple, when your parents would let you ride in the very back of the station wagon on family vacations, when the best tasting water came from the hose, and your friends would all come by to play Atari. Please take a moment to click on the Subscribe button, including that little bell, to stay up to date with new episodes. If you’d like to support future episodes capturing the intrepid spirit of Atari from the 70s and right through the 80s, please consider joining us on Patreon. More information on how you can become a patron today in the video description.

The Harvard Mark II computer, created in 1947 by IBM for the U.S. Navy, was one of the first large-scale computers in the world. It was a massive electromechanical computer that used punched paper tape and banks of rotary switches to store data and was utilized for a variety of scientific and military applications, such as calculating ballistic trajectories and creating numerical simulations. Debugging on the Harvard Mark II presented significant challenges due to the machine’s size and complexity. With thousands of moving parts, there were many opportunities for errors to occur. Additionally, the debugging process was often time-consuming, requiring programmers to manually examine the machine’s logs and output to identify bugs. These processes would take hours or even days.

It’s commonly believed the term debugging originated from an incident with the Harvard Mark II. Grace Hopper, a computer scientist and naval officer, discovered a moth trapped in a relay of the computer. After removing the moth, she taped it to the computer’s logbook and wrote “first actual case of bug being found”. However, Grace was most likely referencing Thomas Edison in 1873, when he first coined the term bug to mean a problem in the design during the development of a quadruplex telegraph system for Western Union Telegram. As a result, the term debugging has evolved to describe the process of finding and fixing errors.

The Stella Atari 2600 emulator was first created by Bradford Mont in 1996 and known at the time as Stella 96. It took the name Stella from the Atari 2600’s code name during development, which in turn was taken from the name of the manufacturer of a bicycle owned by Joe Decuir, one of Atari 2600’s original creators. Stella is an amazing application which is available on a multitude of operating systems. It accurately interprets the game’s binary ROM file and emulates the entire 2600 hardware, including the MOS 6507 processor, the TIA which handles the video output, and the RIOT chip responsible for the 128 bytes of RAM, the timers, and controller inputs. Most importantly for this episode, it also features a disassembler and debugger, allowing you to view the ROM’s disassembled code and move through the execution one instruction at a time. We’ll use these shortly to hack unlimited lives in Pitfall.

In our programming series, we’ve been using Visual Studio Code and the Atari Dev Studio for all of our examples. Unfortunately, neither of these have the functionality to execute our Atari 2600 code and step through it like we would when programming native computer applications. This is because our code is assembled using instructions that our computer processor doesn’t understand. That’s why when assembling the instructions into an Atari 2600 binary ROM file, more casually referred to as just ROM, we pass it from over an application that can emulate the target device, which in this case is the Stella emulator and the Atari 2600 hardware. You can download Visual Studio Code and the Atari Dev Studio using the links in the video description, and you can learn more about them in one of our previous episodes titled “Code Faster with the Atari Dev Studio”. I’ll provide a link to that as well.

There are a ton of features and functions built into the Stella debugger, but we don’t have time to cover everything in one episode. More detailed explanations and concepts will have their own episodes. For this episode, we’ll start with the tour of the debugger and then move on to some special features which will help us when debugging your games. Finally, we’ll look at how we can use the debugger to step through the Pitfall code and modify it to give us unlimited lives.

The first step is figuring out how to get into the debugger. For that, we simply assemble our ROM, launch Stella, and then press the back tick or grave accent key on your keyboard. This will pause execution of the ROM and present the debugger interface. There’s a lot to unpack here, but let’s start right over here where we can view the state of the 6507 registers as well as the full 128 bytes of RAM provided by the RIOT chip.

From the top, we have the program counter, which displays the 16-bit address of the currently executing instruction, much like showing the index of what line of code you’re on. Next is the stack pointer, which shows the address of the next available byte on the stack. After that are the accumulator and the X and Y index registers. These registers, as well as the stack pointer, show the current value in hexadecimal, decimal, and binary, as well as the source label or variable name from where it loaded the value. Under those is the destination label of the last write, when you store a value. These all come in handy when you’re trying to determine how each of the instructions are moving data around as you’re stepping through the code, which brings us to the processor status registers, which will show status of the last operation you performed. It has flags for negative, overflow, break, decimal, interrupt disable (which is unused), zero, and carry. These are very handy to determine if your branching logic is executing properly.

Under the register section is the RAM mapping. This section displays the hexadecimal values for all 128 bytes of RAM, showing zero page addresses between 128 to 255. Addresses below 128 are reserved for the TIA registers. You can click on an address and it will show you the label for that address as well as the value in hexadecimal, decimal, and binary. However, it’s easier just to hover over an address and a tooltip will pop up with the same information.

Over on the left, we have four tabs: prompt, TIA, I/O, and audio. We’ll talk about the prompt tab a little later, so let’s start with the TIA. Here we can see the current state of all the TIA registers. At the top, we can see if we’re currently in VSYNC or VBLANK. If so, these boxes would be filled. Below that are the values for the color of each graphic object, along with the ability to override those colors with your own in order to better see the graphic objects while debugging. Next is the collision registers for all of the possible interactions between graphic objects, along with the ability to clear the collision flag. Below that are the eight bits of the player graphics, the horizontal position on the screen, whether the bits are reflected, if the drawing of the graphic is vertically delayed, and the number of duplicates and size of the graphic. Underneath are the same properties for the missile and ball graphics. Then come the 20 bits for the playfield graphics, along with the reflect, score, and priority flags.

Next, we have the I/O. At the top are the bits for the switch A and B registers, telling you the state of the joysticks and console switches. Then comes the interval timers, the dumped ports for the paddle controllers, and latched ports for the controller buttons.

Next is the audio tab. As we learned in our last episode on sound, the Atari 2600 has two sound channels with three registers each: frequency, sound type, and volume.

Now for one of my favorite parts: the disassembly view. This section will convert the opcodes in a binary ROM file into 6502 assembly instructions, not only for your game, but for any Atari 2600 game. This is useful to analyze how the mechanics in other games work.

The first column shows the ROM addresses of the instruction. You can see the addresses jump up by one, two, or even three for each instruction. This all depends on how many bytes the opcode is. A simple increment opcode uses only a single byte, but loading a value into the accumulator from an offset of a memory address will use three bytes.

The second and third column show the assembly instructions along with its operand. Column 3 is handy when it comes to debugging your timings. The numbers here are how many machine cycles the instructions take to execute. In cases when the instruction is a branching statement, the two numbers represent the machine cycles used when a branch is not taken and when one is taken.

Column four is interesting. These are the instructions and operands represented in hexadecimal. From here, you can see instructions like loading the accumulator can have several different opcodes. A direct load from a memory location is hex A2, while the same instruction that loads from an offset of a memory location is hex BD. Loading an immediate value is hex A9. Your assembler analyzes the context of the instruction and determines which opcode is the correct one to use in your assembled ROM. Technically, you can use a hex editor to program games completely by entering in your own series of opcodes, without the help of an assembly language and an assembler. That is, if you feel programming in assembly for the Atari 2600 isn’t already challenging to begin with.

In the upper right hand corner, we have our transport controls, which help us move through the execution of the code. Step will advance execution to the next instruction. Trace will do the same, except it will jump over subroutines. Scan will jump to the next scan line. Frame will jump execution to the start of the next frame. If you want to continue executing the code, then you can use the Run button to close the debugger window and continue the game. This can also be done by pressing the backtick key.

The debugger also has a rewind feature. By pressing the top arrow button, you can rewind the previous action you took. If it was a step, then execution would move back in time to the previous instruction. For trace, scan, and frame, it would move back to the instruction before using those actions. The bottom arrow button will unwind your rewind, moving execution forward. You can configure how many actions the debugger will remember in the settings.

One last item to cover for the basic features of the debugger is the change highlight. You may have noticed some of the values are highlighted in red. When a processor register, RAM address, or almost any other value changes, the debugger will highlight them for you, so you can visually review the effect of each instruction. One thing to note, however, is that it won’t highlight a value just because the address or register is written to. It’s only highlighted when the value changes. If you write the same value that already exists, it won’t be highlighted. Now that we’ve covered many of the basic features of the Stella debugger, let’s move on to some special features that can greatly improve our debugging experience.

Let’s talk about the console info overlay. You can enable this in the developer settings. You can get there by clicking on Options and then Developer. When this is enabled, your game screen will include an overlay in the top left corner detailing the number of scan lines you’re generating for each frame, the frequency, the region, the number of frames per second, how fast the emulator is running, the size of the ROM bank, and if Stella is currently using developer or player settings. These are all incredibly handy when programming your game. I often take note of the number of scan lines and the frames per second to ensure that I don’t have a rogue loop or branch that’s messing with my timing.

Also in the developer settings are two other handy settings. The first tells Stella to start up as if the Atari 2600 is in a random state. It does this by inserting random values into the ROM, RAM, the TIA, and the CPU registers. We do this because when you turn on a real Atari 2600, you can’t expect the memory to have been cleared. It’s going to have a bunch of random values like static throughout. This is why we would usually zero out the RAM and registers as the first thing we do when the game is executed.

The second setting is to turn on debug colors. This can be found in the video tab. This setting will override all the graphic colors in your game, so you can easily visualize six different graphic objects on the screen. In this example, the playfield graphics are represented in purple, while player 0 is black and player 1 is yellow. If you ever wanted to know just how your favorite game composed its screen, then this will break it all down.

Another special feature is the ability to load the DASM assembler’s symbol and list files. This greatly improves the disassembly view when debugging your own games. But first, we’ll need to tell the assembler to generate these files. We can easily do this in Visual Studio Code and the Atari Dev Studio by going into the settings and locating the option “Generate Debugger Files”.

Now, when we build and launch our code in Stella, the disassembly view will include your labels when it displays ROM addresses as well as instruction operands. This makes it much easier to read and understand the execution. Without the symbols and list files, the debugger doesn’t know any of your labels, so it shows the memory address, which is a lot harder to trace in your code.

The last special feature I’ll cover here is the ability to manually change values in the processor registers, the RAM view, and just about any value. Double clicking on the value will make the box editable, so you can change the value to whatever you want. This can be handy to test changes to scores, how many lives you have left, what level you’re on, or even if you have an item or not. Rather than setting the values in your code before executing it, you can play with them during execution.

Now, we can talk about a couple of advanced features. We’ll start with prompt commands. From the prompt tab, you can enter the commands manually. There are a ton of commands or expressions to execute here, but for now we’ll only touch on two. In a future episode, we’ll cover this more in depth. The first one is palette. If you type in palette and hit enter, a handy color reference chart will appear, showing the palette for the current region. The second is reset. Typing in reset will do just that. It will reset the console and restart the ROM. There are a ton of prompts you can use that cover all and expand on memory of the user interface’s functionality.

The second advanced feature, and the last we’ll cover this episode, is editing the machine code in the disassembly view. As we can see here, the last column of the disassembly view shows the actual opcodes and operands created by the assembler. These are one-to-one instructions executed by the processor. Usually, the assembler will determine the correct opcode to use for an instruction based on the context. This provides the assembly programmer a minor degree of abstraction from the variety of possible opcodes to choose from.

We can modify these codes in the disassembly view by double clicking on them. This will put that line into edit mode. There are some limitations to the modifications you can make here. First, you’re limited to entering 6502 opcodes in hex, along with the operands, also in hex. There’s no assembler, so you can’t use assembly instructions. We’ll have to determine which opcodes to use ourselves. We can do this by opening up a 6502 manual or look it up online. I often go to, which provides descriptions and details on all of the instructions, along with their opcodes and its length in bytes. I’ll include a link in the episode description.

Second, if the line has an opcode with two operands, that’s three bytes. Whatever you replace it with will also need to be three bytes. Likewise, if the line only has an opcode, then you can only replace it with another opcode that does not require any operands. This is because all of the addresses of all of the instructions, jumps, and data are hard coded, baked into the binary ROM. We don’t have the advantage of an assembler to recompute all the addresses before it writes the binary ROM. To put it simply, if you remove or even add a single byte, your ROM will be completely corrupted.

Now that we know how to modify the opcodes and operands, let’s see if we can put it to good use and give Harry unlimited lives in Pitfall. We’ll start by loading up a Pitfall ROM. Harry starts off with three lives. You can see how many extra lives are in the top left corner of the screen. If we send Harry to an untimely death , you can see the number of extra lives changes. If we do this a couple more times, we’ll see Harry’s final demise.

That means the game is over and we need to reset the game to start back at the beginning with three lives. If we want to make Harry immortal, then we need to determine where in the code he dies. For this, we’re going to use a pretty unique feature of the disassembly view.

Stella has two levels of disassembly. The first is dynamic analysis, done by the emulation core, which does its tracking as the code executes. The second is static analysis, which fills in the gaps in sections that haven’t been accessed at runtime. During the dynamic analysis, code done during static analysis is identified with an asterisk. One property of the code tracked using the static analysis is that these can be features of the game that are hidden behind switch presses and in-game events, such as running, jumping, climbing, and even dying.

We know that when Harry dies, the screen pauses and music plays. This isn’t something that happens during normal screen drawing in the display kernel, and we can assume that it’s probably a fairly large section of code. So the first thing we’re going to do is scroll through the disassembly and put a breakpoint at the beginning of any sections with maybe 10 or more lines with asterisks.

Now, let’s continue execution of the game and see what events we can capture. We can still move right and left and jump. Moving off the screen triggers some event, but it’s not dying, so let’s remove that breakpoint. Going back and forth no longer triggers a breakpoint, and neither does swinging on the vine. I think it’s time we killed off our hero.

So, this event triggers on Harry’s death, but is it the code that we wanted? Let’s examine it a bit. The first instruction loads a value from the address RAM 80 into the accumulator and then checks if it’s zero. If it’s not zero, then it shifts the value in the accumulator to the left twice and then stores the accumulator back into the RAM 80 address. Let’s have a look at the values being modified in these five instructions.

When Harry dies the first time, the value in RAM 80 is hex A0. After shifting that value twice to the left, we have the value 80. If we look at the original value and the modified value in binary, we can see that A0 has two bits set, while 80 has a single bit set. Could this be the value used to display the extra lives graphic? Let’s kill Harry a couple more times.

Now, the value changes from A0 to just zero. With his last life, we hit that branch instruction, which was testing for a zero value. This sounds like we found the right spot, but what can we do with this? There are a few options here.

We could always load the accumulator with an immediate value, so the branch instruction never reads zero. For that, we need to change the opcode from A5, which loads a value from zero page RAM, to A9, which loads an immediate value.

This seems to work nicely. What else can we do? We could also store an immediate value back to the RAM 80 address, which will always reset the extra lives each time he dies. Or, we can remove the two left shift instructions. But how can we remove instructions without breaking the ROM? Won’t that mess up the addresses baked into the assembled code? Yes, it would, but not if we replace those instructions with something innocuous. For this, we can replace the one byte left shift instructions with one byte no operation instructions. What does a no operation instruction do? Exactly what you would expect: nothing. The processor just moves on to the next instruction. In 6502 machine code, the no-op instruction is hex EA.

Now that we’ve hacked Pitfall and made Harry immortal, you can save the hacked ROM by using the prompt command save ROM.

There you have it: how to hack 40-year-old Atari 2600 games for fun and profit. This episode provided a guided tour of the debugger within the Stella emulator, as well as some handy features you can use to debug your own games. It takes a while to get used to the new debugger, so I encourage you to try it out for yourself. You’ll be up and running and debugging your old code in no time.

If you’re already familiar with the Stella debugger or you’ve been using it for years, let us know any tips or tricks you’ve picked up in the comments. If you found the video interesting, I’d appreciate it if you’d hit that like button and share this episode with your friends or online communities like Facebook, Reddit, Discord, or anywhere retro gaming and programming enthusiasts hang out.

If you haven’t already done so, please subscribe to the channel and hit that bell to make sure you’re notified when a new video comes out. And finally, help keep history alive by supporting future episodes and becoming an 8-Blit patron. These videos are a ton of work and your contribution is greatly appreciated. Information on how you can become a patron is in the video description.

That’s all for now. Thanks for watching and I’ll catch you later.

Back to top of page