#include #include #include #include #include #include #include #include using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; using u64 = std::uint64_t; struct NameOrIndex { u8 value[8]; std::variant Get() { u32 temp; std::memcpy(&temp, value, 4); if (temp == 0) { std::memcpy(&temp, value + 4, 4); return temp; } std::string ret(value, value + 8); size_t pos = ret.find('\0'); if (pos != std::string::npos) ret.erase(pos); return ret; } std::string GetString(std::FILE* file, u32 offset) { auto v = Get(); if (v.index() == 1) { std::fseek(file, offset + std::get<1>(v), SEEK_SET); char c; std::string ret; while (true) { if (std::fread(&c, 1, 1, file) != 1) throw "unexpected end"; if (c == '\0') break; ret += c; } return ret; } else { return std::get<0>(v); } } }; struct Header { u16 magic; u16 num_section; u32 time; u32 offset_symbol; u32 num_symbol; u16 optheader_size; u16 flags; }; static_assert(sizeof(Header) == 20); struct SectionHeader { NameOrIndex name; u32 prog_addr; u32 data_addr; u32 size; u32 offset_data; u32 offset_rel; u32 offset_line; u16 num_rel; u16 num_line; u32 flags; }; static_assert(sizeof(SectionHeader) == 40); namespace SFlag { constexpr u32 Exec = 0x0001; constexpr u32 Unk2 = 0x0002; constexpr u32 Prog = 0x0008; constexpr u32 Data = 0x0010; constexpr u32 RegionMask = Prog | Data; constexpr u32 Moni = 0x0020; constexpr u32 Un40 = 0x0040; constexpr u32 Dupl = 0x0080; constexpr u32 U200 = 0x0200; } // namespace SFlag #pragma pack(push, 1) struct Symbol { NameOrIndex name; u32 value; u16 section_index; u16 type; u8 storage; u8 num_aux; }; static_assert(sizeof(Symbol) == 18); inline const std::unordered_map storage_names{ {0, "label"}, {1, "auto"}, {2, "external"}, {3, "static"}, {4, "register"}, {8, "struct"}, {9, "arg"}, {10, "struct-tag"}, {11, "union"}, {12, "union-tag"}, {13, "typedef"}, {15, "enum-tag"}, {16, "enum"}, {18, "bitfield"}, {19, "auto-arg"}, {98, "start"}, {99, "end"}, {100, "block"}, {101, "func"}, {102, "struct-size"}, {103, "file"}, {107, "ar"}, {108, "ar"}, {109, "ar"}, {110, "ar"}, {111, "ar"}, {112, "ar"}, {255, "physical-function-end"}, }; /* Storage: 0 labels? | section+address | type = 0 | no aux 1 auto variable | address, rel to sp? | variable type | variable aux 2 externals | section+address | variable type | variable aux 3 statics | section+address | variable type | variable aux 4 register variable | address, abs? | variable type | variable aux 8 structure member | address | variable type | variable aux 9 function argument | address, ? | variable type | variable aux 10 structure tag | debug/no address | type = 8 (structure) | aux = 1 11 union member | address = 0? | type = 5 (long) or 8 (structure) | aux = 0 (long) or 1 (structure) 12 union tag | debug/no address | type = 9 (union) | aux = 1 13 typedef | debug/no address | type = 5 (long) or 8 (structure) | aux = 0 (long) or 1 (structure) 15 enum tag | debug/no address | type = 10 (enum) | aux = 1 16 enum member | abs value | type = 11 (enum member) | aux = 0 18 bitfield | address | type = 5 (long) or 15 ulong | aux = 1 19 auto arg | section+address | type = 17 | aux = 0 98 function start | section + address | type = 0 | aux = 0 99 function end | section + address | type = 0 | aux = 0 100 .eb/.bb | section + address | type = 0 | aux = 1 101 .ef/.bf | section + address | type = 0 | aux = 1 102 struct end | address | type = 0 | aux = 1 103 .file | debug value | type = 1 or 2 | aux = 1 107 .ar | section + address | type = => ar0 value | aux = 0 108 .ar | section + address | type = => ar1 value | aux = 0 109 .ar | section + address | type = => arp0 value | aux = 0 110 .ar | section + address | type = => arp1 value | aux = 0 111 .ar | section + address | type = => arp2 value | aux = 0 112 .ar | section + address | type = => arp3 value | aux = 0 255 physical function end | section + address | type = 0 | aux = 0 */ struct Line { u32 symbol_index_or_addr; u16 line_number; }; static_assert(sizeof(Line) == 6); struct Relocation { u32 addr; u32 symbol; u32 type; }; static_assert(sizeof(Relocation) == 12); // not 10 bytes! #pragma pack(pop) class Coff { public: Coff(std::FILE* in) { fseek(in, 0, SEEK_SET); Header header; if (fread(&header, sizeof(header), 1, in) != 1) throw "failed to read header"; u32 string_offset = header.offset_symbol + header.num_symbol * sizeof(Symbol); sections.resize(header.num_section); u32 expected = 0; for (u32 i = 0; i < header.num_section; ++i) { fseek(in, sizeof(header) + header.optheader_size + i * sizeof(SectionHeader), SEEK_SET); SectionHeader sheader; if (fread(&sheader, sizeof(sheader), 1, in) != 1) throw "failed to read sheader"; sections[i].name = sheader.name.GetString(in, string_offset); sections[i].prog_addr = sheader.prog_addr; sections[i].data_addr = sheader.data_addr; if (expected == 0) expected = sheader.offset_data; if (expected != sheader.offset_data) { throw ""; } sections[i].data.resize(sheader.size); if ((sheader.flags & SFlag::Dupl) == 0) { fseek(in, sheader.offset_data, SEEK_SET); if (sheader.size != 0) if (fread(sections[i].data.data(), sheader.size, 1, in) != 1) throw "failed to read section"; expected += sheader.size; } sections[i].num_rel = sheader.num_rel; sections[i].flags = sheader.flags; if ((sections[i].flags & SFlag::RegionMask) == 0) throw "no region type"; if ((sections[i].flags & SFlag::RegionMask) == SFlag::Prog) { if (sections[i].data_addr != 0) throw "prog section has data addr"; } else if ((sections[i].flags & SFlag::RegionMask) == SFlag::Data) { if (sections[i].prog_addr != 0) throw "data section has prog addr"; } fseek(in, sheader.offset_line, SEEK_SET); u32 current_symbol = 0; for (u16 j = 0; j < sheader.num_line; ++j) { Line line; if (fread(&line, sizeof(line), 1, in) != 1) throw "failed to read line"; if (line.line_number == 0) { current_symbol = line.symbol_index_or_addr; } else { u32 addr = line.symbol_index_or_addr; if (addr < sections[i].prog_addr || addr >= sections[i].prog_addr + sections[i].data.size() / 2) { if (addr != 0xFFFFFFFF && addr != 0xFFFFFFFE && addr != 0xFFFFFFFD) // unknown magic throw "line out of range"; } sections[i].line_numbers[addr].emplace_back( Section::LineNumber{current_symbol, line.line_number}); } } fseek(in, sheader.offset_rel, SEEK_SET); // printf("\n"); for (u16 j = 0; j < sheader.num_rel; ++j) { Relocation relocation; if (fread(&relocation, sizeof(relocation), 1, in) != 1) throw "failed to read relocation"; // printf("%08X, %08X, %08X\n", relocation.addr, relocation.symbol, // relocation.type); sections[i].relocations[relocation.addr].push_back(relocation); } } for (u32 i = 0; i < header.num_symbol; ++i) { Symbol symbol; fseek(in, header.offset_symbol + i * sizeof(symbol), SEEK_SET); if (fread(&symbol, sizeof(symbol), 1, in) != 1) throw "failed to read symbol"; i += symbol.num_aux; printf("%s\n", symbol.name.GetString(in, string_offset).c_str()); printf("value = %08X, section = %04X, type = %04X, storage = %02X, aux = %02X\n", symbol.value, symbol.section_index, symbol.type, symbol.storage, symbol.num_aux); SymbolEx symbol_ex; symbol_ex.name = symbol.name.GetString(in, string_offset); if (symbol.section_index > 0 && symbol.section_index < 0x7FFF) { u16 section_index = symbol.section_index - 1; symbol_ex.region = sections[section_index].flags & SFlag::Prog ? SymbolEx::Prog : SymbolEx::Data; symbol_ex.value = symbol.value + (sections[section_index].flags & SFlag::Prog ? sections[section_index].prog_addr : sections[section_index].data_addr); } else { symbol_ex.region = SymbolEx::Absolute; symbol_ex.value = symbol.value; } symbol_ex.storage = symbol.storage; symbol_ex.type = symbol.type; symbols.push_back(symbol_ex); if (symbols.back().region == SymbolEx::Prog || symbols.back().region == SymbolEx::Data) { symbols_lut[symbols.back().value].push_back(symbols.size() - 1); } symbol_ex.region = SymbolEx::Aux; for (u16 aux = 0; aux < symbol.num_aux; ++aux) { symbols.push_back(symbol_ex); } } // Merge duplicated sections std::sort(sections.begin(), sections.end(), [](const Section& left, const Section& right) { u32 lm = left.flags & SFlag::Dupl; u32 rm = right.flags & SFlag::Dupl; if (lm == rm) { return left.name < right.name; } else { return lm < rm; } }); auto mid = sections.begin() + sections.size() / 2; for (auto a = sections.begin(), b = mid; a != mid; ++a, ++b) { if (a->name != b->name || a->prog_addr != b->prog_addr || a->data_addr != b->data_addr || a->data.size() != b->data.size() || (a->flags + SFlag::Dupl) != b->flags) throw "mismatch"; if (!b->line_numbers.empty()) throw "dup has line numbers"; if (b->num_rel != 0) throw "dup has relocations"; } sections.resize(sections.size() / 2); // Break up multi-region section auto both_begin = std::partition(sections.begin(), sections.end(), [](const Section& section) { return (section.flags & SFlag::RegionMask) != SFlag::RegionMask; }); std::vector
copy(both_begin, sections.end()); for (auto i = both_begin; i != sections.end(); ++i) { i->flags &= ~SFlag::Prog; i->prog_addr = 0; } for (auto& section : copy) { section.flags &= ~SFlag::Data; section.data_addr = 0; } sections.insert(sections.end(), std::make_move_iterator(copy.begin()), std::make_move_iterator(copy.end())); // Sort according to address std::sort(sections.begin(), sections.end(), [](const Section& left, const Section& right) { if ((left.flags & SFlag::RegionMask) == SFlag::Prog) { if ((right.flags & SFlag::RegionMask) == SFlag::Prog) { if (left.prog_addr == right.prog_addr) { return left.data.size() < right.data.size(); } return left.prog_addr < right.prog_addr; } else { return true; } } else { if ((right.flags & SFlag::RegionMask) == SFlag::Prog) { return false; } else { if (left.data_addr == right.data_addr) { return left.data.size() < right.data.size(); } return left.data_addr < right.data_addr; } } }); } struct Section { std::string name; u32 prog_addr; u32 data_addr; std::vector data; u16 num_rel; u32 flags; struct LineNumber { u32 symbol; u16 line; }; std::unordered_map> line_numbers; std::unordered_map> relocations; }; std::vector
sections; struct SymbolEx { std::string name; enum Region { Aux, Absolute, Prog, Data, } region; u32 value; u16 type; u8 storage; }; std::vector symbols; std::unordered_map> symbols_lut; };