diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index 16bb955..d5fb79a 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -230,19 +230,21 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st section_order.resize(input_context.sections.size()); std::iota(section_order.begin(), section_order.end(), 0); - // Sort the vector based on the rom address of the corresponding section. - std::sort(section_order.begin(), section_order.end(), - [&](uint16_t a, uint16_t b) { - const auto& section_a = input_context.sections[a]; - const auto& section_b = input_context.sections[b]; - // Sort primarily by ROM address. - if (section_a.rom_addr != section_b.rom_addr) { - return section_a.rom_addr < section_b.rom_addr; - } - // Sort secondarily by RAM address. - return section_a.ram_addr < section_b.ram_addr; - } - ); + // TODO this sort is currently disabled because sections seem to already be ordered + // by elf offset. Determine if this is always the case and remove this if so. + //// Sort the vector based on the rom address of the corresponding section. + //std::sort(section_order.begin(), section_order.end(), + // [&](uint16_t a, uint16_t b) { + // const auto& section_a = input_context.sections[a]; + // const auto& section_b = input_context.sections[b]; + // // Sort primarily by ROM address. + // if (section_a.rom_addr != section_b.rom_addr) { + // return section_a.rom_addr < section_b.rom_addr; + // } + // // Sort secondarily by RAM address. + // return section_a.ram_addr < section_b.ram_addr; + // } + //); // TODO avoid a copy here. ret.rom = input_context.rom; @@ -254,6 +256,9 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st size_t num_imports = ret.import_symbols.size(); + // Mapping of input section to output section for fixing up relocations. + std::unordered_map input_section_to_output_section{}; + // Iterate over the input sections in their sorted order. for (uint16_t section_index : section_order) { const auto& cur_section = input_context.sections[section_index]; @@ -271,6 +276,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st if (cur_section.ram_addr >= output_section_bss_end && cur_section.ram_addr <= round_up_16(output_section_bss_end)) { // Calculate the output section's bss size by using its non-bss end address and the current section's end address. section_out.bss_size = cur_section.ram_addr + cur_section.size - output_section_bss_start; + input_section_to_output_section[section_index] = output_section_index; } } continue; @@ -294,6 +300,9 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st new_section.ram_addr = cur_section.ram_addr; new_section.size = cur_section.size; } + + // Map this section to the current output section. + input_section_to_output_section[section_index] = output_section_index; // Check for special section names. bool patch_section = cur_section.name == N64Recomp::PatchSectionName; @@ -508,9 +517,11 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st } section_out.relocs.emplace_back(N64Recomp::Reloc{ .address = cur_reloc.address, - .target_section_offset = output_section_offset, + // Use the original target section offset, this will be recalculated based on the output section afterwards. + .target_section_offset = cur_reloc.target_section_offset, .symbol_index = 0, - .target_section = N64Recomp::SectionSelf, + // Use the input section index in the reloc, this will be converted to the output section afterwards. + .target_section = cur_reloc.target_section, .type = cur_reloc.type, .reference_symbol = false, }); @@ -520,6 +531,28 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st } } + // Fix up every internal reloc's target section based on the input to output section mapping. + for (auto& section : ret.sections) { + for (auto& reloc : section.relocs) { + if (!reloc.reference_symbol) { + uint16_t input_section_index = reloc.target_section; + auto find_it = input_section_to_output_section.find(input_section_index); + if (find_it == input_section_to_output_section.end()) { + fmt::print("Reloc at address 0x{:08X} references section {}, which didn't get mapped to an output section\n", + reloc.address, input_context.sections[input_section_index].name); + return {}; + } + uint16_t output_section_index = find_it->second; + const auto& input_section = input_context.sections[input_section_index]; + const auto& output_section = ret.sections[output_section_index]; + // Adjust the reloc's target section offset based on the reloc's new section. + reloc.target_section_offset = reloc.target_section_offset + input_section.ram_addr - output_section.ram_addr; + // Replace the target section with the mapped output section. + reloc.target_section = find_it->second; + } + } + } + // Copy the import reference symbols from the input context as-is to this context. ret.import_symbols = input_context.import_symbols; @@ -607,6 +640,10 @@ int main(int argc, const char** argv) { bool mod_context_good; N64Recomp::Context mod_context = build_mod_context(context, std::move(dependencies), mod_context_good); std::vector symbols_bin = N64Recomp::symbols_to_bin_v1(mod_context); + if (symbols_bin.empty()) { + fmt::print(stderr, "Failed to create symbol file\n"); + return EXIT_FAILURE; + } std::ofstream output_syms_file{ config.output_syms_path, std::ios::binary }; output_syms_file.write(reinterpret_cast(symbols_bin.data()), symbols_bin.size()); diff --git a/include/generator.h b/include/generator.h index 3614ddc..5afcc57 100644 --- a/include/generator.h +++ b/include/generator.h @@ -19,6 +19,7 @@ namespace N64Recomp { uint16_t imm16; + bool reloc_tag_as_reference; RelocType reloc_type; uint32_t reloc_section_index; uint32_t reloc_target_section_offset; diff --git a/include/n64recomp.h b/include/n64recomp.h index b577d8f..66c2445 100644 --- a/include/n64recomp.h +++ b/include/n64recomp.h @@ -57,7 +57,6 @@ namespace N64Recomp { bool reference_symbol; }; - constexpr uint16_t SectionSelf = (uint16_t)-1; constexpr uint16_t SectionAbsolute = (uint16_t)-2; constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods constexpr uint16_t SectionEvent = (uint16_t)-4; @@ -181,6 +180,8 @@ namespace N64Recomp { std::vector> section_functions; // A mapping of vram address to every function with that address. std::unordered_map> functions_by_vram; + // A mapping of bss section index to the corresponding non-bss section index. + std::unordered_map bss_section_to_section; // The target ROM being recompiled, TODO move this outside of the context to avoid making a copy for mod contexts. // Used for reading relocations and for the output binary feature. std::vector rom; @@ -469,7 +470,7 @@ namespace N64Recomp { } }; - bool recompile_function(const Context& context, const Function& func, std::ofstream& output_file, std::span> static_funcs); + bool recompile_function(const Context& context, const Function& func, std::ofstream& output_file, std::span> static_funcs, bool tag_reference_relocs); enum class ModSymbolsError { Good, diff --git a/src/cgenerator.cpp b/src/cgenerator.cpp index c4602cf..7751568 100644 --- a/src/cgenerator.cpp +++ b/src/cgenerator.cpp @@ -103,9 +103,11 @@ std::string fpr_u64_to_string(int fpr_index) { std::string unsigned_reloc(const N64Recomp::InstructionContext& context) { switch (context.reloc_type) { case N64Recomp::RelocType::R_MIPS_HI16: - return fmt::format("RELOC_HI16({}, {:#X})", context.reloc_section_index, context.reloc_target_section_offset); + return fmt::format("{}RELOC_HI16({}, {:#X})", + context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); case N64Recomp::RelocType::R_MIPS_LO16: - return fmt::format("RELOC_LO16({}, {:#X})", context.reloc_section_index, context.reloc_target_section_offset); + return fmt::format("{}RELOC_LO16({}, {:#X})", + context.reloc_tag_as_reference ? "REF_" : "", context.reloc_section_index, context.reloc_target_section_offset); default: throw std::runtime_error(fmt::format("Unexpected reloc type {}\n", static_cast(context.reloc_type))); } diff --git a/src/elf.cpp b/src/elf.cpp index 07f096b..7d6c5da 100644 --- a/src/elf.cpp +++ b/src/elf.cpp @@ -345,6 +345,7 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP if (bss_find != bss_sections_by_name.end()) { section_out.bss_section_index = bss_find->second->get_index(); section_out.bss_size = bss_find->second->get_size(); + context.bss_section_to_section[section_out.bss_section_index] = section_index; } if (context.has_reference_symbols() || section_out.relocatable) { @@ -427,24 +428,21 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP return nullptr; } } + else if (rel_symbol_section_index == ELFIO::SHN_ABS) { + reloc_out.reference_symbol = false; + reloc_out.target_section = N64Recomp::SectionAbsolute; + rel_section_vram = 0; + } else { reloc_out.reference_symbol = false; reloc_out.target_section = rel_symbol_section_index; // Handle special sections. if (rel_symbol_section_index >= context.sections.size()) { - switch (rel_symbol_section_index) { - case ELFIO::SHN_ABS: - rel_section_vram = 0; - break; - default: - fmt::print(stderr, "Reloc {} references symbol {} which is in an unknown section 0x{:04X}!\n", - i, rel_symbol_name, rel_symbol_section_index); - return nullptr; - } - } - else { - rel_section_vram = context.sections[rel_symbol_section_index].ram_addr; + fmt::print(stderr, "Reloc {} references symbol {} which is in an unknown section 0x{:04X}!\n", + i, rel_symbol_name, rel_symbol_section_index); + return nullptr; } + rel_section_vram = context.sections[rel_symbol_section_index].ram_addr; } // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) diff --git a/src/main.cpp b/src/main.cpp index d01261d..fd28eb9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -127,7 +127,7 @@ bool recompile_single_function(const N64Recomp::Context& context, const N64Recom "\n", recomp_include); - if (!N64Recomp::recompile_function(context, func, output_file, static_funcs_out)) { + if (!N64Recomp::recompile_function(context, func, output_file, static_funcs_out, false)) { return false; } @@ -617,7 +617,7 @@ int main(int argc, char** argv) { // Recompile the function. if (config.single_file_output || config.functions_per_output_file > 1) { - result = N64Recomp::recompile_function(context, func, current_output_file, static_funcs_by_section); + result = N64Recomp::recompile_function(context, func, current_output_file, static_funcs_by_section, false); if (!config.single_file_output) { cur_file_function_count++; if (cur_file_function_count >= config.functions_per_output_file) { @@ -704,7 +704,7 @@ int main(int argc, char** argv) { bool result; size_t prev_num_statics = static_funcs_by_section[func.section_index].size(); if (config.single_file_output || config.functions_per_output_file > 1) { - result = N64Recomp::recompile_function(context, func, current_output_file, static_funcs_by_section); + result = N64Recomp::recompile_function(context, func, current_output_file, static_funcs_by_section, false); if (!config.single_file_output) { cur_file_function_count++; if (cur_file_function_count >= config.functions_per_output_file) { diff --git a/src/mod_symbols.cpp b/src/mod_symbols.cpp index 8746735..5d385f6 100644 --- a/src/mod_symbols.cpp +++ b/src/mod_symbols.cpp @@ -32,7 +32,8 @@ struct FuncV1 { uint32_t size; }; -constexpr uint32_t SectionSelfVromV1 = 0xFFFFFFFF; +// Local section flag, if set then the reloc is pointing to a section within the mod and the vrom is the section index. +constexpr uint32_t SectionSelfVromFlagV1 = 0x80000000; // Special sections constexpr uint32_t SectionImportVromV1 = 0xFFFFFFFE; @@ -211,12 +212,7 @@ bool parse_v1(std::span data, const std::unordered_map data, const std::unordered_map(target_section_vrom & ~SectionSelfVromFlagV1); + reloc_target_section_offset = reloc_in.target_section_offset_or_index; + reloc_symbol_index = 0; // Not used for normal relocs. + cur_reloc.reference_symbol = false; + if (reloc_target_section >= mod_context.sections.size()) { + printf("Reloc %zu in section %zu references local section %u, but only %zu exist\n", + reloc_index, section_index, reloc_target_section, mod_context.sections.size()); + } + } else { // TODO lookup by section index by original vrom auto find_section_it = sections_by_vrom.find(target_section_vrom); @@ -593,11 +599,14 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont vec_put(ret, &func_out); } - for (const Reloc& cur_reloc : cur_section.relocs) { + for (size_t reloc_index = 0; reloc_index < cur_section.relocs.size(); reloc_index++) { + const Reloc& cur_reloc = cur_section.relocs[reloc_index]; uint32_t target_section_vrom; uint32_t target_section_offset_or_index = cur_reloc.target_section_offset; - if (cur_reloc.target_section == SectionSelf) { - target_section_vrom = SectionSelfVromV1; + if (cur_reloc.target_section == SectionAbsolute) { + printf("Internal error: reloc %zu in section %zu references an absolute symbol and should have been relocated already\n", + reloc_index, section_index); + return {}; } else if (cur_reloc.target_section == SectionImport) { target_section_vrom = SectionImportVromV1; @@ -611,7 +620,12 @@ std::vector N64Recomp::symbols_to_bin_v1(const N64Recomp::Context& cont target_section_vrom = context.get_reference_section_rom(cur_reloc.target_section); } else { - target_section_vrom = context.sections[cur_reloc.target_section].rom_addr; + if (cur_reloc.target_section >= context.sections.size()) { + printf("Internal error: reloc %zu in section %zu references section %u, but only %zu exist\n", + reloc_index, section_index, cur_reloc.target_section, context.sections.size()); + return {}; + } + target_section_vrom = SectionSelfVromFlagV1 | cur_reloc.target_section; } RelocV1 reloc_out { .section_offset = cur_reloc.address - cur_section.ram_addr, diff --git a/src/recompilation.cpp b/src/recompilation.cpp index 17739d6..9ee1f66 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -110,7 +110,7 @@ std::string_view ctx_gpr_prefix(int reg) { } // Major TODO, this function grew very organically and needs to be cleaned up. Ideally, it'll get split up into some sort of lookup table grouped by similar instruction types. -bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Function& func, const N64Recomp::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, std::span> static_funcs_out) { +bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Function& func, const N64Recomp::FunctionStats& stats, const std::unordered_set& skipped_insns, size_t instr_index, const std::vector& instructions, std::ofstream& output_file, bool indent, bool emit_link_branch, int link_branch_index, size_t reloc_index, bool& needs_link_branch, bool& is_branch_likely, bool tag_reference_relocs, std::span> static_funcs_out) { using namespace N64Recomp; const auto& section = context.sections[func.section_index]; @@ -158,48 +158,50 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun // Get the reloc data for this instruction const auto& reloc = section.relocs[reloc_index]; reloc_section = reloc.target_section; - // Only process this relocation if this section is relocatable or if this relocation targets a reference symbol. - if (section.relocatable || reloc.reference_symbol) { - // Ignore this reloc if it points to a different section. - // Also check if the reloc points to the bss section since that will also be relocated with the section. - // Additionally, always process reference symbol relocations. - if (reloc_section == func.section_index || reloc_section == section.bss_section_index || reloc_section == N64Recomp::SectionSelf || reloc.reference_symbol) { - // Record the reloc's data. - reloc_type = reloc.type; - reloc_target_section_offset = reloc.target_section_offset; - // Ignore all relocs that aren't HI16 or LO16. - if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16 || reloc_type == N64Recomp::RelocType::R_MIPS_26) { - if (reloc.reference_symbol) { - reloc_reference_symbol = reloc.symbol_index; - // Don't try to relocate special section symbols. - if (context.is_regular_reference_section(reloc.target_section)) { - bool ref_section_relocatable = context.is_reference_section_relocatable(reloc.target_section); - uint32_t ref_section_vram = context.get_reference_section_vram(reloc.target_section); - // Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate. - if (!ref_section_relocatable && (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16)) { - uint32_t full_immediate = reloc.target_section_offset + ref_section_vram; - if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16) { - imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1); - reloc_type = N64Recomp::RelocType::R_MIPS_NONE; - } - else if (reloc_type == N64Recomp::RelocType::R_MIPS_LO16) { - imm = full_immediate & 0xFFFF; - reloc_type = N64Recomp::RelocType::R_MIPS_NONE; - } + // Check if the relocation references a relocatable section. + bool target_relocatable = false; + if (!reloc.reference_symbol && reloc_section != N64Recomp::SectionAbsolute) { + const auto& target_section = context.sections[reloc_section]; + target_relocatable = target_section.relocatable; + } - // The reloc has been processed, so set it to none to prevent it getting processed a second time during instruction code generation. - reloc_type = N64Recomp::RelocType::R_MIPS_NONE; - reloc_reference_symbol = (size_t)-1; + // Only process this relocation if the target section is relocatable or if this relocation targets a reference symbol. + if (target_relocatable || reloc.reference_symbol) { + // Record the reloc's data. + reloc_type = reloc.type; + reloc_target_section_offset = reloc.target_section_offset; + // Ignore all relocs that aren't MIPS_HI16, MIPS_LO16 or MIPS_26. + if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16 || reloc_type == N64Recomp::RelocType::R_MIPS_26) { + if (reloc.reference_symbol) { + reloc_reference_symbol = reloc.symbol_index; + // Don't try to relocate special section symbols. + if (context.is_regular_reference_section(reloc.target_section) || reloc_section == N64Recomp::SectionAbsolute) { + bool ref_section_relocatable = context.is_reference_section_relocatable(reloc.target_section); + uint32_t ref_section_vram = context.get_reference_section_vram(reloc.target_section); + // Resolve HI16 and LO16 reference symbol relocs to non-relocatable sections by patching the instruction immediate. + if (!ref_section_relocatable && (reloc_type == N64Recomp::RelocType::R_MIPS_HI16 || reloc_type == N64Recomp::RelocType::R_MIPS_LO16)) { + uint32_t full_immediate = reloc.target_section_offset + ref_section_vram; + + if (reloc_type == N64Recomp::RelocType::R_MIPS_HI16) { + imm = (full_immediate >> 16) + ((full_immediate >> 15) & 1); } + else if (reloc_type == N64Recomp::RelocType::R_MIPS_LO16) { + imm = full_immediate & 0xFFFF; + } + + // The reloc has been processed, so set it to none to prevent it getting processed a second time during instruction code generation. + reloc_type = N64Recomp::RelocType::R_MIPS_NONE; + reloc_reference_symbol = (size_t)-1; } } } + } - // Repoint bss relocations at their non-bss counterpart section. - if (reloc_section != N64Recomp::SectionSelf && reloc_section == section.bss_section_index) { - reloc_section = func.section_index; - } + // Repoint bss relocations at their non-bss counterpart section. + auto find_bss_it = context.bss_section_to_section.find(reloc_section); + if (find_bss_it != context.bss_section_to_section.end()) { + reloc_section = find_bss_it->second; } } } @@ -219,7 +221,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out)) { + if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } } @@ -333,7 +335,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out)) { + if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, true, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } } @@ -503,7 +505,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun if (reloc_index + 1 < section.relocs.size() && next_vram > section.relocs[reloc_index].address) { next_reloc_index++; } - if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, static_funcs_out)) { + if (!process_instruction(context, func, stats, skipped_insns, instr_index + 1, instructions, output_file, false, false, link_branch_index, next_reloc_index, dummy_needs_link_branch, dummy_is_branch_likely, tag_reference_relocs, static_funcs_out)) { return false; } print_indent(); @@ -582,6 +584,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun instruction_context.ft = ft; instruction_context.cop1_cs = cop1_cs; instruction_context.imm16 = imm; + instruction_context.reloc_tag_as_reference = (reloc_reference_symbol != (size_t)-1) && tag_reference_relocs; instruction_context.reloc_type = reloc_type; instruction_context.reloc_section_index = reloc_section; instruction_context.reloc_target_section_offset = reloc_target_section_offset; @@ -730,7 +733,7 @@ bool process_instruction(const N64Recomp::Context& context, const N64Recomp::Fun return true; } -bool N64Recomp::recompile_function(const N64Recomp::Context& context, const N64Recomp::Function& func, std::ofstream& output_file, std::span> static_funcs_out) { +bool N64Recomp::recompile_function(const N64Recomp::Context& context, const N64Recomp::Function& func, std::ofstream& output_file, std::span> static_funcs_out, bool tag_reference_relocs) { //fmt::print("Recompiling {}\n", func.name); std::vector instructions; @@ -813,7 +816,7 @@ bool N64Recomp::recompile_function(const N64Recomp::Context& context, const N64R } // Process the current instruction and check for errors - if (process_instruction(context, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, static_funcs_out) == false) { + if (process_instruction(context, func, stats, skipped_insns, instr_index, instructions, output_file, false, needs_link_branch, num_link_branches, reloc_index, needs_link_branch, is_branch_likely, tag_reference_relocs, static_funcs_out) == false) { fmt::print(stderr, "Error in recompiling {}, clearing output file\n", func.name); output_file.clear(); return false;