Linux: port the emulator to a working state on Linux (#117)

Linux: port the emulator to a working state
This commit is contained in:
Stefanos Costa authored and GitHub committed 2026-07-29 00:25:58 +02:00
1 parent 3dc793d859
commit 2b9cba457e
30 files changed
+2320 -381

No files matched your search

+126 -39
View File
@@ -36,6 +36,12 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <dlfcn.h>
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
#include <sys/uio.h>
#include <unistd.h>
#endif
#endif
namespace Libs::LibKernel {
@@ -354,6 +360,7 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
register uintptr_t guest_rbp_reg asm("r15") = guest_rbp;
#endif
#if defined(__APPLE__) || KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
asm volatile(
"pushq %%r12\n\t"
"pushq %%r13\n\t"
@@ -377,6 +384,22 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
"xmm13", "xmm14", "xmm15");
#else
// Clobbers prevent inputs from being allocated to r12/r13.
asm volatile("movq %%rsp, %%r12\n\t"
"movq %%rbp, %%r13\n\t"
"movq %[guest_rsp], %%rsp\n\t"
"movq %[guest_rbp], %%rbp\n\t"
"callq *%[func]\n\t"
"movq %%r13, %%rbp\n\t"
"movq %%r12, %%rsp\n\t"
:
: [func] "r"(func), "D"(params),
"S"(atexit_func), [guest_rsp] "r"(guest_rsp), [guest_rbp] "r"(guest_rbp)
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "r12", "r13",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8",
"xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
#endif
return;
}
@@ -387,6 +410,7 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
register uintptr_t guest_rbp_reg asm("r14") = reinterpret_cast<uintptr_t>(guest_root_frame);
#endif
#if defined(__APPLE__) || KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
asm volatile("pushq %%r12\n\t"
"pushq %%r13\n\t"
"movq %%rbp, %%r12\n\t"
@@ -406,6 +430,19 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1",
"xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
"xmm12", "xmm13", "xmm14", "xmm15");
#else
// Keep inputs out of r12.
asm volatile("movq %%rbp, %%r12\n\t"
"movq %[guest_rbp], %%rbp\n\t"
"callq *%[func]\n\t"
"movq %%r12, %%rbp\n\t"
:
: [func] "r"(func), "D"(params),
"S"(atexit_func), [guest_rbp] "r"(guest_root_frame)
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "r12", "xmm0",
"xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10",
"xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
#endif
#else
(void)stack_top;
reinterpret_cast<entry_func_t>(addr)(params, atexit_func);
@@ -518,6 +555,69 @@ void KYTY_SYSV_ABI SysStackWalkX86(uint64_t rbp, void** stack, int* depth) {
SysStackWalkX86(rbp, rbp, stack, depth);
}
// Probe diagnostic ranges without raising another fault.
static bool IsReadableRange(uint64_t addr, uint64_t size) {
if (addr == 0 || size == 0) {
return false;
}
const uint64_t end = addr + size;
if (end < addr) {
return false;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t current = addr;
while (current < end) {
MEMORY_BASIC_INFORMATION mbi {};
if (VirtualQuery(reinterpret_cast<const void*>(current), &mbi, sizeof(mbi)) == 0 ||
mbi.State != MEM_COMMIT || (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
return false;
}
const auto region_end = reinterpret_cast<uint64_t>(mbi.BaseAddress) + mbi.RegionSize;
if (region_end <= current) {
return false;
}
current = std::min(region_end, end);
}
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
const auto page_size = static_cast<uint64_t>(sysconf(_SC_PAGESIZE));
if (page_size == 0) {
return false;
}
for (uint64_t current = addr; current < end;) {
uint8_t probe = 0;
iovec local {&probe, sizeof(probe)};
iovec remote {reinterpret_cast<void*>(current), sizeof(probe)};
if (process_vm_readv(getpid(), &local, 1, &remote, 1, 0) !=
static_cast<ssize_t>(sizeof(probe))) {
return false;
}
const uint64_t next = (current & ~(page_size - 1)) + page_size;
if (next <= current) { // wrapped at the top of the address space
break;
}
current = next;
}
#else
(void)end;
#endif
return true;
}
static bool IsDumpableRange(uint64_t addr, uint64_t size) {
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
return IsReadableRange(addr, size);
#else
(void)size;
return addr != 0;
#endif
}
static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exception_info) {
const auto* info = &exception_info;
@@ -563,6 +663,12 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
LOGF("exception module: %s\n", module_name);
}
}
#else
Dl_info module_info {};
if (::dladdr(reinterpret_cast<void*>(info->exception_address), &module_info) != 0 &&
module_info.dli_fname != nullptr) {
LOGF("exception module: %s\n", module_info.dli_fname);
}
#endif
if (info->exception_address != 0) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -586,12 +692,18 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
LOGF("code-32: unavailable\n");
}
#else
LOGF("code-32:");
for (uint64_t i = 0; i < 64; i++) {
LOGF(" %02" PRIx32, static_cast<uint32_t>(*reinterpret_cast<const uint8_t*>(
info->exception_address + i - 32)));
const auto fault_addr = info->exception_address;
const auto dump_start = (fault_addr >= 32 ? fault_addr - 32 : fault_addr);
if (IsReadableRange(dump_start, 64)) {
auto* dump_ptr = reinterpret_cast<const uint8_t*>(dump_start);
LOGF("code-32:");
for (uint32_t i = 0; i < 64; i++) {
LOGF(" %02" PRIx32, static_cast<uint32_t>(dump_ptr[i]));
}
LOGF("\n");
} else {
LOGF("code-32: unavailable\n");
}
LOGF("\n");
#endif
} else {
LOGF("code: unavailable\n");
@@ -609,36 +721,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
LOGF("regs: r12=%016" PRIx64 " r13=%016" PRIx64 " r14=%016" PRIx64 " r15=%016" PRIx64 "\n",
info->r12, info->r13, info->r14, info->r15);
auto is_readable_range = [](uint64_t addr, uint64_t size) {
if (addr == 0 || size == 0) {
return false;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t current = addr;
uint64_t end = addr + size;
if (end < addr) {
return false;
}
while (current < end) {
MEMORY_BASIC_INFORMATION mbi {};
if (VirtualQuery(reinterpret_cast<const void*>(current), &mbi, sizeof(mbi)) == 0 ||
mbi.State != MEM_COMMIT || (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
return false;
}
const auto region_end = reinterpret_cast<uint64_t>(mbi.BaseAddress) + mbi.RegionSize;
if (region_end <= current) {
return false;
}
current = std::min(region_end, end);
}
#else
(void)addr;
(void)size;
#endif
return true;
};
if (is_readable_range(info->rsp, 16u * sizeof(uint64_t))) {
if (IsReadableRange(info->rsp, 16u * sizeof(uint64_t))) {
auto* stack = reinterpret_cast<const uint64_t*>(info->rsp);
LOGF("stack:");
for (uint64_t i = 0; i < 16; i++) {
@@ -669,6 +752,9 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
#else
auto* dump_ptr = reinterpret_cast<const uint8_t*>(addr >= 16 ? addr - 16 : addr);
const auto dump_size = 32u;
if (!IsReadableRange(reinterpret_cast<uint64_t>(dump_ptr), dump_size)) {
return;
}
#endif
LOGF("%s code: addr=%016" PRIx64 ", off=%016" PRIx64 ", module=%s:", name, addr,
@@ -684,7 +770,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
dump_guest_code("guest rbx[0]", info->rbx);
dump_guest_code("guest rcx[0]", info->rcx);
dump_guest_code("guest rsi[0]", info->rsi);
if (info->rsp != 0) {
if (IsDumpableRange(info->rsp, 16u * sizeof(uint64_t))) {
auto* stack = reinterpret_cast<const uint64_t*>(info->rsp);
for (uint64_t i = 0; i < 16; i++) {
char name[32] {};
@@ -713,7 +799,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
}
}
auto dump_guest_qwords = [&is_readable_range](const char* name, uint64_t addr) {
auto dump_guest_qwords = [](const char* name, uint64_t addr) {
if (addr == 0) {
LOGF("%s = 0\n", name);
return;
@@ -728,7 +814,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
}
#endif
if (!is_readable_range(addr, 8u * sizeof(uint64_t))) {
if (!IsReadableRange(addr, 8u * sizeof(uint64_t))) {
LOGF("%s = %016" PRIx64 " (unmapped)\n", name, addr);
return;
}
@@ -753,7 +839,8 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
dump_guest_qwords("guest r14", info->r14);
dump_guest_qwords("guest r15", info->r15);
if (info->exception_address == 0x000000090064364e && info->rbx != 0) {
if (info->exception_address == 0x000000090064364e &&
IsDumpableRange(info->rbx, sizeof(uint64_t))) {
auto* local = reinterpret_cast<const uint64_t*>(info->rbx);
dump_guest_qwords("vorbis obj", local[0]);
dump_guest_qwords("vorbis len", info->rcx);
+126 -10
View File
@@ -4,20 +4,13 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif !defined(__APPLE__)
#include <sched.h>
#include <ucontext.h>
#endif
namespace Loader::X64InstructionEmulator {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static M128A* GetContextXmm(PCONTEXT context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
return &context->Xmm0 + index;
}
static uint64_t ExtractBitField(uint64_t value, uint32_t length, uint32_t index) {
length &= 0x3fu;
index &= 0x3fu;
@@ -63,6 +56,16 @@ static uint64_t InsertBitField(uint64_t dst, uint64_t src, uint32_t length, uint
return (dst & ~shifted) | src_shifted;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static M128A* GetContextXmm(PCONTEXT context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
return &context->Xmm0 + index;
}
static bool TryEmulateSse4a(PCONTEXT context) {
if (context == nullptr) {
return false;
@@ -140,12 +143,125 @@ static bool TryEmulateMonitorxMwaitx(PCONTEXT context) {
return true;
}
#elif !defined(__APPLE__)
// Linux signal contexts expose registers through ucontext_t.
static uint32_t* GetContextXmm(ucontext_t* context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
auto* fpregs = context->uc_mcontext.fpregs;
if (fpregs == nullptr) {
return nullptr;
}
return static_cast<uint32_t*>(fpregs->_xmm[index].element);
}
static uint64_t GetXmmLow(const uint32_t* xmm) {
return static_cast<uint64_t>(xmm[0]) | (static_cast<uint64_t>(xmm[1]) << 32u);
}
static void SetXmmLow(uint32_t* xmm, uint64_t value) {
xmm[0] = static_cast<uint32_t>(value);
xmm[1] = static_cast<uint32_t>(value >> 32u);
}
static void SetXmmHigh(uint32_t* xmm, uint64_t value) {
xmm[2] = static_cast<uint32_t>(value);
xmm[3] = static_cast<uint32_t>(value >> 32u);
}
static bool TryEmulateSse4a(ucontext_t* context) {
if (context == nullptr) {
return false;
}
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
const uint8_t prefix = rip[0];
if (prefix != 0x66 && prefix != 0xf2) {
return false;
}
size_t offset = 1;
uint8_t rex = 0;
if ((rip[offset] & 0xf0u) == 0x40u) {
rex = rip[offset];
offset++;
}
if (rip[offset] != 0x0f || rip[offset + 1] != 0x78) {
return false;
}
auto modrm = rip[offset + 2];
if ((modrm & 0xc0u) != 0xc0u) {
return false;
}
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
const uint8_t length = rip[offset + 3];
const uint8_t index = rip[offset + 4];
// AMD SSE4a immediate-form EXTRQ/INSERTQ.
if (prefix == 0x66) {
auto* dst = GetContextXmm(context, rm);
if (dst == nullptr) {
return false;
}
SetXmmLow(dst, ExtractBitField(GetXmmLow(dst), length, index));
SetXmmHigh(dst, 0);
rip_reg += static_cast<greg_t>(offset + 5);
return true;
}
auto* dst = GetContextXmm(context, reg);
auto* src = GetContextXmm(context, rm);
if (dst == nullptr || src == nullptr) {
return false;
}
SetXmmLow(dst, InsertBitField(GetXmmLow(dst), GetXmmLow(src), length, index));
rip_reg += static_cast<greg_t>(offset + 5);
return true;
}
static bool TryEmulateMonitorxMwaitx(ucontext_t* context) {
if (context == nullptr) {
return false;
}
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
return false;
}
// Approximate AMD MONITORX/MWAITX as no-op/yield.
if (rip[2] == 0xfb) {
::sched_yield();
}
rip_reg += 3;
return true;
}
#endif
bool TryEmulate(void* native_context) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
auto* context = static_cast<PCONTEXT>(native_context);
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context);
#elif !defined(__APPLE__)
auto* context = static_cast<ucontext_t*>(native_context);
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context);
#else
(void)native_context;
return false;