Files
AnyPS5/core/relinker/codegen/tests/RedZoneFramerExecutionTests.cpp
Dean 1748d26517 Merge pull request #10871 from Chronyyx/feat/relinker-frame-short-exits
feat(relinker): frame red-zone leaves whose prologue or exit is shorter than a jump
2026-10-27 10:55:26 +13:00

474 lines
23 KiB
C++

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#endif
#include <codegen/x86/RedZoneFramer.hpp>
#include <codegen/x86/StubBodyBuilder.hpp>
#include <algorithm>
#include <csignal>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <limits>
#include <span>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#if defined(__linux__) && defined(__x86_64__)
#include <signal.h>
#include <sys/mman.h>
#include <ucontext.h>
#endif
#if (defined(__linux__) || defined(_WIN32)) && (defined(__x86_64__) || defined(_M_X64))
namespace {
void require(const bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
constexpr std::size_t kArenaBytes = 0x10000;
constexpr std::size_t kStubBase = 0x4000;
constexpr std::size_t kRunnerBase = 0xc000;
constexpr std::uint8_t kClobber = 0xa5;
constexpr std::uint64_t kA = 0x1111, kB = 0x2222, kC = 0x3333, kD = 0x4444;
std::uint8_t* arena = nullptr;
volatile std::sig_atomic_t steps = 0;
volatile std::sig_atomic_t outsideSteps = 0;
#ifdef _WIN32
DWORD steppingThread = 0;
bool singleStepping = false;
bool steppingFinished = false;
std::uintptr_t steppingReturn = 0;
LONG CALLBACK clobberBelowRsp(EXCEPTION_POINTERS* exception) {
if (GetCurrentThreadId() != steppingThread || !singleStepping || exception->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP)
return EXCEPTION_CONTINUE_SEARCH;
auto& registers = *exception->ContextRecord;
if (registers.Rip == steppingReturn) {
registers.EFlags &= ~0x100u;
steppingFinished = true;
return EXCEPTION_CONTINUE_EXECUTION;
}
const auto base = reinterpret_cast<std::uintptr_t>(arena);
if (registers.Rip < base || registers.Rip >= base + kArenaBytes) {
registers.EFlags &= ~0x100u;
outsideSteps = outsideSteps + 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
std::memset(reinterpret_cast<void*>(registers.Rsp - 0x80), kClobber, 0x80);
registers.EFlags |= 0x100u;
steps = steps + 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
#else
void clobberBelowRsp(int, siginfo_t*, void* context) {
auto& registers = static_cast<ucontext_t*>(context)->uc_mcontext.gregs;
const auto rip = static_cast<std::uintptr_t>(registers[REG_RIP]);
const auto base = reinterpret_cast<std::uintptr_t>(arena);
if (rip < base || rip >= base + kArenaBytes) {
registers[REG_EFL] &= ~static_cast<greg_t>(0x100);
outsideSteps = outsideSteps + 1;
return;
}
std::memset(reinterpret_cast<void*>(static_cast<std::uintptr_t>(registers[REG_RSP]) - 0x80), kClobber, 0x80);
steps = steps + 1;
}
#endif
class ExecutableArena {
public:
ExecutableArena() {
#ifdef _WIN32
arena = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, kArenaBytes, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE));
#else
const auto mapped = mmap(nullptr, kArenaBytes, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
arena = mapped == MAP_FAILED ? nullptr : static_cast<std::uint8_t*>(mapped);
#endif
}
~ExecutableArena() {
if (arena == nullptr) return;
#ifdef _WIN32
VirtualFree(arena, 0, MEM_RELEASE);
#else
munmap(arena, kArenaBytes);
#endif
arena = nullptr;
}
ExecutableArena(const ExecutableArena&) = delete;
ExecutableArena& operator=(const ExecutableArena&) = delete;
};
class SingleStepHandler {
public:
SingleStepHandler() {
#ifdef _WIN32
steppingThread = GetCurrentThreadId();
handler = AddVectoredExceptionHandler(1, clobberBelowRsp);
require(handler != nullptr, "cannot install the single-step handler");
#else
struct sigaction action {};
action.sa_sigaction = clobberBelowRsp;
action.sa_flags = SA_SIGINFO;
sigemptyset(&action.sa_mask);
require(sigaction(SIGTRAP, &action, &previous) == 0, "cannot install the SIGTRAP handler");
#endif
}
~SingleStepHandler() {
#ifdef _WIN32
singleStepping = false;
RemoveVectoredExceptionHandler(handler);
#else
sigaction(SIGTRAP, &previous, nullptr);
#endif
}
SingleStepHandler(const SingleStepHandler&) = delete;
SingleStepHandler& operator=(const SingleStepHandler&) = delete;
private:
#ifdef _WIN32
PVOID handler = nullptr;
#else
struct sigaction previous {};
#endif
};
const std::vector<std::uint8_t> RunnerEntry{
0x53, 0x4c, 0x89, 0xc3, 0x48, 0x89, 0xf8, 0x48, 0x89, 0xf7, 0x48, 0x89, 0xd6, 0x48, 0x89, 0xca,
0x4d, 0x85, 0xc9, 0x74, 0x0a, 0x9c, 0x48, 0x81, 0x0c, 0x24, 0x00, 0x01, 0x00, 0x00, 0x9d,
0xff, 0xd0,
};
const std::vector<std::uint8_t> Runner = [] {
auto bytes = RunnerEntry;
bytes.insert(bytes.end(), {0x9c, 0x48, 0x81, 0x24, 0x24, 0xff, 0xfe, 0xff, 0xff, 0x9d, 0x5b, 0xc3});
return bytes;
}();
#ifdef _WIN32
std::vector<std::uint8_t> windowsRunner() {
std::vector<std::uint8_t> bytes{0x57, 0x56, 0x48, 0x81, 0xec, 0xa8, 0x00, 0x00, 0x00};
const auto transferXmm = [&](const std::uint8_t opcode) {
for (std::uint8_t reg = 6; reg < 16; ++reg) {
bytes.push_back(0xf3);
if (reg >= 8) bytes.push_back(0x44);
bytes.insert(bytes.end(), {0x0f, opcode, static_cast<std::uint8_t>(0x84 | ((reg & 7) << 3)), 0x24,
static_cast<std::uint8_t>((reg - 6) * 16), 0x00, 0x00, 0x00});
}
};
transferXmm(0x7f);
bytes.insert(bytes.end(), {
0x48, 0x89, 0xcf, 0x48, 0x89, 0xd6, 0x4c, 0x89, 0xc2, 0x4c, 0x89, 0xc9,
0x4c, 0x8b, 0x84, 0x24, 0xe0, 0x00, 0x00, 0x00,
0x4c, 0x8b, 0x8c, 0x24, 0xe8, 0x00, 0x00, 0x00,
});
const auto call = bytes.size();
bytes.insert(bytes.end(), {0xe8, 0x00, 0x00, 0x00, 0x00});
transferXmm(0x6f);
bytes.insert(bytes.end(), {0x48, 0x81, 0xc4, 0xa8, 0x00, 0x00, 0x00, 0x5e, 0x5f, 0xc3});
const auto displacement = static_cast<std::uint32_t>(bytes.size() - call - 5);
for (std::size_t index = 0; index < 4; ++index) bytes[call + 1 + index] = static_cast<std::uint8_t>(displacement >> (index * 8));
bytes.insert(bytes.end(), Runner.begin(), Runner.end());
return bytes;
}
#endif
struct Leaf {
const char* Name;
std::vector<std::uint8_t> Code;
std::uint64_t Expected;
};
const std::vector<std::uint8_t> FramelessPushes{0x53, 0x41, 0x56, 0x41, 0x57};
const std::vector<std::uint8_t> FramelessPops{0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3};
std::vector<std::uint8_t> frameless(const std::vector<std::uint8_t>& body) {
std::vector<std::uint8_t> bytes = FramelessPushes;
bytes.insert(bytes.end(), body.begin(), body.end());
bytes.insert(bytes.end(), FramelessPops.begin(), FramelessPops.end());
return bytes;
}
std::vector<Leaf> framedLeaves() {
return {
{"frameless leaf spilling 0x10 below rsp", frameless({
0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x89, 0x74, 0x24, 0xf8, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x48, 0x03, 0x44, 0x24, 0xf0, 0x48, 0x03, 0x44, 0x24, 0xf8, 0xff, 0xc9, 0x75, 0xf2,
}), 8 * (kA + kB)},
{"leaf reaching 0x28 below rsp", frameless({
0x48, 0x89, 0x7c, 0x24, 0xd8, 0x48, 0x89, 0x74, 0x24, 0xe8, 0x48, 0x89, 0x54, 0x24, 0xf0, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x48, 0x03, 0x44, 0x24, 0xd8, 0x48, 0x03, 0x44, 0x24, 0xe8, 0x48, 0x03, 0x44, 0x24, 0xf0, 0xff, 0xc9, 0x75, 0xed,
}), 8 * (kA + kB + kC)},
{"leaf reading its saved rbx at +0x10 moved in place", frameless({
0x48, 0x89, 0x7c, 0x24, 0xf0, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x48, 0x03, 0x44, 0x24, 0xf0, 0x48, 0x03, 0x44, 0x24, 0x10, 0xff, 0xc9, 0x75, 0xf2,
}), 8 * (kA + kD)},
{"leaf taking the address of a slot 0x20 below rsp", frameless({
0x4c, 0x8d, 0x74, 0x24, 0xe0, 0x49, 0x89, 0x3e, 0x49, 0x89, 0x76, 0x08, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x49, 0x03, 0x06, 0x49, 0x03, 0x46, 0x08, 0xff, 0xc9, 0x75, 0xf5,
}), 8 * (kA + kB)},
{"inflate_fast-shaped leaf allocating 0x40 after six pushes", {
0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40,
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,
0x48, 0x83, 0xc4, 0x40, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3,
}, 4 * (kB + 2 * kA)},
};
}
std::vector<Leaf> stubbedLeaves() {
return {
{"inflate_fast-shaped leaf with operands at rsp and at 0x60 shifted by 0x20", {
0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x70,
0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x34, 0x24, 0x48, 0x89, 0x54, 0x24, 0x60, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0,
0x48, 0x03, 0x44, 0x24, 0xe0, 0x48, 0x03, 0x04, 0x24, 0x48, 0x03, 0x44, 0x24, 0x60, 0xff, 0xc9, 0x75, 0xee,
0x48, 0x83, 0xc4, 0x70, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3,
}, 4 * (kA + kB + kC)},
{"leaf shifted by the whole red zone reading its saved rbx at +0x8", {
0x41, 0x57, 0x53, 0x41, 0x56, 0x4c, 0x8d, 0x74, 0x24, 0xe0, 0x49, 0x89, 0x3e, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x49, 0x03, 0x06, 0x48, 0x03, 0x44, 0x24, 0x08, 0xff, 0xc9, 0x75, 0xf4, 0x41, 0x5e, 0x5b, 0x41, 0x5f, 0xc3,
}, 8 * (kA + kD)},
};
}
std::vector<Leaf> backwardStubbedLeaves() {
return {
{"inflate_fast-shaped leaf comparing at rsp between an add and a jne", {
0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40,
0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x7c, 0x24, 0x00, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0,
0x48, 0x03, 0x44, 0x24, 0xe0, 0x49, 0x01, 0xf8, 0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, 0xeb,
0x48, 0x83, 0xc4, 0x40, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3,
}, 4 * (kA + kB)},
{"leaf comparing at rsp after a load below rsp that heads its loop", {
0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40,
0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x7c, 0x24, 0x00, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0,
0x48, 0x03, 0x44, 0x24, 0xe0, 0x48, 0x39, 0x3c, 0x24, 0x75, 0x03, 0x48, 0x01, 0xf0, 0xff, 0xc9, 0x75, 0xee,
0x48, 0x83, 0xc4, 0x40, 0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3,
}, 4 * (kA + kB)},
};
}
std::vector<Leaf> widenedLeaves() {
return {
{"leaf without pushes spilling 0x10 below rsp and adding a spill before its ret", {
0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x89, 0x74, 0x24, 0xf8, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x48, 0x03, 0x44, 0x24, 0xf0, 0x48, 0x03, 0x44, 0x24, 0xf8, 0xff, 0xc9, 0x75, 0xf2,
0x48, 0x03, 0x44, 0x24, 0xf0, 0xc3,
}, 8 * (kA + kB) + kA},
{"leaf pushing only rbx, spilling below rsp and adding rbx before its pop", {
0x53, 0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x89, 0xf3, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x48, 0x03, 0x44, 0x24, 0xf0, 0x48, 0x01, 0xd8, 0xff, 0xc9, 0x75, 0xf4,
0x48, 0x01, 0xd8, 0x5b, 0xc3,
}, 8 * (kA + kB) + kB},
{"frame-pointer leaf without other pushes spilling below rbp", {
0x55, 0x48, 0x89, 0xe5, 0x48, 0x89, 0x7d, 0xf0, 0x48, 0x89, 0x75, 0xf8, 0x31, 0xc0, 0xb9, 0x08, 0x00, 0x00, 0x00,
0x48, 0x03, 0x45, 0xf0, 0x48, 0x03, 0x45, 0xf8, 0xff, 0xc9, 0x75, 0xf4,
0x48, 0x03, 0x45, 0xf0, 0x5d, 0xc3,
}, 8 * (kA + kB) + kA},
{"leaf without pushes returning its first spill through the first of two widened exits", {
0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x89, 0x74, 0x24, 0xf8, 0x48, 0x39, 0xf7, 0x74, 0x06,
0x48, 0x8b, 0x44, 0x24, 0xf0, 0xc3, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3,
}, kA},
{"leaf without pushes returning its second spill through the widened exit a jne reaches", {
0x48, 0x89, 0x7c, 0x24, 0xf0, 0x48, 0x89, 0x74, 0x24, 0xf8, 0x48, 0x39, 0xf7, 0x75, 0x06,
0x48, 0x8b, 0x44, 0x24, 0xf0, 0xc3, 0x48, 0x8b, 0x44, 0x24, 0xf8, 0xc3,
}, kB},
{"leaf without pushes whose widened exit carries a VEX andn below rsp", {
0x48, 0x89, 0x7c, 0x24, 0xf8, 0xc4, 0xe2, 0xf0, 0xf2, 0x44, 0x24, 0xf8, 0xc3,
}, ~kC & kA},
{"leaf allocating with a 4-byte sub widened over the spill after it", {
0x48, 0x83, 0xec, 0x08, 0x48, 0x89, 0x7c, 0x24, 0xf0, 0x31, 0xc0, 0x48, 0x03, 0x44, 0x24, 0xf0,
0x48, 0x83, 0xc4, 0x08, 0xc3,
}, kA},
{"frame-pointer leaf pushing and popping rbx in its body below a slot at rbp-0x10", {
0x55, 0x48, 0x89, 0xe5, 0x48, 0x89, 0x7d, 0xf0, 0x53, 0x48, 0x89, 0xf3, 0x48, 0x8b, 0x45, 0xf0,
0x48, 0x01, 0xd8, 0x5b, 0x48, 0x01, 0xd0, 0x5d, 0xc3,
}, kA + kB + kC},
};
}
void writeRel32(std::uint8_t* at, const std::uint64_t target, const std::uint64_t next) {
const auto displacement = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(next);
require(displacement >= std::numeric_limits<std::int32_t>::min() && displacement <= std::numeric_limits<std::int32_t>::max(), "a stub jump is out of rel32 range");
const auto value = static_cast<std::uint32_t>(static_cast<std::int32_t>(displacement));
for (std::size_t index = 0; index < 4; ++index) at[index] = static_cast<std::uint8_t>(value >> (index * 8));
}
std::vector<std::uint8_t> callTo(const std::size_t from, const std::size_t target) {
std::vector<std::uint8_t> bytes{0xe8, 0, 0, 0, 0};
const auto displacement = static_cast<std::uint32_t>(static_cast<std::int32_t>(target) - static_cast<std::int32_t>(from + 5));
for (std::size_t index = 0; index < 4; ++index) bytes[1 + index] = static_cast<std::uint8_t>(displacement >> (index * 8));
return bytes;
}
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) {
Image image;
std::size_t at = 0x40;
for (const auto& leaf : leaves) {
image.Starts.push_back(at);
at += (leaf.Code.size() + 15) / 16 * 16 + 16;
}
for (const auto start : image.Starts) {
const auto call = callTo(image.Bytes.size(), start);
image.Bytes.insert(image.Bytes.end(), call.begin(), call.end());
}
image.Bytes.push_back(0xc3);
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");
return image;
}
struct Framing {
Codegen::RedZoneFrameReport Report;
std::vector<Codegen::TrampolineSite> Sites;
std::vector<Codegen::RedZoneDisplacement> Displacements;
};
Framing load(const Image& image, const bool frame) {
std::fill_n(arena, kArenaBytes, 0xcc);
std::copy(image.Bytes.begin(), image.Bytes.end(), arena);
#ifdef _WIN32
const auto runner = windowsRunner();
steppingReturn = reinterpret_cast<std::uintptr_t>(arena + kRunnerBase + runner.size() - Runner.size() + RunnerEntry.size());
#else
const auto& runner = Runner;
#endif
require(runner.size() <= kArenaBytes - kRunnerBase, "the runner does not fit in the executable arena");
std::copy(runner.begin(), runner.end(), arena + kRunnerBase);
Framing framing;
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}};
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) {
const auto at = static_cast<std::size_t>(site.Address - base);
require(std::equal(site.OriginalBytes.begin(), site.OriginalBytes.end(), arena + at), "a framed site's bytes changed before it was patched");
cursor = (cursor + 15) / 16 * 16;
require(cursor + site.Body.size() <= kRunnerBase, "the stubs do not fit before the runner");
auto* stub = arena + cursor;
std::copy(site.Body.begin(), site.Body.end(), stub);
Codegen::ApplyStubRelocations(std::span<std::uint8_t>(stub, site.ReturnBranchOffset), site.Relocations, site.Address, base + cursor, site.Offset);
writeRel32(stub + site.ReturnBranchOffset + 1, site.Address + site.Length, base + cursor + site.ReturnBranchOffset + 5);
std::fill_n(arena + at, site.Length, 0x90);
arena[at] = 0xe9;
writeRel32(arena + at + 1, base + cursor, site.Address + 5);
cursor += site.Body.size();
}
return framing;
}
struct Run {
std::uint64_t Result;
int Steps;
};
Run run(const std::size_t start, const bool clobber) {
using Entry = std::uint64_t (*)(std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t, std::uint64_t);
const auto entry = reinterpret_cast<Entry>(arena + kRunnerBase);
steps = 0;
outsideSteps = 0;
#ifdef _WIN32
require(FlushInstructionCache(GetCurrentProcess(), arena, kArenaBytes) != 0, "cannot flush the generated code");
singleStepping = clobber;
steppingFinished = false;
#endif
const auto result = entry(reinterpret_cast<std::uint64_t>(arena + start), kA, kB, kC, kD, clobber ? 1 : 0);
#ifdef _WIN32
singleStepping = false;
require(steppingFinished == clobber, "single-stepping did not finish at the runner's return point");
#endif
require(outsideSteps == 0, "single-stepping escaped the generated code");
require(clobber || steps == 0, "single-stepping ran without the clobber requested");
return {result, static_cast<int>(steps)};
}
std::string hex(const std::uint64_t value) {
std::ostringstream text;
text << "0x" << std::hex << value;
return text.str();
}
Codegen::RedZoneFrameReport runLeaves(const std::vector<Leaf>& leaves) {
const auto image = place(leaves);
load(image, false);
std::vector<Run> plain, clobbered;
for (std::size_t index = 0; index < leaves.size(); ++index) {
plain.push_back(run(image.Starts[index], false));
clobbered.push_back(run(image.Starts[index], true));
require(plain.back().Result == leaves[index].Expected, std::string(leaves[index].Name) + ": the unframed leaf returned " + hex(plain.back().Result) + " without the clobber, not " + hex(leaves[index].Expected));
require(clobbered.back().Steps > 0, std::string(leaves[index].Name) + ": the clobber did not run");
require(clobbered.back().Result != leaves[index].Expected, std::string(leaves[index].Name) + ": the unframed leaf survived writes below rsp, so the case proves nothing");
}
const auto framing = load(image, true);
require(framing.Report.Framed == leaves.size(), "only " + std::to_string(framing.Report.Framed) + " of " + std::to_string(leaves.size()) + " leaves were framed");
for (std::size_t index = 0; index < leaves.size(); ++index) {
const auto calm = run(image.Starts[index], false);
const auto framed = run(image.Starts[index], true);
require(calm.Result == leaves[index].Expected, std::string(leaves[index].Name) + ": the framed leaf returned " + hex(calm.Result) + " without the clobber, not " + hex(leaves[index].Expected));
require(framed.Steps > 0, std::string(leaves[index].Name) + ": the clobber did not run over the framed leaf");
require(framed.Result == leaves[index].Expected, std::string(leaves[index].Name) + ": the framed leaf returned " + hex(framed.Result) + " under writes below rsp, not " + hex(leaves[index].Expected));
std::cout << leaves[index].Name << ": unframed " << hex(plain[index].Result) << " alone and " << hex(clobbered[index].Result) << " under the clobber (" << clobbered[index].Steps << " steps); framed " << hex(framed.Result) << " under the clobber (" << framed.Steps << " steps)\n";
}
return framing.Report;
}
}
int main() {
try {
const ExecutableArena executableArena;
if (arena == nullptr) {
std::cout << "skipped: no writable and executable memory\n";
return 77;
}
const SingleStepHandler singleStepHandler;
runLeaves(framedLeaves());
const auto stubbed = runLeaves(stubbedLeaves());
require(stubbed.OperandStubs == 3 && stubbed.StubbedOperands == 5, "the leaves were framed with " + std::to_string(stubbed.OperandStubs) + " operand stubs carrying " + std::to_string(stubbed.StubbedOperands) + " rsp operands, not 3 carrying 5");
const auto backward = runLeaves(backwardStubbedLeaves());
require(backward.OperandStubs == 2 && backward.StubbedOperands == 3 && backward.MovedOperands == 5, "the leaves comparing at rsp before a branch were framed with " + std::to_string(backward.OperandStubs) + " operand stubs carrying " + std::to_string(backward.StubbedOperands) + " rsp operands and " + std::to_string(backward.MovedOperands) + " moved in place, not 2 carrying 3 and 5");
const auto widened = runLeaves(widenedLeaves());
require(widened.WidenedPrologues == 8 && widened.WidenedExits == 9 && widened.WidenedOperands == 12, "the leaves with short prologues and exits were framed with " + std::to_string(widened.WidenedPrologues) + " widened prologues, " + std::to_string(widened.WidenedExits) + " widened exits and " + std::to_string(widened.WidenedOperands) + " rsp operands in them, not 8, 9 and 12");
std::cout << "red-zone framer execution tests passed\n";
return 0;
} catch (const std::exception& error) {
std::cerr << "red-zone framer execution tests failed: " << error.what() << '\n';
return 1;
}
}
#else
int main() {
std::cout << "skipped: the red-zone framer execution tests require x86-64 Linux or Windows\n";
return 77;
}
#endif