From afc2ff93a5b71b3f5aac34940bb84a87d2ea7e0b Mon Sep 17 00:00:00 2001 From: Wiseguy <68165316+Mr-Wiseguy@users.noreply.github.com> Date: Sat, 6 Sep 2025 18:44:18 -0400 Subject: [PATCH] Implement mdebug parsing for static symbols in IDO elfs (#155) Co-authored-by: Tharo <17233964+Thar0@users.noreply.github.com> --- CMakeLists.txt | 1 + RecompModTool/main.cpp | 3 +- include/recompiler/context.h | 8 + src/config.cpp | 56 +++ src/config.h | 7 + src/elf.cpp | 38 +- src/main.cpp | 13 +- src/mdebug.cpp | 670 +++++++++++++++++++++++++++++++++++ src/mdebug.h | 411 +++++++++++++++++++++ 9 files changed, 1196 insertions(+), 11 deletions(-) create mode 100644 src/mdebug.cpp create mode 100644 src/mdebug.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7fc7581..22279cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,7 @@ add_library(N64RecompElf) target_sources(N64RecompElf PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/elf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/mdebug.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp ) diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index a81f64c..1315f44 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -1181,7 +1181,8 @@ int main(int argc, const char** argv) { .entrypoint_address = 0, .use_absolute_symbols = false, .unpaired_lo16_warnings = false, - .all_sections_relocatable = true + .all_sections_relocatable = true, + .use_mdebug = false, }; bool dummy_found_entrypoint; N64Recomp::DataSymbolMap dummy_syms_map; diff --git a/include/recompiler/context.h b/include/recompiler/context.h index 7b8c1f7..9f22cf0 100644 --- a/include/recompiler/context.h +++ b/include/recompiler/context.h @@ -129,11 +129,19 @@ namespace N64Recomp { std::unordered_map manually_sized_funcs; // The section names that were specified as relocatable std::unordered_set relocatable_sections; + // Symbols to ignore. + std::unordered_set ignored_syms; + // Manual mappings of mdebug file records to elf sections. + std::unordered_map mdebug_text_map; + std::unordered_map mdebug_data_map; + std::unordered_map mdebug_rodata_map; + std::unordered_map mdebug_bss_map; bool has_entrypoint; int32_t entrypoint_address; bool use_absolute_symbols; bool unpaired_lo16_warnings; bool all_sections_relocatable; + bool use_mdebug; }; struct DataSymbol { diff --git a/src/config.cpp b/src/config.cpp index e77099a..1066aff 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -245,6 +245,46 @@ std::vector get_function_hooks(const toml::table* p return ret; } +void get_mdebug_mappings(const toml::array* mdebug_mappings_array, + std::unordered_map& mdebug_text_map, + std::unordered_map& mdebug_data_map, + std::unordered_map& mdebug_rodata_map, + std::unordered_map& mdebug_bss_map +) { + mdebug_mappings_array->for_each([&mdebug_text_map, &mdebug_data_map, &mdebug_rodata_map, &mdebug_bss_map](auto&& el) { + if constexpr (toml::is_table) { + std::optional filename = el["filename"].template value(); + std::optional input_section = el["input_section"].template value(); + std::optional output_section = el["output_section"].template value(); + + if (filename.has_value() && input_section.has_value() && output_section.has_value()) { + const std::string& input_section_val = input_section.value(); + if (input_section_val == ".text") { + mdebug_text_map.emplace(filename.value(), output_section.value()); + } + else if (input_section_val == ".data") { + mdebug_data_map.emplace(filename.value(), output_section.value()); + } + else if (input_section_val == ".rodata") { + mdebug_rodata_map.emplace(filename.value(), output_section.value()); + } + else if (input_section_val == ".bss") { + mdebug_bss_map.emplace(filename.value(), output_section.value()); + } + else { + throw toml::parse_error("Invalid input section in mdebug file mapping entry", el.source()); + } + } + else { + throw toml::parse_error("Mdebug file mappings entry is missing required value(s)", el.source()); + } + } + else { + throw toml::parse_error("Invalid mdebug file mappings entry", el.source()); + } + }); +} + N64Recomp::Config::Config(const char* path) { // Start this config out as bad so that it has to finish parsing without errors to be good. entrypoint = 0; @@ -368,6 +408,22 @@ N64Recomp::Config::Config(const char* path) { unpaired_lo16_warnings = true; } + // Control whether the recompiler should look for and parse mdebug (optional, defaults to false) + std::optional use_mdebug_opt = input_data["use_mdebug"].value(); + if (use_mdebug_opt.has_value()) { + use_mdebug = use_mdebug_opt.value(); + } + else { + use_mdebug = false; + } + + // Symbols to ignore when parsing mdebug (option, defaults to empty) + toml::node_view mdebug_mappings_data = input_data["mdebug_file_mappings"]; + if (mdebug_mappings_data.is_array()) { + get_mdebug_mappings(mdebug_mappings_data.as_array(), + mdebug_text_map, mdebug_data_map, mdebug_rodata_map, mdebug_bss_map); + } + std::optional recomp_include_opt = input_data["recomp_include"].value(); if (recomp_include_opt.has_value()) { recomp_include = recomp_include_opt.value(); diff --git a/src/config.h b/src/config.h index 536c4cc..7d6db23 100644 --- a/src/config.h +++ b/src/config.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace N64Recomp { struct InstructionPatch { @@ -42,6 +43,7 @@ namespace N64Recomp { bool single_file_output; bool use_absolute_symbols; bool unpaired_lo16_warnings; + bool use_mdebug; bool trace_mode; bool allow_exports; bool strict_patch_mode; @@ -62,6 +64,11 @@ namespace N64Recomp { std::vector manual_functions; std::string bss_section_suffix; std::string recomp_include; + // Manual mappings of mdebug file records to elf sections. + std::unordered_map mdebug_text_map; + std::unordered_map mdebug_data_map; + std::unordered_map mdebug_rodata_map; + std::unordered_map mdebug_bss_map; Config(const char* path); bool good() { return !bad; } diff --git a/src/elf.cpp b/src/elf.cpp index 9e23e59..79f412a 100644 --- a/src/elf.cpp +++ b/src/elf.cpp @@ -6,6 +6,8 @@ #include "recompiler/context.h" #include "elfio/elfio.hpp" +#include "mdebug.h" + bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, const N64Recomp::ElfParsingConfig& elf_config, bool dumping_context, std::unordered_map>& data_syms) { bool found_entrypoint_func = false; ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section }; @@ -178,10 +180,13 @@ bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELF fmt::print("Symbol \"{}\" not in a valid section ({})\n", name, section_index); } - // Move this symbol into the corresponding non-bss section if it's in a bss section. + // Move this symbol into the corresponding non-bss section if it's in a bss section and the paired section is relocatable. auto find_bss_it = bss_section_to_target_section.find(target_section_index); if (find_bss_it != bss_section_to_target_section.end()) { - target_section_index = find_bss_it->second; + uint16_t new_target_section_index = find_bss_it->second; + if (new_target_section_index < context.sections.size() && context.sections[new_target_section_index].relocatable) { + target_section_index = find_bss_it->second; + } } data_syms[target_section_index].emplace_back( @@ -215,7 +220,7 @@ std::optional get_segment(const std::vector& segments, ELF return std::nullopt; } -ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) { +ELFIO::section* read_sections(N64Recomp::Context& context, ELFIO::section*& mdebug_section_out, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) { ELFIO::section* symtab_section = nullptr; std::vector segments{}; segments.resize(elf_file.segments.size()); @@ -283,6 +288,11 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP symtab_section = section.get(); } + // Check if this section is an mdebug section and record it if so. Note we expect just one mdebug section + if (type == 0x70000005/* SHT_MIPS_DEBUG */) { + mdebug_section_out = section.get(); + } + if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(section_name)) { section_out.relocatable = true; } @@ -310,10 +320,7 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP if (type == ELFIO::SHT_NOBITS && section_name.ends_with(elf_config.bss_section_suffix)) { std::string bss_target_section = section_name.substr(0, section_name.size() - elf_config.bss_section_suffix.size()); - // If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup - if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(bss_target_section)) { - bss_sections_by_name[bss_target_section] = section.get(); - } + bss_sections_by_name[bss_target_section] = section.get(); } // If this section was marked as being in the ROM in the previous pass, copy it into the ROM now. @@ -630,14 +637,15 @@ bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_pat } if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) { - fmt::print("Incorrect endianness\n"); + fmt::print("Incorrect elf endianness\n"); return false; } setup_context_for_elf(out, elf_file); // Read all of the sections in the elf and look for the symbol table section - ELFIO::section* symtab_section = read_sections(out, elf_config, elf_file); + ELFIO::section* mdebug_section = nullptr; + ELFIO::section* symtab_section = read_sections(out, mdebug_section, elf_config, elf_file); // If no symbol table was found then exit if (symtab_section == nullptr) { @@ -647,5 +655,17 @@ bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_pat // Read all of the symbols in the elf and look for the entrypoint function found_entrypoint_out = read_symbols(out, elf_file, symtab_section, elf_config, for_dumping_context, data_syms_out); + // Process an mdebug section for static symbols. The presence of an mdebug section in the input is optional. + if (elf_config.use_mdebug) { + if (mdebug_section == nullptr) { + fmt::print("\"use_mdebug\" set to true in config, but no mdebug section is present in the elf!\n"); + return false; + } + if (!N64Recomp::MDebug::parse_mdebug(elf_config, mdebug_section->get_data(), static_cast(mdebug_section->get_offset()), out, data_syms_out)) { + fmt::print("Failed to parse mdebug section\n"); + return false; + } + } + return true; } diff --git a/src/main.cpp b/src/main.cpp index 9b33ef7..12f26ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -310,6 +310,9 @@ int main(int argc, char** argv) { std::unordered_set relocatable_sections{}; relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end()); + std::unordered_set ignored_syms_set{}; + ignored_syms_set.insert(config.ignored_funcs.begin(), config.ignored_funcs.end()); + N64Recomp::Context context{}; if (!config.elf_path.empty() && !config.symbols_file_path.empty()) { @@ -347,11 +350,17 @@ int main(int argc, char** argv) { N64Recomp::ElfParsingConfig elf_config { .bss_section_suffix = config.bss_section_suffix, .relocatable_sections = std::move(relocatable_sections), + .ignored_syms = std::move(ignored_syms_set), + .mdebug_text_map = config.mdebug_text_map, + .mdebug_data_map = config.mdebug_data_map, + .mdebug_rodata_map = config.mdebug_rodata_map, + .mdebug_bss_map = config.mdebug_bss_map, .has_entrypoint = config.has_entrypoint, .entrypoint_address = config.entrypoint, .use_absolute_symbols = config.use_absolute_symbols, .unpaired_lo16_warnings = config.unpaired_lo16_warnings, .all_sections_relocatable = false, + .use_mdebug = config.use_mdebug, }; for (const auto& func_size : config.manual_func_sizes) { @@ -359,7 +368,9 @@ int main(int argc, char** argv) { } bool found_entrypoint_func; - N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, dumping_context, data_syms, found_entrypoint_func); + if (!N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, dumping_context, data_syms, found_entrypoint_func)) { + exit_failure("Failed to parse elf\n"); + } // Add any manual functions add_manual_functions(context, config.manual_functions); diff --git a/src/mdebug.cpp b/src/mdebug.cpp new file mode 100644 index 0000000..bc357ca --- /dev/null +++ b/src/mdebug.cpp @@ -0,0 +1,670 @@ +#include +#include + +#include "fmt/format.h" + +#include "mdebug.h" + +struct MDebugSymbol { + std::string name; + uint32_t address; + uint32_t size; + bool is_func; + bool is_static; + bool is_bss; + bool is_rodata; + bool ignored; +}; + +struct MDebugFile { + std::string filename; + std::vector symbols; +}; + +class MDebugInfo { +public: + MDebugInfo(const N64Recomp::ElfParsingConfig& config, const char* mdebug_section, uint32_t mdebug_offset) { + using namespace N64Recomp; + good_ = false; + // Read, byteswap and relocate the symbolic header. Relocation here means convert file-relative offsets to section-relative offsets. + + MDebug::HDRR hdrr; + std::memcpy(&hdrr, mdebug_section, sizeof(MDebug::HDRR)); + hdrr.swap(); + hdrr.relocate(mdebug_offset); + + // Check the magic value and version number are what we expect. + + if (hdrr.magic != MDebug::MAGIC || hdrr.vstamp != 0) { + fmt::print(stderr, "Warning: Found an mdebug section with bad magic value or version (magic={} version={}). Skipping.\n", hdrr.magic, hdrr.vstamp); + return; + } + + // Read the various records that are relevant for collecting static symbols and where they are declared. + + std::vector fdrs = hdrr.read_fdrs(mdebug_section); + std::vector all_auxs = hdrr.read_auxs(mdebug_section); + std::vector all_pdrs = hdrr.read_pdrs(mdebug_section); + std::vector all_symrs = hdrr.read_symrs(mdebug_section); + + // For each file descriptor + for (size_t fdr_index = 0; fdr_index < fdrs.size(); fdr_index++) { + MDebug::FDR& fdr = fdrs[fdr_index]; + MDebugSymbol pending_sym{}; + bool pending_sym_ready = false; + + auto flush_pending_sym = [&]() { + if (pending_sym_ready) { + // Handle ignored symbols. + pending_sym.ignored = config.ignored_syms.contains(pending_sym.name); + // Add the symbol. + add_symbol(fdr_index, std::move(pending_sym)); + } + pending_sym_ready = false; + }; + + const char* fdr_name = fdr.get_name(mdebug_section + hdrr.cbSsOffset); + add_file(fdr_name); + + // For every symbol record in the file descriptor record. + for (auto symr : fdr.get_symrs(all_symrs)) { + MDebug::ST type = symr.get_st(); + MDebug::SC storage_class = symr.get_sc(); + + switch (type) { + case MDebug::ST_PROC: + case MDebug::ST_STATICPROC: + flush_pending_sym(); + if (symr.value != 0) { + pending_sym.name = fdr.get_string(mdebug_section + hdrr.cbSsOffset, symr.iss); + pending_sym.address = symr.value; + pending_sym.size = 0; + pending_sym.is_func = true; + pending_sym.is_static = (type == MDebug::ST_STATICPROC); + pending_sym.is_bss = false; + pending_sym.is_rodata = false; + pending_sym_ready = true; + } + break; + case MDebug::ST_END: + if (pending_sym.is_func) { + pending_sym.size = symr.value; + } + flush_pending_sym(); + break; + case MDebug::ST_GLOBAL: + case MDebug::ST_STATIC: + flush_pending_sym(); + if (symr.value != 0) { + pending_sym.name = fdr.get_string(mdebug_section + hdrr.cbSsOffset, symr.iss); + pending_sym.address = symr.value; + pending_sym.size = 0; + pending_sym.is_func = false; + pending_sym.is_static = (type == MDebug::ST_STATIC); + pending_sym.is_bss = (storage_class == MDebug::SC_BSS); + pending_sym.is_rodata = (storage_class == MDebug::SC_RDATA); + pending_sym_ready = true; + } + break; + default: + flush_pending_sym(); + break; + } + } + flush_pending_sym(); + } + good_ = true; + } + + bool is_identifier_char(char c) { + if (c >= 'a' && c <= 'z') { + return true; + } + if (c >= 'A' && c <= 'Z') { + return true; + } + if (c == '_') { + return true; + } + if (c >= '0' && c <= '9') { + return true; + } + return false; + } + + std::string sanitize_section_name(std::string section_name) { + // Skip periods at the start of the section name. + size_t start_pos = 0; + while (section_name[start_pos] == '.' && start_pos < section_name.size()) { + start_pos++; + } + + std::string ret = section_name.substr(start_pos); + for (size_t char_index = 0; char_index < ret.size(); char_index++) { + if (!is_identifier_char(ret[char_index])) { + ret[char_index] = '_'; + } + } + return ret; + } + + bool populate_context(const N64Recomp::ElfParsingConfig& elf_config, N64Recomp::Context& context, N64Recomp::DataSymbolMap& data_syms) { + size_t num_files = files_.size(); + std::vector file_text_sections{}; + std::vector file_data_sections{}; + std::vector file_rodata_sections{}; + std::vector file_bss_sections{}; + file_text_sections.resize(num_files, (uint16_t)-1); + file_data_sections.resize(num_files, (uint16_t)-1); + file_rodata_sections.resize(num_files, (uint16_t)-1); + file_bss_sections.resize(num_files, (uint16_t)-1); + std::unordered_map> mdebug_symbol_names{}; // Maps symbol name to list of files that have a symbol of that name. + + // Build a lookup of section name to elf section index. + std::unordered_map elf_sections_by_name{}; + for (uint16_t section_index = 0; section_index < context.sections.size(); section_index++) { + const N64Recomp::Section& section = context.sections[section_index]; + elf_sections_by_name.emplace(section.name, section_index); + } + + // First pass to collect symbol names and map mdebug files to elf sections. + for (size_t file_index = 0; file_index < num_files; file_index++) { + const MDebugFile& file = files_[file_index]; + + if (file.symbols.empty()) { + continue; + } + + bool has_funcs = false; + bool has_data = false; + bool has_rodata = false; + bool has_bss = false; + // Find the section that this file's .text was placed into by looking up global functions. + int file_text_section = -1; + uint32_t min_data_address = 0x0; + uint32_t max_data_address = 0x0; + uint32_t min_rodata_address = 0x0; + uint32_t max_rodata_address = 0x0; + uint32_t min_bss_address = 0x0; + uint32_t max_bss_address = 0x0; + for (const auto& sym : file.symbols) { + if (sym.ignored) { + continue; + } + mdebug_symbol_names[sym.name].emplace_back(file_index); + if (sym.is_func) { + has_funcs = true; + if (!sym.is_static) { + auto find_it = context.functions_by_name.find(sym.name); + if (find_it != context.functions_by_name.end()) { + const auto& found_sym = context.functions[find_it->second]; + file_text_section = found_sym.section_index; + } + } + } + else { + if (sym.address != 0) { + // .bss + if (sym.is_bss) { + has_bss = true; + if (min_bss_address == 0) { + min_bss_address = sym.address; + } + else { + min_bss_address = std::min(min_bss_address, sym.address); + } + max_bss_address = std::max(max_bss_address, sym.address + sym.size); + } + // .rodata + else if (sym.is_rodata) { + has_rodata = true; + if (min_rodata_address == 0) { + min_rodata_address = sym.address; + } + else { + min_rodata_address = std::min(min_rodata_address, sym.address); + } + max_rodata_address = std::max(max_rodata_address, sym.address + sym.size); + } + // .data + else { + has_data = true; + if (min_data_address == 0) { + min_data_address = sym.address; + } + else { + min_data_address = std::min(min_data_address, sym.address); + } + max_data_address = std::max(max_data_address, sym.address + sym.size); + } + } + } + } + + if (!has_funcs) { + continue; + } + + // Manual file text section mapping. + { + auto find_text_mapping_it = elf_config.mdebug_text_map.find(file.filename); + if (find_text_mapping_it != elf_config.mdebug_text_map.end()) { + auto find_text_section_it = elf_sections_by_name.find(find_text_mapping_it->second); + if (find_text_section_it == elf_sections_by_name.end()) { + printf(".text section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_text_mapping_it->second.c_str()); + return false; + } + file_text_section = find_text_section_it->second; + } + } + + if (file_text_section == -1) { + printf("Couldn't determine elf section of mdebug info for file %s\n", file.filename.c_str()); + return false; + } + + file_text_sections[file_index] = file_text_section; + const N64Recomp::Section& text_section = context.sections[file_text_section]; + + if (has_data) { + uint16_t file_data_section; + + // Manual file data section mapping. + auto find_data_mapping_it = elf_config.mdebug_data_map.find(file.filename); + if (find_data_mapping_it != elf_config.mdebug_data_map.end()) { + auto find_data_section_it = elf_sections_by_name.find(find_data_mapping_it->second); + if (find_data_section_it == elf_sections_by_name.end()) { + printf(".data section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_data_mapping_it->second.c_str()); + return false; + } + file_data_section = find_data_section_it->second; + } + // Automatic mapping, attempt to use the same section that .text was placed in. + else { + if (min_data_address < text_section.ram_addr || max_data_address > text_section.ram_addr + text_section.size) { + printf("File %s has static data in mdebug which did not overlap with section %s\n", file.filename.c_str(), text_section.name.c_str()); + return false; + } + file_data_section = file_text_section; + } + + file_data_sections[file_index] = file_data_section; + } + + if (has_rodata) { + uint16_t file_rodata_section;; + + // Manual file rodata section mapping. + auto find_rodata_mapping_it = elf_config.mdebug_data_map.find(file.filename); + if (find_rodata_mapping_it != elf_config.mdebug_data_map.end()) { + auto find_rodata_section_it = elf_sections_by_name.find(find_rodata_mapping_it->second); + if (find_rodata_section_it == elf_sections_by_name.end()) { + printf(".rodata section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_rodata_mapping_it->second.c_str()); + return false; + } + file_rodata_section = find_rodata_section_it->second; + } + // Automatic mapping, attempt to use the same section that .text was placed in. + else { + if (min_rodata_address < text_section.ram_addr || max_rodata_address > text_section.ram_addr + text_section.size) { + printf("File %s has static rodata in mdebug which did not overlap with section %s\n", file.filename.c_str(), text_section.name.c_str()); + return false; + } + file_rodata_section = file_text_section; + } + + file_rodata_sections[file_index] = file_rodata_section; + } + + if (has_bss) { + uint16_t file_bss_section; + + // Manual file bss section mapping. + auto find_bss_mapping_it = elf_config.mdebug_data_map.find(file.filename); + if (find_bss_mapping_it != elf_config.mdebug_data_map.end()) { + auto find_bss_section_it = elf_sections_by_name.find(find_bss_mapping_it->second); + if (find_bss_section_it == elf_sections_by_name.end()) { + printf(".bss section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_bss_mapping_it->second.c_str()); + return false; + } + file_bss_section = find_bss_section_it->second; + } + // Automatic mapping, attempt to use the corresponding bss section for the section .text was placed in. + else { + if (text_section.bss_section_index == (uint16_t)-1) { + printf("File %s has static bss in mdebug but no paired bss section. Use the \"bss_section_suffix\" option to pair bss sections.\n", file.filename.c_str()); + return false; + } + const N64Recomp::Section& bss_section = context.sections[text_section.bss_section_index]; + if (min_bss_address < bss_section.ram_addr || max_bss_address > bss_section.ram_addr + bss_section.size) { + printf("File %s has static bss in mdebug which did not overlap with bss section %s\n", file.filename.c_str(), bss_section.name.c_str()); + return false; + } + file_bss_section = text_section.bss_section_index; + } + + file_bss_sections[file_index] = file_bss_section; + } + } + + // Maps symbol name to list of sections that will receive a symbol with that name. + std::unordered_map> symbol_name_to_sections; + // Elf section of each mdebug symbol, indexed by file index and then symbol index. + std::vector> file_symbol_sections; + file_symbol_sections.resize(num_files); + + // Second pass to assign symbols to elf sections. This allows the third pass to rename symbols if necessary. + // This has to be done after the first pass as we don't know section indices while processing symbols at that point. + for (size_t file_index = 0; file_index < num_files; file_index++) { + const MDebugFile& file = files_[file_index]; + file_symbol_sections[file_index].resize(file.symbols.size()); + for (size_t sym_index = 0; sym_index < file.symbols.size(); sym_index++) { + const MDebugSymbol& sym = file.symbols[sym_index]; + if (sym.ignored) { + continue; + } + if (sym.is_static) { + uint16_t sym_section; + // Static .text + if (sym.is_func) { + sym_section = file_text_sections[file_index]; + } + // Static .bss + else if (sym.is_bss) { + sym_section = file_text_sections[file_index]; + } + // Static .data/.rodata + else { + sym_section = file_text_sections[file_index]; + } + symbol_name_to_sections[sym.name].emplace_back(sym_section); + file_symbol_sections[file_index][sym_index] = sym_section; + } + } + } + + // Mapping of datasym name to section. + std::unordered_map datasyms_by_name{}; + for (const auto &[section_index, section_datasyms] : data_syms) { + for (const auto& datasym : section_datasyms) { + datasyms_by_name.emplace(datasym.name, section_index); + } + } + + // Third pass to populate the context and data symbol map, renaming symbols as needed to avoid conflicts. + for (size_t file_index = 0; file_index < num_files; file_index++) { + const MDebugFile& file = files_[file_index]; + for (size_t sym_index = 0; sym_index < file.symbols.size(); sym_index++) { + const MDebugSymbol& sym = file.symbols[sym_index]; + if (sym.ignored) { + continue; + } + + uint16_t sym_section = file_symbol_sections[file_index][sym_index]; + + // Skip symbols with no section. This should only apply to static data/bss symbols in a file that had no functions. + if (sym_section == (uint16_t)-1) { + continue; + } + + if (sym.is_static) { + bool already_exists = false; + bool already_exists_in_section = false; + + // Check if the symbol name exists in the base symbol list already. + auto find_in_context_it = context.functions_by_name.find(sym.name); + if (find_in_context_it != context.functions_by_name.end()) { + already_exists = true; + uint16_t found_section_index = context.functions[find_in_context_it->second].section_index; + if (sym_section == found_section_index) { + already_exists_in_section = true; + } + } + + // Check if the symbol name exists in the data syms already. + auto find_in_datasyms_it = datasyms_by_name.find(sym.name); + if (find_in_datasyms_it != datasyms_by_name.end()) { + already_exists = true; + uint16_t found_section_index = find_in_datasyms_it->second; + if (sym_section == found_section_index) { + already_exists_in_section = true; + } + } + + // Check if the symbol name exists in the mdebug symbols. + auto find_in_mdebug_it = symbol_name_to_sections.find(sym.name); + if (find_in_mdebug_it != symbol_name_to_sections.end()) { + const std::vector& section_list = find_in_mdebug_it->second; + size_t count_in_section = std::count(section_list.begin(), section_list.end(), sym_section); + // The count will always be at least one because of this symbol itself, so check that it's greater than one for duplicates. + if (count_in_section > 1) { + already_exists_in_section = true; + } + // If the symbol name shows up in multiple sections, rename it. + else if (section_list.size() > 1) { + already_exists = true; + } + } + + std::string sym_output_name = sym.name; + if (already_exists_in_section) { + sym_output_name += fmt::format("_{}_{:08X}", sanitize_section_name(context.sections[sym_section].name), sym.address); + printf("Renamed static symbol \"%s\" to \"%s\"\n", sym.name.c_str(), sym_output_name.c_str()); + } + else if (already_exists) { + sym_output_name += "_" + sanitize_section_name(context.sections[sym_section].name); + printf("Renamed static symbol \"%s\" to \"%s\"\n", sym.name.c_str(), sym_output_name.c_str()); + } + + // Emit the symbol. + if (sym.is_func) { + uint32_t section_vram = context.sections[sym_section].ram_addr; + uint32_t section_offset = sym.address - section_vram; + uint32_t rom_address = static_cast(section_offset + context.sections[sym_section].rom_addr); + const uint32_t* words = reinterpret_cast(context.rom.data() + rom_address); + uint32_t num_instructions = sym.size / sizeof(uint32_t); + + std::vector insn_words(num_instructions); + insn_words.assign(words, words + num_instructions); + + context.functions_by_vram[sym.address].push_back(context.functions.size()); + context.section_functions[sym_section].push_back(context.functions.size()); + context.functions.emplace_back(N64Recomp::Function{ + sym.address, + rom_address, + std::move(insn_words), + std::move(sym_output_name), + sym_section, + // TODO read these from elf config. + false, // ignored + false, // reimplemented + false, // stubbed + }); + } + else { + data_syms[sym_section].emplace_back(N64Recomp::DataSymbol { + sym.address, + std::move(sym_output_name) + }); + } + } + } + } + + return true; + } + void print() { + printf("Mdebug Info\n"); + for (const auto& file : files_) { + printf(" File %s\n", file.filename.c_str()); + for (const auto& symbol : file.symbols) { + printf(" %s @ 0x%08X (size 0x%08X)\n", symbol.name.c_str(), symbol.address, symbol.size); + } + } + } + bool good() { + return good_; + } +private: + void add_file(std::string&& filename) { + files_.emplace_back(MDebugFile{ + .filename = std::move(filename), + .symbols = {} + }); + } + void add_symbol(size_t file_index, MDebugSymbol&& sym) { + symbols_by_name_.emplace(sym.name, std::make_pair(file_index, files_[file_index].symbols.size())); + files_[file_index].symbols.emplace_back(std::move(sym)); + } + std::vector files_; + // Map of symbol name to file index, symbol index. Multimap because multiple symbols may have the same name due to statics. + std::unordered_multimap> symbols_by_name_; + bool good_ = false; +}; + +#if 0 +bool get_func(const char *mdata, const MDebug::HDRR& hdrr, const MDebug::FDR& fdr, const MDebug::PDR& pdr, const std::vector& all_auxs, std::span all_symrs, N64Recomp::Function& func_out) { + func_out = {}; + + std::pair sym_bounds = pdr.sym_bounds(all_symrs, all_auxs); + std::span fdr_symrs = fdr.get_symrs(all_symrs); + + for (uint32_t i = sym_bounds.first; i < sym_bounds.second; i++) { + const MDebug::SYMR& symr = fdr_symrs[i]; + MDebug::ST type = symr.get_st(); + + if (type == MDebug::ST_PROC || type == MDebug::ST_STATICPROC) { + const char* name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss); + printf(" %s\n", name); + } + else if (type == MDebug::ST_END) { + printf(" %08lX\n", symr.value); + } + } + + fflush(stdout); + return true; +} +void read_mdebug(N64Recomp::Context& context, ELFIO::section* mdebug_section, std::unordered_map>& data_syms) { + if (mdebug_section == nullptr) { + return; + } + + ELFIO::Elf64_Off base_offset = mdebug_section->get_offset(); + const char *mdata = mdebug_section->get_data(); + + // Read, byteswap and relocate the symbolic header. Relocation here means convert file-relative offsets to section-relative offsets. + + MDebug::HDRR hdrr; + std::memcpy(&hdrr, mdata, sizeof(MDebug::HDRR)); + hdrr.swap(); + hdrr.relocate(base_offset); + + // Check the magic value and version number are what we expect. + + if (hdrr.magic != MDebug::MAGIC || hdrr.vstamp != 0) { + fmt::print(stderr, "Warning: Found an mdebug section with bad magic value or version (magic={} version={}). Skipping.\n", hdrr.magic, hdrr.vstamp); + return; + } + + // Read the various records that are relevant for collecting static symbols and where they are declared. + + std::vector fdrs = hdrr.read_fdrs(mdata); + std::vector all_auxs = hdrr.read_auxs(mdata); + std::vector all_pdrs = hdrr.read_pdrs(mdata); + std::vector all_symrs = hdrr.read_symrs(mdata); + + // For each file descriptor + for (MDebug::FDR& fdr : fdrs) { + const char* fdr_name = fdr.get_name(mdata + hdrr.cbSsOffset); + + printf("%s @ 0x%08X:\n", fdr_name, fdr.adr); + printf(" Procedures:\n"); + + bool at_function = false; + + + // For every symbol record in the file descriptor record. + for (auto symr : fdr.get_symrs(all_symrs)) { + MDebug::ST type = symr.get_st(); + if (type == MDebug::ST_PROC || type == MDebug::ST_STATICPROC) { + at_function = true; + const char* name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss); + printf(" %s @ 0x%08X\n", name, symr.value); + auto find_it = context.functions_by_name.find(name); + if (find_it != context.functions_by_name.end()) { + printf(" in map: 0x%08lX\n", context.functions[find_it->second].vram); + } + } + else if (type == MDebug::ST_END) { + printf(" size: 0x%08lX\n", symr.value); + } + } + + printf(" Globals:\n"); + + for (auto symr : fdr.get_symrs(all_symrs)) { + MDebug::ST type = symr.get_st(); + if (type == MDebug::ST_GLOBAL) { + const char* name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss); + printf(" %s @ 0x%08X\n", name, symr.value); + } + } + + // // Consider only procedures and symbols defined in this file + // std::span auxs = fdr.get_auxs(all_auxs); + // std::span pdrs = fdr.get_pdrs(all_pdrs); + // std::span symrs = fdr.get_symrs(all_symrs); + + // std::vector> bounds(pdrs.size()); + + // // For each procedure record, determine which symbols belong to it + // for (MDebug::PDR& pdr : pdrs) { + // auto res = pdr.sym_bounds(symrs, auxs); + // bounds.push_back(res); + // } + + // // For each symbol defined in this file + // for (uint32_t isym = 0; isym < symrs.size(); isym++) { + // MDebug::SYMR& symr = symrs[isym]; + + // // // Skip non-statics + // // if (symr.get_st() != MDebug::ST_STATIC && symr.get_st() != MDebug::ST_STATICPROC) { + // // continue; + // // } + + // // // Find the name of the PDR, if any, that contains this symbol. We computed the (ordered) + // // // symbol bounds above, bsearch for the PDR. + // // auto it = std::lower_bound(bounds.begin(), bounds.end(), isym, + // // [](const auto& bound, uint32_t x) { + // // return bound.second <= x; + // // } + // // ); + // // const char* pdr_name = nullptr; + // // if (it != bounds.end() && it->first <= isym && isym < it->second) { + // // // The procedure name is the name of the first symbol + // // pdr_name = fdr.get_string(mdata + hdrr.cbSsOffset, symrs[it->first].iss); + // // } + + // // Get the name of the static symbol + // const char* sym_name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss); + + // // // Present info (TODO: plug into everything else) + // // std::printf("0x%08X %s (in %s, in %s(0x%08X))\n", symr.value, sym_name, pdr_name, fdr_name, fdr.adr); + + // // Present info (TODO: plug into everything else) + // std::printf("0x%08X (0x%08X) %s (in %s(0x%08X))\n", symr.value, symr.bits, sym_name, fdr_name, fdr.adr); + // } + } +} +#endif + +bool N64Recomp::MDebug::parse_mdebug(const N64Recomp::ElfParsingConfig& elf_config, const char* mdebug_section, uint32_t mdebug_offset, N64Recomp::Context& context, N64Recomp::DataSymbolMap& data_syms) { + MDebugInfo mdebug_info{ elf_config, mdebug_section, mdebug_offset }; + + if (!mdebug_info.populate_context(elf_config, context, data_syms)) { + return false; + } + + return true; +} diff --git a/src/mdebug.h b/src/mdebug.h new file mode 100644 index 0000000..54105f6 --- /dev/null +++ b/src/mdebug.h @@ -0,0 +1,411 @@ +#ifndef __RECOMP_MDEBUG_H__ +#define __RECOMP_MDEBUG_H__ + +#include +#include +#include +#include +#include + +#include "recompiler/context.h" + +namespace N64Recomp +{ + namespace MDebug { + + #ifdef _MSC_VER + inline uint32_t bswap32(uint32_t val) { + return _byteswap_ulong(val); + } + inline uint16_t bswap16(uint16_t val) { + return _byteswap_ushort(val); + } + #else + constexpr uint32_t bswap32(uint32_t val) { + return __builtin_bswap32(val); + } + constexpr uint16_t bswap16(uint16_t val) { + return __builtin_bswap16(val); + } + #endif + + /* MIPS Symbol Table Debugging Format */ + + enum SC { + SC_NIL = 0, + SC_TEXT = 1, /* .text symbol */ + SC_DATA = 2, /* .data symbol */ + SC_BSS = 3, /* .bss symbol */ + SC_REGISTER = 4, /* value of symbol is register number */ + SC_ABS = 5, /* value of symbol is absolute */ + SC_UNDEFINED = 6, /* value of symbol is undefined */ + SC_CDBLOCAL = 7, /* variable value is in se->va.?? */ + SC_BITS = 8, /* variable is a bit field */ + SC_CDBSYSTEM = 9, /* variable value is in cdb address space */ + SC_REGIMAGE = 10, /* register value is saved on stack */ + SC_INFO = 11, /* symbol contains debugger information */ + SC_USERSTRUCT = 12, /* address in struct user for current process */ + SC_SDATA = 13, /* load time only small data */ + SC_SBSS = 14, /* load time only small common */ + SC_RDATA = 15, /* load time only read-only data */ + SC_VAR = 16, /* var parameter (FORTRAN, Pascal) */ + SC_COMMON = 17, /* common variable */ + SC_SCOMMON = 18, /* small common */ + SC_VARREGISTER = 19, /* var parameter in a register */ + SC_VARIANT = 20, /* variant record */ + SC_SUNDEFINED = 21, /* small undefined (external) data */ + SC_INIT = 22, /* .init section symbol */ + SC_BASEDVAR = 23, /* FORTRAN or PL/1 ptr based var */ + SC_XDATA = 24, /* exception handling data */ + SC_PDATA = 25, /* procedure section */ + SC_FINI = 26, /* .fini section */ + SC_RCONST = 27, /* .rconst section */ + SC_MAX = 32 + }; + + enum ST { + ST_NIL = 0, + ST_GLOBAL = 1, /* external symbol */ + ST_STATIC = 2, /* static symbol */ + ST_PARAM = 3, /* procedure argument */ + ST_LOCAL = 4, /* local variable */ + ST_LABEL = 5, /* label */ + ST_PROC = 6, /* procedure */ + ST_BLOCK = 7, /* beginning of block */ + ST_END = 8, /* end of something */ + ST_MEMBER = 9, /* member of struct/union/enum/.. */ + ST_TYPEDEF = 10, /* type definition */ + ST_FILE = 11, /* filename */ + ST_REGRELOC = 12, /* register relocation */ + ST_FORWARD = 13, /* forwarding address */ + ST_STATICPROC = 14, /* load time only static procedures */ + /* (CONSTANT and STAPARAM are in different orders between different sources...) */ + ST_CONSTANT = 15, /* constant */ + ST_STAPARAM = 16, /* FORTRAN static parameters */ + ST_STRUCT = 26, /* structure */ + ST_UNION = 27, /* union */ + ST_ENUM = 28, /* enum */ + ST_INDIRECT = 34 + }; + + struct SYMR { + int32_t iss; /* index into String Space of name */ + int32_t value; /* symbol value; can be an address, size or frame offset depending on symbol type */ + uint32_t bits; /* Bitfield: */ + #if 0 + ST st : 6; /* symbol type */ + SC sc : 5; /* storage class - text, data, etc */ + int32_t _reserved : 1; /* reserved bit */ + int32_t index : 20; /* index into sym/aux table */ + #endif + + inline ST get_st() const { + return (ST)(bits >> 26 & 0b111111); + } + + inline SC get_sc() const { + return (SC)(bits >> 21 & 0b11111); + } + + inline int32_t get_index() const { + return bits & 0b11111111111111111111; + } + + void swap() { + iss = bswap32(iss); + value = bswap32(value); + bits = bswap32(bits); + } + }; + + union AUX { + uint32_t any_; + uint32_t ti; /* type information record */ + uint32_t rndx; /* relative index into symbol table */ + uint32_t dnLow; /* low dimension of array */ + uint32_t dnHigh; /* high dimension of array */ + uint32_t isym; /* symbol table index (end of proc) */ + uint32_t iss; /* index into string space (not used) */ + uint32_t width; /* width for non-default sized struct fields */ + uint32_t count; /* count of ranges for variant arm */ + + void swap() { + any_ = bswap32(any_); + } + }; + + struct PDR { + uint32_t addr; /* memory address of start of procedure */ + uint32_t isym; /* start of local symbol entries */ + uint32_t iline; /* start of line number entries */ + uint32_t regmask; /* save register mask */ + uint32_t regoffset; /* save register offset */ + uint32_t iopt; /* start of optimization symbol entries */ + uint32_t fregmask; /* save floating point register mask */ + uint32_t fregoffset; /* save floating point register offset */ + uint32_t frameoffset; /* frame size */ + uint16_t framereg; /* frame pointer register */ + uint16_t pcreg; /* offset or reg of return pc */ + int32_t lnLow; /* lowest line in the procedure */ + int32_t lnHigh; /* highest line in the procedure */ + int32_t cbLineOffset; /* byte offset for this procedure from the fd base */ + + void swap() { + addr = bswap32(addr); + isym = bswap32(isym); + iline = bswap32(iline); + regmask = bswap32(regmask); + regoffset = bswap32(regoffset); + iopt = bswap32(iopt); + fregmask = bswap32(fregmask); + fregoffset = bswap32(fregoffset); + frameoffset = bswap32(frameoffset); + framereg = bswap16(framereg); + pcreg = bswap16(pcreg); + lnLow = bswap32(lnLow); + lnHigh = bswap32(lnHigh); + cbLineOffset = bswap32(cbLineOffset); + } + + inline std::pair sym_bounds(std::span symrs, std::span auxs) const { + const SYMR& first = symrs[isym]; + const ST first_st = first.get_st(); + // The first symbol is the symbol of the procedure itself. The procedure name is the name of this symbol. + assert(first_st == ST_PROC || first_st == ST_STATICPROC); + + const AUX& aux = auxs[first.get_index()]; + const SYMR& last = symrs[aux.isym - 1]; + const ST last_st = last.get_st(); + // The last symbol is the END marker, pointed to by the first AUX of the stPROC symbol. + assert(last_st == ST_END); + + // Return the symbol bounds + return std::make_pair(isym, aux.isym); + } + }; + + enum LANG { + LANG_C = 0, + LANG_PASCAL = 1, + LANG_FORTRAN = 2, + LANG_ASM = 3, + LANG_MACHINE = 4, + LANG_NIL = 5, + LANG_ADA = 6, + LANG_PL1 = 7, + LANG_COBOL = 8, + LANG_STDC = 9, + LANG_CPLUSPLUSV2 = 10, + LANG_MAX = 11 + }; + + struct FDR { + uint32_t adr; /* memory address of beginning of file */ + int32_t rss; /* file name (of source, if known) */ + int32_t issBase; /* file's string space */ + int32_t cbSs; /* number of bytes in the ss */ + int32_t isymBase; /* beginning of symbols */ + int32_t csym; /* count file's of symbols */ + int32_t ilineBase; /* file's line symbols */ + int32_t cline; /* count of file's line symbols */ + int32_t ioptBase; /* file's optimization entries */ + int32_t copt; /* count of file's optimization entries */ + uint16_t ipdFirst; /* start of procedures for this file */ + uint16_t cpd; /* count of procedures for this file */ + int32_t iauxBase; /* file's auxiliary entries */ + int32_t caux; /* count of file's auxiliary entries */ + int32_t rfdBase; /* index into the file indirect table */ + int32_t crfd; /* count file indirect entries */ + uint32_t bits; /* Bitfield: */ + #if 0 + LANG lang : 5; /* language for this file */ + uint32_t fMerge : 1; /* whether this file can be merged */ + uint32_t fReadin : 1; /* true if it was read in (not just created) */ + uint32_t fBigEndian : 1; /* true if AUXU's are big endian */ + uint32_t glevel : 2; /* level this file was compiled with */ + uint32_t _reserved : 20; /* reserved bits */ + #endif + int32_t cbLineOffset; /* byte offset from header for this file ln's */ + int32_t cbLine; /* size of lines for this file */ + + inline LANG get_lang() const { + return (LANG)(bits >> 27 & 0b11111); + } + + inline uint32_t get_fMerge() const { + return bits >> 26 & 1; + } + + inline uint32_t get_fReadin() const { + return bits >> 25 & 1; + } + + inline uint32_t get_fBigEndian() const { + return bits >> 24 & 1; + } + + inline uint32_t get_glevel() const { + return bits >> 22 & 0b11; + } + + void swap() { + adr = bswap32(adr); + rss = bswap32(rss); + issBase = bswap32(issBase); + cbSs = bswap32(cbSs); + isymBase = bswap32(isymBase); + csym = bswap32(csym); + ilineBase = bswap32(ilineBase); + cline = bswap32(cline); + ioptBase = bswap32(ioptBase); + copt = bswap32(copt); + ipdFirst = bswap16(ipdFirst); + cpd = bswap16(cpd); + iauxBase = bswap32(iauxBase); + caux = bswap32(caux); + rfdBase = bswap32(rfdBase); + crfd = bswap32(crfd); + bits = bswap32(bits); + cbLineOffset = bswap32(cbLineOffset); + cbLine = bswap32(cbLine); + } + + inline std::span get_auxs(const std::vector& all_auxs) const { + return std::span(all_auxs).subspan(iauxBase, caux); + } + + inline std::span get_pdrs(const std::vector& all_pdrs) const { + return std::span(all_pdrs).subspan(ipdFirst, cpd); + } + + inline std::span get_symrs(std::span all_symrs) const { + return std::span(all_symrs).subspan(isymBase, csym); + } + + inline const char* get_string(const char* data, size_t index) const { + return data + issBase + index; + } + + inline const char* get_name(const char* data) const { + return get_string(data, rss); + } + }; + + static const uint16_t MAGIC = 0x7009; + + /** + * mdebug sections always start with a Symbolic Header (HDRR) containing + * file-relative (not section-relative) offsets for where to find the rest + * of the data. + */ + struct HDRR { + uint16_t magic; /* 0x7009 */ + uint16_t vstamp; /* version stamp */ + int32_t ilineMax; /* number of line number entries */ + int32_t cbLine; /* number of bytes for line number entries */ + int32_t cbLineOffset; /* offset to start of line number entries */ + int32_t idnMax; /* max index into dense number table */ + int32_t cbDnOffset; /* offset to start dense number table */ + int32_t ipdMax; /* number of procedures */ + int32_t cbPdOffset; /* offset to procedure descriptor table */ + int32_t isymMax; /* number of local symbols */ + int32_t cbSymOffset; /* offset to start of local symbols */ + int32_t ioptMax; /* max index into optimization symbol entries */ + int32_t cbOptOffset; /* offset to optimization symbol entries */ + int32_t iauxMax; /* number of auxillary symbol entries */ + int32_t cbAuxOffset; /* offset to start of auxillary symbol entries */ + int32_t issMax; /* max index into local strings */ + int32_t cbSsOffset; /* offset to start of local strings */ + int32_t issExtMax; /* max index into external strings */ + int32_t cbSsExtOffset; /* offset to start of external strings */ + int32_t ifdMax; /* number of file descriptor entries */ + int32_t cbFdOffset; /* offset to file descriptor table */ + int32_t crfd; /* number of relative file descriptor entries */ + int32_t cbRfdOffset; /* offset to relative file descriptor table */ + int32_t iextMax; /* max index into external symbols */ + int32_t cbExtOffset; /* offset to start of external symbol entries */ + + void swap() { + magic = bswap16(magic); + vstamp = bswap16(vstamp); + ilineMax = bswap32(ilineMax); + cbLine = bswap32(cbLine); + cbLineOffset = bswap32(cbLineOffset); + idnMax = bswap32(idnMax); + cbDnOffset = bswap32(cbDnOffset); + ipdMax = bswap32(ipdMax); + cbPdOffset = bswap32(cbPdOffset); + isymMax = bswap32(isymMax); + cbSymOffset = bswap32(cbSymOffset); + ioptMax = bswap32(ioptMax); + cbOptOffset = bswap32(cbOptOffset); + iauxMax = bswap32(iauxMax); + cbAuxOffset = bswap32(cbAuxOffset); + issMax = bswap32(issMax); + cbSsOffset = bswap32(cbSsOffset); + issExtMax = bswap32(issExtMax); + cbSsExtOffset = bswap32(cbSsExtOffset); + ifdMax = bswap32(ifdMax); + cbFdOffset = bswap32(cbFdOffset); + crfd = bswap32(crfd); + cbRfdOffset = bswap32(cbRfdOffset); + iextMax = bswap32(iextMax); + cbExtOffset = bswap32(cbExtOffset); + } + + void relocate(uint32_t offset) { + cbLineOffset -= offset; + cbDnOffset -= offset; + cbPdOffset -= offset; + cbSymOffset -= offset; + cbOptOffset -= offset; + cbAuxOffset -= offset; + cbSsOffset -= offset; + cbSsExtOffset -= offset; + cbFdOffset -= offset; + cbRfdOffset -= offset; + cbExtOffset -= offset; + } + + inline std::vector read_fdrs(const char* data) { + std::vector fdrs(ifdMax); + const FDR* p = reinterpret_cast(data + cbFdOffset); + fdrs.assign(p, p + ifdMax); + for (FDR& fdr : fdrs) + fdr.swap(); + return fdrs; + } + + inline std::vector read_auxs(const char* data) { + std::vector auxs(iauxMax); + const AUX* p = reinterpret_cast(data + cbAuxOffset); + auxs.assign(p, p + iauxMax); + for (AUX& aux : auxs) + aux.swap(); + return auxs; + } + + inline std::vector read_pdrs(const char* data) { + std::vector pdrs(ipdMax); + const PDR* p = reinterpret_cast(data + cbPdOffset); + pdrs.assign(p, p + ipdMax); + for (PDR& pdr : pdrs) + pdr.swap(); + return pdrs; + } + + inline std::vector read_symrs(const char* data) { + std::vector symrs(isymMax); + const SYMR* p = reinterpret_cast(data + cbSymOffset); + symrs.assign(p, p + isymMax); + for (SYMR& symr : symrs) + symr.swap(); + return symrs; + } + + }; + bool parse_mdebug(const N64Recomp::ElfParsingConfig& elf_config, const char* mdebug_section, uint32_t mdebug_offset, N64Recomp::Context& context, N64Recomp::DataSymbolMap& data_syms); + } +} + +#endif