mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-10 01:11:45 -10:00
171 lines
7.0 KiB
C++
171 lines
7.0 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include "gpu/shim/engine/engine_hooks.h"
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <vector>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
namespace {
|
|
int failures = 0;
|
|
void Check(bool ok, const char* what) {
|
|
if (!ok) { std::printf("FAIL: %s\n", what); ++failures; }
|
|
}
|
|
|
|
struct Seen {
|
|
unsigned calls = 0;
|
|
std::uint64_t args[6]{};
|
|
double xmm[2]{};
|
|
bool skip = false;
|
|
std::uint64_t value = 0;
|
|
} seen;
|
|
|
|
std::int64_t __attribute__((sysv_abi)) Watch(std::uint64_t, const std::uint64_t* saved) {
|
|
++seen.calls;
|
|
for (unsigned i = 0; i < 6; ++i) seen.args[i] = saved[5 - i];
|
|
std::memcpy(&seen.xmm[0], BbEngine::SavedXmm(saved, 0), 8);
|
|
std::memcpy(&seen.xmm[1], BbEngine::SavedXmm(saved, 1), 8);
|
|
__asm__ volatile("xorps %%xmm0, %%xmm0\n\txorps %%xmm1, %%xmm1" ::: "xmm0", "xmm1");
|
|
if (!seen.skip) return 0;
|
|
const_cast<std::uint64_t*>(saved)[-1] = seen.value;
|
|
return 1;
|
|
}
|
|
|
|
std::uint8_t* Executable(std::size_t n) {
|
|
#ifdef _WIN32
|
|
return static_cast<std::uint8_t*>(VirtualAlloc(nullptr, n, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE));
|
|
#else
|
|
void* p = mmap(nullptr, n, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
return p == MAP_FAILED ? nullptr : static_cast<std::uint8_t*>(p);
|
|
#endif
|
|
}
|
|
|
|
__attribute__((noinline)) std::uint64_t CallWithRax(const void* fn, std::uint64_t rax) {
|
|
std::uint64_t out = rax;
|
|
__asm__ volatile("sub $128, %%rsp\n\t"
|
|
"call *%[fn]\n\t"
|
|
"add $128, %%rsp"
|
|
: "+a"(out)
|
|
: [fn] "r"(fn)
|
|
: "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory", "cc", "xmm0", "xmm1",
|
|
"xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
|
|
"xmm12", "xmm13", "xmm14", "xmm15");
|
|
return out;
|
|
}
|
|
|
|
template <std::size_t N> void Place(std::uint8_t* at, const std::uint8_t (&code)[N]) {
|
|
std::memcpy(at, code, N);
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
std::uint8_t* image = Executable(8192);
|
|
if (!image) { std::puts("FAIL: no executable memory"); return 1; }
|
|
std::array<std::uint8_t, 16> entry;
|
|
entry.fill(0x90);
|
|
const std::uint8_t sum[] = {0x48, 0x89, 0xf8, 0x48, 0x01, 0xf0, 0x48, 0x01, 0xd0, 0x48, 0x01,
|
|
0xc8, 0x4c, 0x01, 0xc0, 0x4c, 0x01, 0xc8, 0xc3};
|
|
const auto fn = reinterpret_cast<std::uint64_t>(image);
|
|
std::memcpy(image, entry.data(), entry.size());
|
|
Place(image + 16, sum);
|
|
|
|
BbEngine::Hook hook;
|
|
auto other = entry;
|
|
other[3] = 0xcc;
|
|
Check(!BbEngine::Prepare(hook, image, 8192, 0, other, Watch), "a site with other bytes is refused");
|
|
Check(!BbEngine::Prepare(hook, image, 10, 0, entry, Watch), "a site past the image's end is refused");
|
|
Check(!BbEngine::Prepare(hook, image, 8192, 0, std::span(entry).first(13), Watch),
|
|
"fewer bytes than the jump are refused");
|
|
Check(!hook.stub && !std::memcmp(image, entry.data(), entry.size()), "a refused hook changes nothing");
|
|
|
|
Check(BbEngine::Call(fn, 1, 2, 3, 4, 5, 6) == 21, "the bridge passes six arguments");
|
|
Check(BbEngine::Call(fn, 100) == 100, "registers a call does not fill are zero");
|
|
Check(BbEngine::Prepare(hook, image, 8192, 0, entry, Watch), "a matching site is prepared");
|
|
Check(!std::memcmp(image, entry.data(), entry.size()), "Prepare leaves the image alone");
|
|
BbEngine::Commit(std::span(&hook, 1));
|
|
Check(image[0] == 0xff && image[1] == 0x25 && image[14] == 0xcc && image[15] == 0xcc,
|
|
"Commit writes the jump and fills the rest with int3");
|
|
Check(BbEngine::Call(fn, 1, 2, 3, 4, 5, 6) == 21, "the hooked function runs with its arguments");
|
|
Check(seen.calls == 1, "the callback runs once per call");
|
|
Check(seen.args[0] == 1 && seen.args[2] == 3 && seen.args[5] == 6, "saved[] holds r9 down to rdi");
|
|
|
|
seen.skip = true;
|
|
seen.value = 0x1234;
|
|
Check(BbEngine::Call(fn, 1, 2, 3, 4, 5, 6) == 0x1234, "a skipped call returns saved[-1]");
|
|
seen.skip = false;
|
|
|
|
const std::uint8_t just_return[] = {0xc3};
|
|
Place(image + 16, just_return);
|
|
Check(CallWithRax(image, 0x5a5a) == 0x5a5a, "rax survives the stub");
|
|
BbEngine::Discard(hook);
|
|
Check(!hook.stub, "Discard clears the hook");
|
|
|
|
std::memcpy(image, entry.data(), entry.size());
|
|
const std::uint8_t add_doubles[] = {0xf2, 0x0f, 0x58, 0xc1, 0xc3};
|
|
Place(image + 16, add_doubles);
|
|
Check(BbEngine::Prepare(hook, image, 8192, 0, entry, Watch), "a second hook on the same site");
|
|
BbEngine::Commit(std::span(&hook, 1));
|
|
using Doubles = double (__attribute__((sysv_abi)) *)(double, double);
|
|
Check(reinterpret_cast<Doubles>(image)(1.25, 3.5) == 4.75, "xmm0-1 reach the function");
|
|
Check(seen.xmm[0] == 1.25 && seen.xmm[1] == 3.5, "SavedXmm reads xmm0-1");
|
|
BbEngine::Discard(hook);
|
|
|
|
const std::uint8_t seventh[] = {0x55, 0x48, 0x89, 0xe5, 0x48, 0x8b, 0x45, 0x10, 0x4c, 0x01, 0xc8, 0x5d, 0xc3};
|
|
Place(image + 0x100, seventh);
|
|
Check(BbEngine::Call(fn + 0x100, 1, 2, 3, 4, 5, 6, 70) == 76, "the seventh argument is on the stack");
|
|
|
|
seen = {};
|
|
Place(image + 0x300, sum);
|
|
std::uint8_t* tail = BbEngine::TailStub(Watch, fn + 0x300);
|
|
Check(tail != nullptr, "a tail stub is made");
|
|
if (tail) {
|
|
std::uint8_t* p = image + 0x210;
|
|
*p++ = 0x49;
|
|
*p++ = 0xbb;
|
|
const auto to = reinterpret_cast<std::uint64_t>(tail);
|
|
std::memcpy(p, &to, 8);
|
|
p += 8;
|
|
*p++ = 0x41;
|
|
*p++ = 0xff;
|
|
*p = 0xe3;
|
|
image[0x200] = 0xe9;
|
|
const std::int32_t rel = 0x210 - (0x200 + 5);
|
|
std::memcpy(image + 0x201, &rel, 4);
|
|
Check(BbEngine::Call(fn + 0x200, 1, 2, 3, 4, 5, 6) == 21, "the tail call's target gets the arguments");
|
|
Check(seen.calls == 1 && seen.args[4] == 5, "the tail stub calls the host first");
|
|
BbEngine::FreeStub(tail);
|
|
}
|
|
|
|
std::vector<std::uint8_t*> many;
|
|
for (int i = 0; i < 40; ++i) many.push_back(BbEngine::TailStub(Watch, fn + 0x300));
|
|
bool all = true;
|
|
for (auto* s : many) all = all && s;
|
|
Check(all, "forty stubs are made");
|
|
for (auto* s : many) BbEngine::FreeStub(s);
|
|
std::uint8_t* again = BbEngine::TailStub(Watch, fn + 0x300);
|
|
Check(again != nullptr, "a freed page is used again");
|
|
BbEngine::FreeStub(again);
|
|
|
|
const std::uint8_t five[] = {0xb8, 5, 0, 0, 0, 0xc3};
|
|
Place(image + 0x400, five);
|
|
#ifdef _WIN32
|
|
DWORD before = 0;
|
|
VirtualProtect(image, 8192, PAGE_EXECUTE_READ, &before);
|
|
#else
|
|
mprotect(image, 8192, PROT_READ | PROT_EXEC);
|
|
#endif
|
|
const std::uint8_t seven = 7;
|
|
Check(BbEngine::WriteCode(image + 0x401, &seven, 1), "WriteCode writes into read-execute code");
|
|
Check(BbEngine::Call(fn + 0x400) == 7, "the written code runs");
|
|
|
|
if (failures) return 1;
|
|
std::puts("PASS: hook guards, staged commit, saved registers and XMM, skip with saved[-1], rax kept, "
|
|
"seven-argument bridge, tail stubs, stub pages reused, WriteCode");
|
|
return 0;
|
|
}
|