diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index ebceec145..9f427da20 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -83,7 +83,6 @@ public: FEX_CONFIG_OPT(EnableLazyCodeCaching, ENABLELAZYCODECACHINGWIP); FEX_CONFIG_OPT(EnableCodeCacheValidation, ENABLECODECACHEVALIDATION); - uint64_t ComputeCodeMapId(std::string_view Filename, int FD) override; bool SaveData(Core::InternalThreadState&, int TargetFD, const ExecutableFileSectionInfo&, uint64_t SerializedBaseAddress) override; fextl::unique_ptr LoadCache(std::span CacheFile, const ExecutableFileInfo&, uint64_t FileStartVA) override; diff --git a/FEXCore/Source/Interface/Core/CodeCache.cpp b/FEXCore/Source/Interface/Core/CodeCache.cpp index a9de6c722..87a110c84 100644 --- a/FEXCore/Source/Interface/Core/CodeCache.cpp +++ b/FEXCore/Source/Interface/Core/CodeCache.cpp @@ -273,16 +273,6 @@ CodeCache::CodeCache(ContextImpl& CTX_) : CTX(CTX_) {} CodeCache::~CodeCache() = default; -uint64_t CodeCache::ComputeCodeMapId(std::string_view Filename, int FD) { - if (Filename.empty()) { - return 0xffff'ffff'ffff'ffff; - } - - // For now, we just use the file path as an identifier. - // TODO: Ensure the hash is unique enough to distinguish executables while remaining independent of the installation location - return XXH3_64bits(Filename.data(), Filename.size()); -} - struct CodeCacheHeader { std::array Magic = ExpectedMagic; // Version history: diff --git a/FEXCore/include/FEXCore/Core/CodeCache.h b/FEXCore/include/FEXCore/Core/CodeCache.h index 3f34a787b..a09c574fe 100644 --- a/FEXCore/include/FEXCore/Core/CodeCache.h +++ b/FEXCore/include/FEXCore/Core/CodeCache.h @@ -235,14 +235,6 @@ class AbstractCodeCache { public: virtual ~AbstractCodeCache() = default; - /** - * Computes a unique identifier for the referenced binary file to be used for - * generating the code map. - * This identifier is independent of FEX build/runtime configuration and - * stable across FEX updates. - */ - virtual uint64_t ComputeCodeMapId(std::string_view Filename, int FD) = 0; - /** * Bundles the current Core state (CodeBuffer, GuestToHostMapping, ...) to a code cache and writes it to the given file descriptor. * Returns true on success. diff --git a/Source/Tools/CommonTools/Linux/Utils/ELFParser.h b/Source/Tools/CommonTools/Linux/Utils/ELFParser.h index 978a2d5ea..216f33768 100644 --- a/Source/Tools/CommonTools/Linux/Utils/ELFParser.h +++ b/Source/Tools/CommonTools/Linux/Utils/ELFParser.h @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT #pragma once #include -#include +#include #include #include +#include +#include #include #include @@ -288,6 +290,100 @@ struct ELFParser { return false; } + struct MappedSection { + const void* base {}; + const void* ptr {}; + size_t size {}; + }; + + MappedSection MapSection(int fd, uint64_t offset, size_t Size) { + // Need to map from [offset, offset+Size). + const uint64_t PageAlignedBase = FEXCore::AlignDown(offset, FEXCore::Utils::FEX_PAGE_SIZE); + const uint64_t OffsetInPage = (offset - PageAlignedBase); + const uint64_t TotalSize = OffsetInPage + Size; + auto ptr = ::mmap(nullptr, TotalSize, PROT_READ, MAP_PRIVATE, fd, PageAlignedBase); + if (ptr == MAP_FAILED) { + return {}; + } + + return MappedSection { + .base = ptr, + .ptr = reinterpret_cast(reinterpret_cast(ptr) + OffsetInPage), + .size = TotalSize, + }; + } + + void FreeSection(MappedSection& section) { + ::munmap(const_cast(section.base), section.size); + } + + // Returns an ELF file's build-id if it exists. + // Not all ELF files have a build id so it needs to be optional. + fextl::vector GetBuildID() { + if (fd == -1 || !EnsureSectionHeadersLoaded()) { + return {}; + } + + const Elf64_Shdr* StrHeader = &shdrs->at(ehdr.e_shstrndx); + auto SHStringSection = MapSection(fd, StrHeader->sh_offset, StrHeader->sh_size); + if (SHStringSection.base == nullptr) { + return {}; + } + + auto find_name = [&SHStringSection](int offset) -> std::string_view { + if (offset >= SHStringSection.size) { + return {}; + } + + return reinterpret_cast(SHStringSection.ptr) + offset; + }; + + fextl::vector BuildID {}; + + for (const auto& shdr : *shdrs) { + if (shdr.sh_type != SHT_NOTE || shdr.sh_size == 0) { + continue; + } + + auto SectionName = find_name(shdr.sh_name); + if (SectionName != ".note.gnu.build-id") { + continue; + } + + auto BuildIDSection = MapSection(fd, shdr.sh_offset, shdr.sh_size); + if (BuildIDSection.base == nullptr) { + // Couldn't map + break; + } + + struct ELFNote { + uint32_t NameSize; + uint32_t DescSize; + uint32_t Type; + char Name[]; + }; + + auto Note = reinterpret_cast(BuildIDSection.ptr); + const auto DataOffset = (Note->NameSize + 3) & ~3; + + if (Note->Type == NT_GNU_BUILD_ID && Note->NameSize == 4 && std::string_view(Note->Name, Note->NameSize - 1) == "GNU" && + shdr.sh_size <= (12 + DataOffset + Note->DescSize)) { + auto Desc = reinterpret_cast(&Note->Name[0] + DataOffset); + BuildID.insert(BuildID.end(), Desc, Desc + Note->DescSize); + } + + FreeSection(BuildIDSection); + + if (!BuildID.empty()) { + // Found the build-id. + break; + } + } + + FreeSection(SHStringSection); + return BuildID; + } + /** * Parses relocation sections (SHT_REL/SHT_RELA) and returns a map of * offsets to relocations that FEX's JIT must know about. diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h index 7b417e83e..4721fc3b2 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h @@ -205,6 +205,7 @@ public: FEX_CONFIG_OPT(SMCChecks, SMCCHECKS); FEX_CONFIG_OPT(NeedsSeccomp, NEEDSSECCOMP); FEX_CONFIG_OPT(EnableCodeCaching, ENABLECODECACHINGWIP); + FEX_CONFIG_OPT(EnableDiskCache, DISKCACHE); uint32_t GetHostKernelVersion() const { return HostKernelVersion; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/SyscallsSMCTracking.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/SyscallsSMCTracking.cpp index 6739f891c..94bdd378b 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/SyscallsSMCTracking.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/SyscallsSMCTracking.cpp @@ -17,6 +17,7 @@ $end_info$ #include #include #include +#include #include "LinuxSyscalls/Syscalls.h" #include "LinuxSyscalls/SignalDelegator.h" @@ -231,6 +232,7 @@ FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXC struct ReadELFHeadersResult { fextl::vector ProgramHeaders; fextl::robin_map Relocations; + fextl::vector BuildID; bool HasCodeRelocations; }; @@ -256,7 +258,8 @@ static ReadELFHeadersResult ReadELFHeaders(int FD, std::span HeaderDa auto Relocations = Parser.PopulateRelocations(); auto HasCodeRelocations = Parser.HasCodeRelocations(); - return ReadELFHeadersResult {std::move(Parser.phdrs), std::move(Relocations), HasCodeRelocations}; + auto buildid = Parser.GetBuildID(); + return ReadELFHeadersResult {std::move(Parser.phdrs), std::move(Relocations), buildid, HasCodeRelocations}; } static fextl::unique_ptr @@ -574,6 +577,35 @@ uint64_t SyscallHandler::GuestShmdt(bool Is64Bit, FEXCore::Core::InternalThreadS return Result; } +/** + * Computes a unique identifier for the referenced binary file to be used for + * generating the code map. + * This identifier is independent of FEX build/runtime configuration and + * stable across FEX updates. + */ +static uint64_t ComputeCodeMapId(std::string_view Filename, std::optional*> BuildID = std::nullopt) { + // Use a combination of filename and buildid if it exists. + // Not all executables have buildid so this isn't an all encompassing solution. + // `Crypt of the Necrodancer` as an example doesn't have a BuildID on Linux. + + uint64_t Hash = ~0ULL; + if (!Filename.empty()) { + Hash = XXH3_64bits(Filename.data(), Filename.size()); + } + + if (BuildID) { + auto Data = (*BuildID)->data(); + auto Size = (*BuildID)->size(); + if (Size == 8) { + auto ID64Bit = reinterpret_cast(Data); + Hash ^= *ID64Bit; + } else { + Hash ^= XXH3_64bits(Data, Size); + } + } + return Hash; +} + // MMan Tracking std::optional SyscallHandler::TrackMmap(FEXCore::Core::InternalThreadState* Thread, uint64_t addr, size_t length, int prot, int flags, int fd, @@ -611,11 +643,12 @@ SyscallHandler::TrackMmap(FEXCore::Core::InternalThreadState* Thread, uint64_t a if ((prot & PROT_READ) && Inserted) { Resource->MappedFile = fextl::make_unique(); Resource->MappedFile->Filename = fextl::string(Tmp, PathLength); - Resource->MappedFile->FileId = CTX->GetCodeCache().ComputeCodeMapId(Resource->MappedFile->Filename, fd); // Read ELF headers if applicable and needed for code caching. // For performance, skip ELF checks if we're not mapping the file header bool CheckForElfFile = (offset == 0) && EnableCodeCaching; + bool CheckForElfFileDiskCache = (offset == 0) && EnableDiskCache; + #if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED CheckForElfFile = true; #endif @@ -624,6 +657,7 @@ SyscallHandler::TrackMmap(FEXCore::Core::InternalThreadState* Thread, uint64_t a Resource->ProgramHeaders = std::move(ELFResult.ProgramHeaders); Resource->MappedFile->Relocations = std::move(ELFResult.Relocations); Resource->RequiresDelayedCacheLoad = ELFResult.HasCodeRelocations; + Resource->MappedFile->FileId = ComputeCodeMapId(Resource->MappedFile->Filename, &ELFResult.BuildID); // GuestRelocationType::Skip indicates to FEXOfflineCompiler that // any blocks covered by the relocation may not be cached. @@ -654,6 +688,11 @@ SyscallHandler::TrackMmap(FEXCore::Core::InternalThreadState* Thread, uint64_t a LOGMAN_THROW_A_FMT(Resource->ProgramHeaders.empty() || offset == 0, "Expected file offset 0 for the first mapping of an ELF " "file"); + } else if (CheckForElfFileDiskCache) { + auto ELFResult = ReadELFHeaders(fd, std::span {reinterpret_cast(addr), length}); + Resource->MappedFile->FileId = ComputeCodeMapId(Resource->MappedFile->Filename, &ELFResult.BuildID); + } else { + Resource->MappedFile->FileId = ComputeCodeMapId(Resource->MappedFile->Filename); } } else if (ResourceIt->second.ProgramHeaders.empty()) { // Not an ELF file, so we don't need to distinguish between different base addresses