Showing posts with label Disassembler. Show all posts
Showing posts with label Disassembler. Show all posts

Tuesday, November 19, 2019

A 68000 Disassembler


I have found that a good way to really understand a microprocessor's instruction set is to write a disassembler for it. I have done this for a number of processors including the 6502, 6800, 6809, and Z80. My udis disassembler was implemented on Python and supports a number of processors.

Having recently worked on my 68000 single-board computer, I decided to write a disassembler for the Motorola 68000. This is more challenging than for 8-bit processors due to it's complex instruction set and many addressing modes. I decided to again use Python as it is fast to develop, readable, and cross-platform.

I wanted the design to be at least partially table based. I started with the table on the Motorola MC68000 Programmers Reference Card, but it did not adapt will to a software-based table. I found a very nice and succinct table  written by someone in France under the name of GoldenCrystal that was a better fit. It organized all of the 68000 instructions and decoding of fields in a logical manner. I made a spreadsheet based on the table data.

The 68000 uses 16-bit opcodes, but they are not unique. Various bits in the opcode control the addressing modes and operands, so I needed to use an approach where each instruction has a bit pattern and a mask indicating which bits to examine when looking for a match to a specific opcode. For example, a NOP is $4E71 and all bits are valid but a MOVE instruction has thw two most significant bits as zeroes but the rest vary with the address mode.

I exported the spreadsheet into CSV format, which can easily be read into a data structure from Python. I then use this data for determining what instruction is read. Then I handle the encoding of the instruction and any extension words or operands. Many instructions follow similar encoding and can use the same logic, while others are unique.

It was somewhat tedious and time consuming to work my way through all of the possible instructions. As I proceeded, I wrote a test program with the instructions I was implementing and examples of each addressing mode. An additional good "stress test" of the code is to use random data (such as /dev/urandom on Linux) as input and make sure that it does not crash or produce errors.

After few weeks of occasional evenings (interrupted by a trip to Europe) I had finished support for all instructions. The most complex was the MOVE instruction as it supports almost every addressing mode for both source and destination operands. The final program is just over 1000 lines of Python code including comments and blank lines.

Here is some sample output:

00000000  4E 71                          NOP
00000004  A2 34                          UNIMPLEMENTED
00000006  4A FC                          ILLEGAL
00000008  4E 70                          RESET
00000012  4E 40                          TRAP      #$00
0000001A  00 7C AA 55                    ORI       #$AA55,SR
0000002A  02 7C AA 55                    ANDI      #$AA55,SR
00000032  60 5E                          BRA       $00000092
000000BA  48 C2                          EXT.l     D2
000000BE  4E 69                          MOVE      USP,A1
000000DE  57 CF 00 22                    DBEQ      D7,$00000102
00000112  72 01                          MOVEQ     #$01,D1
00000146  EF 82                          ASL.l     #7,D2
000006E4  08 78 00 08 12 34              BCHG      #$08,$1234
00000C9E  4C FB 55 AA 90 12              MOVEM.l   $12(PC,A1),D1/D3/D5/D7/A0/A2/A4/A6
00000CF4  2C 6D 12 34                    MOVEA.l   $1234(A5),A6
00000D3A  18 3A 12 34                    MOVE.b    $1234(PC),D4
00000F24  55 91                          SUBQ.l    #2,(A1)
00001334  DF B8 12 34                    ADD.l     D7,$1234

With the -n or --nolist option, it only disassembles the instructions. This could be used to feed the output back into an assembler, if you were reverse engineering some code for example. Here is some sample output in this mode:

 NOP
 UNIMPLEMENTED
 ILLEGAL
 RESET
 TRAP      #$00
 ORI       #$AA55,SR
 ANDI      #$AA55,SR
 BRA       $00000092
 EXT.l     D2
 MOVE      USP,A1
 DBEQ      D7,$00000102
 MOVEQ     #$01,D1
 ASL.l     #7,D2
 BCHG      #$08,$1234
 MOVEM.l   $12(PC,A1),D1/D3/D5/D7/A0/A2/A4/A6
 MOVEA.l   $1234(A5),A6
 MOVE.b    $1234(PC),D4
 SUBQ.l    #2,(A1)
 ADD.l     D7,$1234

The source code and test program can be found here.

This process gave me an appreciation for the effort that the Motorola engineers must have gone through to implement the native 68000 dissasembler in the TUTOR firmware which was written in assembly language.

I can also appreciate that significant more work would be needed to extend this to support the 68020 or later processors which have more instructions and addressing modes.

While it was not meant to be a production program, it was fun to write and I now have a much better understanding of the 68000 instruction set and its complexity, quirks and limitations.

Tuesday, February 5, 2019

A 6809 Single Board Computer: Disassembler and Thoughts on the 6809


As a project to use my 6809 SBC and learn more about 6809 assembly language programming, I wrote a disassembler that can run on the board. The design was loosely based on the one I did for the 6502.

While straightforward in principle, a disassembler for the 6809 is a little more challenging that for the 6502 for a number of reasons:
  • It supports many instructions.
  • Some instructions have unique operand formats (e.g. TFR/EXG, PSH/PULS).
  • There are many (24) different indexed addressing modes.
  • The instruction length can change based on the indexed addressing mode being used.
  • Some instructions are two bytes long, prefixed by $10 or $11 (the so-called page 2 and page 3 op codes).
Implementation was pretty straightforward. I used lookup tables for a number of things including op codes, instruction types, addressing modes, and mnemonics. I strove to make the code readable and avoided any tricks that might increase efficiency slightly at the expense of clarity.

It is mostly portable. It uses ASSIST09 monitor routines for i/o, but other than that could be used on other systems with minor changes.

I implemented over a few evenings, building it up in pieces. It took some time (but less than I expected) to handle all the index addressing modes and to correctly handle the differing instruction lengths and page 2/3 instructions.

My basic approaches for testing and debug were:
  1. Test low level functions, like PrintString, on their own with some test code.
  2. "Desk check" complex code on paper to try to verify the logic, use of registers, etc.
  3. Build it up in stages and confirm them working before adding on (e.g. initially just display hex bytes)
When I ran into bugs I used the "desk check" method as well as making use of breakpoints when running the code to see what it was doing at various steps. Having a working monitor to run, display and change memory and registers, etc. was a requirement and ASSIST09 worked well for that. Downloading new versions of code over the serial port only took a few seconds.

I initially ran it standalone, but at the end I was able to also support installing it as an external command in the ASSSIST09 monitor, so you can run it by typing U (for unassemble). ASSIST09 has calls to add new commands, so it can be done without changing the code in ROM.

Here is some sample output (disassembling the start of the ASSIST09 ROM code):

>U F800
F800  30 8D 68 BE  LEAX  $68BE ,PCR
F804  1F 10        TFR   X,D
F806  1F 8B        TFR   A,DP
F808  97 9D        STA   $9D 
F80A  33 84        LEAU  ,X
F80C  31 8C 35     LEAY  $35 ,PCR
F80F  EF 81        STU   ,X++
F811  C6 16        LDB   #$16 
F813  34 04        PSHS  B
F815  1F 20        TFR   Y,D
F817  E3 A1        ADDD  ,Y++
F819  ED 81        STD   ,X++
F81B  6A E4        DEC   ,S
F81D  26 F6        BNE   $F815 
F81F  C6 0D        LDB   #$0D 
F821  A6 A0        LDA   ,Y+
F823  A7 80        STA   ,X+
F825  5A           DECB
F826  26 F9        BNE   $F821 
F828  31 8D F7 D4  LEAY  $F7D4 ,PCR
F82C  8E 20 FE     LDX   #$20FE 
F82F  AC A1        CMPX  ,Y++
F831  26 02        BNE   $F835 
F833  AD A4        JSR   ,Y
PRESS SPACE TO CONTINUE, Q TO QUIT 

With a little more 6809 assembly language programming experience under my belt, it is interesting to compare it to the other 8-bit processor I am most familiar with, the 6502. Let me list a few thoughts here.

Having two accumulators is very handy. Often one is being used as an accumulator for some form of data, but another may be needed for indexing or as a parameter to calling another routine. Surprisingly, I also found myself using the 16-bit D register (which uses A and B) because I needed 16-bit data. Some functions, like addition and subtraction, are supported on the D register, but many only have 8-bit versions which you need to combine using several instructions to work on 16 bits of data.

Two index registers is also handy (the 6502 has two, but the 6809's predecessor the 6800 only had one). But I found in practice that I rarely needed a second one. Having 16-bit index registers, unlike the 8-bit registers of the 6502, was much more convenient as you often find you are using them to store addresses.

The 6502 typically makes the programmer heavily use addresses in the first 256 bytes of memory (page 0). Some key instructions and addressing modes can only work with page zero. For that reason, these tend to become valuable real estate and you can run out of them or run into collisions between different uses of them. In contrast, the 6809 has a more orthogonal instruction set and all relevant instructions can work with full 16-bit addresses. It does have direct mode (8-bit addresses) but with the Direct Page register you can have them work with any page of memory, not just page zero. I did not use this feature (the ASSIST09 monitor does) but I can see it being useful when you wanted to optimize the size of your code.

In general the 6809 is more orthogonal than the 6502, with few limitations on the addressing modes or operands of instructions. Unlike the 6502 you can push, pull, transfer, or exchange any registers. The push and pull (PSHS/PSHU/PULS/PULU) instructions are particularly nice in that you can push or pull a set of registers in one instruction. The order of push and pull is defined, so that you do not have to ensure that the order is correct in your code (provided you push and pull the same list of registers). There are two stack pointers, but I only used one. And of course, the 16-bit stack pointer is much more reasonable than the 8-bit stack on the 6502 that is fixed in page 1 of memory.

Transfer (TFR) and Exchange (EXG) are two other instructions which are very handy for moving registers around without any restrictions. The 6502 had some limitations on what registers you could transfer and had no equivalent to EXG.

The 6809 introduced a MULtiply instruction, which sounded at the time like a luxury for assembly language programmers. I actually used it in an early version of my disassembler program when I needed to multiply the offset to a table by 4 and get a 16-bit result. I felt it was overkill using a multiply to do this, and later did it using two shifts (you can easily implement a 16-bit shift of the D register using two instructions: ASLB, ROLA).

The 6809 has many more addressing modes than the 6502, with 24 variations of indexed addressing. I think it is unlikely that assembly language programmers would use the majority of these as just a few of them generally suffice. I suspect the original intention may have been to help support compilers of high-level languages that could use these.

One quirk with indexed addressing that could confuse programmers is that the 5, 8, and 16 b-t offset modes consider the offset to be signed. So, for example, the 5 bit offsets are not 0 to 31 but rather -16 to 15. If you have a lookup table of 256 elements, for example, and expect the 8-bit offset indexed addressing mode to access all of them starting from an offset of zero, you will be surprised when values of $80 and higher don't read the table entries you expected. I ran into this, and solved it by using 16-bit offset in this case.

One instruction that you could easily overlook is LEA (Load Effective Address). It might not seem particularly useful, essentially removing one level of indirection. In fact it is very useful, and is the key to a lot of efficient programming as it can make use of all of the indexed addressing modes and make code position independent.

The 6809 supports position independent code (PIC). The ASSIST09 monitor, for example, is fully position independent. It is not particularly hard to write PIC code, and I made some effort to try this in the disassembler. Mostly it is a matter of using branches rather than jumps and using the PCR (program counter relative) addressing mode when working with addresses. I did leave the memory locations in RAM used by the program at fixed addresses. One annoyance is that as code gets larger during development 8-bit branches often fail and need to be converted to long branches. Unlike some assemblers, the one I was using did not automatically select short or long branches as needed.

Overall, the 6809 is quite a pleasant processor to program, with more instructions, more and larger registers than the 6502. Of course, it is not really a fair comparison as it came a generation later and could make use of advanced in technology that allowed a more complex chip. The 6800 was of the same vintage as the 6502. In fact, the decision to use the 6502 over the 6800 in a number of early computers was based on cost. Compared to the $300 price tag of the 6800, the 6502 sold for $25. The Apple 1 was originally designed to use a 6800, but was converted to the 6502 when Steve Wozniak learned about it. The schematic diagram for the Apple 1 still listed the circuit changes needed on the board to use a 6800.

Saturday, September 28, 2013

8080 Disassembler

I've ordered an Altair 8800 replica computer from Briel Computers. While I'm waiting for it to arrive, I've been reading up on the 8080 microprocessor, the Altair 8800, and CP/M operating system.

For fun I wrote an 8080 assembly language disassembler to help understand 8080 machine language and in preparation for playing with some 8080 assembler programs. I also used it as an opportunity to get more familiar with the Python programming language. It's the first significant program I've written in Python and it was relatively painless to code.

You can get the source code here. The program reads a binary file specified on the command line and produces a disassembly. It requires Python 3. It has been tested on Linux but should work on any platform that supports Python. See the source code for more details.

Here is some sample output using the first example program in the Altair 8800 manual:

% ./disasm8080.py ex2.bin 
0000            org     $0000
0000  3A 80 00  lda     $0080
0003  47        mov     b,a
0004  3A 81 00  lda     $0081
0007  80        add     b
0008  32 82 00  sta     $0082
000B  C3 00 00  jmp     $0000
000E            end

I implemented a number of options to control the output format such as the start address and whether to show the hex bytes or just instructions.

I'd say about one third of the effort was coding, one third was entering the table of 8080 instructions, and one third was learning how to do various things in Python, like reading a binary file and manipulating strings.

If is free software released under the Apache License. Give it a try if you are interested.

Saturday, July 7, 2012

65816 Disassembly - 8 and 16 bit modes


Today I made JMON a little smarter about 65816 disassembly. It now handles 8-bit and 16-bit instructions, looking at SEP and REP instructions to determine what mode the CPU is currently in. Of course, it cannot always get this correct as it doesn't know if code could be called when the CPU is in a specified mode. It starts in all 8-bit mode. This is similar to how the CC65 assembler works in "auto" mode unless you use assembler directives to explicitly set the mode.

While I was testing this I noticed the SEP instruction was disassembled as CPX. There is actually an error in the Western Design Center manual in Chapter 19, Instruction Lists, which I had copied when I entered the op codes. The byte $E2 is SEP but is listed as CPX.

Here is sample disassembly output from JMON showing all 4 combinations of 8 and 16-bit accumulator and index register modes (I've manually added spaces to highlight where the mode changes).

JMON MONITOR 0.97 BY JEFF TRANTER
? U 60B9
60B9   E2 30       SEP   #$30
60BB   09 12       ORA   #$12
60BD   29 12       AND   #$12
60BF   49 12       EOR   #$12
60C1   69 12       ADC   #$12
60C3   89 12       BIT   #$12
60C5   A9 12       LDA   #$12
60C7   C9 12       CMP   #$12
60C9   E9 12       SBC   #$12
60CB   A0 12       LDY   #$12
60CD   A2 12       LDX   #$12
60CF   C0 12       CPY   #$12
60D1   E0 12       CPX   #$12


60D3   C2 30       REP   #$30
60D5   09 34 12    ORA   #$1234
60D8   29 34 12    AND   #$1234
60DB   49 34 12    EOR   #$1234
60DE   69 34 12    ADC   #$1234
60E1   89 34 12    BIT   #$1234
60E4   A9 34 12    LDA   #$1234
60E7   C9 34 12    CMP   #$1234
60EA   E9 34 12    SBC   #$1234
60ED   A0 34 12    LDY   #$1234
60F0   A2 34 12    LDX   #$1234
60F3   C0 34 12    CPY   #$1234
60F6   E0 34 12    CPX   #$1234


60F9   C2 20       REP   #$20
60FB   E2 10       SEP   #$10
60FD   09 34 12    ORA   #$1234
6100   29 34 12    AND   #$1234
6103   49 34 12    EOR   #$1234
6106   69 34 12    ADC   #$1234
6109   89 34 12    BIT   #$1234
610C   A9 34 12    LDA   #$1234
610F   C9 34 12    CMP   #$1234
6112   E9 34 12    SBC   #$1234
6115   A0 12       LDY   #$12
6117   A2 12       LDX   #$12
6119   C0 12       CPY   #$12
611B   E0 12       CPX   #$12


611D   E2 20       SEP   #$20
611F   C2 10       REP   #$10
6121   09 12       ORA   #$12
6123   29 12       AND   #$12
6125   49 12       EOR   #$12
6127   69 12       ADC   #$12
6129   89 12       BIT   #$12
612B   A9 12       LDA   #$12
612D   C9 12       CMP   #$12
612F   E9 12       SBC   #$12
6131   A0 34 12    LDY   #$1234
6134   A2 34 12    LDX   #$1234
6137   C0 34 12    CPY   #$1234
613A   E0 34 12    CPX   #$1234

Wednesday, March 28, 2012

A 65C02 Disassembler

I just wrote a disassembler that runs on the Replica 1. I did it mostly for a personal programming challenge as there are lots of them around. Woz did one for the Apple 1 -- I think it was published in Byte and was probably the one included in the Apple II ROMs.

Years ago on my first computer (a 6502-based Ohio Scientific) I wrote one, first in BASIC and then in machine language. I remember it used a somewhat simplified format, e.g. STAX $nn for STA $nn,X and JMPI $1234 for JMP ($1344) etc.  It was written on paper and hand assembled. Once I had a disassembler it made it much easier to catch errors in hand assembled code.

My implementation is in assembler, written for the CC65 cross-assembler. It supports all 65C02 mnemonics including the Western Digital only opcodes. The output is virtually identically to Krusader's disassembler. To test it I captured the output of my disassembler and Krusader's and checked for any differences.

I have a standalone version which disassembles memory a screen at a time. I also integrated it into my JMON machine language monitor program to which I added a new U (unassemble) command.

Here is a screen shot with some sample output:

Screen Shot of Dissembler Output
And here is an example of some 65C02 instructions being disassembled:

some 65C02 Instructions

I didn't look at any other disassembler implementations, but there are only so many ways to do it. About half of the information is in data structures or tables.

I have one lookup table of all the instruction mnemonics. They are 3 characters each and there are 71 of them including all the 65C02 instructions. I have another table of all 256 possible opcodes. For each opcode, the table has two entries - the instruction (an index into the previously described table) and an entry listing the addressing mode. Thus, for a given op code, say $EA, I can look up in the table that is is a NOP instruction, using implicit addressing mode, and the mnemonic is "NOP". Another small table lists the number of instruction bytes for each addressing mode. For example, for implicit addressing it is one byte. There are 16 possible addressing modes.

I initially included the number of instruction bytes in the table of opcodes until I realized that for a given addressing mode it was always the same so I could use a small lookup table based on the addressing mode.

The major part that is hardcoded rather than in tables is the logic that displays the instruction operands appropriately given the addressing mode. The total size is about 1.5K including all utility routines of which a little over half is code and the rest is data. It will run out of ROM if desired.

If one really wanted to optimize the code for size I suppose you could reduce the size of the opcode table by taking advantage of the fact that over a quarter of it is not valid opcodes (instructions ending with hex value 3, 7, B, and F, for example) but this would complicate the logic for the table lookup, and once you add 65C02 instructions many of the invalid opcodes are used.

Adding 65C02 support did not add much code although handling the SMB, RMB, BBR, and BBS instructions was a little complex due to the funky format they have.

I put in an assemble time option so that the output can contain only the instructions and not the memory data bytes, so you could feed the output into an assembler. Here is an example of it running in that mode:

    JSR   $0540
    LDX   #$94
    LDY   #$08
    JSR   $0579
    JSR   $0540
    LDA   #$80
    STA   $37
    LDA   #$02
    STA   $38
    JSR   $0540
    LDA   #$17
    PHA
    JSR   $02B7
    PLA
    SEC
    SBC   #$01
    BNE   $029A
    LDX   #$6F
    LDY   #$08
    JSR   $0579
    JSR   $055F
    CMP   #$20
    BEQ   $0295

The code is licensed under Apache license so you are free to use it if you wish.  This first version can be considered beta -- it is complete but may still have bugs.

The standalone version and the version of JMON with the disassembler can be downloaded from these link:

https://docs.google.com/open?id=0B54TLlZjWIytQ2duZThPR1hSLWV4ekllencwV29VZw