diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index bb35a7a3c..8c1c7c5e2 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -2002,6 +2002,38 @@ namespace ps2recomp return best; }; + // Ghidra exports standalone code blocks (static initializers, thunks, ...) as entry_* labels. Only entry_* + // blocks nested inside a real function are synthetic wrappers; standalone ones need their own resume + // points, otherwise a thread switched out inside a call from them cannot be resumed. + std::vector> ownerRanges; + for (const auto &function : m_functions) + { + if (function.isRecompiled && !function.isStub && !function.isSkipped && + !isEntryFunctionName(function.name) && function.end > function.start) + { + ownerRanges.emplace_back(function.start, function.end); + } + } + std::sort(ownerRanges.begin(), ownerRanges.end()); + uint32_t maxEnd = 0u; + std::vector prefixMaxEnd; + prefixMaxEnd.reserve(ownerRanges.size()); + for (const auto &range : ownerRanges) + { + maxEnd = std::max(maxEnd, range.second); + prefixMaxEnd.push_back(maxEnd); + } + auto nestedInRealFunction = [&](uint32_t address) -> bool + { + auto it = std::upper_bound(ownerRanges.begin(), ownerRanges.end(), std::make_pair(address, UINT32_MAX)); + if (it == ownerRanges.begin()) + { + return false; + } + const size_t idx = static_cast(std::distance(ownerRanges.begin(), it)) - 1u; + return prefixMaxEnd[idx] > address; + }; + for (const auto &function : m_functions) { if (!function.isRecompiled || function.isStub || function.isSkipped) @@ -2009,7 +2041,7 @@ namespace ps2recomp continue; } - if (isEntryFunctionName(function.name)) + if (isEntryFunctionName(function.name) && nestedInRealFunction(function.start)) { continue; } diff --git a/ps2xTest/src/ps2_recompiler_tests.cpp b/ps2xTest/src/ps2_recompiler_tests.cpp index 86e5e3f9f..201b71e8c 100644 --- a/ps2xTest/src/ps2_recompiler_tests.cpp +++ b/ps2xTest/src/ps2_recompiler_tests.cpp @@ -270,6 +270,65 @@ static bool writeMinimalMipsElfWithUnmappedEntryHint(const std::filesystem::path return writer.save(elfPath.string()); } +static bool writeMinimalMipsElfWithStandaloneEntryBlock(const std::filesystem::path &elfPath) +{ + ELFIO::elfio writer; + writer.create(ELFIO::ELFCLASS32, ELFIO::ELFDATA2LSB); + writer.set_os_abi(ELFIO::ELFOSABI_NONE); + writer.set_type(ELFIO::ET_EXEC); + writer.set_machine(ELFIO::EM_MIPS); + writer.set_entry(0x00100000u); + + ELFIO::section *text = writer.sections.add(".text"); + text->set_type(ELFIO::SHT_PROGBITS); + text->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR); + text->set_addr_align(4); + text->set_address(0x00100000u); + + const std::array textWords = { + 0x0C040004u, // entry_00100000: jal 0x00100010 + 0x00000000u, // nop + 0x03E00008u, // jr $ra (the JAL return address, 0x00100008, is a resume point) + 0x00000000u, // nop + 0x03E00008u, // callee at 0x00100010: jr $ra + 0x00000000u, // nop + 0x03E00008u, // other at 0x00100018: jr $ra + 0x00000000u // nop + }; + text->set_data(reinterpret_cast(textWords.data()), + static_cast(textWords.size() * sizeof(uint32_t))); + + ELFIO::section *strtab = writer.sections.add(".strtab"); + strtab->set_type(ELFIO::SHT_STRTAB); + strtab->set_addr_align(1); + + ELFIO::section *symtab = writer.sections.add(".symtab"); + symtab->set_type(ELFIO::SHT_SYMTAB); + symtab->set_info(1); + symtab->set_link(strtab->get_index()); + symtab->set_addr_align(4); + symtab->set_entry_size(writer.get_default_entry_size(ELFIO::SHT_SYMTAB)); + + ELFIO::symbol_section_accessor symbols(writer, symtab); + ELFIO::string_section_accessor strings(strtab); + symbols.add_symbol(strings, "", 0, 0, + ELFIO::STB_LOCAL, ELFIO::STT_NOTYPE, 0, ELFIO::SHN_UNDEF); + symbols.add_symbol(strings, "entry_00100000", 0x00100000u, 0x10u, + ELFIO::STB_GLOBAL, ELFIO::STT_FUNC, 0, text->get_index()); + symbols.add_symbol(strings, "callee", 0x00100010u, 8u, + ELFIO::STB_GLOBAL, ELFIO::STT_FUNC, 0, text->get_index()); + symbols.add_symbol(strings, "other", 0x00100018u, 8u, + ELFIO::STB_GLOBAL, ELFIO::STT_FUNC, 0, text->get_index()); + + ELFIO::segment *textSegment = writer.segments.add(); + textSegment->set_type(ELFIO::PT_LOAD); + textSegment->set_flags(ELFIO::PF_R | ELFIO::PF_X); + textSegment->set_align(0x1000); + textSegment->add_section_index(text->get_index(), text->get_addr_align()); + + return writer.save(elfPath.string()); +} + static bool writeMinimalMipsElfWithAddressTakenCallbacks(const std::filesystem::path &elfPath, bool includePartialDwarf = false) { @@ -1412,6 +1471,45 @@ void register_ps2_recompiler_tests() std::filesystem::remove_all(tempRoot, removeError); }); + tc.Run("standalone entry_ blocks register resume points for their JAL return addresses", [](TestCase &t) { + const std::string uniqueSuffix = + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const std::filesystem::path tempRoot = + std::filesystem::temp_directory_path() / ("ps2recomp-standalone-entry-" + uniqueSuffix); + const std::filesystem::path elfPath = tempRoot / "standalone-entry.elf"; + const std::filesystem::path configPath = tempRoot / "standalone-entry.toml"; + const std::filesystem::path outputPath = tempRoot / "output"; + std::filesystem::create_directories(tempRoot); + + const bool elfWritten = writeMinimalMipsElfWithStandaloneEntryBlock(elfPath); + const bool configWritten = writeRecompilerTestConfig(configPath, elfPath, outputPath, {}); + t.IsTrue(elfWritten && configWritten, + "standalone entry block regression inputs should be generated"); + + if (elfWritten && configWritten) + { + PS2Recompiler recompiler(configPath.string()); + t.IsTrue(recompiler.initialize(), + "standalone entry block config should initialize"); + t.IsTrue(recompiler.recompile(), + "standalone entry block should recompile"); + recompiler.generateOutput(); + + // entry_00100000 is not nested inside any other function, so a thread switched out inside its + // JAL must be able to resume at the return address (0x100008) through the registered owner. + const std::filesystem::path registrationPath = outputPath / "register_functions.cpp"; + std::ifstream registrationFile(registrationPath); + const std::string registration{ + std::istreambuf_iterator(registrationFile), + std::istreambuf_iterator()}; + t.IsTrue(registration.find("// 0x100008") != std::string::npos, + "JAL return address inside a standalone entry_ block should be registered as a resume point"); + } + + std::error_code removeError; + std::filesystem::remove_all(tempRoot, removeError); + }); + tc.Run("elf parser ignores STT_FUNC symbols in non-executable sections", [](TestCase &t) { const auto uniqueSuffix = std::to_string( static_cast(std::chrono::steady_clock::now().time_since_epoch().count()));