mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-29 12:15:39 +00:00
405 lines
24 KiB
C++
405 lines
24 KiB
C++
#include <codegen/x86/RedZoneFramer.hpp>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
void require(const bool condition, const std::string& message) {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
constexpr std::uint64_t Base = 0x1000;
|
|
|
|
std::vector<std::uint8_t> padTo(std::vector<std::uint8_t> bytes, const std::size_t size) {
|
|
bytes.resize(size, 0xcc);
|
|
return bytes;
|
|
}
|
|
|
|
void append(std::vector<std::uint8_t>& bytes, const std::vector<std::uint8_t>& more) {
|
|
bytes.insert(bytes.end(), more.begin(), more.end());
|
|
}
|
|
|
|
std::vector<std::uint8_t> callTo(const std::size_t from, const std::size_t target) {
|
|
const auto displacement = static_cast<std::int32_t>(target) - static_cast<std::int32_t>(from + 5);
|
|
return {0xe8, static_cast<std::uint8_t>(displacement), static_cast<std::uint8_t>(displacement >> 8), static_cast<std::uint8_t>(displacement >> 16), static_cast<std::uint8_t>(displacement >> 24)};
|
|
}
|
|
|
|
const std::vector<std::uint8_t> Prologue{0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x53};
|
|
const std::vector<std::uint8_t> Exit{0x5b, 0x41, 0x5f, 0x5d, 0xc3};
|
|
const std::vector<std::uint8_t> StoreBelowPushes{0x48, 0x89, 0x7d, 0xe8};
|
|
const std::vector<std::uint8_t> LoadBelowPushes{0x48, 0x8b, 0x45, 0xe8};
|
|
const std::vector<std::uint8_t> LoadAbovePushes{0x48, 0x8b, 0x45, 0x08};
|
|
|
|
std::vector<std::uint8_t> leaf(const std::vector<std::uint8_t>& body) {
|
|
std::vector<std::uint8_t> bytes = Prologue;
|
|
append(bytes, body);
|
|
append(bytes, Exit);
|
|
return bytes;
|
|
}
|
|
|
|
struct Image {
|
|
std::vector<std::uint8_t> bytes;
|
|
std::vector<Domain::ProgramHeader> segments;
|
|
};
|
|
|
|
Image image(const std::vector<std::vector<std::uint8_t>>& functions) {
|
|
Image result;
|
|
std::vector<std::size_t> starts;
|
|
std::size_t at = 0x40;
|
|
for (const auto& function : functions) {
|
|
starts.push_back(at);
|
|
at += (function.size() + 15) / 16 * 16 + 16;
|
|
}
|
|
std::vector<std::uint8_t> caller;
|
|
for (const auto start : starts) append(caller, callTo(caller.size(), start));
|
|
caller.push_back(0xc3);
|
|
result.bytes = padTo(caller, 0x40);
|
|
for (std::size_t index = 0; index < functions.size(); ++index) {
|
|
result.bytes = padTo(result.bytes, starts[index]);
|
|
append(result.bytes, functions[index]);
|
|
}
|
|
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});
|
|
return result;
|
|
}
|
|
|
|
void framesRedZoneLeaf() {
|
|
std::vector<std::uint8_t> body = StoreBelowPushes;
|
|
append(body, LoadBelowPushes);
|
|
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);
|
|
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");
|
|
|
|
const auto& entry = sites[0];
|
|
require(entry.Address == Base + 0x40 && entry.Length == Prologue.size(), "the prologue site does not cover the pushes");
|
|
require(entry.OriginalBytes == Prologue, "the prologue site lost its bytes");
|
|
std::vector<std::uint8_t> entryBody = Prologue;
|
|
append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0x80, 0xe9, 0, 0, 0, 0});
|
|
require(entry.Body == entryBody && entry.ReturnBranchOffset == Prologue.size() + 5, "the prologue stub does not lower rsp after the pushes");
|
|
|
|
const auto& exit = sites[1];
|
|
require(exit.Address == Base + 0x40 + Prologue.size() + body.size() && exit.Length == Exit.size(), "the exit site does not cover the pops and ret");
|
|
std::vector<std::uint8_t> exitBody{0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0};
|
|
append(exitBody, Exit);
|
|
append(exitBody, {0xe9, 0, 0, 0, 0});
|
|
require(exit.Body == exitBody && exit.ReturnBranchOffset == 8 + Exit.size(), "the exit stub does not raise rsp before the pops");
|
|
}
|
|
|
|
const std::vector<std::uint8_t> FramelessPrologue{0x53, 0x41, 0x56, 0x41, 0x57};
|
|
const std::vector<std::uint8_t> FramelessExit{0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3};
|
|
const std::vector<std::uint8_t> StoreBelowRsp{0x48, 0x89, 0x7c, 0x24, 0xf0};
|
|
const std::vector<std::uint8_t> LoadBelowRsp{0x48, 0x8b, 0x44, 0x24, 0xf0};
|
|
const std::vector<std::uint8_t> LoadAboveRsp{0x48, 0x8b, 0x44, 0x24, 0x08};
|
|
const std::vector<std::uint8_t> LoadFarAboveRsp{0x48, 0x8b, 0x44, 0x24, 0x78};
|
|
const std::vector<std::uint8_t> LoadAtRsp{0x48, 0x8b, 0x04, 0x24};
|
|
const std::vector<std::uint8_t> StoreDeepBelowRsp{0x48, 0x89, 0x74, 0x24, 0xd8};
|
|
const std::vector<std::uint8_t> LoadDeepBelowRsp{0x48, 0x8b, 0x44, 0x24, 0xd8};
|
|
const std::vector<std::uint8_t> AddressBelowRsp{0x48, 0x8d, 0x44, 0x24, 0xe0};
|
|
const std::vector<std::uint8_t> AddressAboveRsp{0x48, 0x8d, 0x44, 0x24, 0x08};
|
|
const std::vector<std::uint8_t> StoreIndexedBelowRsp{0x48, 0x89, 0x7c, 0xcc, 0xf0};
|
|
|
|
std::vector<std::uint8_t> framelessLeaf(const std::vector<std::uint8_t>& prologue, const std::vector<std::uint8_t>& body, const std::vector<std::uint8_t>& exit) {
|
|
std::vector<std::uint8_t> bytes = prologue;
|
|
append(bytes, body);
|
|
append(bytes, exit);
|
|
return bytes;
|
|
}
|
|
|
|
void framesLeafWithoutFramePointer() {
|
|
std::vector<std::uint8_t> body = StoreBelowRsp;
|
|
append(body, LoadBelowRsp);
|
|
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);
|
|
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];
|
|
require(entry.Address == Base + 0x40 && entry.Length == FramelessPrologue.size(), "the prologue site does not cover the pushes");
|
|
std::vector<std::uint8_t> entryBody = FramelessPrologue;
|
|
append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xf0, 0xe9, 0, 0, 0, 0});
|
|
require(entry.Body == entryBody, "the prologue stub does not lower rsp by the leaf's depth below rsp after the pushes");
|
|
const auto& exit = sites[1];
|
|
require(exit.Address == Base + 0x40 + FramelessPrologue.size() + body.size() && exit.OriginalBytes == FramelessExit, "the exit site does not cover the pops and ret");
|
|
std::vector<std::uint8_t> exitBody{0x48, 0x8d, 0xa4, 0x24, 0x10, 0, 0, 0};
|
|
append(exitBody, FramelessExit);
|
|
append(exitBody, {0xe9, 0, 0, 0, 0});
|
|
require(exit.Body == exitBody, "the exit stub does not raise rsp by the leaf's depth below rsp before the pops");
|
|
const auto storeDisplacement = 0x40 + FramelessPrologue.size() + 4;
|
|
const auto loadDisplacement = storeDisplacement + StoreBelowRsp.size();
|
|
require(displacements.size() == 2, "both rsp operands must move");
|
|
require(displacements[0].Offset == storeDisplacement && displacements[0].Bytes == std::vector<std::uint8_t>{0x00}, "the store below rsp was not moved to the lowered rsp");
|
|
require(displacements[1].Offset == loadDisplacement && displacements[1].Bytes == std::vector<std::uint8_t>{0x00}, "the load below rsp was not moved to the lowered rsp");
|
|
}
|
|
|
|
void shiftsByTheDeepestOperandBelowRsp() {
|
|
std::vector<std::uint8_t> body = StoreBelowRsp;
|
|
append(body, StoreDeepBelowRsp);
|
|
append(body, LoadDeepBelowRsp);
|
|
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);
|
|
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});
|
|
require(sites[0].Body == entryBody, "rsp is not lowered by the deepest displacement rounded up to 16");
|
|
require(std::vector<std::uint8_t>(sites[1].Body.begin(), sites[1].Body.begin() + 8) == std::vector<std::uint8_t>{0x48, 0x8d, 0xa4, 0x24, 0x30, 0, 0, 0}, "rsp is not raised by the shift it was lowered by");
|
|
require(displacements[0].Bytes == std::vector<std::uint8_t>{0x20} && displacements[1].Bytes == std::vector<std::uint8_t>{0x08} && displacements[2].Bytes == std::vector<std::uint8_t>{0x08}, "the operands below rsp were not moved by the shift");
|
|
}
|
|
|
|
void movesPositiveOperandsThatFitAfterTheShift() {
|
|
std::vector<std::uint8_t> body = StoreBelowRsp;
|
|
append(body, LoadAboveRsp);
|
|
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);
|
|
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");
|
|
}
|
|
|
|
void shiftsByTheWholeRedZoneForAddressesBelowRsp() {
|
|
std::vector<std::uint8_t> below = StoreBelowRsp;
|
|
append(below, AddressBelowRsp);
|
|
std::vector<std::uint8_t> above = StoreBelowRsp;
|
|
append(above, AddressAboveRsp);
|
|
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);
|
|
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");
|
|
}
|
|
|
|
void shiftsByTheWholeRedZoneForIndexedOperands() {
|
|
std::vector<std::uint8_t> body = StoreIndexedBelowRsp;
|
|
append(body, LoadBelowRsp);
|
|
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);
|
|
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");
|
|
}
|
|
|
|
void skipsLeavesWithOperandsThatCannotMove() {
|
|
std::vector<std::uint8_t> farAbove = StoreBelowRsp;
|
|
append(farAbove, LoadFarAboveRsp);
|
|
std::vector<std::uint8_t> atRsp = StoreBelowRsp;
|
|
append(atRsp, LoadAtRsp);
|
|
const auto input = image({framelessLeaf(FramelessPrologue, farAbove, FramelessExit), framelessLeaf(FramelessPrologue, atRsp, 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);
|
|
require(sites.empty() && displacements.empty() && report.RedZoneFunctions == 3, "a leaf whose rsp operands cannot all move, or whose prologue is too short, was framed");
|
|
require(report.Skipped.at("rsp operand that cannot move with rsp") == 2 && report.Skipped.at("prologue shorter than a jump") == 1, "the skipped leaves were not reported with their reasons");
|
|
}
|
|
|
|
void skipsLeavesThatCopyRsp() {
|
|
std::vector<std::uint8_t> withCmov = StoreBelowRsp;
|
|
append(withCmov, {0x48, 0x0f, 0x45, 0xc4});
|
|
std::vector<std::uint8_t> withShlx = StoreBelowRsp;
|
|
append(withShlx, {0xc4, 0xe2, 0xf1, 0xf7, 0xc4});
|
|
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);
|
|
require(sites.empty() && displacements.empty() && report.Skipped.at("other use of rsp") == 2, "a leaf that copies rsp into another register was framed");
|
|
}
|
|
|
|
const std::vector<std::uint8_t> InflatePrologue{0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40};
|
|
const std::vector<std::uint8_t> InflatePops{0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3};
|
|
const std::vector<std::uint8_t> InflateFree{0x48, 0x83, 0xc4, 0x40};
|
|
|
|
std::vector<std::uint8_t> inflateExit() {
|
|
std::vector<std::uint8_t> bytes = InflateFree;
|
|
append(bytes, InflatePops);
|
|
return bytes;
|
|
}
|
|
|
|
std::vector<std::uint8_t> inflateBody() {
|
|
return {
|
|
0x48, 0x89, 0x7c, 0x24, 0xe0,
|
|
0x48, 0x89, 0x7c, 0x24, 0x00,
|
|
0x48, 0x89, 0x74, 0x24, 0x08,
|
|
0x48, 0x89, 0x54, 0x24, 0x38,
|
|
0xb9, 0x04, 0x00, 0x00, 0x00,
|
|
0x31, 0xc0,
|
|
0x48, 0x03, 0x44, 0x24, 0x08,
|
|
0x48, 0x03, 0x44, 0x24, 0xe0,
|
|
0x48, 0x03, 0x44, 0x24, 0x00,
|
|
0xff, 0xc9,
|
|
0x75, 0xed,
|
|
0x48, 0x8b, 0x54, 0x24, 0x38,
|
|
};
|
|
}
|
|
|
|
void framesLeavesThatAllocateBelowTheirPushes() {
|
|
const auto body = inflateBody();
|
|
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);
|
|
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");
|
|
std::vector<std::uint8_t> entryBody = InflatePrologue;
|
|
append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xe0, 0xe9, 0, 0, 0, 0});
|
|
require(entry.Body == entryBody, "the prologue stub does not lower rsp by 0x20 after the sub");
|
|
const auto& exit = sites[1];
|
|
require(exit.Address == Base + 0x40 + InflatePrologue.size() + body.size() && exit.OriginalBytes == inflateExit(), "the exit site does not cover the add, the pops and the ret");
|
|
std::vector<std::uint8_t> exitBody{0x48, 0x8d, 0xa4, 0x24, 0x20, 0, 0, 0};
|
|
append(exitBody, inflateExit());
|
|
append(exitBody, {0xe9, 0, 0, 0, 0});
|
|
require(exit.Body == exitBody, "the exit stub does not raise rsp by 0x20 before the add");
|
|
const std::vector<std::uint8_t> moved{0x00, 0x20, 0x28, 0x58, 0x28, 0x00, 0x20, 0x58};
|
|
require(displacements.size() == moved.size() && report.MovedOperands == moved.size(), "not every rsp operand of the allocating leaf was moved");
|
|
for (std::size_t index = 0; index < moved.size(); ++index)
|
|
require(displacements[index].Bytes == std::vector<std::uint8_t>{moved[index]}, "an rsp operand of the allocating leaf was not moved by 0x20 from the post-sub rsp");
|
|
require(displacements[0].Offset == 0x40 + InflatePrologue.size() + 4, "the spill below the allocation was not the first operand moved");
|
|
}
|
|
|
|
void framesLeavesThatAllocateWithLeaOrALargeSub() {
|
|
const std::vector<std::uint8_t> leaPrologue{0x53, 0x41, 0x56, 0x48, 0x8d, 0x64, 0x24, 0xc0};
|
|
const std::vector<std::uint8_t> leaExit{0x48, 0x8d, 0x64, 0x24, 0x40, 0x41, 0x5e, 0x5b, 0xc3};
|
|
const std::vector<std::uint8_t> largePrologue{0x53, 0x48, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00};
|
|
const std::vector<std::uint8_t> largeExit{0x48, 0x81, 0xc4, 0x00, 0x01, 0x00, 0x00, 0x5b, 0xc3};
|
|
std::vector<std::uint8_t> largeBody = StoreBelowRsp;
|
|
append(largeBody, {0x48, 0x8b, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00});
|
|
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);
|
|
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");
|
|
require(displacements[2].Bytes == std::vector<std::uint8_t>{0x90, 0x00, 0x00, 0x00}, "the 32-bit displacement into the allocation was not moved by the shift");
|
|
}
|
|
|
|
void skipsAllocatingLeavesWithoutAMatchingFree() {
|
|
const std::vector<std::uint8_t> otherFree{0x48, 0x83, 0xc4, 0x30};
|
|
std::vector<std::uint8_t> wrongFree = otherFree;
|
|
append(wrongFree, InflatePops);
|
|
std::vector<std::uint8_t> moreRsp = StoreBelowRsp;
|
|
append(moreRsp, {0x48, 0x83, 0xec, 0x08});
|
|
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);
|
|
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");
|
|
}
|
|
|
|
Image calledThenUncalled(const std::vector<std::uint8_t>& called, const std::vector<std::uint8_t>& uncalled) {
|
|
Image result;
|
|
result.bytes = callTo(0, 0x40);
|
|
result.bytes.push_back(0xc3);
|
|
result.bytes = padTo(result.bytes, 0x40);
|
|
append(result.bytes, called);
|
|
result.bytes.resize(0x60, 0x90);
|
|
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});
|
|
return result;
|
|
}
|
|
|
|
void endsBodiesAtAlignedCodeAfterARet() {
|
|
std::vector<std::uint8_t> body = StoreBelowRsp;
|
|
append(body, LoadBelowRsp);
|
|
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);
|
|
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");
|
|
}
|
|
|
|
void endsBodiesAtTailCallsToTheNextFunction() {
|
|
std::vector<std::uint8_t> called = FramelessPrologue;
|
|
append(called, StoreBelowRsp);
|
|
append(called, {0x85, 0xff, 0x75, 0x06});
|
|
append(called, FramelessExit);
|
|
append(called, {0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xe9, 0x02, 0x00, 0x00, 0x00});
|
|
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);
|
|
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");
|
|
}
|
|
|
|
void keepsLoopsAlignedAfterAJump() {
|
|
std::vector<std::uint8_t> loop = FramelessPrologue;
|
|
append(loop, StoreBelowRsp);
|
|
append(loop, {0x31, 0xc0, 0xeb, 0x04, 0x66, 0x90, 0xff, 0xc0, 0x3b, 0x44, 0x24, 0xf0, 0x72, 0xf8});
|
|
append(loop, FramelessExit);
|
|
const auto input = image({loop});
|
|
Codegen::RedZoneFrameReport report;
|
|
std::vector<Codegen::RedZoneDisplacement> displacements;
|
|
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, 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");
|
|
}
|
|
|
|
void leavesOtherFunctions() {
|
|
std::vector<std::uint8_t> nonLeaf = Prologue;
|
|
append(nonLeaf, StoreBelowPushes);
|
|
append(nonLeaf, {0xff, 0xd0});
|
|
append(nonLeaf, Exit);
|
|
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);
|
|
require(sites.empty() && report.RedZoneFunctions == 0, "a leaf without red-zone use or a function that calls was framed");
|
|
}
|
|
|
|
void skipsSitesTakenByOtherStubs() {
|
|
const auto input = image({leaf(StoreBelowPushes)});
|
|
Codegen::TrampolineSite other{};
|
|
other.Address = Base + 0x40;
|
|
other.Length = 5;
|
|
Codegen::RedZoneFrameReport report;
|
|
std::vector<Codegen::RedZoneDisplacement> displacements;
|
|
const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {other}, report, displacements);
|
|
require(sites.empty() && report.Skipped.at("overlaps another stub") == 1, "a site over another stub was framed");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
framesRedZoneLeaf();
|
|
leavesOtherFunctions();
|
|
skipsSitesTakenByOtherStubs();
|
|
framesLeafWithoutFramePointer();
|
|
shiftsByTheDeepestOperandBelowRsp();
|
|
movesPositiveOperandsThatFitAfterTheShift();
|
|
shiftsByTheWholeRedZoneForAddressesBelowRsp();
|
|
shiftsByTheWholeRedZoneForIndexedOperands();
|
|
framesLeavesThatAllocateBelowTheirPushes();
|
|
framesLeavesThatAllocateWithLeaOrALargeSub();
|
|
skipsAllocatingLeavesWithoutAMatchingFree();
|
|
skipsLeavesWithOperandsThatCannotMove();
|
|
skipsLeavesThatCopyRsp();
|
|
endsBodiesAtAlignedCodeAfterARet();
|
|
endsBodiesAtTailCallsToTheNextFunction();
|
|
keepsLoopsAlignedAfterAJump();
|
|
std::cout << "red-zone framer tests passed\n";
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::cerr << "red-zone framer tests failed: " << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
}
|