CHIP-8 Disassembler

Let's write a CHIP-8 disassembler. As before, the goals for writing the disassembler are

  1. To get familiar with the instruction set

  2. Have a tool to use to examine programs

  3. End up with code that we can use in the emulator for debugging

CHIP-8 doesn't have an official syntax for assembly language, so I'm going to make one up as I go loosely basing it on the 8080 assembly syntax. Hopefully you'll be able to follow it easily.

Looking at the table in the Wikipedia article, it seems like the opcodes are organized by the upper half (nibble) if the first byte, so I'll use that to key off of. I'll write the main switch statement and pick off a few easy ones:

   void DisassembleChip8Op(uint8_t *codebuffer, int pc)    
    uint8_t *code = &codebuffer[pc];    
    uint8_t firstnib = (code[0] >> 4);

    printf("%04x %02x %02x ", pc, code[0], code[1]);    
    switch (firstnib)    
        case 0x00: printf("0 not handled yet"); break;    
        case 0x01: printf("1 not handled yet"); break;    
        case 0x02: printf("2 not handled yet"); break;    
        case 0x03: printf("3 not handled yet"); break;    
        case 0x04: printf("4 not handled yet"); break;    
        case 0x05: printf("5 not handled yet"); break;    
        case 0x06:    
                uint8_t reg = code[0] & 0x0f;    
                printf("%-10s V%01X,#$%02x", "MVI", reg, code[1]);    
        case 0x07: printf("7 not handled yet"); break;    
        case 0x08: printf("8 not handled yet"); break;    
        case 0x09: printf("9 not handled yet"); break;    
        case 0x0a:    
                uint8_t addresshi = code[0] & 0x0f;    
                printf("%-10s I,#$%01x%02x", "MVI", addresshi, code[1]);    
        case 0x0b: printf("b not handled yet"); break;    
        case 0x0c: printf("c not handled yet"); break;    
        case 0x0d: printf("d not handled yet"); break;    
        case 0x0e: printf("e not handled yet"); break;    
        case 0x0f: printf("f not handled yet"); break;    

Referencing the Wikipedia table again, I wrote the code to handle 6XNN (which moves an immediate value to a register) and ANNN which moves an address to the memory address register I.

I'll write a main routine to read in a CHIP-8 program file and process it, something like this. (See Developing on the Command Line to see how I compile and build these programs.)

   int main (int argc, char**argv)    
    FILE *f= fopen(argv[1], "rb");    
    if (f==NULL)    
        printf("error: Couldn't open %s\n", argv[1]);    

    //Get the file size    
    fseek(f, 0L, SEEK_END);    
    int fsize = ftell(f);    
    fseek(f, 0L, SEEK_SET);    

    //CHIP-8 convention puts programs in memory at 0x200    
    // They will all have hardcoded addresses expecting that    
    //Read the file into memory at 0x200 and close it.    
    unsigned char *buffer=malloc(fsize+0x200);    
    fread(buffer+0x200, fsize, 1, f);    

    int pc = 0x200;    
    while (pc < (fsize+0x200))    
        DisassembleChip8Op2(buffer, pc);    
        pc += 2;    
        printf ("\n");    
    return 0;    

Compiling and running this on the Fishie.ch8 program, I see this:

   $cc -g chip8dis.c    
   $./a.out "Fishie.ch8"    
   0200 00 e0 0 not handled yet    
   0202 a2 20 MVI        I,#$220    
   0204 62 08 MVI        V2,#$08    
   0206 60 f8 MVI        V0,#$f8    
   0208 70 08 7 not handled yet    
   020a 61 10 MVI        V1,#$10    
   020c 40 20 4 not handled yet    
   020e 12 0e 1 not handled yet    
   0210 d1 08 d not handled yet    
   0212 f2 1e f not handled yet    

It looks reasonable - A good start! How does yours look? (You are writing your own code as you follow, right?) In the next section I'll dig into the instructions in detail.

← Prev: introduction-to-chip-8   Next: chip-8-instruction-set →