Troubleshooting a Suite8080 assembler bug
The iLoad feature of the Z80-MBC2 homebrew Z80 computer allows uploading binary code that runs on the bare metal.
I thought it would be fun to try iLoad with some Intel 8080 code generated by the
asm80 assembler of Suite8080, the suite of 8080 Assembly cross-development tools I'm writing in Python. But the code crashed the Z80-MBC2, uncovering a major
It all started when, to practice the toolchain and process, I started with a Z80 Assembly demo that comes with the Z80-MBC2, which prints a message to the console and blinks one of the devices's LEDs. I assembled the program with the zasm Z80 and 8080 assembler, uploaded it via iLoad, and successfully ran it.
Next, I ported the blinking LED demo from Z80 to 8080 code and assembled it with
asm80. But when I ran the demo on the Z80-MBC2, it crashed the device.
The baffling crash left me stuck for threee months, as I had no tools for debugging on the bare metal and there were only a few vague clues.
I carefully studied the less than a hundred lines of code and they looked fine. To isolate the issue I cut the code in half, leaving the part that prints a message to the console, and transforming the blinking demo into this
bare.asm hello world for the bare metal:
OPCODE_PORT equ 01h EXEC_WPORT equ 00h TX_OPCODE equ 01h EOS equ 00h CR equ 0dh LF equ 0ah org 0h jmp start ds 16 stack: start: lxi sp, start lxi h, message call puts hlt message: db CR, LF, 'Greetings from the bare metal', CR, LF, EOS puts: push psw push h puts_loop: mov a, m cpi EOS jz puts_end call putc inx h jmp puts_loop puts_end: pop h pop psw ret putc: push psw mvi a, TX_OPCODE out OPCODE_PORT pop psw out EXEC_WPORT ret end
The constants at the beginning define the addresses of the output ports, the opcode for sending a character over the serial line, and a couple of control characters. Next, the program sets up the stack and iterates over the output string to print every character.
The simplified demo program still crashed the Z80-MBC2, forcing me back to the drawing board.
Then I had an epiphany. What if the binary code
asm80 generates is different from
I fired up the
dis80 disassembler of Suite8080 and compared the output of the assemblers. Sure enough, the difference jumped at me: the destination addresses of all the branches after the label
message are off by 5 bytes.
The instructions branch to addresses 5 bytes lower, so the call to
puts executes random string data that chrashes the device. The last correct address
asm80 outputs is that of the label
message. The address of the next one,
puts, is wrong and leads to the crash.
Indeed, the same demo code assembled with
zasm ran fine on the Z80-MBC2 and printed the expected message. This confirmed my hunch.
The next step is to find the bug in the Python source of
asm80, which I'm developing with Replit. Although Replit provides a debugger, I won't use it. The tool is not well documented and I'm not sure how it works. In addition the Replit debugger is best suited to code started from the run button. This is inconvenient for command line programs like the Python scripts of Suite8080.
Therefore, I'll take the opportunity to use Python's native debugger
pdb, which I always wanted to try in a real project. I played with
pdb a bit and it looks easy to use, with all the commands and options handy.
Let's see if
pdb can help me pinpoint the bug in the Python code.