mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
fix(relinker): take red-zone framing's function bounds from .eh_frame FDEs
Function starts were direct call targets plus 16-byte-aligned code after int3 padding. A function reached only through a pointer and not preceded by int3 then ended up inside the previous region, and its pop rbp; ret exit could get the epilogue lea without a matching prologue. The bounds now come from the executable's .eh_frame FDEs (the existing EhFrameReader), each function is decoded from its FDE start to its FDE end, and code outside every FDE is not framed. Conversion stops with an error when the executable has no FDE. Sites added in one pass also count as taken for the next function.
This commit is contained in:
1 parent
542cb30f70
commit
606fa2ef91
7 files changed
+132
-72
No files matched your search
@@ -11,6 +11,11 @@
|
||||
|
||||
namespace Codegen {
|
||||
|
||||
struct RedZoneFunction {
|
||||
std::uint64_t Begin = 0;
|
||||
std::uint64_t End = 0;
|
||||
};
|
||||
|
||||
struct RedZoneFrameReport {
|
||||
std::size_t Functions = 0;
|
||||
std::size_t RedZoneFunctions = 0;
|
||||
@@ -31,6 +36,7 @@ struct RedZoneDisplacement {
|
||||
std::vector<TrampolineSite> FrameRedZoneFunctions(
|
||||
const std::vector<std::uint8_t>& fileBytes,
|
||||
const std::vector<Domain::ProgramHeader>& codeSegments,
|
||||
const std::vector<RedZoneFunction>& functions,
|
||||
const std::vector<TrampolineSite>& existing,
|
||||
RedZoneFrameReport& report,
|
||||
std::vector<RedZoneDisplacement>& displacements
|
||||
|
||||
@@ -211,6 +211,7 @@ struct OperandStub {
|
||||
void frameSegment(
|
||||
const std::vector<std::uint8_t>& fileBytes,
|
||||
const Domain::ProgramHeader& ph,
|
||||
const std::vector<RedZoneFunction>& functions,
|
||||
const std::vector<TrampolineSite>& existing,
|
||||
std::vector<TrampolineSite>& sites,
|
||||
std::vector<RedZoneDisplacement>& displacements,
|
||||
@@ -220,24 +221,15 @@ void frameSegment(
|
||||
const auto segSize = static_cast<std::size_t>(ph.FileSize);
|
||||
if (segOffset > fileBytes.size() || segSize > fileBytes.size() - segOffset) return;
|
||||
const std::vector<std::uint8_t> seg(fileBytes.begin() + static_cast<std::ptrdiff_t>(segOffset), fileBytes.begin() + static_cast<std::ptrdiff_t>(segOffset + segSize));
|
||||
const auto matches = MakeInstructionScanner()->ScanCodeSection(seg, 0, seg.size());
|
||||
const auto scanner = MakeInstructionScanner();
|
||||
const X64InstructionDecoder decoder;
|
||||
std::vector<Insn> insns;
|
||||
insns.reserve(matches.size());
|
||||
for (const auto& match : matches) insns.push_back({static_cast<std::size_t>(match.Offset), match.Length, decoder.DecodeInstruction(seg.data() + match.Offset, match.Length)});
|
||||
|
||||
std::set<std::size_t> targets;
|
||||
std::set<std::size_t> starts;
|
||||
for (std::size_t i = 0; i < insns.size(); ++i) {
|
||||
const auto& insn = insns[i];
|
||||
if (insn.Info.HasBranchTarget && !insn.Info.HasRipRelativeDisp) {
|
||||
const auto target = static_cast<std::int64_t>(insn.Offset + insn.Length) + insn.Info.BranchDisp;
|
||||
if (target >= 0 && static_cast<std::size_t>(target) < seg.size()) {
|
||||
if (insn.Info.FlowKind == ControlFlowKind::Call) starts.insert(static_cast<std::size_t>(target));
|
||||
else targets.insert(static_cast<std::size_t>(target));
|
||||
}
|
||||
}
|
||||
if (i > 0 && insns[i - 1].Length == 1 && seg[insns[i - 1].Offset] == 0xcc && seg[insn.Offset] != 0xcc && insn.Offset % 16 == 0) starts.insert(insn.Offset);
|
||||
for (const auto& match : scanner->ScanCodeSection(seg, 0, seg.size())) {
|
||||
const auto info = decoder.DecodeInstruction(seg.data() + match.Offset, match.Length);
|
||||
if (!info.HasBranchTarget || info.HasRipRelativeDisp || info.FlowKind == ControlFlowKind::Call) continue;
|
||||
const auto target = static_cast<std::int64_t>(match.Offset + match.Length) + info.BranchDisp;
|
||||
if (target >= 0 && static_cast<std::size_t>(target) < seg.size()) targets.insert(static_cast<std::size_t>(target));
|
||||
}
|
||||
std::vector<std::pair<std::size_t, std::size_t>> taken;
|
||||
for (const auto& site : existing) {
|
||||
@@ -252,11 +244,6 @@ void frameSegment(
|
||||
const auto hit = targets.upper_bound(begin);
|
||||
return hit != targets.end() && *hit < end;
|
||||
};
|
||||
const auto indexOf = [&](const std::size_t offset) -> std::optional<std::size_t> {
|
||||
const auto found = std::lower_bound(insns.begin(), insns.end(), offset, [](const Insn& insn, const std::size_t value) { return insn.Offset < value; });
|
||||
if (found == insns.end() || found->Offset != offset) return std::nullopt;
|
||||
return static_cast<std::size_t>(found - insns.begin());
|
||||
};
|
||||
const auto padding = [&](const Insn& insn) {
|
||||
if (insn.Info.IsTwoByteOpcode) return insn.Info.Opcode == 0x1f;
|
||||
return seg[insn.Offset + insn.Info.OpcodeOffset] == 0x90 && (insn.Info.RexPrefix & 1) == 0;
|
||||
@@ -268,52 +255,52 @@ void frameSegment(
|
||||
return static_cast<std::size_t>(target);
|
||||
};
|
||||
|
||||
for (auto it = starts.begin(); it != starts.end(); ++it) {
|
||||
const auto start = *it;
|
||||
const auto next = std::next(it);
|
||||
const auto limit = next == starts.end() ? seg.size() : *next;
|
||||
const auto first = indexOf(start);
|
||||
if (!first) continue;
|
||||
for (const auto& function : functions) {
|
||||
if (function.Begin < ph.MappedAddress || function.End <= function.Begin || function.End - ph.MappedAddress > segSize) continue;
|
||||
const auto start = static_cast<std::size_t>(function.Begin - ph.MappedAddress);
|
||||
const auto limit = static_cast<std::size_t>(function.End - ph.MappedAddress);
|
||||
++report.Functions;
|
||||
const auto skip = [&](const char* reason) { ++report.Skipped[reason]; };
|
||||
|
||||
std::vector<std::size_t> body;
|
||||
const std::vector<std::uint8_t> code(seg.begin() + static_cast<std::ptrdiff_t>(start), seg.begin() + static_cast<std::ptrdiff_t>(limit));
|
||||
std::vector<Insn> body;
|
||||
bool afterReturn = false;
|
||||
std::optional<std::size_t> jumpedTo;
|
||||
for (auto i = *first; i < insns.size() && insns[i].Offset < limit; ++i) {
|
||||
if (seg[insns[i].Offset] == 0xcc && insns[i].Length == 1) break;
|
||||
if (!padding(insns[i])) {
|
||||
if (insns[i].Offset % 16 == 0 && (afterReturn || jumpedTo == insns[i].Offset)) break;
|
||||
afterReturn = insns[i].Info.FlowKind == ControlFlowKind::Return || insns[i].Info.FlowKind == ControlFlowKind::Trap;
|
||||
jumpedTo = jumpTarget(insns[i]);
|
||||
for (const auto& match : scanner->ScanCodeSection(code, 0, code.size())) {
|
||||
if (code[match.Offset] == 0xcc && match.Length == 1) break;
|
||||
const Insn insn{start + static_cast<std::size_t>(match.Offset), match.Length, decoder.DecodeInstruction(code.data() + match.Offset, match.Length)};
|
||||
if (!padding(insn)) {
|
||||
if (insn.Offset % 16 == 0 && (afterReturn || jumpedTo == insn.Offset)) break;
|
||||
afterReturn = insn.Info.FlowKind == ControlFlowKind::Return || insn.Info.FlowKind == ControlFlowKind::Trap;
|
||||
jumpedTo = jumpTarget(insn);
|
||||
}
|
||||
body.push_back(i);
|
||||
body.push_back(insn);
|
||||
}
|
||||
if (body.size() < 3) continue;
|
||||
const auto at = [&](const std::size_t k) { return seg.data() + insns[body[k]].Offset; };
|
||||
const bool rbpPrologue = insns[body[0]].Length == 1 && at(0)[0] == 0x55 && insns[body[1]].Length == 3 && std::memcmp(at(1), "\x48\x89\xe5", 3) == 0;
|
||||
const auto at = [&](const std::size_t k) { return seg.data() + body[k].Offset; };
|
||||
const bool rbpPrologue = body[0].Length == 1 && at(0)[0] == 0x55 && body[1].Length == 3 && std::memcmp(at(1), "\x48\x89\xe5", 3) == 0;
|
||||
std::vector<std::uint8_t> pushed;
|
||||
std::size_t p = rbpPrologue ? 2 : 0;
|
||||
while (p < body.size())
|
||||
if (const auto reg = pushedRegister(at(p), insns[body[p]])) { pushed.push_back(*reg); ++p; } else break;
|
||||
if (const auto reg = pushedRegister(at(p), body[p])) { pushed.push_back(*reg); ++p; } else break;
|
||||
std::int64_t allocation = 0;
|
||||
if (!rbpPrologue && p < body.size())
|
||||
if (const auto adjustment = rspAdjustment(at(p), insns[body[p]]); adjustment && *adjustment < 0) { allocation = -*adjustment; ++p; }
|
||||
if (const auto adjustment = rspAdjustment(at(p), body[p]); adjustment && *adjustment < 0) { allocation = -*adjustment; ++p; }
|
||||
std::vector<std::uint8_t> expected(pushed.begin(), pushed.end());
|
||||
if (rbpPrologue) expected.insert(expected.begin(), kRegRbp);
|
||||
const auto popsBefore = [&](const std::size_t k) -> std::optional<std::size_t> {
|
||||
std::size_t q = k;
|
||||
std::vector<std::uint8_t> popped;
|
||||
while (q > p && popped.size() < expected.size())
|
||||
if (const auto reg = poppedRegister(at(q - 1), insns[body[q - 1]])) { popped.push_back(*reg); --q; } else break;
|
||||
if (const auto reg = poppedRegister(at(q - 1), body[q - 1])) { popped.push_back(*reg); --q; } else break;
|
||||
if (popped != expected) return std::nullopt;
|
||||
return q;
|
||||
};
|
||||
std::set<std::size_t> frees;
|
||||
if (allocation > 0)
|
||||
for (std::size_t k = p; k < body.size(); ++k)
|
||||
if (insns[body[k]].Info.FlowKind == ControlFlowKind::Return)
|
||||
if (const auto q = popsBefore(k); q && *q > p && rspAdjustment(at(*q - 1), insns[body[*q - 1]]) == allocation) frees.insert(*q - 1);
|
||||
if (body[k].Info.FlowKind == ControlFlowKind::Return)
|
||||
if (const auto q = popsBefore(k); q && *q > p && rspAdjustment(at(*q - 1), body[*q - 1]) == allocation) frees.insert(*q - 1);
|
||||
|
||||
bool leaf = true;
|
||||
bool redZone = false;
|
||||
@@ -321,18 +308,18 @@ void frameSegment(
|
||||
std::vector<std::pair<std::size_t, std::size_t>> exits;
|
||||
std::map<std::size_t, std::pair<std::size_t, std::vector<std::uint8_t>>> moves;
|
||||
std::vector<std::size_t> stubbed;
|
||||
const auto functionEnd = insns[body.back()].Offset + insns[body.back()].Length;
|
||||
const auto functionEnd = limit;
|
||||
std::int64_t depth = 0;
|
||||
bool wholeRedZone = rbpPrologue;
|
||||
for (std::size_t k = p; k < body.size(); ++k) {
|
||||
const auto use = memoryUse(at(k), insns[body[k]]);
|
||||
const auto use = memoryUse(at(k), body[k]);
|
||||
if (!use.RspBased) continue;
|
||||
if (use.RspDisp < 0) depth = std::max(depth, -use.RspDisp);
|
||||
if (use.Indexed || (use.Address && use.RspDisp < 0)) wholeRedZone = true;
|
||||
}
|
||||
const auto shift = wholeRedZone ? kRedZoneBytes : std::min(kRedZoneBytes, (depth + kStackAlignment - 1) / kStackAlignment * kStackAlignment);
|
||||
for (std::size_t k = p; k < body.size(); ++k) {
|
||||
const auto& insn = insns[body[k]];
|
||||
const auto& insn = body[k];
|
||||
const auto* code = at(k);
|
||||
const auto flow = insn.Info.FlowKind;
|
||||
if (flow == ControlFlowKind::Call || flow == ControlFlowKind::IndirectCall) { leaf = false; break; }
|
||||
@@ -367,7 +354,7 @@ void frameSegment(
|
||||
if (q == p || !frees.contains(q - 1)) { reason = "exit does not free the prologue's allocation"; continue; }
|
||||
--q;
|
||||
}
|
||||
const auto siteStart = insns[body[q]].Offset;
|
||||
const auto siteStart = body[q].Offset;
|
||||
const auto siteEnd = insn.Offset + 1;
|
||||
if (siteEnd - siteStart < kJumpBytes) { reason = "exit shorter than a jump"; continue; }
|
||||
if (targetInside(siteStart, siteEnd)) { reason = "branch into an exit"; continue; }
|
||||
@@ -376,7 +363,7 @@ void frameSegment(
|
||||
}
|
||||
if (!leaf || !redZone) continue;
|
||||
++report.RedZoneFunctions;
|
||||
const auto prologueEnd = p > 0 ? insns[body[p - 1]].Offset + insns[body[p - 1]].Length : start;
|
||||
const auto prologueEnd = p > 0 ? body[p - 1].Offset + body[p - 1].Length : start;
|
||||
if (reason == nullptr && ((pushed.empty() && allocation == 0) || prologueEnd - start < kJumpBytes)) reason = "prologue shorter than a jump";
|
||||
if (reason == nullptr && targetInside(start, prologueEnd)) reason = "branch into the prologue";
|
||||
if (reason == nullptr && exits.empty()) reason = "no ret exit";
|
||||
@@ -390,9 +377,9 @@ void frameSegment(
|
||||
for (const auto k : stubbed) {
|
||||
if (reason != nullptr) break;
|
||||
if (!stubs.empty() && k < stubs.back().Last) continue;
|
||||
OperandStub stub{k, k, insns[body[k]].Offset, insns[body[k]].Offset, {}};
|
||||
OperandStub stub{k, k, body[k].Offset, body[k].Offset, {}};
|
||||
while (stub.Last < body.size() && stub.End - stub.Begin < kJumpBytes) {
|
||||
const auto& covered = insns[body[stub.Last]];
|
||||
const auto& covered = body[stub.Last];
|
||||
if (covered.Offset != stub.End || covered.Info.FlowKind != ControlFlowKind::Sequential || covered.Info.HasRipRelativeDisp || frees.contains(stub.Last)) break;
|
||||
const auto use = memoryUse(at(stub.Last), covered);
|
||||
const auto bytes = use.RspBased ? withDisplacement(at(stub.Last), covered, use, use.RspDisp + shift) : std::vector<std::uint8_t>(at(stub.Last), at(stub.Last) + covered.Length);
|
||||
@@ -418,6 +405,7 @@ void frameSegment(
|
||||
site.ReturnBranchOffset = code.size();
|
||||
code.insert(code.end(), std::begin(kJumpBack), std::end(kJumpBack));
|
||||
site.Body = std::move(code);
|
||||
taken.emplace_back(begin, end);
|
||||
sites.push_back(std::move(site));
|
||||
};
|
||||
const auto original = [&](const std::size_t begin, const std::size_t end) { return std::vector<std::uint8_t>(seg.begin() + static_cast<std::ptrdiff_t>(begin), seg.begin() + static_cast<std::ptrdiff_t>(end)); };
|
||||
@@ -447,12 +435,13 @@ void frameSegment(
|
||||
std::vector<TrampolineSite> FrameRedZoneFunctions(
|
||||
const std::vector<std::uint8_t>& fileBytes,
|
||||
const std::vector<Domain::ProgramHeader>& codeSegments,
|
||||
const std::vector<RedZoneFunction>& functions,
|
||||
const std::vector<TrampolineSite>& existing,
|
||||
RedZoneFrameReport& report,
|
||||
std::vector<RedZoneDisplacement>& displacements
|
||||
) {
|
||||
std::vector<TrampolineSite> sites;
|
||||
for (const auto& ph : codeSegments) frameSegment(fileBytes, ph, existing, sites, displacements, report);
|
||||
for (const auto& ph : codeSegments) frameSegment(fileBytes, ph, functions, existing, sites, displacements, report);
|
||||
return sites;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ std::vector<std::uint8_t> callTo(const std::size_t from, const std::size_t targe
|
||||
struct Image {
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
std::vector<std::size_t> Starts;
|
||||
std::vector<std::size_t> Ends;
|
||||
};
|
||||
|
||||
Image place(const std::vector<Leaf>& leaves) {
|
||||
@@ -144,6 +145,7 @@ Image place(const std::vector<Leaf>& leaves) {
|
||||
for (std::size_t index = 0; index < leaves.size(); ++index) {
|
||||
image.Bytes.resize(image.Starts[index], 0xcc);
|
||||
image.Bytes.insert(image.Bytes.end(), leaves[index].Code.begin(), leaves[index].Code.end());
|
||||
image.Ends.push_back(image.Bytes.size());
|
||||
}
|
||||
image.Bytes.resize((image.Bytes.size() + 15) / 16 * 16 + 16, 0xcc);
|
||||
require(image.Bytes.size() <= kStubBase, "the leaves do not fit before the stubs");
|
||||
@@ -164,7 +166,9 @@ Framing load(const Image& image, const bool frame) {
|
||||
if (!frame) return framing;
|
||||
const auto base = reinterpret_cast<std::uint64_t>(arena);
|
||||
const std::vector<Domain::ProgramHeader> segments{{1, 5, 0, base, base, image.Bytes.size(), image.Bytes.size(), 16}};
|
||||
framing.Sites = Codegen::FrameRedZoneFunctions(image.Bytes, segments, {}, framing.Report, framing.Displacements);
|
||||
std::vector<Codegen::RedZoneFunction> functions;
|
||||
for (std::size_t index = 0; index < image.Starts.size(); ++index) functions.push_back({base + image.Starts[index], base + image.Ends[index]});
|
||||
framing.Sites = Codegen::FrameRedZoneFunctions(image.Bytes, segments, functions, {}, framing.Report, framing.Displacements);
|
||||
for (const auto& moved : framing.Displacements) std::copy(moved.Bytes.begin(), moved.Bytes.end(), arena + moved.Offset);
|
||||
std::size_t cursor = kStubBase;
|
||||
for (const auto& site : framing.Sites) {
|
||||
|
||||
@@ -43,6 +43,7 @@ std::vector<std::uint8_t> leaf(const std::vector<std::uint8_t>& body) {
|
||||
struct Image {
|
||||
std::vector<std::uint8_t> bytes;
|
||||
std::vector<Domain::ProgramHeader> segments;
|
||||
std::vector<Codegen::RedZoneFunction> functions;
|
||||
};
|
||||
|
||||
Image image(const std::vector<std::vector<std::uint8_t>>& functions) {
|
||||
@@ -60,6 +61,7 @@ Image image(const std::vector<std::vector<std::uint8_t>>& functions) {
|
||||
for (std::size_t index = 0; index < functions.size(); ++index) {
|
||||
result.bytes = padTo(result.bytes, starts[index]);
|
||||
append(result.bytes, functions[index]);
|
||||
result.functions.push_back({Base + starts[index], Base + starts[index] + functions[index].size()});
|
||||
}
|
||||
result.bytes = padTo(result.bytes, (result.bytes.size() + 15) / 16 * 16 + 16);
|
||||
result.segments.push_back({1, 5, 0, Base, Base, result.bytes.size(), result.bytes.size(), 16});
|
||||
@@ -72,7 +74,7 @@ void framesRedZoneLeaf() {
|
||||
const auto input = image({leaf(body)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.Exits == 1, "a red-zone leaf was not framed");
|
||||
require(sites.size() == 2, "a framed leaf needs one prologue and one exit site");
|
||||
require(displacements.empty() && report.FramedWithoutFramePointer == 0, "a leaf with a frame pointer had rsp operands moved");
|
||||
@@ -129,7 +131,7 @@ void framesLeafWithoutFramePointer() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.FramedWithoutFramePointer == 1 && report.MovedOperands == 2, "a leaf that only pushes was not framed");
|
||||
require(sites.size() == 2, "a framed leaf needs one prologue and one exit site");
|
||||
const auto& entry = sites[0];
|
||||
@@ -157,7 +159,7 @@ void shiftsByTheDeepestOperandBelowRsp() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && sites.size() == 2 && displacements.size() == 3, "a leaf reaching 0x28 bytes below rsp was not framed");
|
||||
std::vector<std::uint8_t> entryBody = FramelessPrologue;
|
||||
append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xd0, 0xe9, 0, 0, 0, 0});
|
||||
@@ -172,7 +174,7 @@ void movesPositiveOperandsThatFitAfterTheShift() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && report.MovedOperands == 2 && sites.size() == 2, "a leaf with an 8-bit displacement above rsp that fits after the shift was not framed");
|
||||
const auto loadDisplacement = 0x40 + FramelessPrologue.size() + StoreBelowRsp.size() + 4;
|
||||
require(displacements.size() == 2 && displacements[1].Offset == loadDisplacement && displacements[1].Bytes == std::vector<std::uint8_t>{0x18}, "the load above rsp was not moved in place by the shift");
|
||||
@@ -186,7 +188,7 @@ void shiftsByTheWholeRedZoneForAddressesBelowRsp() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, below, FramelessExit), framelessLeaf(FramelessPrologue, above, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 2 && sites.size() == 4 && displacements.size() == 4, "leaves that take the address of a stack slot were not framed");
|
||||
require(sites[0].Body[FramelessPrologue.size() + 4] == 0x80 && displacements[0].Bytes == std::vector<std::uint8_t>{0x70} && displacements[1].Bytes == std::vector<std::uint8_t>{0x60}, "a leaf that takes the address of a slot below rsp was not shifted by the whole red zone");
|
||||
require(sites[2].Body[FramelessPrologue.size() + 4] == 0xf0 && displacements[2].Bytes == std::vector<std::uint8_t>{0x00} && displacements[3].Bytes == std::vector<std::uint8_t>{0x18}, "a leaf that only takes the address of a slot above rsp was shifted by more than its depth");
|
||||
@@ -198,7 +200,7 @@ void shiftsByTheWholeRedZoneForIndexedOperands() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && sites.size() == 2 && displacements.size() == 2, "a leaf with an indexed operand below rsp was not framed");
|
||||
require(sites[0].Body[FramelessPrologue.size() + 4] == 0x80 && displacements[0].Bytes == std::vector<std::uint8_t>{0x70} && displacements[1].Bytes == std::vector<std::uint8_t>{0x70}, "a leaf whose indexed operand can reach deeper than its displacement was not shifted by the whole red zone");
|
||||
}
|
||||
@@ -210,7 +212,7 @@ void stubsOperandsWhoseDisplacementOverflows() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && report.MovedOperands == 1 && report.OperandStubs == 2 && report.StubbedOperands == 2 && sites.size() == 4, "a leaf whose 8-bit displacements above rsp overflow after the shift was not framed with operand stubs");
|
||||
const auto load = Base + 0x40 + FramelessPrologue.size() + StoreBelowRsp.size();
|
||||
require(sites[2].Address == load && sites[2].Length == LoadFarAboveRsp.size() && sites[2].OriginalBytes == LoadFarAboveRsp, "the stub site does not cover the load that cannot move in place");
|
||||
@@ -230,7 +232,7 @@ void stubsOperandsAtRspWithTheInstructionsAfterThem() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, atRsp, FramelessExit), framelessLeaf(FramelessPrologue, indexed, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 2 && report.MovedOperands == 2 && report.OperandStubs == 2 && report.StubbedOperands == 3 && sites.size() == 6, "leaves with operands at rsp were not framed with operand stubs");
|
||||
const auto first = 0x40 + FramelessPrologue.size() + StoreBelowRsp.size();
|
||||
require(sites[2].Address == Base + first && sites[2].Length == LoadAtRsp.size() + LoadAboveRsp.size(), "the stub site does not run on to the next instruction to make room for its jump");
|
||||
@@ -246,7 +248,7 @@ void stubsVexOperandsAtRsp() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && report.OperandStubs == 1 && report.StubbedOperands == 1 && sites.size() == 3, "a leaf with a VEX store at rsp was not framed with an operand stub");
|
||||
require(sites[2].OriginalBytes == VexStoreAtRsp && sites[2].Body == stubBody({0xc5, 0xf8, 0x11, 0x44, 0x24, 0x10}), "the VEX store at rsp was not re-encoded with an 8-bit displacement moved by the shift");
|
||||
}
|
||||
@@ -265,7 +267,7 @@ void skipsLeavesWithOperandsThatCannotMove() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, evex, FramelessExit), framelessLeaf(FramelessPrologue, beforeExit, FramelessExit), framelessLeaf(FramelessPrologue, beforeRipRelative, FramelessExit), framelessLeaf(FramelessPrologue, beforeTarget, FramelessExit), framelessLeaf({0x41, 0x56, 0x41, 0x57}, StoreBelowRsp, {0x41, 0x5f, 0x41, 0x5e, 0xc3})});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(sites.empty() && displacements.empty() && report.RedZoneFunctions == 5, "a leaf whose rsp operands can neither move nor be stubbed, or whose prologue is too short, was framed");
|
||||
require(report.Skipped.at("rsp operand that cannot move with rsp") == 1, "an EVEX operand, whose 8-bit displacement is scaled, was not refused");
|
||||
require(report.Skipped.at("rsp operand that cannot move and has no room for a stub") == 3, "an operand at rsp followed by an exit, a rip-relative instruction or a branch target was not refused for want of room");
|
||||
@@ -280,7 +282,7 @@ void skipsLeavesThatCopyRsp() {
|
||||
const auto input = image({framelessLeaf(FramelessPrologue, withCmov, FramelessExit), framelessLeaf(FramelessPrologue, withShlx, FramelessExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(sites.empty() && displacements.empty() && report.Skipped.at("other use of rsp") == 2, "a leaf that copies rsp into another register was framed");
|
||||
}
|
||||
|
||||
@@ -316,7 +318,7 @@ void framesLeavesThatAllocateBelowTheirPushes() {
|
||||
const auto input = image({framelessLeaf(InflatePrologue, body, inflateExit())});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.FramedWithoutFramePointer == 1 && report.Exits == 1 && sites.size() == 2, "a leaf that allocates with sub after its pushes and spills below its allocation was not framed");
|
||||
const auto& entry = sites[0];
|
||||
require(entry.Address == Base + 0x40 && entry.OriginalBytes == InflatePrologue, "the prologue site does not cover the pushes and the sub");
|
||||
@@ -346,7 +348,7 @@ void framesLeavesThatAllocateWithLeaOrALargeSub() {
|
||||
const auto input = image({framelessLeaf(leaPrologue, StoreBelowRsp, leaExit), framelessLeaf(largePrologue, largeBody, largeExit)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 2 && sites.size() == 4 && displacements.size() == 3, "leaves that allocate with lea or a 32-bit sub were not framed");
|
||||
require(sites[0].OriginalBytes == leaPrologue && sites[1].OriginalBytes == leaExit, "the lea allocation and free are not part of the prologue and exit sites");
|
||||
require(sites[2].OriginalBytes == largePrologue && sites[3].OriginalBytes == largeExit, "the 32-bit sub and add are not part of the prologue and exit sites");
|
||||
@@ -362,7 +364,7 @@ void skipsAllocatingLeavesWithoutAMatchingFree() {
|
||||
const auto input = image({framelessLeaf(InflatePrologue, StoreBelowRsp, InflatePops), framelessLeaf(InflatePrologue, StoreBelowRsp, wrongFree), framelessLeaf(InflatePrologue, moreRsp, inflateExit())});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(sites.empty() && displacements.empty() && report.RedZoneFunctions == 3, "an allocating leaf without a matching free, or with more rsp arithmetic, was framed");
|
||||
require(report.Skipped.at("exit does not free the prologue's allocation") == 1, "an exit that does not free the allocation was not reported with its own reason");
|
||||
require(report.Skipped.at("other use of rsp") == 2, "an add of another size before the pops, or more rsp arithmetic in the body, was not reported as another use of rsp");
|
||||
@@ -378,6 +380,7 @@ Image calledThenUncalled(const std::vector<std::uint8_t>& called, const std::vec
|
||||
append(result.bytes, uncalled);
|
||||
result.bytes = padTo(result.bytes, 0x80);
|
||||
result.segments.push_back({1, 5, 0, Base, Base, result.bytes.size(), result.bytes.size(), 16});
|
||||
result.functions.push_back({Base + 0x40, Base + 0x40 + called.size()});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -387,7 +390,7 @@ void endsBodiesAtAlignedCodeAfterARet() {
|
||||
const auto input = calledThenUncalled(framelessLeaf(FramelessPrologue, body, FramelessExit), framelessLeaf(FramelessPrologue, StoreBelowRsp, FramelessExit));
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && report.Exits == 1 && sites.size() == 2 && displacements.size() == 2, "the called leaf was not framed on its own");
|
||||
for (const auto& site : sites) require(site.Address < Base + 0x60, "a leaf reached without a call was framed as part of the leaf before it");
|
||||
for (const auto& displacement : displacements) require(displacement.Offset < 0x60, "an rsp operand of a leaf reached without a call was moved");
|
||||
@@ -402,7 +405,7 @@ void endsBodiesAtTailCallsToTheNextFunction() {
|
||||
const auto input = calledThenUncalled(called, framelessLeaf(FramelessPrologue, StoreBelowRsp, FramelessExit));
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(sites.empty() && displacements.empty(), "a leaf that tail-calls the function after it was framed together with that function");
|
||||
require(report.Skipped.at("branch leaves the function") == 1, "the tail call was not reported as leaving the function");
|
||||
}
|
||||
@@ -415,7 +418,7 @@ void keepsLoopsAlignedAfterAJump() {
|
||||
const auto input = image({loop});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(report.Framed == 1 && sites.size() == 2 && displacements.size() == 2, "a leaf whose loop header is aligned after a jump was not framed");
|
||||
}
|
||||
|
||||
@@ -427,7 +430,7 @@ void leavesOtherFunctions() {
|
||||
const auto input = image({leaf(LoadAbovePushes), nonLeaf});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {}, report, displacements);
|
||||
require(sites.empty() && report.RedZoneFunctions == 0, "a leaf without red-zone use or a function that calls was framed");
|
||||
}
|
||||
|
||||
@@ -438,10 +441,41 @@ void skipsSitesTakenByOtherStubs() {
|
||||
other.Length = 5;
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {other}, report, displacements);
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, input.functions, {other}, report, displacements);
|
||||
require(sites.empty() && report.Skipped.at("overlaps another stub") == 1, "a site over another stub was framed");
|
||||
}
|
||||
|
||||
void framesAPointerOnlyFunctionByItsOwnBounds() {
|
||||
const auto first = leaf(StoreBelowPushes);
|
||||
std::vector<std::uint8_t> secondBody = StoreBelowPushes;
|
||||
append(secondBody, LoadBelowPushes);
|
||||
const auto second = leaf(secondBody);
|
||||
std::vector<std::uint8_t> bytes = callTo(0, 0x40);
|
||||
bytes.push_back(0xc3);
|
||||
bytes = padTo(bytes, 0x40);
|
||||
append(bytes, first);
|
||||
const auto secondStart = bytes.size();
|
||||
append(bytes, second);
|
||||
bytes = padTo(bytes, (bytes.size() + 15) / 16 * 16 + 16);
|
||||
const std::vector<Domain::ProgramHeader> segments{{1, 5, 0, Base, Base, bytes.size(), bytes.size(), 16}};
|
||||
const std::vector<Codegen::RedZoneFunction> functions{{Base + 0x40, Base + 0x40 + first.size()}, {Base + secondStart, Base + secondStart + second.size()}};
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(bytes, segments, functions, {}, report, displacements);
|
||||
require(report.Functions == 2 && report.Framed == 2 && report.Exits == 2, "a function reached only through a pointer was not framed by its own bounds");
|
||||
require(sites.size() == 4, "two framed leaves need two prologue and two exit sites");
|
||||
require(sites[2].Address == Base + secondStart && sites[2].OriginalBytes == Prologue, "the second function's prologue site is not at its own start");
|
||||
require(sites[1].Address + sites[1].Length == Base + 0x40 + first.size(), "the first function's exit runs past its end");
|
||||
}
|
||||
|
||||
void leavesCodeWithoutAFunctionRange() {
|
||||
const auto input = image({leaf(StoreBelowPushes)});
|
||||
Codegen::RedZoneFrameReport report;
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, {}, report, displacements);
|
||||
require(sites.empty() && report.Functions == 0, "code without a function range was framed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -465,6 +499,8 @@ int main() {
|
||||
endsBodiesAtAlignedCodeAfterARet();
|
||||
endsBodiesAtTailCallsToTheNextFunction();
|
||||
keepsLoopsAlignedAfterAJump();
|
||||
framesAPointerOnlyFunctionByItsOwnBounds();
|
||||
leavesCodeWithoutAFunctionRange();
|
||||
std::cout << "red-zone framer tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <codegen/x86/RedZoneFramer.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <map>
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <algorithm>
|
||||
@@ -110,10 +111,14 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti
|
||||
const auto intelSites = trampolines.size();
|
||||
std::vector<Codegen::RedZoneDisplacement> displacements;
|
||||
if (options.FrameRedZone) {
|
||||
const auto headers = Relinker::ElfReader(sourceBytes).ReadProgramHeaders();
|
||||
std::vector<Domain::ProgramHeader> codeSegments;
|
||||
for (const auto& header : Relinker::ElfReader(sourceBytes).ReadProgramHeaders()) if (header.Type == 1 && (header.Flags & 1) != 0) codeSegments.push_back(header);
|
||||
for (const auto& header : headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeSegments.push_back(header);
|
||||
std::vector<Codegen::RedZoneFunction> functions;
|
||||
for (const auto& region : Relinker::UnusedNidFilter::ReadExceptionFunctions(sourceBytes, headers, {}, {})) functions.push_back({region.Begin, region.End});
|
||||
if (functions.empty()) throw Domain::RelinkerException("Red-zone framing needs function bounds from the executable's .eh_frame, and it has none");
|
||||
Codegen::RedZoneFrameReport report;
|
||||
auto framed = Codegen::FrameRedZoneFunctions(sourceBytes, codeSegments, trampolines, report, displacements);
|
||||
auto framed = Codegen::FrameRedZoneFunctions(sourceBytes, codeSegments, functions, trampolines, report, displacements);
|
||||
for (const auto& moved : displacements) {
|
||||
if (moved.Offset > sourceBytes.size() || moved.Bytes.size() > sourceBytes.size() - moved.Offset)
|
||||
throw Domain::RelinkerException("Red-zone displacement exceeds source image", moved.Offset);
|
||||
|
||||
@@ -362,6 +362,26 @@ std::vector<std::uint8_t> framedLeafFixture() {
|
||||
const std::vector<std::uint8_t> leaf{0x53, 0x41, 0x56, 0x41, 0x57, 0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x8b, 0x04, 0x24, 0x48, 0x8b, 0x44, 0x24, 0x08, 0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3};
|
||||
std::copy(caller.begin(), caller.end(), bytes.begin() + 0x1000);
|
||||
std::copy(leaf.begin(), leaf.end(), bytes.begin() + 0x1040);
|
||||
const auto ehFrameHeader = 64 + 7 * 56;
|
||||
Io::WriteU32(bytes, ehFrameHeader, 0x6474e550);
|
||||
Io::WriteU32(bytes, ehFrameHeader + 4, 4);
|
||||
for (const auto field : {8, 16, 24}) Io::WriteU64(bytes, ehFrameHeader + field, 0x2900);
|
||||
Io::WriteU64(bytes, ehFrameHeader + 32, 32);
|
||||
Io::WriteU64(bytes, ehFrameHeader + 40, 32);
|
||||
Io::WriteU64(bytes, ehFrameHeader + 48, 4);
|
||||
Io::WriteU32(bytes, 0x2800, 9);
|
||||
const std::vector<std::uint8_t> cie{1, 0, 1, 0x78, 16};
|
||||
std::copy(cie.begin(), cie.end(), bytes.begin() + 0x2808);
|
||||
Io::WriteU32(bytes, 0x2840, 20);
|
||||
Io::WriteU32(bytes, 0x2844, 0x44);
|
||||
Io::WriteU64(bytes, 0x2848, 0x1040);
|
||||
Io::WriteU64(bytes, 0x2850, leaf.size());
|
||||
bytes[0x2900] = 1;
|
||||
bytes[0x2902] = 3;
|
||||
Io::WriteU64(bytes, 0x2904, 0x2800);
|
||||
Io::WriteU32(bytes, 0x290c, 1);
|
||||
Io::WriteU64(bytes, 0x2910, 0x1040);
|
||||
Io::WriteU64(bytes, 0x2918, 0x2840);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- `--frame-red-zone` frames only leaf functions that start with a chain of pushes of at least five bytes (`push rbp; mov rbp, rsp; push ...`, or pushes alone, which may be followed by `sub $n, %rsp` or `lea -n(%rsp), %rsp`) and leave through exits of at least five bytes that undo that allocation with `add` or `lea`, pop the same registers and `ret` ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). Leaves with a frame pointer that allocate, leaves with neither pushes nor an allocation, shorter prologues and exits, exits that free another size, and functions with indirect jumps or other RSP arithmetic keep their red zone.
|
||||
- In a red-zone leaf without a frame pointer, `--frame-red-zone` lowers RSP after the prologue by the deepest displacement below RSP, rounded up to 16, and moves every RSP operand by the same amount ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). A leaf that takes the address of a slot below RSP, or uses an RSP operand with an index register, is lowered by the whole 128 bytes, since a pointer or an index can reach deeper than any displacement; values below RSP reached through any other register are not seen. Leaves with a frame pointer are lowered by 128 bytes. Inside a moved frame the CFA rules of the guest's unwind tables are off by the shift.
|
||||
- An RSP operand that `--frame-red-zone` cannot move in place, an 8-bit displacement the shift takes past 0x7f or one without a displacement, moves to a stub with the instructions after it up to five bytes and is re-encoded there with the moved displacement ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)). The leaf is skipped when that site would take in a branch target, a branch, ret or RIP-relative instruction, or another site, and when the operand is EVEX, whose 8-bit displacement is scaled. A game patch over a framed site or a moved displacement is refused.
|
||||
- In `--frame-red-zone`, function starts are direct call targets and 16-byte-aligned code after int3 padding, so a leaf reached only through a pointer without padding before it is not seen. A body ends at int3 padding, at 16-byte-aligned code after a ret or ud2, and at the target of a jmp with only padding between them (a tail call to the next function), so such a leaf is not taken into the body before it there; one that follows a jmp to elsewhere, such as a loop's back edge, without int3 padding still is ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)).
|
||||
- In `--frame-red-zone`, function bounds come from the executable's `.eh_frame` FDEs: the relinker stops with an error when it has none, and code outside every FDE is not framed. A body ends at int3 padding, at 16-byte-aligned code after a ret or ud2, and at the target of a jmp with only padding between them (a tail call to the next function), so such a leaf is not taken into the body before it there; one that follows a jmp to elsewhere, such as a loop's back edge, without int3 padding still is ([framer](../../../core/relinker/codegen/src/x86/RedZoneFramer.cpp)).
|
||||
- `--to-intel` lowers MCOMMIT to STC plus NOP padding and RDPRU through a runtime-checked stub in the [matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp): AMD APM Vol. 3 defines RDPRU ECX 0 as MPERF and ECX 1 as APERF, neither readable on Intel hosts, so both selectors return the host TSC as a disclosed monotonic-counter approximation (their ratio always reads 1.0) while other selectors yield zero EDX:EAX. STC asserts CF=1 because under x86-TSO prior stores are already ordered and guest memory is host-DRAM-backed, leaving the commit-success case as the only observable one. Non-zero RDPRU selectors, the TSC-for-MPERF/APERF substitution, and any console path reporting MCOMMIT CF=0 are unverified.
|
||||
- [Unscoped imports on Windows](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) - an import without a module that no searched library exports under its name is searched again under the NID of that name, because homebrew built with the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) imports by symbol name (Quake, from the websrv homebrew launcher; Mari0, #6239) while the libraries in `libs/` export NIDs. A dependency named with `.sprx`, `.suprx` or `.debug_prx` loads the `.prx` library of the same stem from `libs/`, and `libkernel_web` also loads `libkernel.prx`, as `libSceLibcInternal` already loads `libc.prx`: AnyPS5's `libkernel_web.prx` exports only two functions, where the console's `libkernel_web.sprx` is a libkernel variant. A name that matches a library's export as written still binds to that export first. macOS resolves these imports through the host loader by name and is unchanged.
|
||||
- [Unscoped imports on Linux](../../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) - in an executable that names a dependency with `.sprx`, `.suprx` or `.debug_prx`, as homebrew built with the [ps5-payload-dev SDK](https://github.com/ps5-payload-dev/sdk) does, an import without a module that is named by symbol is bound to the NID of that name only. Unlike Windows, the name as written is not tried first, because the host loader also searches the host C library, which exports `malloc` or `pthread_create` under their names. A name that a bundled module exports still binds to that module, and such a dependency loads the `.prx` library of the same stem. Unqualified imports of other executables, and imports of bundled modules, are still bound by name.
|
||||
|
||||
Reference in new issue
Block a user