mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-10 16:00:31 +02:00
Merge pull request #4980 from neobrain/fix_infer_mapping_base
LinuxSyscalls: Fix base address inference for ELF binaries
This commit is contained in:
5 files changed
+226
-25
No files matched your search
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <FEXCore/Utils/TypeDefines.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
#include <elf.h>
|
||||
|
||||
namespace FEXCore {
|
||||
|
||||
/**
|
||||
* Infers the base virtual address from a file mapping (as described by parameters to a single
|
||||
* call to mmap()).
|
||||
*
|
||||
* The file offset of any given mapping need not match its virtual address offset from the base
|
||||
* mapping (file offset = 0). Instead, this function searches the corresponding ELF program headers
|
||||
* for an entry that generated the given file mapping.
|
||||
*/
|
||||
inline std::optional<uint64_t>
|
||||
InferMappingBaseAddress(std::span<const Elf64_Phdr> ProgramHeaders, uint64_t Addr, uint64_t Size, uint64_t FileOffset, int AccessFlags) {
|
||||
for (auto& phdr : ProgramHeaders) {
|
||||
if (phdr.p_type != PT_LOAD) {
|
||||
// Skip headers that don't trigger memory mappings
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((phdr.p_flags & (PF_X | PF_W | PF_R)) != (AccessFlags & (PF_X | PF_W | PF_R))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The mapped file offset must be included at the start of the section header
|
||||
auto SegmentStartOffset = phdr.p_offset - (phdr.p_vaddr & 0xfff);
|
||||
if (FileOffset >= SegmentStartOffset && FileOffset < SegmentStartOffset + phdr.p_filesz &&
|
||||
(FileOffset & Utils::FEX_PAGE_MASK) == (phdr.p_offset & Utils::FEX_PAGE_MASK)) {
|
||||
// Compute VA offset relative to the base mapping
|
||||
return Addr - (phdr.p_vaddr - (phdr.p_offset & 0xfff)) + (ProgramHeaders[0].p_vaddr - (ProgramHeaders[0].p_offset & 0xfff)) -
|
||||
(FileOffset - SegmentStartOffset);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace FEXCore
|
||||
@@ -8,6 +8,7 @@ $end_info$
|
||||
*/
|
||||
|
||||
#include "Common/FDUtils.h"
|
||||
#include "Common/FileMappingBaseAddress.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/shm.h>
|
||||
@@ -23,6 +24,7 @@ $end_info$
|
||||
#include <FEXCore/Utils/SignalScopeGuards.h>
|
||||
#include <FEXCore/Utils/TypeDefines.h>
|
||||
#include <FEXHeaderUtils/Filesystem.h>
|
||||
#include <Linux/Utils/ELFParser.h>
|
||||
|
||||
namespace FEX::HLE {
|
||||
// SMC interactions
|
||||
@@ -172,15 +174,13 @@ std::optional<FEXCore::ExecutableFileSectionInfo>
|
||||
SyscallHandler::LookupExecutableFileSection(FEXCore::Core::InternalThreadState& Thread, uint64_t GuestAddr) {
|
||||
auto lk = FEXCore::GuardSignalDeferringSection<std::shared_lock>(VMATracking.Mutex, &Thread);
|
||||
|
||||
// Get the first mapping after GuestAddr, or end
|
||||
// GuestAddr is inclusive
|
||||
// If the write spans two pages, they will be flushed one at a time (generating two faults)
|
||||
auto Entry = VMATracking.FindVMAEntry(GuestAddr);
|
||||
if (Entry == VMATracking.VMAs.end() || !Entry->second.Resource) {
|
||||
auto EntryIt = VMATracking.FindVMAEntry(GuestAddr);
|
||||
if (EntryIt == VMATracking.VMAs.end() || !EntryIt->second.Resource) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return FEXCore::ExecutableFileSectionInfo {*Entry->second.Resource->MappedFile, Entry->second.Base - Entry->second.Offset};
|
||||
auto& [MappingBaseAddr, Entry] = *EntryIt;
|
||||
return FEXCore::ExecutableFileSectionInfo {*Entry.Resource->MappedFile, Entry.Resource->FirstVMA->Base};
|
||||
}
|
||||
|
||||
FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXCore::Core::InternalThreadState* Thread, uint64_t Address) {
|
||||
@@ -195,6 +195,22 @@ FEXCore::HLE::ExecutableRangeInfo SyscallHandler::QueryGuestExecutableRange(FEXC
|
||||
return {Entry->first, Entry->second.Length, Entry->second.Prot.Writable};
|
||||
}
|
||||
|
||||
static fextl::vector<Elf64_Phdr> ReadELFHeaders(int FD, std::span<std::byte> HeaderData = {}) {
|
||||
std::string_view ELFMagic = ELFMAG;
|
||||
if (HeaderData.data()) {
|
||||
if (HeaderData.size_bytes() < ELFMagic.size() || std::memcmp(ELFMagic.data(), HeaderData.data(), ELFMagic.size()) != 0) {
|
||||
// Not an ELF file
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
// Read from FD in case the caller didn't have a mapped header available
|
||||
}
|
||||
|
||||
ELFParser Parser;
|
||||
Parser.ReadElf(dup(FD));
|
||||
return std::move(Parser.phdrs);
|
||||
}
|
||||
|
||||
void* SyscallHandler::GuestMmap(bool Is64Bit, FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags,
|
||||
int fd, off_t offset) {
|
||||
LOGMAN_THROW_A_FMT(Is64Bit || (length >> 32) == 0, "values must fit to 32 bits");
|
||||
@@ -383,19 +399,56 @@ std::optional<SyscallHandler::LateApplyExtendedVolatileMetadata> SyscallHandler:
|
||||
if (!(flags & MAP_ANONYMOUS)) {
|
||||
struct stat64 buf;
|
||||
fstat64(fd, &buf);
|
||||
VMATracking::MRID mrid {buf.st_dev, buf.st_ino};
|
||||
|
||||
const VMATracking::MRID mrid {buf.st_dev, buf.st_ino};
|
||||
|
||||
char Tmp[PATH_MAX];
|
||||
auto PathLength = FEX::get_fdpath(fd, Tmp);
|
||||
|
||||
if (PathLength != -1) {
|
||||
auto [Iter, Inserted] = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0});
|
||||
Resource = &Iter->second;
|
||||
auto [ResourceIt, ResourceEnd] = VMATracking.FindResources(mrid);
|
||||
bool Inserted = false;
|
||||
const bool MappedELFHeaderAgain = ResourceIt != ResourceEnd && offset == 0 && !ResourceIt->second.ProgramHeaders.empty();
|
||||
if (ResourceIt == ResourceEnd || MappedELFHeaderAgain) {
|
||||
// Create a new MappedResource for previously unseen file and for re-mappings of an ELF header
|
||||
ResourceIt = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0});
|
||||
ResourceIt->second.Iterator = ResourceIt;
|
||||
Inserted = true;
|
||||
}
|
||||
Resource = &ResourceIt->second;
|
||||
|
||||
// Only handle FDs that are backed by regular files that are executable
|
||||
if (PathLength != -1 && S_ISREG(buf.st_mode) && (buf.st_mode & S_IXUSR)) {
|
||||
// ELF files that are mapped multiple times get a separate MappedResource for each base virtual address
|
||||
if (Inserted) {
|
||||
Resource->MappedFile = fextl::make_unique<FEXCore::ExecutableFileInfo>();
|
||||
Resource->MappedFile->Filename = fextl::string(Tmp, PathLength);
|
||||
Resource->Iterator = Iter;
|
||||
|
||||
// Read ELF headers if applicable.
|
||||
// For performance, skip ELF checks if we're not mapping the file header
|
||||
bool CheckForElfFile = (offset == 0);
|
||||
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
||||
CheckForElfFile = true;
|
||||
#endif
|
||||
if (CheckForElfFile) {
|
||||
Resource->ProgramHeaders = ReadELFHeaders(fd, std::span {reinterpret_cast<std::byte*>(addr), length});
|
||||
LOGMAN_THROW_A_FMT(Resource->ProgramHeaders.empty() || offset == 0, "Expected file offset 0 for the first mapping of an ELF "
|
||||
"file");
|
||||
}
|
||||
} else if (ResourceIt->second.ProgramHeaders.empty()) {
|
||||
// Not an ELF file, so we don't need to distinguish between different base addresses
|
||||
} else {
|
||||
// Mapped a non-header section of an ELF file.
|
||||
// Look up the corresponding MappedResource using the expected base address.
|
||||
|
||||
ResourceIt = std::find_if(ResourceIt, ResourceEnd, [&](const VMATracking::MappedResource::ContainerType::value_type& ResourcePair) {
|
||||
auto& Resource = ResourcePair.second;
|
||||
auto ExpectedBase = FEXCore::InferMappingBaseAddress(
|
||||
Resource.ProgramHeaders, addr, Size, offset,
|
||||
(ProtMapping.Executable ? PF_X : 0) | (ProtMapping.Writable ? PF_W : 0) | (ProtMapping.Readable ? PF_R : 0));
|
||||
return ExpectedBase == Resource.FirstVMA->Base;
|
||||
});
|
||||
LOGMAN_THROW_A_FMT(ResourceIt != ResourceEnd, "ERROR: Could not find base for file mapping at {:#x} (offset {:#x})", addr, offset);
|
||||
Resource = &ResourceIt->second;
|
||||
}
|
||||
|
||||
const fextl::string Filename = FHU::Filesystem::GetFilename(Resource->MappedFile->Filename);
|
||||
@@ -416,12 +469,12 @@ std::optional<SyscallHandler::LateApplyExtendedVolatileMetadata> SyscallHandler:
|
||||
} else if (flags & MAP_SHARED) {
|
||||
VMATracking::MRID mrid {VMATracking::SpecialDev::Anon, AnonSharedId++};
|
||||
|
||||
auto [Iter, Inserted] = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0});
|
||||
LOGMAN_THROW_A_FMT(Inserted == true, "VMA tracking error");
|
||||
auto [Iter, IterEnd] = VMATracking.FindResources(mrid);
|
||||
LOGMAN_THROW_A_FMT(Iter == IterEnd, "VMA tracking error");
|
||||
|
||||
Iter = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, 0});
|
||||
Resource = &Iter->second;
|
||||
Resource->Iterator = Iter;
|
||||
} else {
|
||||
Resource = nullptr;
|
||||
}
|
||||
|
||||
VMATracking.TrackVMARange(CTX, Resource, addr, offset, Size, VMATracking::VMAFlags::fromFlags(flags), ProtMapping);
|
||||
@@ -477,11 +530,12 @@ void SyscallHandler::TrackMremap(FEXCore::Core::InternalThreadState* Thread, uin
|
||||
void SyscallHandler::TrackShmat(FEXCore::Core::InternalThreadState* Thread, int shmid, uint64_t shmaddr, int shmflg, uint64_t Length) {
|
||||
VMATracking::MRID mrid {VMATracking::SpecialDev::SHM, static_cast<uint64_t>(shmid)};
|
||||
|
||||
auto [Iter, Inserted] = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, Length});
|
||||
auto Resource = &Iter->second;
|
||||
if (Inserted) {
|
||||
Resource->Iterator = Iter;
|
||||
auto [Iter, IterEnd] = VMATracking.FindResources(mrid);
|
||||
if (Iter == IterEnd) {
|
||||
Iter = VMATracking.InsertMappedResource(mrid, {nullptr, nullptr, Length});
|
||||
Iter->second.Iterator = Iter;
|
||||
}
|
||||
auto Resource = &Iter->second;
|
||||
VMATracking.TrackVMARange(CTX, Resource, shmaddr, 0, Length, VMATracking::VMAFlags::fromFlags(MAP_SHARED), VMATracking::VMAProt::fromSHM(shmflg));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <FEXCore/fextl/memory.h>
|
||||
#include <FEXCore/Utils/SignalScopeGuards.h>
|
||||
|
||||
#include <elf.h>
|
||||
|
||||
namespace FEX::HLE::VMATracking {
|
||||
///// VMA (Virtual Memory Area) tracking /////
|
||||
|
||||
@@ -30,15 +32,24 @@ struct MRID {
|
||||
|
||||
struct VMAEntry;
|
||||
|
||||
// Used to all MAP_SHARED VMAs of a system resource.
|
||||
/**
|
||||
* Meta data associated to one system resource.
|
||||
*
|
||||
* Typically there is one instance of this type per ELF/PE file or special device.
|
||||
* However if an ELF/PE file is mapped multiple times at different base addresses,
|
||||
* there will be one separate MappedResource for each base address. The MRID
|
||||
* is the same in this case.
|
||||
*/
|
||||
struct MappedResource {
|
||||
using ContainerType = fextl::map<MRID, MappedResource>;
|
||||
using ContainerType = fextl::multimap<MRID, MappedResource>;
|
||||
|
||||
fextl::unique_ptr<FEXCore::ExecutableFileInfo> MappedFile;
|
||||
// Pointer to lowest memory range this file is mapped to
|
||||
VMAEntry* FirstVMA;
|
||||
uint64_t Length; // 0 if not fixed size
|
||||
ContainerType::iterator Iterator;
|
||||
|
||||
fextl::vector<Elf64_Phdr> ProgramHeaders;
|
||||
};
|
||||
|
||||
union VMAProt {
|
||||
@@ -116,6 +127,14 @@ struct VMATracking {
|
||||
return MappedResources.emplace(mrid, std::move(Resource));
|
||||
}
|
||||
|
||||
// Returns an iterator pair spanning the range of all MappedResources matching the given MRID.
|
||||
// Typically there is only one associated resource, however sometimes the same file gets mapped
|
||||
// multiple times at different base addresses. In that case, each MappedResource will cover an
|
||||
// exclusive set of VMAEntries that refer to a consistent base mapping address.
|
||||
inline auto FindResources(const MRID& mrid) {
|
||||
return MappedResources.equal_range(mrid);
|
||||
}
|
||||
|
||||
private:
|
||||
MappedResource::ContainerType MappedResources;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
set (TESTS
|
||||
Allocator
|
||||
ArgumentParser
|
||||
InterruptableConditionVariable
|
||||
Filesystem
|
||||
StringUtils
|
||||
fextl_function
|
||||
ExtendedVolatileMetadata
|
||||
fextl_function
|
||||
FileMappingBaseAddress
|
||||
Filesystem
|
||||
InterruptableConditionVariable
|
||||
StringUtils
|
||||
)
|
||||
|
||||
list(APPEND LIBS Common FEXCore JemallocLibs)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <Common/FileMappingBaseAddress.h>
|
||||
|
||||
#include <FEXCore/fextl/vector.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Mapping {
|
||||
uint64_t Addr;
|
||||
uint64_t Size;
|
||||
uint64_t FileOffset;
|
||||
int Flags; // PF_*
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_CASE("libm") {
|
||||
uint64_t BaseAddr = 0x123400000;
|
||||
|
||||
fextl::vector<Elf64_Phdr> Headers = {
|
||||
{.p_type = PT_LOAD, .p_offset = 0x00000, .p_vaddr = 0x00000, .p_paddr = 0x00000, .p_filesz = 0x7bdd5, .p_memsz = 0x7bdd5},
|
||||
{.p_type = PT_LOAD, .p_offset = 0x7c000, .p_vaddr = 0x7c000, .p_paddr = 0x7c000, .p_filesz = 0x6f3a8, .p_memsz = 0x6f3a8},
|
||||
{.p_type = PT_LOAD, .p_offset = 0xebbd0, .p_vaddr = 0xecbd0, .p_paddr = 0xecbd0, .p_filesz = 0x434, .p_memsz = 0x440},
|
||||
};
|
||||
|
||||
fextl::vector<Mapping> Mappings = {
|
||||
{.Addr = BaseAddr, .Size = 0x7c000, .FileOffset = 0x00000},
|
||||
{.Addr = BaseAddr + 0x7c000, .Size = 0x70000, .FileOffset = 0x7c000},
|
||||
{.Addr = BaseAddr + 0xec000, .Size = 0x2000, .FileOffset = 0xeb000},
|
||||
};
|
||||
|
||||
for (auto& Mapping : Mappings) {
|
||||
INFO("Mapping to 0x" << std::hex << Mapping.Addr << "-0x" << Mapping.Addr + Mapping.Size << " from file offset 0x" << Mapping.FileOffset);
|
||||
auto DeducedBase = FEXCore::InferMappingBaseAddress(Headers, Mapping.Addr, Mapping.Size, Mapping.FileOffset, Mapping.Flags);
|
||||
CHECK(DeducedBase.value_or(0) == BaseAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// E.g. libX11-xcb
|
||||
TEST_CASE("Access flags are checked") {
|
||||
uint64_t BaseAddr = 0x123400000;
|
||||
|
||||
fextl::vector<Elf64_Phdr> Headers = {
|
||||
{.p_type = PT_LOAD, .p_flags = PF_R | PF_X, .p_offset = 0x0000, .p_vaddr = 0x0000, .p_paddr = 0x0000, .p_filesz = 0x00040d, .p_memsz = 0x00040d},
|
||||
{.p_type = PT_LOAD, .p_flags = PF_R, .p_offset = 0x1000, .p_vaddr = 0x1000, .p_paddr = 0x1000, .p_filesz = 0x00036c, .p_memsz = 0x00036c},
|
||||
{.p_type = PT_LOAD, .p_flags = PF_W, .p_offset = 0x1dc8, .p_vaddr = 0x2dc8, .p_paddr = 0x2dc8, .p_filesz = 0x000238, .p_memsz = 0x000240},
|
||||
};
|
||||
|
||||
fextl::vector<Mapping> Mappings = {
|
||||
{.Addr = BaseAddr + 0x1000, .Size = 0x1000, .FileOffset = 0x1000, .Flags = PF_R},
|
||||
{.Addr = BaseAddr + 0x2000, .Size = 0x1000, .FileOffset = 0x1000, .Flags = PF_W},
|
||||
};
|
||||
|
||||
for (auto& Mapping : Mappings) {
|
||||
INFO("Mapping to 0x" << std::hex << Mapping.Addr << "-0x" << Mapping.Addr + Mapping.Size << " from file offset 0x" << Mapping.FileOffset);
|
||||
auto DeducedBase = FEXCore::InferMappingBaseAddress(Headers, Mapping.Addr, Mapping.Size, Mapping.FileOffset, Mapping.Flags);
|
||||
CHECK(DeducedBase.value_or(0) == BaseAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Program headers that don't generate memory mappings can't be used to infer base addresses
|
||||
TEST_CASE("Non-mapping program headers are ignored") {
|
||||
uint64_t BaseAddr = 0x123400000;
|
||||
|
||||
fextl::vector<Elf64_Phdr> Headers = {
|
||||
{.p_type = PT_LOAD, .p_offset = 0x00000, .p_vaddr = 0x0000, .p_paddr = 0x00000, .p_filesz = 0x1000, .p_memsz = 0x1000},
|
||||
{.p_type = PT_INTERP, .p_offset = 0x10000, .p_vaddr = 0xa000, .p_paddr = 0xa0000, .p_filesz = 0x1000, .p_memsz = 0x1000},
|
||||
{.p_type = PT_LOAD, .p_offset = 0x10000, .p_vaddr = 0x1000, .p_paddr = 0x10000, .p_filesz = 0x1000, .p_memsz = 0x1000},
|
||||
};
|
||||
|
||||
fextl::vector<Mapping> Mappings = {
|
||||
{.Addr = BaseAddr + 0x1000, .Size = 0x1000, .FileOffset = 0x10000},
|
||||
};
|
||||
|
||||
for (auto& Mapping : Mappings) {
|
||||
INFO("Mapping to 0x" << std::hex << Mapping.Addr << "-0x" << Mapping.Addr + Mapping.Size << " from file offset 0x" << Mapping.FileOffset);
|
||||
auto DeducedBase = FEXCore::InferMappingBaseAddress(Headers, Mapping.Addr, Mapping.Size, Mapping.FileOffset, Mapping.Flags);
|
||||
CHECK(DeducedBase.value_or(0) == BaseAddr);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user