From fb9d6e1b2bcac7d8a951868c3e74f0c408f413a8 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 17 Apr 2016 21:10:57 -0500 Subject: [PATCH] Work on ROM file (i.e., non-inline) disassembler. --- src/disassembler.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/disassembler.h | 6 +- 2 files changed, 221 insertions(+), 7 deletions(-) diff --git a/src/disassembler.c b/src/disassembler.c index 6f3b4c7..b9906c9 100644 --- a/src/disassembler.c +++ b/src/disassembler.c @@ -1,11 +1,44 @@ /* Copyright (C) 2014-2016 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ +#include +#include +#include + #include "disassembler.h" #include "disassembler/arguments.h" #include "disassembler/mnemonics.h" #include "disassembler/sizes.h" +#include "mmu.h" +#include "rom.h" #include "util.h" +#include "version.h" + +#define HRULE \ + "----------------------------------------------------------------------------" + +#define NUM_BANKS(rom) \ + (((rom)->size + MMU_ROM_BANK_SIZE - 1) / MMU_ROM_BANK_SIZE) + +/* Structs and things */ + +typedef struct { + size_t cap, len; + char **lines; +} Disassembly; + +typedef enum { + DT_BINARY = 0, + DT_CODE, + DT_HEADER +} DataType; + +typedef struct { + const uint8_t *data; + DataType *type; + size_t size; + int8_t slot; +} ROMBank; /* Format a sequence of bytes of a certain length as a pretty string. @@ -69,6 +102,176 @@ DisasInstr* disassemble_instruction(const uint8_t *bytes) } /* + Append a line to the end of a disassembly. +*/ +static void write_line(Disassembly *dis, char *line) +{ + dis->lines[dis->len++] = line; + if (dis->len >= dis->cap) { + dis->cap *= 2; + dis->lines = cr_realloc(dis->lines, sizeof(char*) * dis->cap); + } +} + +/* + Macro that wraps write_line() in a printf-like interface. +*/ +#define WRITE_LINE_(dis, fmt, ...) \ + do { \ + char *tmp_buffer_; \ + if (asprintf(&tmp_buffer_, fmt "\n", __VA_ARGS__) < 0) \ + OUT_OF_MEMORY() \ + write_line(dis, tmp_buffer_); \ + } while(0); + +#define WRITE_LINE(dis, ...) WRITE_LINE_(dis, __VA_ARGS__, NULL) + +/* + Write some metadata comments to the top of the disassembly. +*/ +static void write_metadata(Disassembly *dis, const ROM *rom) +{ + time_t t; + struct tm *tm_info; + char buf[64]; + + time(&t); + tm_info = localtime(&t); + strftime(buf, sizeof buf, "on %a %b %d, %Y at %H:%M:%S", tm_info); + + WRITE_LINE(dis, ";; GAME GEAR ROM DISASSEMBLY") + WRITE_LINE(dis, ";; File: %s", rom->name) + WRITE_LINE(dis, ";; Generated %s by crater %s", buf, CRATER_VERSION) + WRITE_LINE(dis, ";; " HRULE) + WRITE_LINE(dis, "") +} + +/* + Given a size, fill 'output' with a pretty string. Modified from rom.c. +*/ +static char* size_to_string(char *output, size_t size) +{ + if (size >= (1 << 20)) + sprintf(output, "%zu MB", size >> 20); + else + sprintf(output, "%zu KB", size >> 10); + return output; +} + +/* + Extract appropriate assembler directives from a ROM's header. +*/ +static void disassemble_header(Disassembly *dis, const ROM *rom) +{ + char buf[64]; + DEBUG("Disassembling header") + + WRITE_LINE(dis, ".rom_size\t\"%s\"\t\t; $%zX bytes in %zu banks", + size_to_string(buf, rom->size), rom->size, NUM_BANKS(rom)) + WRITE_LINE(dis, ".rom_header\t$%04X", + rom->header_location) + WRITE_LINE(dis, ".rom_checksum\t%s", + (rom->reported_checksum == rom->expected_checksum) ? "on" : "off") + WRITE_LINE(dis, ".rom_product\t%u\t\t; %s", + rom->product_code, rom_product(rom) ? rom_product(rom) : "(unknown)") + WRITE_LINE(dis, ".rom_version\t%u", + rom->version) + WRITE_LINE(dis, ".rom_region\t%u\t\t; %s", + rom->region_code, rom_region(rom) ? rom_region(rom) : "(unknown)") + WRITE_LINE(dis, ".rom_declsize\t$%X\t\t; %s", + rom->declared_size, + size_to_string(buf, size_code_to_bytes(rom->declared_size))) +} + +/* + Initialize and return an array of ROMBank objects for the given ROM. +*/ +static ROMBank* init_banks(const ROM *rom) +{ + size_t nbanks = NUM_BANKS(rom), i; + ROMBank *banks = cr_malloc(sizeof(ROMBank) * nbanks); + + for (i = 0; i < nbanks; i++) { + if (i == nbanks - 1 && rom->size % MMU_ROM_BANK_SIZE) + banks[i].size = rom->size % MMU_ROM_BANK_SIZE; + else + banks[i].size = MMU_ROM_BANK_SIZE; + banks[i].data = rom->data + (i * MMU_ROM_BANK_SIZE); + banks[i].type = cr_calloc(sizeof(DataType), banks[i].size); + banks[i].slot = -1; + } + return banks; +} + +/* + Deallocate the given array of ROM banks. +*/ +static void free_banks(const ROM *rom, ROMBank *banks) +{ + size_t nbanks = NUM_BANKS(rom), i; + for (i = 0; i < nbanks; i++) + free(banks[i].type); + free(banks); +} + +/* + Disassemble a ROM into an array of strings, each storing one source line. + + Each line is newline-terminated. The array itself is terminated with a NULL + element. Each line, and the overall array, must be free()d by the caller. +*/ +char** disassemble(const ROM *rom) +{ + Disassembly dis = {.cap = 16, .len = 0}; + dis.lines = cr_malloc(sizeof(char*) * dis.cap); + + write_metadata(&dis, rom); + disassemble_header(&dis, rom); + + ROMBank *banks = init_banks(rom); + // TODO + // mark_header(): set DT_HEADER where appropriate + // analyze(): set DT_CODE (future: make labels, slots) where appropriate + // render(): WRITE_LINE a bunch of of times + free_banks(rom, banks); + + write_line(&dis, NULL); + return dis.lines; +} + +/* + Write a disassembly created by disassemble() to the given output file. + + Return whether the file was written successfully. This function frees the + disassembly along the way. +*/ +static bool write_disassembly(const char *path, char **lines) +{ + FILE *fp; + char **itr = lines; + + if (!(fp = fopen(path, "w"))) { + ERROR_ERRNO("couldn't open destination file") + return false; + } + + while (*itr) { + if (!fwrite(*itr, strlen(*itr), 1, fp)) { + fclose(fp); + do free(*itr); while (*(++itr)); + ERROR_ERRNO("couldn't write to destination file") + return false; + } + free(*itr); + itr++; + } + + fclose(fp); + free(lines); + return true; +} + +/* Disassemble the binary file at the input path into z80 source code. Return true if the operation was a success and false if it was a failure. @@ -77,8 +280,19 @@ DisasInstr* disassemble_instruction(const uint8_t *bytes) */ bool disassemble_file(const char *src_path, const char *dst_path) { - // TODO - (void) src_path; - (void) dst_path; - return true; + ROM *rom; + const char *errmsg; + char **lines; + + DEBUG("Disassembling: %s -> %s", src_path, dst_path) + if ((errmsg = rom_open(&rom, src_path))) { + ERROR("couldn't load ROM image '%s': %s", src_path, errmsg) + return false; + } + + lines = disassemble(rom); + rom_close(rom); + + DEBUG("Writing output file") + return write_disassembly(dst_path, lines); } diff --git a/src/disassembler.h b/src/disassembler.h index a4715d8..2a3efb3 100644 --- a/src/disassembler.h +++ b/src/disassembler.h @@ -7,6 +7,8 @@ #include #include +#include "rom.h" + /* Structs */ typedef struct { @@ -15,12 +17,10 @@ typedef struct { char *line; } DisasInstr; -// typedef struct { ... } Disassembly; - /* Functions */ void disas_instr_free(DisasInstr*); DisasInstr* disassemble_instruction(const uint8_t*); -// Disassembly* disassemble(const uint8_t*); // TODO +char** disassemble(const ROM*); bool disassemble_file(const char*, const char*);