// // dump_x65.cpp // dump_x65 // // Created by Carl-Henrik Skårstedt on 10/26/15. // Copyright © 2015 Carl-Henrik Skårstedt. All rights reserved. // // The MIT License (MIT) // // Copyright (c) 2015 Carl-Henrik Skårstedt // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Details, source and documentation at https://github.com/Sakrac/x65. // // "struse.h" can be found at https://github.com/Sakrac/struse, only the header file is required. // #define _CRT_SECURE_NO_WARNINGS // Windows shenanigans #define STRUSE_IMPLEMENTATION // include implementation of struse in this file #include #include #include #include "struse.h" // // // OBJECT FILE HANDLING // // enum SectionType : char { ST_UNDEFINED, // not set ST_CODE, // default type ST_DATA, // data section (matters for GS/OS OMF) ST_BSS, // uninitialized data section ST_ZEROPAGE // ununitialized data section in zero page / direct page }; struct ObjFileHeader { int16_t id; // 'x6' int16_t sections; int16_t relocs; int16_t labels; int16_t late_evals; int16_t map_symbols; uint32_t stringdata; int bindata; }; struct ObjFileStr { int offs; // offset into string table }; struct ObjFileSection { enum SectionFlags { OFS_DUMMY, OFS_FIXED, OFS_MERGED, }; struct ObjFileStr name; struct ObjFileStr exp_app; int start_address; int end_address; int output_size; // assembled binary size int align_address; int16_t next_group; // next section of group int16_t first_group; // first section of group int16_t relocs; SectionType type; int8_t flags; }; struct ObjFileReloc { int base_value; int section_offset; int16_t target_section; int8_t bytes; int8_t shift; }; struct ObjFileLabel { enum LabelFlags { OFL_EVAL = (1<<15), // Evaluated (may still be relative) OFL_ADDR = (1<<14), // Address or Assign OFL_CNST = (1<<13), // Constant OFL_XDEF = OFL_CNST-1 // External (index into file array) }; struct ObjFileStr name; int value; int flags; // 1<<(LabelFlags) int16_t section; // -1 if resolved, file section # if section rel int16_t mapIndex; // -1 if resolved, index into map if relative }; struct ObjFileLateEval { struct ObjFileStr label; struct ObjFileStr expression; int address; // PC relative to section or fixed int target; // offset into section memory int16_t section; // section to target int16_t rept; // value of rept for this late eval int16_t scope; // PC start of scope int16_t type; // label, byte, branch, word (LateEval::Type) }; struct ObjFileMapSymbol { struct ObjFileStr name; // symbol name int value; int16_t section; bool local; // local labels are probably needed }; enum ShowFlags { SHOW_SECTIONS = 1, SHOW_RELOCS = 2, SHOW_LABELS = 4, SHOW_MAP_SYMBOLS = 8, SHOW_LATE_EVAL = 16, SHOW_CODE_RANGE = 32, SHOW_DEFAULT = SHOW_SECTIONS }; char* LoadBinary(const char* filename, size_t &size) { if (FILE *f = fopen(filename, "rb")) { fseek(f, 0, SEEK_END); size_t _size = ftell(f); fseek(f, 0, SEEK_SET); if (char *buf = (char*)malloc(_size)) { fread(buf, _size, 1, f); fclose(f); size = _size; return buf; } fclose(f); } size = 0; return nullptr; } strref PoolStr(struct ObjFileStr off, const char *str_pool) { return off.offs>=0 ? strref(str_pool + off.offs) : strref(); } enum LEType { // When an expression is evaluated late, determine how to encode the result LET_LABEL, // this evaluation applies to a label and not memory LET_ABS_REF, // calculate an absolute address and store at 0, +1 LET_ABS_L_REF, // calculate a bank + absolute address and store at 0, +1, +2 LET_ABS_4_REF, // calculate a 32 bit number LET_BRANCH, // calculate a branch offset and store at this address LET_BRANCH_16, // calculate a branch offset of 16 bits and store at this address LET_BYTE, // calculate a byte and store at this address }; static const char *late_type[] = { "LABEL", "ABS_REF", "ABS_L_REF", "ABS_4_REF", "BRANCH", "BRANCH_16", "BYTE" }; static const char *section_type[] = { "UNDEFINED", // not set "CODE", // default type "DATA", // data section (matters for GS/OS OMF) "BSS", // uninitialized data section "ZEROPAGE" // ununitialized data section in zero page / direct page }; static const int section_type_str = sizeof(section_type) / sizeof(section_type[0]); void ReadObjectFile(const char *file, uint32_t show = SHOW_DEFAULT) { size_t size; if (char *data = LoadBinary(file, size)) { struct ObjFileHeader &hdr = *(struct ObjFileHeader*)data; size_t sum = sizeof(hdr) + hdr.sections*sizeof(struct ObjFileSection) + hdr.relocs * sizeof(struct ObjFileReloc) + hdr.labels * sizeof(struct ObjFileLabel) + hdr.late_evals * sizeof(struct ObjFileLateEval) + hdr.map_symbols * sizeof(struct ObjFileMapSymbol) + hdr.stringdata + hdr.bindata; if (hdr.id == 0x7836 && sum == size) { struct ObjFileSection *aSect = (struct ObjFileSection*)(&hdr + 1); struct ObjFileReloc *aReloc = (struct ObjFileReloc*)(aSect + hdr.sections); struct ObjFileLabel *aLabels = (struct ObjFileLabel*)(aReloc + hdr.relocs); struct ObjFileLateEval *aLateEval = (struct ObjFileLateEval*)(aLabels + hdr.labels); struct ObjFileMapSymbol *aMapSyms = (struct ObjFileMapSymbol*)(aLateEval + hdr.late_evals); const char *str_orig = (const char*)(aMapSyms + hdr.map_symbols); size_t code_start = sum - hdr.bindata, code_curr = code_start; // sections if (show & SHOW_SECTIONS) { int reloc_idx = 0; for (int si = 0; si < hdr.sections; si++) { struct ObjFileSection &s = aSect[si]; int16_t f = s.flags; const char *tstr = s.type > 0 && s.type < section_type_str ? section_type[s.type] : "error"; if (f & (1 << ObjFileSection::OFS_MERGED)) { printf("Section %d: \"" STRREF_FMT "\": (Merged)", reloc_idx, STRREF_ARG(PoolStr(s.name, str_orig))); } else if (f & (1 << ObjFileSection::OFS_DUMMY)) { if (f&(1 << ObjFileSection::OFS_FIXED)) { printf("Section %d: \"" STRREF_FMT "\": (Dummy [%s], fixed at $%04x)", reloc_idx, STRREF_ARG(PoolStr(s.name, str_orig)), tstr, s.start_address); } else { printf("Section %d: \"" STRREF_FMT "\": (Dummy [%s], relative)", reloc_idx, STRREF_ARG(PoolStr(s.name, str_orig)), tstr); } } else { if (f&(1 << ObjFileSection::OFS_FIXED)) { printf("Section %d: \"" STRREF_FMT "\": (Fixed [%s] $%04x, $%x bytes)", reloc_idx, STRREF_ARG(PoolStr(s.name, str_orig)), tstr, s.start_address, s.end_address - s.start_address); } else { printf("Section %d: \"" STRREF_FMT "\": (Relative [%s], $%x bytes, align to $%x)", reloc_idx, STRREF_ARG(PoolStr(s.name, str_orig)), tstr, s.end_address - s.start_address, s.align_address); } strref export_append = PoolStr(s.exp_app, str_orig); if (export_append) printf(" Export as: \"" STRREF_FMT "\"", STRREF_ARG(export_append)); } if (s.first_group >= 0) printf(" grouped with section %d", s.first_group); if (s.next_group >= 0) printf(" next in group: %d", s.next_group); if ((show & SHOW_CODE_RANGE) && s.output_size) printf(" code: $%x-$%x", (int)code_curr, (int)(code_curr + s.output_size)); code_curr += s.output_size; printf("\n"); reloc_idx++; } } if (show & SHOW_RELOCS) { int curRel = 0; for (int si = 0; si < hdr.sections; si++) { printf("section %d relocs: %d\n", si, aSect[si].relocs); for (int r = 0; r < aSect[si].relocs; r++) { struct ObjFileReloc &rs = aReloc[curRel++]; printf("Reloc: section %d offset $%x base $%x bytes: %d shift: %d\n", rs.target_section, rs.section_offset, rs.base_value, rs.bytes, rs.shift); } } } if (show & SHOW_MAP_SYMBOLS) { for (int mi = 0; mi < hdr.map_symbols; mi++) { struct ObjFileMapSymbol &m = aMapSyms[mi]; printf("Symbol: \"" STRREF_FMT "\" section: %d value: $%04x%s\n", STRREF_ARG(PoolStr(m.name, str_orig)), m.section, m.value, m.local ? " (local)" : ""); } } if (show & SHOW_LABELS) { for (int li = 0; li < hdr.labels; li++) { struct ObjFileLabel &l = aLabels[li]; strref name = PoolStr(l.name, str_orig); int16_t f = l.flags; int external = f & ObjFileLabel::OFL_XDEF; printf("Label: " STRREF_FMT " %s base $%x", STRREF_ARG(name), external==ObjFileLabel::OFL_XDEF ? "external" : "protected", l.value); if (external!=ObjFileLabel::OFL_XDEF) printf(" file %d", external); if (f & ObjFileLabel::OFL_CNST) printf(" constant"); if (f & ObjFileLabel::OFL_ADDR) printf(" PC rel"); if (f & ObjFileLabel::OFL_EVAL) printf(" evaluated"); printf(" map index: %d\n", l.mapIndex); } } if (show & SHOW_LATE_EVAL) { for (int li = 0; li < hdr.late_evals; ++li) { struct ObjFileLateEval &le = aLateEval[li]; strref name = PoolStr(le.label, str_orig); if (le.type == LEType::LET_LABEL) { printf("Late eval label: " STRREF_FMT " expression: " STRREF_FMT "\n", STRREF_ARG(name), STRREF_ARG(PoolStr(le.expression, str_orig))); } else { printf("Late eval section: %d addr: $%04x scope: $%04x target: $%04x expression: " STRREF_FMT " %s\n", le.section, le.address, le.scope, le.target, STRREF_ARG(PoolStr(le.expression, str_orig)), late_type[le.type]); } } } if (show & SHOW_CODE_RANGE) printf("Code block: $%x - $%x (%d bytes)\n", (int)code_start, (int)size, (int)(size - hdr.bindata)); // restore previous section } else printf("Not a valid x65 file\n"); } else printf("Could not open %s\n", file); } int main(int argc, char **argv) { const char *file = nullptr; uint32_t show = 0; for (int a = 1; a1) { ReadObjectFile(file, show ? show : SHOW_DEFAULT); } }