Files
niakw--Prospero.Eden-Encore/tools/check-jit-allocator.py
T

142 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Exercise the production Xbyak allocator with real RW/RX mappings."""
from pathlib import Path
import subprocess
import sys
import tempfile
root = Path(__file__).resolve().parents[1]
scratch = Path((root / '.local/headless-cache').read_text().strip())
code = (scratch / 'native-local/headless/block_of_code.cpp').read_text()
assert ', EdenJitAllocator())' in code
if sys.platform == 'darwin':
# The runtime harness below is Linux/x86 specific: memfd aliases, mincore
# semantics, GNU ld --wrap and generated x86 execution. The injected
# production allocator is still required above and is compiled for PS5 by
# the native target; the behavioral harness remains mandatory in Linux CI.
print('Production JIT allocator injection PASS (Linux/x86 alias runtime harness deferred to CI)')
raise SystemExit(0)
source = r'''
#include "jit-allocator.h"
#include <cassert>
#include <cerrno>
#include <limits>
#include <memory>
#include <vector>
#include <csignal>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#ifdef EDEN_JIT_ALIAS_NATIVE
namespace Common { std::size_t DenseJitDirectBytes() noexcept; }
static unsigned fail_stage, stage;
static std::vector<void*> views;
static std::vector<int> handles;
static bool enabled;
extern "C" std::int64_t sceKernelGetDirectMemorySize() { return std::int64_t{1}<<40; }
extern "C" int sceKernelEnableDmemAliasing() { enabled=true; return 0; }
// The same source file also holds the heap's growing range and the sparse tables; this check
// does not use them (tools/check-heap-growth.py and eden-memory-check do).
extern "C" int sceKernelDebugOutText(int, const char*) { return 0; }
extern "C" int sceKernelReserveVirtualRange(void**, size_t, int, size_t) { return std::int32_t(0x80020001u); }
extern "C" int sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, size_t size,
size_t, int type, std::int64_t* out) {
if (++stage==fail_stage) return std::int32_t(0x80020001u);
assert(type==12);
int fd=memfd_create("native-direct-mock",MFD_CLOEXEC); assert(fd>=0);
handles.push_back(fd); assert(ftruncate(fd,size)==0);
*out=std::int64_t(fd)<<32; return 0;
}
extern "C" int sceKernelReleaseDirectMemory(std::int64_t physical, size_t) {
return close(physical>>32);
}
extern "C" int sceKernelMapDirectMemory(void** out, size_t size, int prot, int,
std::int64_t physical, size_t) {
if (++stage==fail_stage) return std::int32_t(0x80020001u);
const auto offset=physical & 0xffffffff;
assert(!(prot&PROT_EXEC) && (!offset || enabled));
assert(reinterpret_cast<uintptr_t>(*out)==0x1000000000ull);
const bool low = fail_stage==5 && stage==2;
*out=mmap(low ? reinterpret_cast<void*>(0x280000000ull) : nullptr,size,prot,
MAP_SHARED | (low ? MAP_FIXED_NOREPLACE : 0),physical>>32,offset);
assert(*out!=MAP_FAILED); views.push_back(*out); return 0;
}
extern "C" int __real_mprotect(void*,size_t,int);
extern "C" int __wrap_mprotect(void* p,size_t size,int prot) {
assert(!((prot&PROT_WRITE) && (prot&PROT_EXEC)));
if (++stage==fail_stage) { errno=EPERM; return -1; }
return __real_mprotect(p,size,prot);
}
#endif
void fault(const unsigned char* p, bool write) {
auto child=fork(); assert(child>=0);
if (!child) {
if (write) *const_cast<volatile unsigned char*>(p)=0;
else reinterpret_cast<void(*)()>(const_cast<unsigned char*>(p))();
_exit(0);
}
int status; assert(waitpid(child,&status,0)==child);
assert(WIFSIGNALED(status) && WTERMSIG(status)==SIGSEGV);
}
int main() {
auto* allocator = EdenJitAllocator();
assert(!allocator->alloc(std::numeric_limits<std::size_t>::max()));
allocator->free(nullptr);
#ifdef EDEN_JIT_ALIAS_NATIVE
assert(Common::DenseJitDirectBytes() == 0);
#endif
std::vector<std::unique_ptr<Xbyak::CodeGenerator>> caches;
std::vector<const unsigned char*> pointers;
const auto page = sysconf(_SC_PAGESIZE);
for (auto mib : {512, 64, 64, 16}) {
auto code = std::make_unique<Xbyak::CodeGenerator>(std::size_t(mib)*1024*1024,
Xbyak::DontSetProtectRWE, allocator);
code->mov(code->eax, 42); code->ret();
auto* p = code->getCode(); pointers.push_back(p);
auto* writable=code->writableAddress(p); pointers.push_back(writable);
assert(code->hasWritableAlias() && p!=writable);
assert(code->getCode<int(*)()>()()==42);
fault(p,true); fault(writable,false);
code->rewrite(1,84,4);
assert(code->getCode<int(*)()>()()==84);
caches.push_back(std::move(code));
#ifdef EDEN_JIT_ALIAS_NATIVE
assert(Common::DenseJitDirectBytes() > 0);
#endif
}
caches.clear();
#ifdef EDEN_JIT_ALIAS_NATIVE
assert(Common::DenseJitDirectBytes() == 0);
#endif
for (auto* p : pointers) {
unsigned char state;
errno=0;
assert(mincore(const_cast<unsigned char*>(p), page, &state)==-1 && errno==ENOMEM);
}
#ifdef EDEN_JIT_ALIAS_NATIVE
for (fail_stage=1; fail_stage<=5; ++fail_stage) {
stage=0; views.clear(); handles.clear();
// All five fault stages MUST fail allocation without returning
// a RW/NX address as executable code. A previous test explicitly
// accepted that unsafe fallback on alias-map/mprotect failure.
assert(!allocator->alloc(page));
for (int fd : handles) { errno=0; assert(fcntl(fd,F_GETFD)==-1 && errno==EBADF); }
for (void* p : views) { unsigned char state; errno=0; assert(mincore(p,page,&state)==-1 && errno==ENOMEM); }
assert(Common::DenseJitDirectBytes() == 0);
}
#endif
}
'''
with tempfile.TemporaryDirectory() as temp:
cpp=Path(temp)/'check.cpp'; exe=Path(temp)/'check'
cpp.write_text(source)
for native_mock in (False, True):
subprocess.run(['clang++-18','-std=c++20','-O2','-I'+str(root/'headless'),
'-I'+str(scratch/'native-local/headless/include'),
'-I'+str(scratch/'source/.cache/cpm/xbyak/v7.40.1'),str(cpp),
*(['-DEDEN_JIT_ALIAS_NATIVE=1','-DPS5_NATIVE=1','-Wl,--wrap=mprotect'] if native_mock else []),
str(root/'src/memory_pages.cpp'),'-o',str(exe)],check=True)
subprocess.run([str(exe)],check=True, cwd=temp)
print('Production JIT aliases: 512/64/64/16 MiB, overflow, RX write/RW execute faults, live rewrite, both-view release and native API partial-failure cleanup PASS (host backend/mocked native APIs)')