Merge pull request #1218 from hashaam1217/feat/relinker-rip-relative-stubs
feat(relinker): relocate RIP-relative operands into --to-intel stubs
This commit is contained in:
commit
73ac9ef7f4
|
|
@ -36,6 +36,7 @@ struct TrampolineSite {
|
|||
std::vector<std::uint8_t> OriginalBytes;
|
||||
std::vector<std::uint8_t> Body;
|
||||
std::size_t ReturnBranchOffset;
|
||||
std::vector<StubRelocation> Relocations = {};
|
||||
};
|
||||
|
||||
struct ConvertResult {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@ enum class Amd64OnlyLowering : std::uint8_t {
|
|||
Kept
|
||||
};
|
||||
|
||||
struct StubRelocation {
|
||||
std::size_t DisplacementOffset;
|
||||
std::size_t InstructionEnd;
|
||||
std::int64_t SiteTarget;
|
||||
};
|
||||
|
||||
struct Amd64OnlyMatch {
|
||||
std::string InstructionName;
|
||||
std::size_t Length;
|
||||
|
|
@ -23,6 +29,7 @@ struct Amd64OnlyMatch {
|
|||
std::vector<std::uint8_t> StubBody;
|
||||
std::size_t ReturnBranchOffset;
|
||||
bool Optional = false;
|
||||
std::vector<StubRelocation> Relocations = {};
|
||||
};
|
||||
|
||||
struct Amd64OnlySubstitutionReport {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@
|
|||
#include <codegen/x86/ClzeroOperands.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace Codegen {
|
||||
|
||||
class ClzeroLowering {
|
||||
public:
|
||||
void EmitOutOfLine(StubBodyBuilder& body, const ClzeroOperands& operands) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const ClzeroOperands& operands, std::span<const std::uint8_t> trailing = {}) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const ClzeroOperands& operands) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@
|
|||
|
||||
#include <codegen/x86/ReciprocalOperands.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <span>
|
||||
|
||||
namespace Codegen {
|
||||
|
||||
class ReciprocalLowering {
|
||||
public:
|
||||
void EmitOutOfLine(StubBodyBuilder& body, const ReciprocalOperands& operands) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const ReciprocalOperands& operands, std::span<const std::uint8_t> trailing = {}) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const ReciprocalOperands& operands) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@
|
|||
#include <codegen/x86/Sha1Operands.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace Codegen {
|
||||
|
||||
class Sha1Lowering {
|
||||
public:
|
||||
void EmitOutOfLine(StubBodyBuilder& body, const Sha1Operands& operands) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const Sha1Operands& operands, std::span<const std::uint8_t> trailing = {}) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const Sha1Operands& operands) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@
|
|||
#include <codegen/x86/Sha256Operands.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace Codegen {
|
||||
|
||||
class Sha256Lowering {
|
||||
public:
|
||||
void EmitOutOfLine(StubBodyBuilder& body, const Sha256Operands& operands) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const Sha256Operands& operands, std::span<const std::uint8_t> trailing = {}) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const Sha256Operands& operands) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ class Sse4aLowering {
|
|||
public:
|
||||
[[nodiscard]] std::optional<std::vector<std::uint8_t>> LowerInPlace(const Sse4aOperands& operands, std::size_t originalLength) const;
|
||||
void EmitOutOfLine(StubBodyBuilder& body, const Sse4aOperands& operands) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const Sse4aOperands& operands, std::span<const std::uint8_t> trailing = {}) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(std::span<const Sse4aOperands> sequence, std::span<const std::uint8_t> trailing) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(const Sse4aOperands& operands) const;
|
||||
[[nodiscard]] LoweredBody LowerOutOfLine(std::span<const Sse4aOperands> sequence) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
#ifndef CODEGEN_X86_STUBBODYBUILDER_HPP
|
||||
#define CODEGEN_X86_STUBBODYBUILDER_HPP
|
||||
|
||||
#include <codegen/x86/Amd64OnlySubstitutionTypes.hpp>
|
||||
#include <domain/Types.hpp>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -13,6 +16,7 @@ namespace Codegen {
|
|||
struct LoweredBody {
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
std::size_t ReturnBranchOffset;
|
||||
std::vector<StubRelocation> Relocations = {};
|
||||
};
|
||||
|
||||
using StubConstant = std::array<std::uint8_t, 16>;
|
||||
|
|
@ -26,12 +30,15 @@ struct MemoryOperand {
|
|||
std::int32_t Displacement;
|
||||
bool StackBase;
|
||||
std::size_t EncodedSize;
|
||||
bool RipRelative;
|
||||
std::size_t InstructionLength;
|
||||
};
|
||||
|
||||
[[nodiscard]] MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, std::size_t length, std::size_t modRmOffset, std::uint8_t rex, std::vector<std::uint8_t> prefixes);
|
||||
|
||||
void EmitSse(std::vector<std::uint8_t>& out, std::uint8_t prefix, std::initializer_list<std::uint8_t> opcode, std::uint8_t dst, std::uint8_t src);
|
||||
void EmitShiftImm(std::vector<std::uint8_t>& out, std::uint8_t extension, std::uint8_t reg, std::uint8_t imm);
|
||||
void ApplyStubRelocations(std::span<std::uint8_t> body, std::span<const StubRelocation> relocations, std::uint64_t siteAddress, std::uint64_t stubAddress, Domain::FileByteOffset failureOffset);
|
||||
|
||||
class StubBodyBuilder {
|
||||
public:
|
||||
|
|
@ -45,6 +52,8 @@ public:
|
|||
void Spill(std::uint8_t reg);
|
||||
void Restore(std::uint8_t reg);
|
||||
void Raw(std::span<const std::uint8_t> bytes);
|
||||
void Move(std::span<const std::uint8_t> instruction, std::optional<std::size_t> ripDisplacementOffset);
|
||||
void Advance(std::size_t length);
|
||||
[[nodiscard]] LoweredBody Finish();
|
||||
|
||||
private:
|
||||
|
|
@ -57,7 +66,9 @@ private:
|
|||
std::vector<std::uint8_t> _bytes;
|
||||
std::vector<Fixup> _fixups;
|
||||
std::vector<StubConstant> _constants;
|
||||
std::vector<StubRelocation> _relocations;
|
||||
std::size_t _stackDepth = 0;
|
||||
std::size_t _siteOffset = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ void Amd64OnlyConverter::_convertSegment(
|
|||
if (amdOnly && trailingBytes == 0) {
|
||||
sequence.push_back(bytes);
|
||||
taken.push_back(next);
|
||||
} else if (amdOnly || info.FlowKind != ControlFlowKind::Sequential || info.HasRipRelativeDisp || info.HasBranchTarget) {
|
||||
} else if (amdOnly || info.FlowKind != ControlFlowKind::Sequential || info.HasBranchTarget) {
|
||||
throw CodegenException("AMD-only instruction too short for a jump is followed by an instruction that cannot move", ph.Offset + following.Offset);
|
||||
} else {
|
||||
trailingBytes += following.Length;
|
||||
|
|
@ -167,7 +167,8 @@ void Amd64OnlyConverter::_convertSegment(
|
|||
siteLength,
|
||||
std::vector<std::uint8_t>(begin, begin + static_cast<std::ptrdiff_t>(siteLength)),
|
||||
stub.StubBody,
|
||||
stub.ReturnBranchOffset
|
||||
stub.ReturnBranchOffset,
|
||||
stub.Relocations
|
||||
});
|
||||
replacementLength = stub.StubBody.size();
|
||||
consumed.insert(taken.begin(), taken.end());
|
||||
|
|
|
|||
|
|
@ -12,11 +12,14 @@
|
|||
#include <codegen/x86/Sha1Lowering.hpp>
|
||||
#include <codegen/x86/Sha1Operands.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <codegen/x86/X64InstructionDecoder.hpp>
|
||||
#include <codegen/x86/X64OpcodeConstants.hpp>
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Codegen {
|
||||
|
|
@ -67,6 +70,30 @@ const Entry& _sha1Entry(const Sha1Operands& operands) {
|
|||
return kSha1rnds4;
|
||||
}
|
||||
|
||||
Amd64OnlyMatch _trampoline(std::string name, const std::size_t length, LoweredBody body, const bool optional = false) {
|
||||
return Amd64OnlyMatch{std::move(name), length, Amd64OnlyLowering::Trampoline, {}, std::move(body.Bytes), body.ReturnBranchOffset, optional, std::move(body.Relocations)};
|
||||
}
|
||||
|
||||
void _moveTrailing(StubBodyBuilder& body, std::span<const std::uint8_t> trailing) {
|
||||
const X64InstructionDecoder decoder;
|
||||
while (!trailing.empty()) {
|
||||
const auto info = decoder.DecodeInstruction(trailing.data(), trailing.size());
|
||||
if (info.Length == 0 || info.Length > trailing.size())
|
||||
throw CodegenException("Trailing bytes do not split into whole instructions");
|
||||
body.Move(trailing.first(info.Length), info.HasRipRelativeDisp ? std::optional<std::size_t>(info.RipRelativeDispOffset) : std::nullopt);
|
||||
trailing = trailing.subspan(info.Length);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TEmit>
|
||||
LoweredBody _outOfLine(const std::size_t length, const std::span<const std::uint8_t> trailing, const TEmit& emit) {
|
||||
StubBodyBuilder body;
|
||||
emit(body);
|
||||
body.Advance(length);
|
||||
_moveTrailing(body, trailing);
|
||||
return body.Finish();
|
||||
}
|
||||
|
||||
Amd64OnlyMatch _inPlace(const Entry& entry, const std::size_t length, std::vector<std::uint8_t> replacement) {
|
||||
while (replacement.size() < length) {
|
||||
const auto& nop = kNops[std::min<std::size_t>(length - replacement.size(), std::size(kNops)) - 1];
|
||||
|
|
@ -131,26 +158,23 @@ Amd64OnlyMatch Amd64OnlyInstructionMatcher::_matchSse4a(const DecodedInstruction
|
|||
if (auto inPlace = _lowering.LowerInPlace(operands, instr.Length))
|
||||
return Amd64OnlyMatch{entry.Name, instr.Length, Amd64OnlyLowering::InPlace, std::move(*inPlace), {}, 0};
|
||||
}
|
||||
auto body = _lowering.LowerOutOfLine(operands, trailing);
|
||||
const auto& name = operands.RegisterForm ? registerFormEntry.Name : entry.Name;
|
||||
return Amd64OnlyMatch{name, instr.Length, Amd64OnlyLowering::Trampoline, {}, std::move(body.Bytes), body.ReturnBranchOffset};
|
||||
return _trampoline(name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { _lowering.EmitOutOfLine(body, operands); }));
|
||||
}
|
||||
|
||||
Amd64OnlyMatch Amd64OnlyInstructionMatcher::_matchSha256(const DecodedInstruction& instr, std::span<const std::uint8_t> trailing) const {
|
||||
const auto operands = DecodeSha256(instr.Data, instr.Length);
|
||||
auto body = _sha256Lowering.LowerOutOfLine(operands, trailing);
|
||||
return Amd64OnlyMatch{_sha256Entry(operands).Name, instr.Length, Amd64OnlyLowering::Trampoline, {}, std::move(body.Bytes), body.ReturnBranchOffset};
|
||||
return _trampoline(_sha256Entry(operands).Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { _sha256Lowering.EmitOutOfLine(body, operands); }));
|
||||
}
|
||||
|
||||
Amd64OnlyMatch Amd64OnlyInstructionMatcher::_matchSha1(const DecodedInstruction& instr, std::span<const std::uint8_t> trailing) const {
|
||||
const auto operands = DecodeSha1(instr.Data, instr.Length);
|
||||
auto body = _sha1Lowering.LowerOutOfLine(operands, trailing);
|
||||
return Amd64OnlyMatch{_sha1Entry(operands).Name, instr.Length, Amd64OnlyLowering::Trampoline, {}, std::move(body.Bytes), body.ReturnBranchOffset};
|
||||
return _trampoline(_sha1Entry(operands).Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { _sha1Lowering.EmitOutOfLine(body, operands); }));
|
||||
}
|
||||
|
||||
Amd64OnlyMatch Amd64OnlyInstructionMatcher::_matchClzero(const DecodedInstruction& instr, std::span<const std::uint8_t> trailing) const {
|
||||
auto body = _clzeroLowering.LowerOutOfLine(DecodeClzero(instr.Data, instr.Length), trailing);
|
||||
return Amd64OnlyMatch{kClzero.Name, instr.Length, Amd64OnlyLowering::Trampoline, {}, std::move(body.Bytes), body.ReturnBranchOffset};
|
||||
const auto operands = DecodeClzero(instr.Data, instr.Length);
|
||||
return _trampoline(kClzero.Name, instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { _clzeroLowering.EmitOutOfLine(body, operands); }));
|
||||
}
|
||||
|
||||
std::optional<Amd64OnlyMatch> Amd64OnlyInstructionMatcher::MatchSequence(
|
||||
|
|
@ -190,11 +214,11 @@ std::optional<Amd64OnlyMatch> Amd64OnlyInstructionMatcher::MatchSequence(
|
|||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
body.Advance(instr.Length);
|
||||
}
|
||||
body.Raw(trailing);
|
||||
auto lowered = body.Finish();
|
||||
_moveTrailing(body, trailing);
|
||||
const bool optional = DecodeVexReciprocal(instructions.front().data(), instructions.front().size()).has_value();
|
||||
return Amd64OnlyMatch{name, instructions.front().size(), Amd64OnlyLowering::Trampoline, {}, std::move(lowered.Bytes), lowered.ReturnBranchOffset, optional};
|
||||
return _trampoline(name, instructions.front().size(), body.Finish(), optional);
|
||||
}
|
||||
|
||||
std::optional<Amd64OnlyMatch> Amd64OnlyInstructionMatcher::Match(
|
||||
|
|
@ -229,8 +253,7 @@ std::optional<Amd64OnlyMatch> Amd64OnlyInstructionMatcher::Match(
|
|||
return _validWait(instr) ? _inPlace(kMwaitx, length, std::vector<std::uint8_t>(kPause.Bytes, kPause.Bytes + kPause.Size)) : _unsupported(kMwaitx, length);
|
||||
|
||||
if (const auto reciprocal = DecodeVexReciprocal(data, length)) {
|
||||
auto body = _reciprocalLowering.LowerOutOfLine(*reciprocal, trailing);
|
||||
return Amd64OnlyMatch{_reciprocalEntry(*reciprocal).Name, length, Amd64OnlyLowering::Trampoline, {}, std::move(body.Bytes), body.ReturnBranchOffset, true};
|
||||
return _trampoline(_reciprocalEntry(*reciprocal).Name, length, _outOfLine(length, trailing, [&](StubBodyBuilder& body) { _reciprocalLowering.EmitOutOfLine(body, *reciprocal); }), true);
|
||||
}
|
||||
|
||||
if (instr.IsClzero())
|
||||
|
|
|
|||
|
|
@ -41,10 +41,9 @@ void ClzeroLowering::EmitOutOfLine(StubBodyBuilder& body, const ClzeroOperands&
|
|||
body.Restore(kScratch);
|
||||
}
|
||||
|
||||
LoweredBody ClzeroLowering::LowerOutOfLine(const ClzeroOperands& operands, std::span<const std::uint8_t> trailing) const {
|
||||
LoweredBody ClzeroLowering::LowerOutOfLine(const ClzeroOperands& operands) const {
|
||||
StubBodyBuilder body;
|
||||
EmitOutOfLine(body, operands);
|
||||
body.Raw(trailing);
|
||||
return body.Finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,10 +55,9 @@ void ReciprocalLowering::EmitOutOfLine(StubBodyBuilder& body, const ReciprocalOp
|
|||
body.Restore(scratch);
|
||||
}
|
||||
|
||||
LoweredBody ReciprocalLowering::LowerOutOfLine(const ReciprocalOperands& operands, std::span<const std::uint8_t> trailing) const {
|
||||
LoweredBody ReciprocalLowering::LowerOutOfLine(const ReciprocalOperands& operands) const {
|
||||
StubBodyBuilder body;
|
||||
EmitOutOfLine(body, operands);
|
||||
body.Raw(trailing);
|
||||
return body.Finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,10 +183,9 @@ void Sha1Lowering::EmitOutOfLine(StubBodyBuilder& body, const Sha1Operands& oper
|
|||
}
|
||||
}
|
||||
|
||||
LoweredBody Sha1Lowering::LowerOutOfLine(const Sha1Operands& operands, std::span<const std::uint8_t> trailing) const {
|
||||
LoweredBody Sha1Lowering::LowerOutOfLine(const Sha1Operands& operands) const {
|
||||
StubBodyBuilder body;
|
||||
EmitOutOfLine(body, operands);
|
||||
body.Raw(trailing);
|
||||
return body.Finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) {
|
|||
}
|
||||
operands.Function = static_cast<std::uint8_t>(data[immediateOffset] & 3);
|
||||
}
|
||||
if (operands.Memory && operands.Memory->RipRelative && immediateOffset + (operands.Operation == Sha1Operation::Rnds4 ? 1 : 0) != length)
|
||||
throw CodegenException("RIP-relative SHA-1 operand does not end the instruction");
|
||||
return operands;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -205,10 +205,9 @@ void Sha256Lowering::EmitOutOfLine(StubBodyBuilder& body, const Sha256Operands&
|
|||
}
|
||||
}
|
||||
|
||||
LoweredBody Sha256Lowering::LowerOutOfLine(const Sha256Operands& operands, std::span<const std::uint8_t> trailing) const {
|
||||
LoweredBody Sha256Lowering::LowerOutOfLine(const Sha256Operands& operands) const {
|
||||
StubBodyBuilder body;
|
||||
EmitOutOfLine(body, operands);
|
||||
body.Raw(trailing);
|
||||
return body.Finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ Sha256Operands DecodeSha256(const std::uint8_t* data, const std::size_t length)
|
|||
operands.Destination = static_cast<std::uint8_t>(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0));
|
||||
if (((modrm >> ModRmModShift) & ModRmModMask) != ModRmModRegister) {
|
||||
operands.Memory = DecodeMemoryOperand(data, length, pos + 3, rex, std::move(prefixes));
|
||||
if (operands.Memory->RipRelative && pos + 3 + operands.Memory->EncodedSize != length)
|
||||
throw CodegenException("RIP-relative SHA-256 operand does not end the instruction");
|
||||
return operands;
|
||||
}
|
||||
operands.Source = static_cast<std::uint8_t>((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0));
|
||||
|
|
|
|||
|
|
@ -196,15 +196,14 @@ void Sse4aLowering::EmitOutOfLine(StubBodyBuilder& body, const Sse4aOperands& op
|
|||
_emitOutOfLine(body, operands);
|
||||
}
|
||||
|
||||
LoweredBody Sse4aLowering::LowerOutOfLine(const Sse4aOperands& operands, std::span<const std::uint8_t> trailing) const {
|
||||
return LowerOutOfLine(std::span<const Sse4aOperands>(&operands, 1), trailing);
|
||||
LoweredBody Sse4aLowering::LowerOutOfLine(const Sse4aOperands& operands) const {
|
||||
return LowerOutOfLine(std::span<const Sse4aOperands>(&operands, 1));
|
||||
}
|
||||
|
||||
LoweredBody Sse4aLowering::LowerOutOfLine(std::span<const Sse4aOperands> sequence, std::span<const std::uint8_t> trailing) const {
|
||||
LoweredBody Sse4aLowering::LowerOutOfLine(std::span<const Sse4aOperands> sequence) const {
|
||||
StubBodyBuilder body;
|
||||
for (const auto& operands : sequence)
|
||||
_emitOutOfLine(body, operands);
|
||||
body.Raw(trailing);
|
||||
return body.Finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ std::size_t _displacementSize(const std::uint8_t mod, const std::uint8_t rm, con
|
|||
using namespace X64OpcodeConstants;
|
||||
if (mod == ModRmModDisp8)
|
||||
return Disp8Size;
|
||||
if (mod == ModRmModDisp32 || (mod == ModRmModIndirect && rm == ModRmRmSibPresent && (sib & SibBaseMask) == SibBaseDisp32))
|
||||
if (mod == ModRmModDisp32 || (mod == ModRmModIndirect && rm == ModRmRmRipRelative) || (mod == ModRmModIndirect && rm == ModRmRmSibPresent && (sib & SibBaseMask) == SibBaseDisp32))
|
||||
return Disp32Size;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -58,11 +58,10 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le
|
|||
if (modRmOffset >= length)
|
||||
throw CodegenException("Memory operand truncated before its ModRM byte");
|
||||
const auto modrm = data[modRmOffset];
|
||||
MemoryOperand operand{std::move(prefixes), static_cast<std::uint8_t>(rex & (kRexX | kRexB)), static_cast<std::uint8_t>((modrm >> ModRmModShift) & ModRmModMask), static_cast<std::uint8_t>(modrm & ModRmRmMask), 0, 0, false, 0};
|
||||
MemoryOperand operand{std::move(prefixes), static_cast<std::uint8_t>(rex & (kRexX | kRexB)), static_cast<std::uint8_t>((modrm >> ModRmModShift) & ModRmModMask), static_cast<std::uint8_t>(modrm & ModRmRmMask), 0, 0, false, 0, false, length};
|
||||
if (operand.Mod == ModRmModRegister)
|
||||
throw CodegenException("Register operand where a memory operand was expected");
|
||||
if (operand.Mod == ModRmModIndirect && operand.Rm == ModRmRmRipRelative)
|
||||
throw CodegenException("RIP-relative memory operand cannot move into a stub");
|
||||
operand.RipRelative = operand.Mod == ModRmModIndirect && operand.Rm == ModRmRmRipRelative;
|
||||
auto pos = modRmOffset + 1;
|
||||
if (operand.Rm == ModRmRmSibPresent) {
|
||||
if (pos >= length)
|
||||
|
|
@ -81,6 +80,21 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le
|
|||
return operand;
|
||||
}
|
||||
|
||||
void ApplyStubRelocations(const std::span<std::uint8_t> body, const std::span<const StubRelocation> relocations, const std::uint64_t siteAddress, const std::uint64_t stubAddress, const Domain::FileByteOffset failureOffset) {
|
||||
using namespace X64OpcodeConstants;
|
||||
for (const auto& relocation : relocations) {
|
||||
if (relocation.InstructionEnd > body.size() || relocation.DisplacementOffset > relocation.InstructionEnd || relocation.InstructionEnd - relocation.DisplacementOffset < Disp32Size)
|
||||
throw CodegenException("Stub relocation lies outside the stub body", failureOffset);
|
||||
const auto target = siteAddress + static_cast<std::uint64_t>(relocation.SiteTarget);
|
||||
const auto displacement = static_cast<std::int64_t>(target - (stubAddress + relocation.InstructionEnd));
|
||||
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
|
||||
throw CodegenException("RIP-relative operand is out of rel32 range from its stub", failureOffset);
|
||||
const auto value = static_cast<std::uint32_t>(static_cast<std::int32_t>(displacement));
|
||||
for (std::size_t index = 0; index < Disp32Size; ++index)
|
||||
body[relocation.DisplacementOffset + index] = static_cast<std::uint8_t>(value >> (index * 8));
|
||||
}
|
||||
}
|
||||
|
||||
void EmitSse(std::vector<std::uint8_t>& out, const std::uint8_t prefix, const std::initializer_list<std::uint8_t> opcode, const std::uint8_t dst, const std::uint8_t src) {
|
||||
_emit(out, prefix, dst, src, opcode, static_cast<std::uint8_t>(kModRmRegister | ((dst & 7) << 3) | (src & 7)));
|
||||
}
|
||||
|
|
@ -135,6 +149,11 @@ void StubBodyBuilder::Load(const std::uint8_t reg, const MemoryOperand& operand)
|
|||
_bytes.insert(_bytes.end(), {TwoByteOpcodeEscape, kMovdqu, static_cast<std::uint8_t>((mod << ModRmModShift) | ((reg & 7) << ModRmRegShift) | operand.Rm)});
|
||||
if (operand.Rm == ModRmRmSibPresent)
|
||||
_bytes.push_back(operand.Sib);
|
||||
if (operand.RipRelative) {
|
||||
_relocations.push_back({_bytes.size(), _bytes.size() + Disp32Size, static_cast<std::int64_t>(_siteOffset + operand.InstructionLength) + operand.Displacement});
|
||||
_bytes.insert(_bytes.end(), Disp32Size, 0);
|
||||
return;
|
||||
}
|
||||
const auto value = static_cast<std::uint32_t>(static_cast<std::int32_t>(displacement));
|
||||
for (std::size_t index = 0; index < _displacementSize(mod, operand.Rm, operand.Sib); ++index)
|
||||
_bytes.push_back(static_cast<std::uint8_t>(value >> (index * 8)));
|
||||
|
|
@ -158,6 +177,27 @@ void StubBodyBuilder::Raw(std::span<const std::uint8_t> bytes) {
|
|||
_bytes.insert(_bytes.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
void StubBodyBuilder::Move(const std::span<const std::uint8_t> instruction, const std::optional<std::size_t> ripDisplacementOffset) {
|
||||
using namespace X64OpcodeConstants;
|
||||
const auto start = _bytes.size();
|
||||
Raw(instruction);
|
||||
if (ripDisplacementOffset) {
|
||||
if (*ripDisplacementOffset > instruction.size() || instruction.size() - *ripDisplacementOffset < Disp32Size)
|
||||
throw CodegenException("RIP-relative displacement lies outside its instruction");
|
||||
std::uint32_t value = 0;
|
||||
for (std::size_t index = 0; index < Disp32Size; ++index) {
|
||||
value |= static_cast<std::uint32_t>(instruction[*ripDisplacementOffset + index]) << (index * 8);
|
||||
_bytes[start + *ripDisplacementOffset + index] = 0;
|
||||
}
|
||||
_relocations.push_back({start + *ripDisplacementOffset, start + instruction.size(), static_cast<std::int64_t>(_siteOffset + instruction.size()) + static_cast<std::int32_t>(value)});
|
||||
}
|
||||
Advance(instruction.size());
|
||||
}
|
||||
|
||||
void StubBodyBuilder::Advance(const std::size_t length) {
|
||||
_siteOffset += length;
|
||||
}
|
||||
|
||||
LoweredBody StubBodyBuilder::Finish() {
|
||||
const auto returnBranchOffset = _bytes.size();
|
||||
_bytes.insert(_bytes.end(), kJmpRel32.Bytes, kJmpRel32.Bytes + kJmpRel32.Size);
|
||||
|
|
@ -174,7 +214,7 @@ LoweredBody StubBodyBuilder::Finish() {
|
|||
for (std::size_t index = 0; index < 4; ++index)
|
||||
_bytes[fixup.DisplacementOffset + index] = static_cast<std::uint8_t>(value >> (index * 8));
|
||||
}
|
||||
return {std::move(_bytes), returnBranchOffset};
|
||||
return {std::move(_bytes), returnBranchOffset, std::move(_relocations)};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <codegen/x86/ClzeroOperands.hpp>
|
||||
#include <codegen/x86/ClzeroLowering.hpp>
|
||||
#include <codegen/x86/ReciprocalOperands.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <codegen/x86/DecodedInstruction.hpp>
|
||||
#include <codegen/x86/X64InstructionDecoder.hpp>
|
||||
#include <codegen/x86/X64InstructionRewriter.hpp>
|
||||
|
|
@ -235,7 +236,11 @@ void sha256Operands() {
|
|||
const Bytes absolute = {0x0F, 0x38, 0xCC, 0x14, 0x25, 0x78, 0x56, 0x34, 0x12};
|
||||
const auto noBase = Codegen::DecodeSha256(absolute.data(), absolute.size());
|
||||
require(!noBase.Memory->StackBase && noBase.Memory->Displacement == 0x12345678, "SIB without a base was decoded incorrectly");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0x15, 0, 0, 0, 0}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "SHA-256 RIP-relative operand was accepted");
|
||||
const Bytes ripRelative = {0x0F, 0x38, 0xCC, 0x15, 0x78, 0x56, 0x34, 0x12};
|
||||
const auto rip = Codegen::DecodeSha256(ripRelative.data(), ripRelative.size());
|
||||
require(rip.Destination == 2 && rip.Memory && rip.Memory->RipRelative && rip.Memory->Displacement == 0x12345678 && rip.Memory->EncodedSize == 5 && rip.Memory->InstructionLength == 8 && !noBase.Memory->RipRelative, "SHA-256 RIP-relative operand was decoded incorrectly");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0x15, 0, 0, 0, 0, 0x90}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "SHA-256 RIP-relative operand that does not end the instruction was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0x15, 0, 0, 0}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "Truncated SHA-256 RIP-relative operand was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0x54, 0x24}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "Truncated SHA-256 memory operand was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x66, 0x0F, 0x38, 0xCB, 0xCA}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "Prefixed 0F 38 CB was decoded as SHA-256");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0xCA}; (void)Codegen::DecodeSha256(bytes.data(), bytes.size()); }, "SHA-1 was decoded as SHA-256");
|
||||
|
|
@ -270,8 +275,14 @@ void sha1Operands() {
|
|||
const Bytes message = {0x0F, 0x38, 0xC9, 0x08};
|
||||
const auto decodedMessage = Codegen::DecodeSha1(message.data(), message.size());
|
||||
require(decodedMessage.Operation == Codegen::Sha1Operation::Msg1 && decodedMessage.Destination == 1 && decodedMessage.Memory && decodedMessage.Memory->Mod == 0 && decodedMessage.Memory->Rm == 0, "SHA1MSG1 memory operand was decoded incorrectly");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x15, 0, 0, 0, 0}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 RIP-relative operand was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x15, 0, 0, 0, 0, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 RIP-relative operand was accepted");
|
||||
const Bytes ripMessage = {0x0F, 0x38, 0xC9, 0x15, 0x10, 0x00, 0x00, 0x00};
|
||||
const auto ripDecoded = Codegen::DecodeSha1(ripMessage.data(), ripMessage.size());
|
||||
require(ripDecoded.Operation == Codegen::Sha1Operation::Msg1 && ripDecoded.Destination == 2 && ripDecoded.Memory && ripDecoded.Memory->RipRelative && ripDecoded.Memory->Displacement == 0x10 && ripDecoded.Memory->InstructionLength == 8, "SHA-1 RIP-relative operand was decoded incorrectly");
|
||||
const Bytes ripRounds = {0x0F, 0x3A, 0xCC, 0x15, 0xF0, 0xFF, 0xFF, 0xFF, 0x03};
|
||||
const auto ripRoundsDecoded = Codegen::DecodeSha1(ripRounds.data(), ripRounds.size());
|
||||
require(ripRoundsDecoded.Operation == Codegen::Sha1Operation::Rnds4 && ripRoundsDecoded.Function == 3 && ripRoundsDecoded.Memory && ripRoundsDecoded.Memory->RipRelative && ripRoundsDecoded.Memory->Displacement == -16 && ripRoundsDecoded.Memory->InstructionLength == 9, "SHA1RNDS4 RIP-relative operand was decoded incorrectly");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x15, 0, 0, 0, 0, 0x90}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 RIP-relative operand that does not end the instruction was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x15, 0, 0, 0, 0, 0x00, 0x90}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 RIP-relative operand that does not end the instruction was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x54, 0x24}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Truncated SHA-1 memory operand was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 without its immediate was accepted");
|
||||
requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x50, 0x02}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory form without its immediate was accepted");
|
||||
|
|
@ -393,6 +404,49 @@ void goldenBodies() {
|
|||
require(insertqRegisterForm.Bytes[0] == 0x48 && insertqRegisterForm.Bytes.size() % 16 == 0 && insertqRegisterForm.ReturnBranchOffset < insertqRegisterForm.Bytes.size(), "INSERTQ register form body does not start with the red-zone skip");
|
||||
}
|
||||
|
||||
void ripRelativeBodies() {
|
||||
const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher();
|
||||
const auto relocated = [](const Codegen::Amd64OnlyMatch& match, const std::size_t displacementOffset, const std::size_t instructionEnd, const std::int64_t siteTarget) {
|
||||
return match.Relocations.size() == 1 && match.Relocations[0].DisplacementOffset == displacementOffset && match.Relocations[0].InstructionEnd == instructionEnd && match.Relocations[0].SiteTarget == siteTarget;
|
||||
};
|
||||
const Bytes message = {0x0F, 0x38, 0xCC, 0x15, 0x78, 0x56, 0x34, 0x12};
|
||||
const auto sha256 = matcher->Match(message.data(), message.size());
|
||||
require(sha256 && sha256->StubBody.size() > 21 && Bytes(sha256->StubBody.begin() + 13, sha256->StubBody.begin() + 21) == Bytes{0xF3, 0x0F, 0x6F, 0x0D, 0x00, 0x00, 0x00, 0x00}, "SHA-256 RIP-relative operand was not reloaded through a RIP-relative MOVDQU");
|
||||
require(relocated(*sha256, 17, 21, 8 + 0x12345678), "SHA-256 RIP-relative operand has the wrong relocation");
|
||||
const Bytes prefixed = {0x65, 0x67, 0x0F, 0x38, 0xCC, 0x15, 0x78, 0x56, 0x34, 0x12};
|
||||
const auto segmentOverride = matcher->Match(prefixed.data(), prefixed.size());
|
||||
require(segmentOverride && segmentOverride->StubBody.size() > 23 && Bytes(segmentOverride->StubBody.begin() + 13, segmentOverride->StubBody.begin() + 23) == Bytes{0x65, 0x67, 0xF3, 0x0F, 0x6F, 0x0D, 0x00, 0x00, 0x00, 0x00}, "RIP-relative reload lost the segment or address-size prefix");
|
||||
require(relocated(*segmentOverride, 19, 23, 10 + 0x12345678), "Prefixed RIP-relative operand has the wrong relocation");
|
||||
const Bytes rounds = {0x0F, 0x3A, 0xCC, 0x15, 0xF0, 0xFF, 0xFF, 0xFF, 0x03};
|
||||
const auto sha1 = matcher->Match(rounds.data(), rounds.size());
|
||||
require(sha1 && sha1->StubBody.size() > 21 && Bytes(sha1->StubBody.begin() + 13, sha1->StubBody.begin() + 21) == Bytes{0xF3, 0x0F, 0x6F, 0x05, 0x00, 0x00, 0x00, 0x00}, "SHA1RNDS4 RIP-relative operand was not reloaded through a RIP-relative MOVDQU");
|
||||
require(relocated(*sha1, 17, 21, 9 - 16), "SHA1RNDS4 RIP-relative operand does not count its immediate");
|
||||
const Bytes extrq = {0x66, 0x0F, 0x79, 0xCA};
|
||||
const Bytes followers = {0x90, 0x48, 0x8D, 0x05, 0x10, 0x00, 0x00, 0x00};
|
||||
const auto moved = matcher->Match(extrq.data(), extrq.size(), followers);
|
||||
require(moved && moved->ReturnBranchOffset >= followers.size(), "EXTRQ with followers was not lowered through a stub");
|
||||
const auto start = moved->ReturnBranchOffset - followers.size();
|
||||
require(Bytes(moved->StubBody.begin() + static_cast<std::ptrdiff_t>(start), moved->StubBody.begin() + static_cast<std::ptrdiff_t>(moved->ReturnBranchOffset)) == Bytes{0x90, 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}, "Moved RIP-relative LEA was not copied with a cleared displacement");
|
||||
require(relocated(*moved, start + 4, start + 8, 4 + 8 + 0x10), "Moved RIP-relative LEA has the wrong relocation");
|
||||
const std::vector<std::span<const std::uint8_t>> sequence = {extrq, message};
|
||||
const auto pair = matcher->MatchSequence(sequence, {});
|
||||
require(pair && pair->Relocations.size() == 1 && pair->Relocations[0].SiteTarget == 4 + 8 + 0x12345678, "RIP-relative operand in a sequence is not relative to the site start");
|
||||
}
|
||||
|
||||
void stubRelocations() {
|
||||
Bytes body(8, 0xCC);
|
||||
const std::vector<Codegen::StubRelocation> relocations = {{2, 6, 0x1040}};
|
||||
Codegen::ApplyStubRelocations(body, relocations, 0x401000, 0x400F00, 0);
|
||||
require(read<std::int32_t>(body, 2) == 0x401000 + 0x1040 - (0x400F00 + 6) && body[0] == 0xCC && body[6] == 0xCC, "Stub relocation was not resolved against the site address");
|
||||
Codegen::ApplyStubRelocations(body, relocations, 0x1000, 0x7FFF0000, 0);
|
||||
require(read<std::int32_t>(body, 2) == 0x1000 + 0x1040 - (0x7FFF0000 + 6), "Stub placed above its site was not relocated backwards");
|
||||
requireFailure([&] { Codegen::ApplyStubRelocations(body, relocations, 0x1000, 0x1000 + 0x90000000ull, 0); }, "RIP-relative operand beyond rel32 range was accepted");
|
||||
const std::vector<Codegen::StubRelocation> outside = {{6, 10, 0}};
|
||||
requireFailure([&] { Codegen::ApplyStubRelocations(body, outside, 0x1000, 0x2000, 0); }, "Stub relocation outside the body was accepted");
|
||||
const std::vector<Codegen::StubRelocation> narrow = {{4, 6, 0}};
|
||||
requireFailure([&] { Codegen::ApplyStubRelocations(body, narrow, 0x1000, 0x2000, 0); }, "Stub relocation narrower than a displacement was accepted");
|
||||
}
|
||||
|
||||
Bytes segmentFixture() {
|
||||
Bytes file(0x300, 0xCC);
|
||||
const Bytes text = {
|
||||
|
|
@ -459,7 +513,16 @@ void converterRipRelativeFollower() {
|
|||
};
|
||||
for (const auto& following : kRipRelativeVectorLoads) {
|
||||
const auto [file, header] = withFollower(following);
|
||||
require(failureOffset([&] { (void)converter->Convert(file, {header}); }, "RIP-relative vector load was moved into an EXTRQ stub") == 0x204, "RIP-relative follower failure does not carry its file offset");
|
||||
const auto result = converter->Convert(file, {header});
|
||||
require(result.Trampolines.size() == 1 && result.Trampolines[0].Length == 4 + following.size(), "RIP-relative vector load was not moved into the EXTRQ stub");
|
||||
const auto& site = result.Trampolines[0];
|
||||
const auto info = Codegen::X64InstructionDecoder{}.DecodeInstruction(following.data(), following.size());
|
||||
const auto start = site.ReturnBranchOffset - following.size();
|
||||
require(site.Relocations.size() == 1 && site.Relocations[0].DisplacementOffset == start + info.RipRelativeDispOffset && site.Relocations[0].InstructionEnd == site.ReturnBranchOffset && site.Relocations[0].SiteTarget == static_cast<std::int64_t>(4 + following.size()) + 0x10, "Moved RIP-relative vector load has the wrong relocation");
|
||||
}
|
||||
for (const Bytes& following : {Bytes{0xFF, 0x15, 0x10, 0x00, 0x00, 0x00}, Bytes{0xFF, 0x25, 0x10, 0x00, 0x00, 0x00}}) {
|
||||
const auto [file, header] = withFollower(following);
|
||||
require(failureOffset([&] { (void)converter->Convert(file, {header}); }, "RIP-relative indirect branch was moved into an EXTRQ stub") == 0x204, "RIP-relative branch failure does not carry its file offset");
|
||||
}
|
||||
for (const auto& following : kRegisterVectorOperations) {
|
||||
const auto [file, header] = withFollower(following);
|
||||
|
|
@ -494,9 +557,12 @@ void converterSha256() {
|
|||
const auto memoryResult = converter->Convert(memoryForm, {segmentHeader(text.size())});
|
||||
require(memoryResult.Trampolines.size() == 2 && memoryResult.Reports[1].InstructionName == "SHA256MSG2", "SHA-256 memory form was not lowered through a stub");
|
||||
auto ripRelative = file;
|
||||
const Bytes ripMessage = {0x0F, 0x38, 0xCD, 0x2D, 0x00, 0x00, 0x00, 0x00, 0xC3};
|
||||
const Bytes ripMessage = {0x0F, 0x38, 0xCD, 0x2D, 0x40, 0x00, 0x00, 0x00, 0xC3};
|
||||
std::copy(ripMessage.begin(), ripMessage.end(), ripRelative.begin() + 0x20C);
|
||||
require(failureOffset([&] { (void)converter->Convert(ripRelative, {segmentHeader(text.size() + 3)}); }, "SHA-256 RIP-relative form was accepted") == 0x20C, "SHA-256 operand failure does not carry the file offset");
|
||||
const auto ripResult = converter->Convert(ripRelative, {segmentHeader(text.size() + 3)});
|
||||
require(ripResult.Trampolines.size() == 2 && ripResult.Bytes == ripRelative && ripResult.Reports[1].InstructionName == "SHA256MSG2", "SHA-256 RIP-relative form was not lowered through a stub");
|
||||
const auto& ripSite = ripResult.Trampolines[1];
|
||||
require(ripSite.Offset == 0x20C && ripSite.Length == 8 && ripSite.Relocations.size() == 1 && ripSite.Relocations[0].SiteTarget == 8 + 0x40 && ripSite.Relocations[0].InstructionEnd <= ripSite.ReturnBranchOffset, "SHA-256 RIP-relative site was recorded without its relocation");
|
||||
}
|
||||
|
||||
void converterSha1() {
|
||||
|
|
@ -526,9 +592,12 @@ void converterSha1() {
|
|||
const auto memoryResult = converter->Convert(memoryForm, {segmentHeader(text.size())});
|
||||
require(memoryResult.Trampolines.size() == 3 && memoryResult.Reports[2].InstructionName == "SHA1MSG2", "SHA-1 memory form was not lowered through a stub");
|
||||
auto ripRelative = file;
|
||||
const Bytes ripMessage = {0x0F, 0x38, 0xCA, 0x2D, 0x00, 0x00, 0x00, 0x00, 0xC3};
|
||||
const Bytes ripMessage = {0x0F, 0x38, 0xCA, 0x2D, 0xF0, 0xFF, 0xFF, 0xFF, 0xC3};
|
||||
std::copy(ripMessage.begin(), ripMessage.end(), ripRelative.begin() + 0x211);
|
||||
require(failureOffset([&] { (void)converter->Convert(ripRelative, {segmentHeader(text.size() + 3)}); }, "SHA-1 RIP-relative form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset");
|
||||
const auto ripResult = converter->Convert(ripRelative, {segmentHeader(text.size() + 3)});
|
||||
require(ripResult.Trampolines.size() == 3 && ripResult.Bytes == ripRelative && ripResult.Reports[2].InstructionName == "SHA1MSG2", "SHA-1 RIP-relative form was not lowered through a stub");
|
||||
const auto& ripSite = ripResult.Trampolines[2];
|
||||
require(ripSite.Offset == 0x211 && ripSite.Length == 8 && ripSite.Relocations.size() == 1 && ripSite.Relocations[0].SiteTarget == 8 - 16 && ripSite.Relocations[0].InstructionEnd <= ripSite.ReturnBranchOffset, "SHA-1 RIP-relative site was recorded without its relocation");
|
||||
}
|
||||
|
||||
void converterMonitorWait() {
|
||||
|
|
@ -614,8 +683,10 @@ void converterReciprocal() {
|
|||
auto ripRelative = file;
|
||||
const Bytes load = {0xC5, 0xF8, 0x52, 0xD5, 0x8B, 0x05, 0x10, 0x00, 0x00, 0x00, 0xC3};
|
||||
std::copy(load.begin(), load.end(), ripRelative.begin() + 0x200);
|
||||
const auto kept = converter->Convert(ripRelative, {segmentHeader(load.size())});
|
||||
require(kept.Trampolines.empty() && kept.KeptCount == 1 && kept.Bytes == ripRelative, "A following RIP-relative instruction was moved into a stub");
|
||||
const auto moved = converter->Convert(ripRelative, {segmentHeader(load.size())});
|
||||
require(moved.Trampolines.size() == 1 && moved.KeptCount == 0 && moved.Bytes == ripRelative && moved.Trampolines[0].Length == 10, "VRSQRTPS did not absorb the following RIP-relative load");
|
||||
const auto& movedSite = moved.Trampolines[0];
|
||||
require(movedSite.Relocations.size() == 1 && movedSite.Relocations[0].DisplacementOffset == movedSite.ReturnBranchOffset - 4 && movedSite.Relocations[0].InstructionEnd == movedSite.ReturnBranchOffset && movedSite.Relocations[0].SiteTarget == 10 + 0x10, "Absorbed RIP-relative load has the wrong relocation");
|
||||
}
|
||||
|
||||
void converterStrayRex() {
|
||||
|
|
@ -709,35 +780,40 @@ std::vector<Domain::ProgramHeader> elfHeaders() {
|
|||
return {{1, 5, 0x200, 0x1000, 0, 0x100, 0x100, 0x1000}, {1, 6, 0x300, 0x2000, 0, 0x100, 0x100, 0x1000}};
|
||||
}
|
||||
|
||||
void linuxPlacement() {
|
||||
const auto source = elfFixture({0xEB, 0x06, 0xF2, 0x0F, 0x78, 0xDB, 0x08, 0x08, 0xC3});
|
||||
const auto headers = elfHeaders();
|
||||
const auto converted = Codegen::MakeAmd64OnlyConverter()->Convert(source, {headers[0]});
|
||||
require(converted.Trampolines.size() == 1 && converted.Bytes == source, "Linux fixture conversion produced unexpected results");
|
||||
Elfpatcher::Linux::LinuxElfPatcher linuxPatcher() {
|
||||
const auto byteWriter = std::make_shared<Io::ByteWriter>();
|
||||
Elfpatcher::Linux::LinuxElfPatcher patcher(
|
||||
return Elfpatcher::Linux::LinuxElfPatcher(
|
||||
std::make_shared<Elfpatcher::EntryStubBuilder>(),
|
||||
std::make_shared<Elfpatcher::ProgramHeaderLayoutBuilder>(std::make_shared<Elfpatcher::SegmentFilter>(), byteWriter),
|
||||
std::make_shared<Elfpatcher::SectionHeaderTableBuilder>(byteWriter),
|
||||
byteWriter);
|
||||
const auto output = patcher.Patch(converted.Bytes, headers, {}, 0, "$ORIGIN/libs", true, false, converted.Trampolines);
|
||||
require(output[0x202] == 0xE9 && output[0x207] == 0x90, "Linux site was not replaced by a jump");
|
||||
const auto target = 0x1002 + 5 + static_cast<std::int64_t>(read<std::int32_t>(output, 0x203));
|
||||
require(target % 16 == 0 && target > 0x2100, "Linux stub is misaligned or inside the original image");
|
||||
}
|
||||
|
||||
std::uint64_t executableFileOffset(const Bytes& output, const std::uint64_t address) {
|
||||
const auto phNum = read<std::uint16_t>(output, 56);
|
||||
std::uint64_t bodyOffset = 0;
|
||||
bool found = false;
|
||||
for (std::uint16_t index = 0; index < phNum; ++index) {
|
||||
const auto header = 64 + index * 56;
|
||||
if (read<std::uint32_t>(output, header) != 1) continue;
|
||||
const auto vaddr = read<std::uint64_t>(output, header + 16);
|
||||
const auto memSize = read<std::uint64_t>(output, header + 40);
|
||||
if (static_cast<std::uint64_t>(target) < vaddr || static_cast<std::uint64_t>(target) >= vaddr + memSize) continue;
|
||||
if (address < vaddr || address >= vaddr + memSize) continue;
|
||||
require((read<std::uint32_t>(output, header + 4) & 1) != 0, "Linux stub segment is not executable");
|
||||
bodyOffset = read<std::uint64_t>(output, header + 8) + (static_cast<std::uint64_t>(target) - vaddr);
|
||||
found = true;
|
||||
return read<std::uint64_t>(output, header + 8) + (address - vaddr);
|
||||
}
|
||||
require(found, "Linux stub is not inside a PT_LOAD segment");
|
||||
throw std::runtime_error("Linux stub is not inside a PT_LOAD segment");
|
||||
}
|
||||
|
||||
void linuxPlacement() {
|
||||
const auto source = elfFixture({0xEB, 0x06, 0xF2, 0x0F, 0x78, 0xDB, 0x08, 0x08, 0xC3});
|
||||
const auto headers = elfHeaders();
|
||||
const auto converted = Codegen::MakeAmd64OnlyConverter()->Convert(source, {headers[0]});
|
||||
require(converted.Trampolines.size() == 1 && converted.Bytes == source, "Linux fixture conversion produced unexpected results");
|
||||
auto patcher = linuxPatcher();
|
||||
const auto output = patcher.Patch(converted.Bytes, headers, {}, 0, "$ORIGIN/libs", true, false, converted.Trampolines);
|
||||
require(output[0x202] == 0xE9 && output[0x207] == 0x90, "Linux site was not replaced by a jump");
|
||||
const auto target = 0x1002 + 5 + static_cast<std::int64_t>(read<std::int32_t>(output, 0x203));
|
||||
require(target % 16 == 0 && target > 0x2100, "Linux stub is misaligned or inside the original image");
|
||||
const auto bodyOffset = executableFileOffset(output, static_cast<std::uint64_t>(target));
|
||||
auto expectedBody = kInsertqSelfBody;
|
||||
write<std::int32_t>(expectedBody, 10, static_cast<std::int32_t>(0x1008 - (target + 9 + 5)));
|
||||
const Bytes actualBody(output.begin() + static_cast<std::ptrdiff_t>(bodyOffset), output.begin() + static_cast<std::ptrdiff_t>(bodyOffset + expectedBody.size()));
|
||||
|
|
@ -747,6 +823,23 @@ void linuxPlacement() {
|
|||
requireFailure([&] { (void)patcher.Patch(altered, headers, {}, 0, "$ORIGIN/libs", true, false, converted.Trampolines); }, "Changed Linux site bytes were accepted");
|
||||
}
|
||||
|
||||
void linuxRipRelativePlacement() {
|
||||
Bytes text = {0x0F, 0x38, 0xCC, 0x15, 0x00, 0x00, 0x00, 0x00, 0xC3};
|
||||
write<std::int32_t>(text, 4, 0x2040 - 0x1008);
|
||||
const auto source = elfFixture(text);
|
||||
const auto headers = elfHeaders();
|
||||
const auto converted = Codegen::MakeAmd64OnlyConverter()->Convert(source, {headers[0]});
|
||||
require(converted.Trampolines.size() == 1 && converted.Trampolines[0].Relocations.size() == 1, "RIP-relative Linux fixture was not lowered with a relocation");
|
||||
auto patcher = linuxPatcher();
|
||||
const auto output = patcher.Patch(converted.Bytes, headers, {}, 0, "$ORIGIN/libs", true, false, converted.Trampolines);
|
||||
require(output[0x200] == 0xE9, "RIP-relative Linux site was not replaced by a jump");
|
||||
const auto stub = 0x1000 + 5 + static_cast<std::int64_t>(read<std::int32_t>(output, 0x201));
|
||||
const auto& relocation = converted.Trampolines[0].Relocations[0];
|
||||
const auto bodyOffset = executableFileOffset(output, static_cast<std::uint64_t>(stub));
|
||||
const auto operand = stub + static_cast<std::int64_t>(relocation.InstructionEnd) + read<std::int32_t>(output, bodyOffset + relocation.DisplacementOffset);
|
||||
require(operand == 0x2040, "Relocated RIP-relative operand does not reach the original address");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if defined(__linux__) && defined(__x86_64__)
|
||||
|
|
@ -946,19 +1039,24 @@ struct StubRun {
|
|||
std::uint64_t Rcx;
|
||||
std::uint64_t Flags;
|
||||
std::uint64_t Xmm[16][2];
|
||||
std::uint64_t SiteAddress;
|
||||
};
|
||||
|
||||
StubRun runStubBody(const Codegen::Amd64OnlyMatch& match, const std::uint64_t rax, const std::uint64_t (&xmmIn)[16][2]) {
|
||||
StubRun runStubBody(const Codegen::Amd64OnlyMatch& match, const std::uint64_t rax, const std::uint64_t (&xmmIn)[16][2], const Bytes& data = {}) {
|
||||
require(match.Lowering == Codegen::Amd64OnlyLowering::Trampoline, "Stub was not produced");
|
||||
auto body = match.StubBody;
|
||||
const auto ret = body.size();
|
||||
body.push_back(0xC3);
|
||||
const auto displacement = static_cast<std::int32_t>(ret - (match.ReturnBranchOffset + 5));
|
||||
std::memcpy(body.data() + match.ReturnBranchOffset + 1, &displacement, sizeof(displacement));
|
||||
void* code = mmap(nullptr, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
require(body.size() <= 4096 && data.size() <= 4096, "Stub or its data does not fit in a page");
|
||||
auto* code = static_cast<std::uint8_t*>(mmap(nullptr, 3 * 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
||||
require(code != MAP_FAILED, "cannot map executable memory for the stub");
|
||||
std::memcpy(code, body.data(), body.size());
|
||||
StubRun run{};
|
||||
run.SiteAddress = reinterpret_cast<std::uint64_t>(code) + 4096;
|
||||
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(body).first(match.ReturnBranchOffset), match.Relocations, run.SiteAddress, reinterpret_cast<std::uint64_t>(code), 0);
|
||||
std::memcpy(code, body.data(), body.size());
|
||||
std::copy(data.begin(), data.end(), code + 2 * 4096);
|
||||
asm volatile(
|
||||
"movdqu 0x00(%[xmmIn]), %%xmm0\n\t"
|
||||
"movdqu 0x10(%[xmmIn]), %%xmm1\n\t"
|
||||
|
|
@ -1006,7 +1104,7 @@ StubRun runStubBody(const Codegen::Amd64OnlyMatch& match, const std::uint64_t ra
|
|||
: [flags] "=&r"(run.Flags), [raxOut] "=&r"(run.Rax), [rcxOut] "=&r"(run.Rcx)
|
||||
: [xmmIn] "r"(xmmIn), [xmmOut] "r"(run.Xmm), [raxIn] "r"(rax), [code] "r"(code)
|
||||
: "rax", "rcx", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", "memory", "cc");
|
||||
munmap(code, 4096);
|
||||
munmap(code, 3 * 4096);
|
||||
return run;
|
||||
}
|
||||
|
||||
|
|
@ -1126,6 +1224,57 @@ void sha1Execution() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ripRelativeExecution() {
|
||||
const auto matcher = Codegen::MakeAmd64OnlyInstructionMatcher();
|
||||
std::uint64_t xmmIn[16][2];
|
||||
for (unsigned reg = 0; reg < 16; ++reg) {
|
||||
xmmIn[reg][0] = 0x9e3779b97f4a7c15ull * (reg + 1);
|
||||
xmmIn[reg][1] = 0xc2b2ae3d27d4eb4full * (reg + 3);
|
||||
}
|
||||
const std::uint64_t words[2] = {0x510e527f9b05688cull, 0x1f83d9ab5be0cd19ull};
|
||||
Bytes data(0x40, 0xA5);
|
||||
std::memcpy(data.data() + 0x20, words, sizeof(words));
|
||||
const std::uint64_t rax = 0x5A5A5A5A5A5A5A5Aull;
|
||||
const auto unchanged = [&](const StubRun& run, const std::uint32_t changed, const char* message) {
|
||||
for (unsigned reg = 0; reg < 16; ++reg)
|
||||
if ((changed & (1u << reg)) == 0)
|
||||
require(run.Xmm[reg][0] == xmmIn[reg][0] && run.Xmm[reg][1] == xmmIn[reg][1], message);
|
||||
require(run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), message);
|
||||
};
|
||||
for (const std::uint8_t opcode : {std::uint8_t{0xCB}, std::uint8_t{0xCC}, std::uint8_t{0xCD}}) {
|
||||
Bytes site = {0x0F, 0x38, opcode, 0x15, 0x00, 0x00, 0x00, 0x00};
|
||||
write<std::int32_t>(site, 4, 4096 + 0x20 - 8);
|
||||
const auto match = matcher->Match(site.data(), site.size());
|
||||
require(match && match->Relocations.size() == 1, "SHA-256 RIP-relative stub was not produced");
|
||||
const auto run = runStubBody(*match, rax, xmmIn, data);
|
||||
require(std::array<std::uint64_t, 2>{run.Xmm[2][0], run.Xmm[2][1]} == sha256Reference(opcode, xmmIn[2], words, xmmIn[0]), "SHA-256 RIP-relative stub did not read the original operand");
|
||||
require(run.Rax == rax, "SHA-256 RIP-relative stub changed rax");
|
||||
unchanged(run, 1u << 2, "SHA-256 RIP-relative stub clobbered a register or RFLAGS");
|
||||
}
|
||||
Bytes rounds = {0x0F, 0x3A, 0xCC, 0x15, 0x00, 0x00, 0x00, 0x00, 0x02};
|
||||
write<std::int32_t>(rounds, 4, 4096 + 0x20 - 9);
|
||||
const auto roundsMatch = matcher->Match(rounds.data(), rounds.size());
|
||||
require(roundsMatch && roundsMatch->Relocations.size() == 1, "SHA1RNDS4 RIP-relative stub was not produced");
|
||||
std::uint32_t x[4];
|
||||
std::uint32_t y[4];
|
||||
std::memcpy(x, xmmIn[2], sizeof(x));
|
||||
std::memcpy(y, words, sizeof(y));
|
||||
const auto expected = sha1Reference(Codegen::DecodeSha1(rounds.data(), rounds.size()), x, y);
|
||||
const auto roundsRun = runStubBody(*roundsMatch, rax, xmmIn, data);
|
||||
require(std::memcmp(roundsRun.Xmm[2], expected.data(), 16) == 0, "SHA1RNDS4 RIP-relative stub did not read the original operand");
|
||||
unchanged(roundsRun, 1u << 2, "SHA1RNDS4 RIP-relative stub clobbered a register or RFLAGS");
|
||||
const Bytes extrq = {0x66, 0x0F, 0x79, 0xCA};
|
||||
Bytes followers = {0xF3, 0x0F, 0x6F, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00};
|
||||
write<std::int32_t>(followers, 4, 4096 + 0x20 - 12);
|
||||
write<std::int32_t>(followers, 11, 4096 + 0x30 - 19);
|
||||
const auto moved = matcher->Match(extrq.data(), extrq.size(), followers);
|
||||
require(moved && moved->Relocations.size() == 2, "EXTRQ with RIP-relative followers was not lowered with two relocations");
|
||||
const auto movedRun = runStubBody(*moved, rax, xmmIn, data);
|
||||
require(movedRun.Xmm[3][0] == words[0] && movedRun.Xmm[3][1] == words[1], "Moved RIP-relative MOVDQU did not load from the original address");
|
||||
require(movedRun.Rax == movedRun.SiteAddress + 4096 + 0x30, "Moved RIP-relative LEA did not compute the original address");
|
||||
unchanged(movedRun, (1u << 1) | (1u << 3), "Stub with moved RIP-relative instructions clobbered a register or RFLAGS");
|
||||
}
|
||||
#else
|
||||
void reciprocalExecution() {}
|
||||
void registerFormExecution() {}
|
||||
|
|
@ -1133,6 +1282,7 @@ void sha1Execution() {}
|
|||
void sha256Execution() {}
|
||||
void clzeroExecution() {}
|
||||
void immediateFormExecution() {}
|
||||
void ripRelativeExecution() {}
|
||||
#endif
|
||||
|
||||
void scannerZeroTail() {
|
||||
|
|
@ -1155,12 +1305,15 @@ int main() {
|
|||
reciprocalOperands();
|
||||
matcherSubstitutions();
|
||||
goldenBodies();
|
||||
ripRelativeBodies();
|
||||
stubRelocations();
|
||||
registerFormExecution();
|
||||
immediateFormExecution();
|
||||
sha256Execution();
|
||||
sha1Execution();
|
||||
clzeroExecution();
|
||||
reciprocalExecution();
|
||||
ripRelativeExecution();
|
||||
converterSegment();
|
||||
converterRipRelativeFollower();
|
||||
converterSha256();
|
||||
|
|
@ -1172,6 +1325,7 @@ int main() {
|
|||
rewriterStrayRex();
|
||||
converterFailureOffsets();
|
||||
linuxPlacement();
|
||||
linuxRipRelativePlacement();
|
||||
scannerZeroTail();
|
||||
std::cout << "AMD64-only converter tests passed\n";
|
||||
} catch (const std::exception& error) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
#include <elfpatcher/general/ProgramHeaderLayoutRequest.hpp>
|
||||
#include <elfpatcher/general/SectionHeaderTableRequest.hpp>
|
||||
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace Elfpatcher::Linux {
|
||||
|
|
@ -47,6 +49,7 @@ void LinuxElfPatcher::_appendTrampoline(
|
|||
const auto bodyVaddr = vaddrOfExtraBlockOffset(bodyOff);
|
||||
for (const std::uint8_t b : site.Body)
|
||||
buf.push_back(b);
|
||||
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(buf.data() + bodyOff, site.ReturnBranchOffset), site.Relocations, site.Address, bodyVaddr, site.Offset);
|
||||
|
||||
const auto inRange = [](const std::int64_t displacement) {
|
||||
return displacement >= std::numeric_limits<std::int32_t>::min() && displacement <= std::numeric_limits<std::int32_t>::max();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
|
||||
namespace Elfpatcher {
|
||||
|
||||
|
|
@ -154,6 +156,7 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
|
|||
const auto returnDisplacement = displacement(returnAddress, stubReturn, site.Offset);
|
||||
const auto siteDisplacement = displacement(stubAddress, siteNext, site.Offset);
|
||||
bytes.insert(bytes.end(), site.Body.begin(), site.Body.end());
|
||||
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(bytes.data() + stubOffset, site.ReturnBranchOffset), site.Relocations, site.Address, stubAddress, site.Offset);
|
||||
Io::WriteU32(bytes, stubOffset + site.ReturnBranchOffset + 1,
|
||||
static_cast<std::uint32_t>(returnDisplacement));
|
||||
std::fill_n(bytes.begin() + static_cast<std::ptrdiff_t>(site.Offset), site.Length, kNop1.Bytes[0]);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#include <elfpatcher/windows/WindowsTrampolineBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsStubEmitter.hpp>
|
||||
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
|
||||
#include <codegen/x86/StubBodyBuilder.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
|
|
@ -40,6 +42,7 @@ void WindowsTrampolineBuilder::Build(const std::vector<Codegen::TrampolineSite>&
|
|||
const auto stubRva = CheckedRva(sectionRva + bytes.size());
|
||||
const auto bodyOffset = bytes.size();
|
||||
bytes.insert(bytes.end(), site.Body.begin(), site.Body.end());
|
||||
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(bytes.data() + bodyOffset, site.ReturnBranchOffset), site.Relocations, siteRva, stubRva, site.Offset);
|
||||
const auto returnRva = CheckedRva(siteRva + site.Length);
|
||||
const auto displacement = static_cast<std::int64_t>(returnRva) - (static_cast<std::int64_t>(stubRva) + static_cast<std::int64_t>(site.ReturnBranchOffset + kJmpRel32.Size));
|
||||
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ from test_linux_load_alignment import fixture as executable_fixture
|
|||
SITE = bytes.fromhex("f2 0f 78 db 08 08")
|
||||
SITE_ADDRESSES = (0x1002, 0x1012)
|
||||
PLAIN_SITE = b"\x90" * len(SITE)
|
||||
RIP_DISPLACEMENT = 0x10F6
|
||||
RIP_SITE = bytes.fromhex("0f 38 cc 15") + struct.pack("<i", RIP_DISPLACEMENT)
|
||||
CASES = (("plain", PLAIN_SITE), ("stub", SITE), ("rip", RIP_SITE))
|
||||
|
||||
|
||||
def guest_fixture(site):
|
||||
|
|
@ -26,8 +29,9 @@ def guest_fixture(site):
|
|||
struct.pack_into("<IIQQQQQQ", image, 176,
|
||||
2, 6, 0x600, 0x2000, 0x2000, len(tags) * 16, len(tags) * 16, 8)
|
||||
image[0x400:0x500] = b"\x90" * 0x100
|
||||
image[0x400:0x409] = b"\xeb\x06" + site + b"\xc3"
|
||||
image[0x410:0x419] = b"\xeb\x06" + site + b"\xc3"
|
||||
code = bytes((0xEB, len(site))) + site + b"\xc3"
|
||||
image[0x400:0x400 + len(code)] = code
|
||||
image[0x410:0x410 + len(code)] = code
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into("<qQ", image, 0x600 + index * 16, *tag)
|
||||
struct.pack_into("<II", image, 0x840, 1, 1)
|
||||
|
|
@ -96,19 +100,30 @@ def check_trampoline(read, site_address):
|
|||
assert continuation == site_address + len(SITE), (target, continuation)
|
||||
|
||||
|
||||
def check_rip_trampoline(read, site_address):
|
||||
patched = read(site_address, len(RIP_SITE))
|
||||
assert patched[0] == 0xE9 and patched[5:] == b"\x90" * (len(RIP_SITE) - 5), patched.hex()
|
||||
target = site_address + 5 + struct.unpack_from("<i", patched, 1)[0]
|
||||
body = read(target, 21)
|
||||
assert body[13:17] == bytes.fromhex("f3 0f 6f 0d"), body.hex()
|
||||
operand = target + 21 + struct.unpack_from("<i", body, 17)[0]
|
||||
assert operand == site_address + len(RIP_SITE) + RIP_DISPLACEMENT, (site_address, operand)
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-guest-intel-") as directory:
|
||||
work = Path(directory)
|
||||
for windows in (False, True):
|
||||
for has_stub in (False, True):
|
||||
case = work / ("windows" if windows else "linux") / ("stub" if has_stub else "plain")
|
||||
for kind, site in CASES:
|
||||
has_stub = kind != "plain"
|
||||
case = work / ("windows" if windows else "linux") / kind
|
||||
module_dir = case / "sce_module"
|
||||
module_dir.mkdir(parents=True)
|
||||
source = case / "input.elf"
|
||||
output = case / ("output.exe" if windows else "output.elf")
|
||||
source.write_bytes(main_fixture())
|
||||
(module_dir / "sample.prx").write_bytes(guest_fixture(SITE if has_stub else PLAIN_SITE))
|
||||
(module_dir / "sample.prx").write_bytes(guest_fixture(site))
|
||||
arguments = [str(relinker), "--to-intel"]
|
||||
if windows:
|
||||
arguments.append("--windows")
|
||||
|
|
@ -129,7 +144,10 @@ def main():
|
|||
loads = elf_loads(data)
|
||||
site_addresses = SITE_ADDRESSES
|
||||
read = lambda address, size: elf_bytes_at(data, loads, address, size)
|
||||
if has_stub:
|
||||
if kind == "rip":
|
||||
for site_address in site_addresses:
|
||||
check_rip_trampoline(read, site_address)
|
||||
elif has_stub:
|
||||
for site_address in site_addresses:
|
||||
check_trampoline(read, site_address)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented.
|
||||
- [Per-stage descriptor count](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - a pixel shader's colour attachments are not counted against `maxPerStageResources`, so its buffers and images can fit the limit while the pipeline exceeds it
|
||||
- [v_rcp, v_rsq, v_sqrt, v_log, v_exp, v_sin and v_cos](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (f32 and f16) use the driver's approximation for finite results, not the console's: the low bits can differ. Only NaN, invalid, zero and infinite results and the exact sin/cos zeros are chosen on the bits. v_sin_f32/v_cos_f32 with inputs beyond ±256 cycles were not compared with the console.
|
||||
- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink.
|
||||
- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead.
|
||||
- The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
|
||||
- [DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp) (libSceAgcDriver) becomes `DepthLess`/`DepthGreater`. For a pixel shader that breaks the promise, the host driver decides whether its interpolated Z is depth-tested before shading; what the console does then was not measured.
|
||||
- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test.
|
||||
|
|
@ -286,7 +286,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none.
|
||||
- [COPY_DATA from the GPU clock](../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp, not the console's GPU clock. The `COPY_DATA` cache policy fields are accepted and have no effect.
|
||||
- `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows.
|
||||
- `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564).
|
||||
- `--to-intel` replaces the approximate VEX.128 register forms of VRSQRTPS and VRCPPS with [correctly rounded](../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). The 256-bit, scalar, legacy and memory forms keep the native instruction, as does a 4-byte site with no movable instruction after it for the jump (5 sites in PPSA21564, counted before a following RIP-relative instruction could move into the stub; not recounted).
|
||||
- [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates the 101-key (US) arrangement and the alphanumeric layer of the 106-key (Japanese) one, which follows the JIS layout of the HID usages: the Yen (0x89) and Ro (0x87) keys return `\` (0x5C) unshifted, as JIS X 0201 places the yen sign there; whether the console returns U+00A5 instead is unverified. With the Kana LED (bit 4, the HID position) set the 106-key arrangement throws, as the kana the console returns are unknown. Ctrl and Alt do not change the character.
|
||||
- [sceAudioOut2Set3DLatency](../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) and [sceAudioOut2MasteringInit and sceAudioOut2MasteringSetParam](../../core/libs/prx/libSceAudioOut/src/AudioOut2Mastering.cpp) (libSceAudioOut) have no effect: there is no 3D rendering or mastering stage.
|
||||
- [sceImeKeyboardGetInfo](../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) returns `0x80bc0023` (no resource id) for every id while a keyboard is open, since the IME keyboard never reports a connected device. [sceImeKeyboardSetMode](../../core/libs/prx/libSceIme/Export.cpp) validates the mode bits but the mode has no effect, as no IME keyboard events are delivered.
|
||||
|
|
|
|||
Loading…
Reference in New Issue