mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 13:00:15 +02:00
Merge pull request #6008 from Sonicadvance1/265
DiskCache: Support FileID hash with gnu build-id
This commit is contained in:
6 files changed
+139
-22
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user