From 2878583627bbccb068479438d8b7e5dbf1b6c892 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 15 Dec 2025 14:55:19 +0000 Subject: [PATCH 1/5] OpcodeDispatcher: Support relocated operand type variants In order to support code caching of 32-bit libraries, any library-base relative relocations on the guest must be transformed into FEX relocations so e.g. absolute jumps or loads refer to the correct location when the library is loaded at a different base address. --- FEXCore/Source/Interface/Core/Frontend.cpp | 2 +- .../Interface/Core/OpcodeDispatcher.cpp | 40 ++++++++++++++----- .../Source/Interface/Core/OpcodeDispatcher.h | 2 +- .../Core/OpcodeDispatcher/AVX_128.cpp | 19 +++++++-- .../Core/OpcodeDispatcher/Vector.cpp | 19 +++++++-- .../Interface/Core/X86Tables/X86Tables.h | 36 ++++++++++++----- 6 files changed, 91 insertions(+), 27 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 7f834d3f9..1f2b5ec25 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -305,7 +305,7 @@ void Decoder::DecodeModRM_64(X86Tables::DecodedOperand* Operand, X86Tables::ModR const uint32_t Literal = ReadData(4); Operand->Type = DecodedOperand::OpType::RIPRelative; - Operand->Data.RIPLiteral.Value.u = Literal; + Operand->Data.RIPLiteral.Value = Literal; } else { // Register-direct addressing Operand->Type = DecodedOperand::OpType::GPRDirect; diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index 9334f8a25..69d988e5a 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -1347,7 +1347,7 @@ void OpDispatchBuilder::MOVOffsetOp(OpcodeArgs) { // Source is memory(literal) // Dest is GPR Ref Src {}; - if (Op->Src[0].Data.Literal.Size <= 4) { + if (Op->Src[0].IsLiteralRelocation() || Op->Src[0].Data.Literal.Size <= 4) { Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.ForceLoad = true}); } else { const auto OpSize = OpSizeFromSrc(Op); @@ -1365,7 +1365,7 @@ void OpDispatchBuilder::MOVOffsetOp(OpcodeArgs) { // This one is a bit special since the destination is a literal // So the destination gets stored in Src[1] - if (Op->Src[1].Data.Literal.Size <= 4) { + if (Op->Src[1].IsLiteralRelocation() || Op->Src[1].Data.Literal.Size <= 4) { StoreResultGPR(Op, Op->Src[1], Src); } else { const auto OpSize = OpSizeFromSrc(Op); @@ -4234,18 +4234,26 @@ AddressMode OpDispatchBuilder::DecodeAddress(const X86Tables::DecodedOp& Op, con } else if (Operand.IsGPRDirect()) { A.Base = LoadGPRRegister(Operand.Data.GPR.GPR, GPRSize); A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.GPR.GPR); - } else if (Operand.IsGPRIndirect()) { + } else if (Operand.IsGPRIndirect() || Operand.IsGPRIndirectRelocation()) { A.Base = LoadGPRRegister(Operand.Data.GPRIndirect.GPR, GPRSize); - A.Offset = Operand.Data.GPRIndirect.Displacement; + if (Operand.IsGPRIndirectRelocation()) { + A.Base = Add(GPRSize, _EntrypointOffset(GPRSize, Operand.Data.GPRIndirect.Displacement), A.Base); + } else { + A.Offset = static_cast(Operand.Data.GPRIndirect.Displacement); + } A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.GPRIndirect.GPR); - } else if (Operand.IsRIPRelative()) { + } else if (Operand.IsRIPRelative() || Operand.IsRIPRelativeRelocation()) { if (Is64BitMode) { - A.Base = GetRelocatedPC(Op, Operand.Data.RIPLiteral.Value.s); + A.Base = GetRelocatedPC(Op, static_cast(Operand.Data.RIPLiteral.Value)); } else { // 32bit this isn't RIP relative but instead absolute - A.Offset = Operand.Data.RIPLiteral.Value.u; + if (Operand.IsRIPRelativeRelocation()) { + A.Base = _EntrypointOffset(GPRSize, Operand.Data.RIPLiteral.Value); + } else { + A.Offset = Operand.Data.RIPLiteral.Value; + } } - } else if (Operand.IsSIB()) { + } else if (Operand.IsSIB() || Operand.IsSIBRelocation()) { const bool IsVSIB = IsLoad && ((Op->Flags & X86Tables::DecodeFlags::FLAG_VSIB_BYTE) != 0); if (Operand.Data.SIB.Base != FEXCore::X86State::REG_INVALID) { @@ -4266,8 +4274,20 @@ AddressMode OpDispatchBuilder::DecodeAddress(const X86Tables::DecodedOp& Op, con A.IndexScale = Operand.Data.SIB.Scale; } - A.Offset = Operand.Data.SIB.Offset; + if (Operand.IsSIBRelocation()) { + auto EPOffset = _EntrypointOffset(GPRSize, Operand.Data.SIB.Offset); + if (A.Base) { + A.Base = Add(GPRSize, EPOffset, A.Base); + } else { + A.Base = EPOffset; + } + } else { + A.Offset = static_cast(Operand.Data.SIB.Offset); + } + A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.SIB.Base) || IsNonTSOReg(AccessType, Operand.Data.SIB.Index); + } else if (Operand.IsLiteralRelocation()) { + A.Base = _EntrypointOffset(GPRSize, Operand.Data.LiteralRelocation.EntrypointOffset); } else { LOGMAN_MSG_A_FMT("Unknown Src Type: {}\n", Operand.Type); } @@ -4502,7 +4522,7 @@ void OpDispatchBuilder::MOVGPROp(OpcodeArgs, uint32_t SrcIndex) { void OpDispatchBuilder::MOVGPRImmediate(OpcodeArgs) { Ref Src {}; - if (Op->Src[0].Data.Literal.Size <= 4) { + if (Op->Src[0].IsLiteralRelocation() || Op->Src[0].Data.Literal.Size <= 4) { Src = LoadSourceGPR(Op, Op->Src[0], Op->Flags, {.Align = OpSize::i8Bit, .AllowUpperGarbage = true}); } else { // 8-byte literal is special cased. diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.h b/FEXCore/Source/Interface/Core/OpcodeDispatcher.h index 5c2a5cab1..8aea9d91c 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.h +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.h @@ -1544,7 +1544,7 @@ private: [[nodiscard]] static bool IsOperandMem(const X86Tables::DecodedOperand& Operand, bool Load) { // Literals are immediates as sources but memory addresses as destinations. - return !(Load && Operand.IsLiteral()) && !Operand.IsGPR(); + return !(Load && (Operand.IsLiteral() || Operand.IsLiteralRelocation())) && !Operand.IsGPR(); } [[nodiscard]] diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher/AVX_128.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher/AVX_128.cpp index 740423946..1dcfd1e14 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher/AVX_128.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher/AVX_128.cpp @@ -52,7 +52,8 @@ OpDispatchBuilder::RefPair OpDispatchBuilder::AVX128_LoadSource_WithOpSize( OpDispatchBuilder::RefVSIB OpDispatchBuilder::AVX128_LoadVSIB(const X86Tables::DecodedOp& Op, const X86Tables::DecodedOperand& Operand, uint32_t Flags, bool NeedsHigh) { const bool IsVSIB = (Op->Flags & X86Tables::DecodeFlags::FLAG_VSIB_BYTE) != 0; - LOGMAN_THROW_A_FMT(Operand.IsSIB() && IsVSIB, "Trying to load VSIB for something that isn't the correct type!"); + LOGMAN_THROW_A_FMT((Operand.IsSIB() || Operand.IsSIBRelocation()) && IsVSIB, "Trying to load VSIB for something that isn't the correct " + "type!"); // VSIB is a very special case which has a ton of encoded data. // Get it in a format we can reason about. @@ -64,13 +65,25 @@ OpDispatchBuilder::AVX128_LoadVSIB(const X86Tables::DecodedOp& Op, const X86Tabl "Base must be a GPR."); const auto Index_XMM_gpr = Index_gpr - X86State::REG_XMM_0; - return { + OpDispatchBuilder::RefVSIB A { .Low = AVX128_LoadXMMRegister(Index_XMM_gpr, false), .High = NeedsHigh ? AVX128_LoadXMMRegister(Index_XMM_gpr, true) : Invalid(), .BaseAddr = Base_gpr != FEXCore::X86State::REG_INVALID ? LoadGPRRegister(Base_gpr, OpSize::i64Bit, 0, false) : nullptr, - .Displacement = Operand.Data.SIB.Offset, .Scale = Operand.Data.SIB.Scale, }; + + if (Operand.IsSIBRelocation()) { + auto EPOffset = _EntrypointOffset(OpSize::i64Bit, Operand.Data.SIB.Offset); + if (A.BaseAddr) { + A.BaseAddr = Add(OpSize::i64Bit, EPOffset, A.BaseAddr); + } else { + A.BaseAddr = EPOffset; + } + } else { + A.Displacement = static_cast(Operand.Data.SIB.Offset); + } + + return A; } void OpDispatchBuilder::AVX128_StoreResult_WithOpSize(FEXCore::X86Tables::DecodedOp Op, const FEXCore::X86Tables::DecodedOperand& Operand, diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher/Vector.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher/Vector.cpp index 19283444e..c9295cbef 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher/Vector.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher/Vector.cpp @@ -5017,7 +5017,8 @@ void OpDispatchBuilder::VFMAddSubImpl(OpcodeArgs, bool AddSub, uint8_t Src1Idx, OpDispatchBuilder::RefVSIB OpDispatchBuilder::LoadVSIB(const X86Tables::DecodedOp& Op, const X86Tables::DecodedOperand& Operand, uint32_t Flags) { const bool IsVSIB = (Op->Flags & X86Tables::DecodeFlags::FLAG_VSIB_BYTE) != 0; - LOGMAN_THROW_A_FMT(Operand.IsSIB() && IsVSIB, "Trying to load VSIB for something that isn't the correct type!"); + LOGMAN_THROW_A_FMT((Operand.IsSIB() || Operand.IsSIBRelocation()) && IsVSIB, "Trying to load VSIB for something that isn't the correct " + "type!"); // VSIB is a very special case which has a ton of encoded data. // Get it in a format we can reason about. @@ -5029,12 +5030,24 @@ OpDispatchBuilder::RefVSIB OpDispatchBuilder::LoadVSIB(const X86Tables::DecodedO "Base must be a GPR."); const auto Index_XMM_gpr = Index_gpr - X86State::REG_XMM_0; - return { + OpDispatchBuilder::RefVSIB A { .Low = LoadXMMRegister(Index_XMM_gpr), .BaseAddr = Base_gpr != FEXCore::X86State::REG_INVALID ? LoadGPRRegister(Base_gpr, OpSize::i64Bit, 0, false) : nullptr, - .Displacement = Operand.Data.SIB.Offset, .Scale = Operand.Data.SIB.Scale, }; + + if (Operand.IsSIBRelocation()) { + auto EPOffset = _EntrypointOffset(OpSize::i64Bit, Operand.Data.SIB.Offset); + if (A.BaseAddr) { + A.BaseAddr = Add(OpSize::i64Bit, EPOffset, A.BaseAddr); + } else { + A.BaseAddr = EPOffset; + } + } else { + A.Displacement = static_cast(Operand.Data.SIB.Offset); + } + + return A; } template diff --git a/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h b/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h index 96fe7ea52..83e3063c5 100644 --- a/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h +++ b/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h @@ -117,9 +117,13 @@ struct DecodedOperand { GPR, GPRDirect, GPRIndirect, + GPRIndirectRelocation, RIPRelative, + RIPRelativeRelocation, Literal, + LiteralRelocation, SIB, + SIBRelocation }; bool IsNone() const { @@ -134,15 +138,28 @@ struct DecodedOperand { bool IsGPRIndirect() const { return Type == OpType::GPRIndirect; } + bool IsGPRIndirectRelocation() const { + return Type == OpType::GPRIndirectRelocation; + } bool IsRIPRelative() const { return Type == OpType::RIPRelative; } + bool IsRIPRelativeRelocation() const { + return Type == OpType::RIPRelativeRelocation; + } bool IsLiteral() const { return Type == OpType::Literal; } + bool IsLiteralRelocation() const { + return Type == OpType::LiteralRelocation; + } bool IsSIB() const { return Type == OpType::SIB; } + bool IsSIBRelocation() const { + return Type == OpType::SIBRelocation; + } + uint64_t Literal() const { LOGMAN_THROW_A_FMT(IsLiteral(), "Precondition: must be a literal"); if (Data.Literal.SignExtend) { @@ -159,16 +176,13 @@ struct DecodedOperand { } GPR; struct { - int32_t Displacement; + int64_t Displacement; uint8_t GPR; - } GPRIndirect; + } GPRIndirect; // Shared with GPRIndirectRelocation struct { - union { - int32_t s; - uint32_t u; - } Value; - } RIPLiteral; + int64_t Value; + } RIPLiteral; // Shared with RIPLiteralRelocation struct LiteralType { uint32_t Value; @@ -178,11 +192,15 @@ struct DecodedOperand { } Literal; struct { - int32_t Offset; + int64_t EntrypointOffset; + } LiteralRelocation; + + struct { + int64_t Offset; uint8_t Scale; uint8_t Index; // ~0 invalid uint8_t Base; // ~0 invalid - } SIB; + } SIB; // Shared with SIBRelocation }; TypeUnion Data; From 4044b39a2f0b1002a06f0c2bd3c4dd6a5435f9c6 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 15 Dec 2025 15:00:47 +0000 Subject: [PATCH 2/5] CodeCache: Track relocations in ExecutableFileInfo --- FEXCore/include/FEXCore/Core/CodeCache.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FEXCore/include/FEXCore/Core/CodeCache.h b/FEXCore/include/FEXCore/Core/CodeCache.h index 7c43e99e4..68edeaac4 100644 --- a/FEXCore/include/FEXCore/Core/CodeCache.h +++ b/FEXCore/include/FEXCore/Core/CodeCache.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,8 @@ namespace HLE { struct SourcecodeMap; } // namespace HLE +enum class GuestRelocationType : uint32_t { Rel32, Rel64 }; + // Generic information associated with an executable file. struct ExecutableFileInfo { ~ExecutableFileInfo(); @@ -39,6 +42,7 @@ struct ExecutableFileInfo { fextl::unique_ptr SourcecodeMap; uint64_t FileId = 0; fextl::string Filename; + fextl::unordered_map Relocations; }; // Information associated with a specific section of an executable file From 9571a1bc3008be33ec63009eb455a924d5885882 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 15 Dec 2025 15:13:50 +0000 Subject: [PATCH 3/5] Frontend: Clip multiblocks to section boundaries when generating caches When compiling code at runtime there is no harm to including jumps to different sections within a multiblock, when enforcing as such would introduce a lookup cost for every decode invocation (or some caching). However when compiling offline as each cache blob is tied to a specific library these boundaries should be enforced. --- FEXCore/Source/Interface/Core/Core.cpp | 1 - FEXCore/Source/Interface/Core/Frontend.cpp | 19 +++++++++++-------- FEXCore/Source/Interface/Core/Frontend.h | 7 +------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index fa929b7b7..03effc809 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -1037,6 +1037,5 @@ void ContextImpl::MonoBackpatcherWrite(FEXCore::Core::CpuStateFrame* Frame, uint void ContextImpl::ConfigureAOTGen(FEXCore::Core::InternalThreadState* Thread, fextl::set* ExternalBranches, uint64_t SectionMaxAddress) { Thread->FrontendDecoder->SetExternalBranches(ExternalBranches); - Thread->FrontendDecoder->SetSectionMaxAddress(SectionMaxAddress); } } // namespace FEXCore::Context diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 1f2b5ec25..a0a5969b3 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -1129,7 +1129,7 @@ void Decoder::BranchTargetInMultiblockRange() { // Forbid distant branches to have the cost code better match the guest code layout, avoiding massive (range-wise) code // blocks in highly fragmented guest code. Such branches are often not-taken branches to garbage in obfuscated code. constexpr uint64_t MAX_FORWARD_BRANCH_DIST = FEXCore::Utils::FEX_PAGE_SIZE * 4; - bool ValidMultiblockMember = TargetRIP >= SymbolMinAddress && TargetRIP < std::min(InstEnd + MAX_FORWARD_BRANCH_DIST, SymbolMaxAddress); + bool ValidMultiblockMember = TargetRIP >= EntryPoint && TargetRIP < std::min(InstEnd + MAX_FORWARD_BRANCH_DIST, SectionMaxAddress); #ifdef _M_ARM_64EC ValidMultiblockMember = ValidMultiblockMember && !RtlIsEcCode(TargetRIP); @@ -1322,19 +1322,22 @@ void Decoder::DecodeInstructionsAtEntry(FEXCore::Core::InternalThreadState* Thre BlockInfo.Is64BitMode = CSSegment->L == 1; LOGMAN_THROW_A_FMT(BlockInfo.Is64BitMode == CTX->Config.Is64BitMode, "Expected operating mode to not change at runtime!"); - // XXX: Load symbol data - SymbolAvailable = false; EntryPoint = PC; BlockInfo.EntryPoints = {PC}; InstStream = _InstStream; uint64_t TotalInstructions {}; - // If we don't have symbols available then we become a bit optimistic about multiblock ranges - if (!SymbolAvailable) { - // If we don't have a symbol available then assume all branches are valid for multiblock - SymbolMaxAddress = SectionMaxAddress; - SymbolMinAddress = EntryPoint; + SectionMinAddress = 0; + SectionMaxAddress = ~0ULL; + Relocations = nullptr; + + if (CTX->GetCodeCache().IsGeneratingCache) { + // If generating cache, attempt to load section bounds and relocations + if (auto SectionInfo = CTX->SyscallHandler->LookupExecutableFileSection(*Thread, EntryPoint)) { + SectionMinAddress = SectionInfo->FileStartVA; + SectionMaxAddress = SectionInfo->EndVA; + } } DecodedMinAddress = EntryPoint; diff --git a/FEXCore/Source/Interface/Core/Frontend.h b/FEXCore/Source/Interface/Core/Frontend.h index 66e790fb8..291223203 100644 --- a/FEXCore/Source/Interface/Core/Frontend.h +++ b/FEXCore/Source/Interface/Core/Frontend.h @@ -59,9 +59,6 @@ public: uint64_t DecodedMinAddress {}; uint64_t DecodedMaxAddress {~0ULL}; - void SetSectionMaxAddress(uint64_t v) { - SectionMaxAddress = v; - } void SetExternalBranches(fextl::set* v) { ExternalBranches = v; } @@ -130,13 +127,11 @@ private: FEXCore::X86Tables::DecodedInst* DecodeInst; // This is for multiblock data tracking - bool SymbolAvailable {false}; uint64_t EntryPoint {}; uint64_t MaxCondBranchForward {}; uint64_t MaxCondBranchBackwards {~0ULL}; - uint64_t SymbolMaxAddress {}; - uint64_t SymbolMinAddress {~0ULL}; uint64_t SectionMaxAddress {~0ULL}; + uint64_t SectionMinAddress {}; uint64_t NextBlockStartAddress {~0ULL}; DecodedBlockInformation BlockInfo; From bcf48c21eb05d647f9e0a755c1b6945af925edc2 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 15 Dec 2025 15:21:54 +0000 Subject: [PATCH 4/5] Frontend: Support relocated instruction operands when generating caches When relocations are loaded, all immediates read from memory are checked against the relocation map and transformed into an appropriately sign-extended entrypoint-relative variant of the specific operands addressing mode. As almost every case of an unhandled relocation will lead to a later, likely harder to debug, crash at runtime just bail out early if any such cases are encountered. Note that while this handles/detects all cases of relocated immediates, if relocations were applied to instructions themselves (occurs in some malware variants) these would be missed without any errors reported. --- FEXCore/Source/Interface/Core/Frontend.cpp | 82 ++++++++++++++-------- FEXCore/Source/Interface/Core/Frontend.h | 7 +- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index a0a5969b3..f2c42c8e4 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -132,7 +132,7 @@ std::optional Decoder::PeekByte(uint8_t Offset) { } } -uint64_t Decoder::ReadData(uint8_t Size) { +std::pair Decoder::ReadData(uint8_t Size) { LOGMAN_THROW_A_FMT(Size != 0 && Size <= sizeof(uint64_t), "Unknown data size to read"); uint64_t Res = 0; @@ -154,7 +154,20 @@ uint64_t Decoder::ReadData(uint8_t Size) { SkipBytes(Size); #endif - return Res; + if (Relocations) { + uint32_t SectionOffset = static_cast(Address - SectionMinAddress); + if (auto It = Relocations->find(SectionOffset); It != Relocations->end()) { + if (It->second == GuestRelocationType::Rel32 && Size == 4) { + return {static_cast(static_cast(Res) - static_cast(EntryPoint)), true}; + } else if (It->second == GuestRelocationType::Rel64 && Size == 8) { + return {static_cast(Res) - static_cast(EntryPoint), true}; + } else { + ERROR_AND_DIE_FMT("Unhandled relocation combination, type: {} size: {}", static_cast(It->second), Size); + } + } + } + + return {Res, false}; } void Decoder::DecodeModRM_16(X86Tables::DecodedOperand* Operand, X86Tables::ModRMDecoded ModRM) { @@ -186,7 +199,9 @@ void Decoder::DecodeModRM_16(X86Tables::DecodedOperand* Operand, X86Tables::ModR DisplacementSize = 1; } if (DisplacementSize) { - Literal = ReadData(DisplacementSize); + bool IsRelocation = false; + std::tie(Literal, IsRelocation) = ReadData(DisplacementSize); + LOGMAN_THROW_A_FMT(!IsRelocation, "1/2 byte relocations unsupported"); if (DisplacementSize == 1) { Literal = static_cast(Literal); } @@ -292,7 +307,10 @@ void Decoder::DecodeModRM_64(X86Tables::DecodedOperand* Operand, X86Tables::ModR LOGMAN_THROW_A_FMT(Displacement <= 4, "Number of bytes should be <= 4 for literal src"); if (Displacement) { - uint64_t Literal = ReadData(Displacement); + auto [Literal, IsRelocation] = ReadData(Displacement); + if (IsRelocation) { + Operand->Type = DecodedOperand::OpType::SIBRelocation; + } if (Displacement == 1) { Literal = static_cast(Literal); } @@ -302,9 +320,8 @@ void Decoder::DecodeModRM_64(X86Tables::DecodedOperand* Operand, X86Tables::ModR // Explained in Table 1-14. "Operand Addressing Using ModRM and SIB Bytes" if (ModRM.rm == 0b101) { // 32bit Displacement - const uint32_t Literal = ReadData(4); - - Operand->Type = DecodedOperand::OpType::RIPRelative; + auto [Literal, IsRelocation] = ReadData(4); + Operand->Type = IsRelocation ? DecodedOperand::OpType::RIPRelativeRelocation : DecodedOperand::OpType::RIPRelative; Operand->Data.RIPLiteral.Value = Literal; } else { // Register-direct addressing @@ -313,12 +330,12 @@ void Decoder::DecodeModRM_64(X86Tables::DecodedOperand* Operand, X86Tables::ModR } } else { uint8_t DisplacementSize = ModRM.mod == 1 ? 1 : 4; - uint32_t Literal = ReadData(DisplacementSize); + auto [Literal, IsRelocation] = ReadData(DisplacementSize); if (DisplacementSize == 1) { Literal = static_cast(Literal); } - Operand->Type = DecodedOperand::OpType::GPRIndirect; + Operand->Type = IsRelocation ? DecodedOperand::OpType::GPRIndirectRelocation : DecodedOperand::OpType::GPRIndirect; Operand->Data.GPRIndirect.GPR = MapModRMToReg(DecodeInst->Flags & DecodeFlags::FLAG_REX_XGPR_B ? 1 : 0, ModRM.rm, false, false, false, false); Operand->Data.GPRIndirect.Displacement = Literal; } @@ -614,31 +631,37 @@ bool Decoder::NormalOp(const FEXCore::X86Tables::X86InstInfo* Info, uint16_t Op, if (Bytes != 0) { LOGMAN_THROW_A_FMT(Bytes <= 8, "Number of bytes should be <= 8 for literal src"); - DecodeInst->Src[CurrentSrc].Data.Literal.Size = Bytes; - uint64_t Literal = ReadData(Bytes); + auto [Literal, IsRelocation] = ReadData(Bytes); + if (IsRelocation) { + DecodeInst->Src[CurrentSrc].Type = DecodedOperand::OpType::LiteralRelocation; + DecodeInst->Src[CurrentSrc].Data.LiteralRelocation.EntrypointOffset = Literal; + } else { + DecodeInst->Src[CurrentSrc].Data.Literal.Size = Bytes; - if ((Info->Flags & FEXCore::X86Tables::InstFlags::FLAGS_SRC_SEXT) || (DecodeFlags::GetSizeDstFlags(DecodeInst->Flags) == DecodeFlags::SIZE_64BIT && - Info->Flags & FEXCore::X86Tables::InstFlags::FLAGS_SRC_SEXT64BIT)) { - if (Bytes == 1) { - Literal = static_cast(Literal); - } else if (Bytes == 2) { - Literal = static_cast(Literal); - } else { - Literal = static_cast(Literal); + if ((Info->Flags & FEXCore::X86Tables::InstFlags::FLAGS_SRC_SEXT) || + (DecodeFlags::GetSizeDstFlags(DecodeInst->Flags) == DecodeFlags::SIZE_64BIT && + Info->Flags & FEXCore::X86Tables::InstFlags::FLAGS_SRC_SEXT64BIT)) { + if (Bytes == 1) { + Literal = static_cast(Literal); + } else if (Bytes == 2) { + Literal = static_cast(Literal); + } else { + Literal = static_cast(Literal); + } + DecodeInst->Src[CurrentSrc].Data.Literal.Size = DestSize; + DecodeInst->Src[CurrentSrc].Data.Literal.SignExtend = true; } - DecodeInst->Src[CurrentSrc].Data.Literal.Size = DestSize; - DecodeInst->Src[CurrentSrc].Data.Literal.SignExtend = true; - } - DecodeInst->Src[CurrentSrc].Type = DecodedOperand::OpType::Literal; - DecodeInst->Src[CurrentSrc].Data.Literal.Value = Literal; - ++CurrentSrc; - - if (Bytes == 8) [[unlikely]] { - DecodeInst->Src[CurrentSrc].Data.Literal.Size = 4; DecodeInst->Src[CurrentSrc].Type = DecodedOperand::OpType::Literal; - DecodeInst->Src[CurrentSrc].Data.Literal.Value = Literal >> 32; + DecodeInst->Src[CurrentSrc].Data.Literal.Value = Literal; + ++CurrentSrc; + + if (Bytes == 8) [[unlikely]] { + DecodeInst->Src[CurrentSrc].Data.Literal.Size = 4; + DecodeInst->Src[CurrentSrc].Type = DecodedOperand::OpType::Literal; + DecodeInst->Src[CurrentSrc].Data.Literal.Value = Literal >> 32; + } } Bytes = 0; @@ -1337,6 +1360,7 @@ void Decoder::DecodeInstructionsAtEntry(FEXCore::Core::InternalThreadState* Thre if (auto SectionInfo = CTX->SyscallHandler->LookupExecutableFileSection(*Thread, EntryPoint)) { SectionMinAddress = SectionInfo->FileStartVA; SectionMaxAddress = SectionInfo->EndVA; + Relocations = &SectionInfo->FileInfo.Relocations; } } diff --git a/FEXCore/Source/Interface/Core/Frontend.h b/FEXCore/Source/Interface/Core/Frontend.h index 291223203..22a0868a2 100644 --- a/FEXCore/Source/Interface/Core/Frontend.h +++ b/FEXCore/Source/Interface/Core/Frontend.h @@ -4,9 +4,11 @@ #include "Interface/Core/X86Tables/X86Tables.h" #include "Interface/IR/IR.h" +#include #include #include #include +#include #include #include @@ -97,7 +99,8 @@ private: uint8_t ReadByte(); std::optional PeekByte(uint8_t Offset); - uint64_t ReadData(uint8_t Size); + std::pair ReadData(uint8_t Size); + void SkipBytes(uint8_t Size) { InstructionSize += Size; } @@ -140,6 +143,8 @@ private: fextl::set VisitedBlocks; fextl::set* ExternalBranches {nullptr}; + fextl::unordered_map* Relocations {nullptr}; + // ModRM rm decoding using DecodeModRMPtr = void (FEXCore::Frontend::Decoder::*)(X86Tables::DecodedOperand* Operand, X86Tables::ModRMDecoded ModRM); void DecodeModRM_16(X86Tables::DecodedOperand* Operand, X86Tables::ModRMDecoded ModRM); From 2f4cd33950bec7a67a61a0d9a927272655aa937b Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 22 Dec 2025 14:52:27 +0000 Subject: [PATCH 5/5] Frontend: Fail gracefully upon encountering unexpected relocations As the decoder can occasionally explore non-code (e.g. after a noreturn call) it must tolerate cases where when doing so a relocation is encountered. --- FEXCore/Source/Interface/Core/Core.cpp | 3 ++- FEXCore/Source/Interface/Core/Frontend.cpp | 20 ++++++++++++-------- FEXCore/Source/Interface/Core/Frontend.h | 2 ++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 03effc809..a061a6e45 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -655,7 +655,8 @@ ContextImpl::GenerateIR(FEXCore::Core::InternalThreadState* Thread, uint64_t Gue LogMan::Msg::EFmt("Invalid or Unknown instruction: {} 0x{:x}", TableInfo->Name ?: "UND", Block.Entry - GuestRIP); } - if (Block.BlockStatus == Frontend::Decoder::DecodedBlockStatus::INVALID_INST) { + if (Block.BlockStatus == Frontend::Decoder::DecodedBlockStatus::INVALID_INST || + Block.BlockStatus == Frontend::Decoder::DecodedBlockStatus::BAD_RELOCATION) { Thread->OpDispatcher->InvalidOp(DecodedInfo); } else { Thread->OpDispatcher->NoExecOp(DecodedInfo); diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index f2c42c8e4..680e855a5 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -162,7 +162,8 @@ std::pair Decoder::ReadData(uint8_t Size) { } else if (It->second == GuestRelocationType::Rel64 && Size == 8) { return {static_cast(Res) - static_cast(EntryPoint), true}; } else { - ERROR_AND_DIE_FMT("Unhandled relocation combination, type: {} size: {}", static_cast(It->second), Size); + HitBadRelocation = true; + Res = 0; } } } @@ -1058,15 +1059,17 @@ bool Decoder::DecodeInstructionImpl(uint64_t PC) { Decoder::DecodedBlockStatus Decoder::DecodeInstruction(uint64_t PC) { // Will be set if DecodeInstructionImpl tries to read non-executable memory HitNonExecutableRange = false; + HitBadRelocation = false; bool ErrorDuringDecoding = !DecodeInstructionImpl(PC); - if (ErrorDuringDecoding || HitNonExecutableRange) [[unlikely]] { + if (ErrorDuringDecoding || HitNonExecutableRange || HitBadRelocation) [[unlikely]] { // Put an invalid instruction in the stream so the core can raise SIGILL if hit // Error while decoding instruction. We don't know the table or instruction size DecodeInst->TableInfo = nullptr; - auto Result = ErrorDuringDecoding ? DecodedBlockStatus::INVALID_INST : - DecodeInst->InstSize ? DecodedBlockStatus::PARTIAL_DECODE_INST : - DecodedBlockStatus::NOEXEC_INST; + auto Result = ErrorDuringDecoding ? DecodedBlockStatus::INVALID_INST : + DecodeInst->InstSize ? DecodedBlockStatus::PARTIAL_DECODE_INST : + HitNonExecutableRange ? DecodedBlockStatus::NOEXEC_INST : + DecodedBlockStatus::BAD_RELOCATION; DecodeInst->InstSize = 0; return Result; } else if (!DecodeInst->TableInfo || (DecodeInst->TableInfo->Type == TYPE_INST && !DecodeInst->TableInfo->OpcodeDispatcher.OpDispatch)) { @@ -1474,9 +1477,10 @@ void Decoder::DecodeInstructionsAtEntry(FEXCore::Core::InternalThreadState* Thre EraseBlock = true; } else { LogMan::Msg::EFmt("{} instruction in entry block: {:X}", - BlockIt->BlockStatus == DecodedBlockStatus::INVALID_INST ? "Invalid" : - BlockIt->BlockStatus == DecodedBlockStatus::NOEXEC_INST ? "NoExec" : - "PartialDecode", + BlockIt->BlockStatus == DecodedBlockStatus::INVALID_INST ? "Invalid" : + BlockIt->BlockStatus == DecodedBlockStatus::NOEXEC_INST ? "NoExec" : + BlockIt->BlockStatus == DecodedBlockStatus::BAD_RELOCATION ? "BadRelocation" : + "PartialDecode", OpAddress); } break; diff --git a/FEXCore/Source/Interface/Core/Frontend.h b/FEXCore/Source/Interface/Core/Frontend.h index 22a0868a2..5e8812853 100644 --- a/FEXCore/Source/Interface/Core/Frontend.h +++ b/FEXCore/Source/Interface/Core/Frontend.h @@ -30,6 +30,7 @@ public: INVALID_INST, NOEXEC_INST, PARTIAL_DECODE_INST, + BAD_RELOCATION, }; // New Frontend decoding @@ -117,6 +118,7 @@ private: uint64_t ExecutableRangeEnd {}; bool ExecutableRangeWritable {}; bool HitNonExecutableRange {}; + bool HitBadRelocation {}; const uint8_t* InstStream {}; IR::OpSize GetGPROpSize() const {