Merge pull request #6008 from Sonicadvance1/265

DiskCache: Support FileID hash with gnu build-id
This commit is contained in:
Ryan Houdek authored and GitHub committed 2026-10-05 11:26:53 -07:00
commit d2b6e371e2
6 files changed
+139 -22

No files matched your search

@@ -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<MappedCodeCacheFile> LoadCache(std::span<std::byte> CacheFile, const ExecutableFileInfo&, uint64_t FileStartVA) override;
@@ -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<char, 4> Magic = ExpectedMagic;
// Version history:
-8
View File
@@ -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.
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: MIT
#pragma once
#include <FEXCore/Core/CodeCache.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/fextl/fmt.h>
#include <FEXCore/fextl/string.h>
#include <FEXCore/fextl/vector.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/Utils/MathUtils.h>
#include <elf.h>
#include <fcntl.h>
@@ -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<const void*>(reinterpret_cast<uintptr_t>(ptr) + OffsetInPage),
.size = TotalSize,
};
}
void FreeSection(MappedSection& section) {
::munmap(const_cast<void*>(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<uint8_t> 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<const char*>(SHStringSection.ptr) + offset;
};
fextl::vector<uint8_t> 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<const ELFNote*>(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<const uint8_t*>(&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.
@@ -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;
@@ -17,6 +17,7 @@ $end_info$
#include <sys/mman.h>
#include <sys/personality.h>
#include <sys/shm.h>
#include <xxhash.h>
#include "LinuxSyscalls/Syscalls.h"
#include "LinuxSyscalls/SignalDelegator.h"
@@ -231,6 +232,7 @@ FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXC
struct ReadELFHeadersResult {
fextl::vector<Elf64_Phdr> ProgramHeaders;
fextl::robin_map<uint32_t, FEXCore::GuestRelocationType> Relocations;
fextl::vector<uint8_t> BuildID;
bool HasCodeRelocations;
};
@@ -256,7 +258,8 @@ static ReadELFHeadersResult ReadELFHeaders(int FD, std::span<std::byte> 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<FEXCore::MappedCodeCacheFile>
@@ -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<fextl::vector<uint8_t>*> 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<uint64_t*>(Data);
Hash ^= *ID64Bit;
} else {
Hash ^= XXH3_64bits(Data, Size);
}
}
return Hash;
}
// MMan Tracking
std::optional<SyscallHandler::LateApplyExtendedVolatileMetadata>
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<VMATracking::ExecutableFileState>();
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<std::byte*>(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