Disassembling Firmware for a Ham Radio TNC: Part 2
Aha! I think I figured something out. I did some web searching to see if I could figure out how to identify the firmware entry point address. I found a StackExchange post that mentioned something called a “reset vector”. It sounds like a reset vector is a pointer that the CPU looks at to tell it where to begin program execution. The reset vector location is CPU-specific.
I went back to the Motorola datasheet and searched for information about reset vectors. I found that there are three reset vectors for the 68HC11 architecture:
- COP Failure
- Clock Monitor Fail
- RESET
I guess the three different vectors are used in different cases. Depending on the specific trigger, a different entry point can be defined. The datasheet also listed the memory locations of the reset vectors:
0xFFFA - 0xFFFB : COP Failure
0xFFFC - 0XFFFD : Clock Monitor Fail
0xFFFE - 0xFFFF : RESET
RESET is for power-on reset, so I think that’s the normal entry point for the program. Based on the other reset vector names, I assume they are triggered after some failure state. I went back to my firmware image and checked the data in memory at those locations:
0000ffc0: ffff ffff ffff ffff ffff ffff ffff ffff ................
0000ffd0: ffff ffff ffff 814a 8008 8002 8002 8002 .......J........
0000ffe0: 8002 8002 8109 808c 807c 8002 8002 8002 .........|......
0000fff0: 800e 8004 854e 8002 8002 81c5 81c5 81c5 .....N..........
00010000: 6502 20fe dea9 6e00 dc29 dea7 6e00 cc00 e. ...n..)..n...
The values at 0xFFFA, 0xFFFC, and 0xFFFE are all 0x81c5. If these are reset vectors, then they all point to the same memory location. That actually makes sense because the TNC probably just restarts itself in the event of one of those failure states. I also noticed that data is the repeating pattern I saw in my last post! That means each 32KB chunk of EPROM has these same values at the same offset. It’s likely that these really are the reset vectors! If they are, then it seems the program’s entry point would be at offset 0x81c5.
I re-ran the disassembler and told it to start at address 0x81c5:
;****************************************************
; Program's Code Areas
;****************************************************
ORG $81C5
vec_RST SEI ; 81C5: 0F '.'
LDS #M00FF ; 81C6: 8E 00 FF '...'
LDAA M003F ; 81C9: 96 3F '.?'
ANDA #$37 ; 81CB: 84 37 '.7'
CMPA #$10 ; 81CD: 81 10 '..'
BNE Z817E ; 81CF: 26 AD '&.'
CLR M005B ; 81D1: 7F 00 5B '..['
CLR M005A ; 81D4: 7F 00 5A '..Z'
LDAA #$23 ; 81D7: 86 23 '.#'
STAA M0039 ; 81D9: 97 39 '.9'
LDAA #$01 ; 81DB: 86 01 '..'
STAA M0024 ; 81DD: 97 24 '.$'
JSR Z9392 ; 81DF: BD 93 92 '...'
LDAA #$03 ; 81E2: 86 03 '..'
Oh, interesting. It actually labeled that section of code as vec_RST. That makes it seem like I’m on the right track. But wait, how did it know that address is the reset vector? I didn’t tell the disassembler to do that. Is it smart enough to identify the reset vector on its own? If that were the case, why didn’t the basic disassembly work without specifying a start point? I tried again to disassemble the binary without specifying a start or end location. Then I searched the output for “vec_RST”.
Z81C1 DEX ; 81C1: 09 '.'
BNE Z81C1 ; 81C2: 26 FD '&.'
RTS ; 81C4: 39 '9'
vec_RST SEI ; 81C5: 0F '.'
LDS #Z00FF ; 81C6: 8E 00 FF '...'
LDAA M003F ; 81C9: 96 3F '.?'
ANDA #$37 ; 81CB: 84 37 '.7'
CMPA #$10 ; 81CD: 81 10 '..'
BNE Z817E ; 81CF: 26 AD '&.'
CLR M005B ; 81D1: 7F 00 5B '..['
CLR M005A ; 81D4: 7F 00 5A '..Z'
LDAA #$23 ; 81D7: 86 23 '.#'
STAA M0039 ; 81D9: 97 39 '.9'
LDAA #$01 ; 81DB: 86 01 '..'
STAA M0024 ; 81DD: 97 24 '.$'
JSR Z9392 ; 81DF: BD 93 92 '...'
LDAA #$03 ; 81E2: 86 03 '..'
OK… so I guess maybe the disassembler is smarter than I gave it credit for earlier. It seems that it did disassemble the binary correctly after all. At least, it’s looking more and more that way. It must know where the reset vectors are located and it automatically pulled them out and worked off of that. But wait, if I scroll through the output it’s still attempting to disassemble ASCII data into CPU instructions. You can see here where it turned “ERROR” into instructions:
FCB $45 ; 063B: 45 'E'
FCB $52 ; 063C: 52 'R'
FCB $52 ; 063D: 52 'R'
CLRA ; 063E: 4F 'O'
FCB $52 ; 063F: 52 'R'
Maybe this is actually OK and these bad “code paths” are just never taken? It could be that it’s just attempting to disassemble everything, even if there is no way to reach those bad instructions. If that’s the case then it’s possible that it correctly disassembled all of the actual code paths, but it also disassembled the data into code. Reading the manual, it sounds like that’s exactly what’s happening:
Normally, dasmfw will try to interpret everything as code; it doesn't try to find out which areas contain code and which areas contain data, or the format of the data. Using an info file, you can give it detailed instructions how to process the file, add comments, patch existing areas, insert additional stuff, and so on.
That will make it even more difficult to follow what’s going on. The disassembler has a feature where you can build an “info” file to tell it what portions of the image are code and what portions are data. Though this would take a long time for me to figure out manually. It may be the only way to get it properly disassembled though.
There may also be another problem. I noticed that the disassembly only goes up to address 0xFFFF. The firmware size is actually 128KB, which should end at 0x1FFFF. So I don’t think the disassembler is actually disassembling everything. It seems like it’s only working with about half of the firmware image. Something else to ponder I guess.