feat(libc): windows way for ex handling (wip)

This commit is contained in:
boykopovar 2026-09-11 08:17:12 +03:00
parent ea3f7b5ae0
commit a1116fe948
8 changed files with 290 additions and 15 deletions

View File

@ -32,6 +32,13 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(const std::vector<std::uint8_t
auto sections = image.BuildSections();
std::array<PeDirectory, 16> directories{};
auto nextRva = image.GetEndRva();
for (const auto& header : originalHeaders) {
if (header.Type != 0x6474e550) continue;
std::vector<std::uint8_t> metadata(4);
Io::WriteU32(metadata, 0, image.GetRva(header.MappedAddress, header.FileSize));
sections.push_back({".ehmeta", nextRva, SectionRead | 0x40u, std::move(metadata)});
nextRva = AlignRva(nextRva + sections.back().Data.size());
}
directories[9] = WindowsTlsBuilder().Build(sourceElf, originalHeaders, image, sections, relocations.BaseRelocations, nextRva);
auto relocationData = relocationBuilder.BuildBaseRelocations(relocations.BaseRelocations);
if (!relocationData.empty()) {

View File

@ -49,6 +49,22 @@ void PeNidPatcher::PatchNids(std::vector<std::uint8_t>& pe, const std::string& l
const auto sizeOfOptionalHeader = Read<std::uint16_t>(pe, coffHeaderOffset + 16u);
if (sizeOfOptionalHeader == 0u) throw std::runtime_error("no optional header");
const std::size_t sectionTableOffset = coffHeaderOffset + 20u + sizeOfOptionalHeader;
const auto symbolTable = Read<std::uint32_t>(pe, coffHeaderOffset + 8u);
const auto symbolCount = Read<std::uint32_t>(pe, coffHeaderOffset + 12u);
for (std::uint16_t i = 0; i < numberOfSections; ++i) {
const auto offset = sectionTableOffset + i * sizeof(PeSectionHeader);
auto section = Read<PeSectionHeader>(pe, offset);
if (pe[offset] != '/' || !symbolTable) continue;
const std::string reference(reinterpret_cast<const char*>(pe.data() + offset), 8);
const auto stringOffset = std::stoul(reference.substr(1));
const auto name = ReadCStr(pe, static_cast<std::size_t>(symbolTable) + static_cast<std::size_t>(symbolCount) * 18 + stringOffset);
if (name == ".eh_frame") {
const char shortName[8] = ".ehfram";
std::memcpy(pe.data() + offset, shortName, 8);
}
}
const std::size_t optionalHeaderOffset = coffHeaderOffset + 20u;
const auto magic = Read<std::uint16_t>(pe, optionalHeaderOffset);

View File

@ -9,6 +9,9 @@
struct _Unwind_Context {
std::uintptr_t registers[17] {};
#ifdef _WIN32
std::uintptr_t vectorRegisters[16][2] {};
#endif
std::uintptr_t cfa {};
std::uintptr_t region {};
std::uintptr_t lsda {};

View File

@ -20,3 +20,42 @@ GCC_GLOBAL_ALIAS(_Unwind_Resume_nid_no_patch_cut, _Unwind_Resume_nid_postfix);
GCC_LOCAL_ALIAS(__cxa_free_exception, __cxa_free_exception_nid_postfix);
GCC_LOCAL_ALIAS(_ZSt9terminatev, _ZSt9terminatev_nid_postfix);
GCC_LOCAL_ALIAS(__cxa_call_terminate, __cxa_call_terminate_nid_postfix);
#ifdef _WIN32
extern "C" {
void* NativeAllocateException(std::size_t size) asm("__cxa_allocate_exception");
void* NativeAllocateException(std::size_t size) {
void* object = __cxa_allocate_exception_nid_postfix(size);
LibcException::FromObject(object)->_pad = 1;
return object;
}
void NativeFreeException(void* object) asm("__cxa_free_exception");
void NativeFreeException(void* object) { __cxa_free_exception_nid_postfix(object); }
[[noreturn]] void NativeThrow(void* object, std::type_info* type, void (*destructor)(void*)) asm("__cxa_throw");
[[noreturn]] void NativeThrow(void* object, std::type_info* type, void (*destructor)(void*)) {
__cxa_throw_nid_postfix(object, type, destructor);
}
void* NativeBeginCatch(void* exception) asm("__cxa_begin_catch");
void* NativeBeginCatch(void* exception) { return __cxa_begin_catch_nid_postfix(exception); }
void NativeEndCatch() asm("__cxa_end_catch");
void NativeEndCatch() { __cxa_end_catch_nid_postfix(); }
[[noreturn]] void NativeRethrow() asm("__cxa_rethrow");
[[noreturn]] void NativeRethrow() { __cxa_rethrow_nid_postfix(); }
[[noreturn]] void NativeResume(_Unwind_Exception* exception) asm("_Unwind_Resume");
[[noreturn]] void NativeResume(_Unwind_Exception* exception) { _Unwind_Resume_nid_postfix(exception); }
[[noreturn]] void NativeTerminate() asm("_ZSt9terminatev");
[[noreturn]] void NativeTerminate() { LibcException::Terminate(); }
[[noreturn]] void NativeCallTerminate(void* exception) asm("__cxa_call_terminate");
[[noreturn]] void NativeCallTerminate(void* exception) { __cxa_call_terminate_nid_postfix(exception); }
void* NativeExceptionPointer(void* exception) asm("__cxa_get_exception_ptr");
void* NativeExceptionPointer(void* exception) { return __cxa_get_exception_ptr_nid_postfix(exception); }
void* NativeGlobals() asm("__cxa_get_globals");
void* NativeGlobals() { return &LibcException::globals; }
void* NativeGlobalsFast() asm("__cxa_get_globals_fast");
void* NativeGlobalsFast() { return &LibcException::globals; }
unsigned NativeUncaughtExceptions() asm("__cxa_uncaught_exceptions");
unsigned NativeUncaughtExceptions() { return LibcException::globals.uncaught; }
bool NativeUncaughtException() asm("__cxa_uncaught_exception");
bool NativeUncaughtException() { return LibcException::globals.uncaught != 0; }
}
#endif

View File

@ -13,6 +13,12 @@ namespace LibcException {
struct VtableLayout { std::ptrdiff_t offset; const void* type; void (*destroy)(void*); void (*del)(void*); const char* (*whatFn)(const void*); };
const void* vtable = *static_cast<const void* const*>(primary->adjusted);
auto* layout = reinterpret_cast<const VtableLayout*>(static_cast<const char*>(vtable) - offsetof(VtableLayout, destroy));
#ifdef _WIN32
if (!primary->_pad) {
using GuestWhat = const char* (__attribute__((sysv_abi)) *)(const void*);
what = reinterpret_cast<GuestWhat>(layout->whatFn)(primary->adjusted);
} else
#endif
what = layout->whatFn(primary->adjusted);
}
int status = 0;
@ -46,7 +52,17 @@ void Release(void* object) {
auto* header = FromObject(object);
auto* allocation = AllocationOf(header);
if (allocation->references.fetch_sub(1, std::memory_order_acq_rel) == 1) {
try { if (header->destructor) header->destructor(object); }
try {
if (header->destructor) {
#ifdef _WIN32
if (!header->_pad) {
using GuestDestructor = void (__attribute__((sysv_abi)) *)(void*);
reinterpret_cast<GuestDestructor>(header->destructor)(object);
} else
#endif
header->destructor(object);
}
}
catch (...) { Terminate(); }
allocation->~Allocation();
std::free(allocation);

View File

@ -2,11 +2,35 @@
#include "prx/libc/include/specifics/linux/ElfTypes.hpp"
#include "prx/libc/src/specifics/x86_64/RegisterContext.cpp"
#if defined(__linux__)
#ifdef _WIN32
#include <windows.h>
#endif
#if defined(__linux__) || defined(_WIN32)
#ifdef _WIN32
extern "C" _Unwind_Reason_Code __gxx_personality_v0(int, _Unwind_Action, std::uint64_t, _Unwind_Exception*, _Unwind_Context*);
#endif
namespace LibcUnwind {
bool OwnPersonality(Word personality) {
#ifdef _WIN32
const auto* code = reinterpret_cast<const Byte*>(personality);
if (code[0] == 0xff && code[1] == 0x25) {
std::int32_t displacement;
std::memcpy(&displacement, code + 2, 4);
std::memcpy(&personality, code + 6 + displacement, sizeof(personality));
}
#endif
if (personality == reinterpret_cast<Word>(__gxx_personality_v0_nid_postfix)) return true;
#ifdef _WIN32
if (personality == reinterpret_cast<Word>(__gxx_personality_v0)) return true;
#endif
return false;
}
struct Lookup { Word pc; const Byte* fde {}; Word text {}; Word data {}; };
#ifdef __linux__
int FindFrame(dl_phdr_info* info, std::size_t, void* argument) {
auto& query = *static_cast<Lookup*>(argument);
const Byte* header = nullptr;
@ -40,6 +64,8 @@ int FindFrame(dl_phdr_info* info, std::size_t, void* argument) {
return 1;
}
#endif
struct Frame {
const Byte* cieBegin {};
const Byte* cieEnd {};
@ -52,9 +78,7 @@ struct Frame {
bool signal {};
};
bool DecodeFrame(_Unwind_Context& context, Frame& frame) {
Lookup query {context.registers[16] - !context.signalFrame};
dl_iterate_phdr(FindFrame, &query);
bool DecodeCandidate(_Unwind_Context& context, Frame& frame, const Lookup& query) {
if (!query.fde) return false;
const Byte* p = query.fde;
auto length = Read<std::uint32_t>(p);
@ -75,7 +99,11 @@ bool DecodeFrame(_Unwind_Context& context, Frame& frame) {
frame.codeAlign = Uleb(c);
frame.dataAlign = Sleb(c);
frame.returnRegister = version == 1 ? *c++ : Uleb(c);
#ifdef _WIN32
if (frame.returnRegister >= 17 && frame.returnRegister != 32) return false;
#else
if (frame.returnRegister >= 17) return false;
#endif
Byte pointerEncoding = 0, lsdaEncoding = 255;
if (*augmentation == 'z') {
Word size = Uleb(c);
@ -110,8 +138,73 @@ bool DecodeFrame(_Unwind_Context& context, Frame& frame) {
return true;
}
bool DecodeFrame(_Unwind_Context& context, Frame& frame) {
Lookup query {context.registers[16] - !context.signalFrame};
#ifdef __linux__
dl_iterate_phdr(FindFrame, &query);
return DecodeCandidate(context, frame, query);
#else
MEMORY_BASIC_INFORMATION memory{};
if (!VirtualQuery(reinterpret_cast<void*>(query.pc), &memory, sizeof(memory)) || memory.Type != MEM_IMAGE)
return false;
const auto* base = static_cast<const Byte*>(memory.AllocationBase);
const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(base);
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false;
const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS64*>(base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return false;
const auto* sections = IMAGE_FIRST_SECTION(nt);
for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) {
const auto& section = sections[i];
if (std::memcmp(section.Name, ".ehmeta", 8) == 0) {
const Byte* metadata = base + section.VirtualAddress;
const Byte* header = base + Read<std::uint32_t>(metadata);
if (header[0] != 1 || header[3] == 255) return false;
const Byte* p = header + 4;
Encoded(p, header[1], Word(header));
const Word count = Encoded(p, header[2]);
const auto width = EncodingSize(header[3]);
const Byte* table = p;
Word lo = 0, hi = count;
while (lo < hi) {
const Word mid = lo + (hi - lo) / 2;
p = table + mid * width * 2;
if (Encoded(p, header[3], Word(header)) <= query.pc) lo = mid + 1;
else hi = mid;
}
if (!lo) return false;
p = table + (lo - 1) * width * 2 + width;
query.fde = reinterpret_cast<const Byte*>(Encoded(p, header[3], Word(header)));
return DecodeCandidate(context, frame, query);
}
if (std::memcmp(section.Name, ".ehfram", 8) != 0) continue;
const Byte* p = base + section.VirtualAddress;
const Byte* end = p + section.Misc.VirtualSize;
while (end - p >= 8) {
const Byte* record = p;
const auto length = Read<std::uint32_t>(p);
if (!length) break;
if (length == 0xffffffff || length < 4 || Word(end - p) < length) return false;
const Byte* next = p + length;
if (Read<std::uint32_t>(p)) {
query.fde = record;
frame = {};
if (DecodeCandidate(context, frame, query)) return true;
}
p = next;
}
}
return false;
#endif
}
struct Rule { unsigned kind {}; std::intptr_t value {}; const Byte* expression {}; };
struct Rules { Rule registers[17] {}; unsigned cfaRegister {7}; std::intptr_t cfaOffset {}; const Byte* cfaExpression {}; };
#ifdef _WIN32
constexpr unsigned RuleCount = 33;
#else
constexpr unsigned RuleCount = 17;
#endif
struct Rules { Rule registers[RuleCount] {}; unsigned cfaRegister {7}; std::intptr_t cfaOffset {}; const Byte* cfaExpression {}; };
bool Instructions(const Byte* p, const Byte* end, const Frame& frame, Word target, Rules& state, const Rules& initial) {
Word location = frame.start;
@ -124,12 +217,12 @@ bool Instructions(const Byte* p, const Byte* end, const Frame& frame, Word targe
if ((opcode & 192) == 128) {
reg = opcode & 63;
auto offset = Uleb(p) * frame.dataAlign;
if (reg < 17) state.registers[reg] = {1, static_cast<std::intptr_t>(offset)};
if (reg < RuleCount) state.registers[reg] = {1, static_cast<std::intptr_t>(offset)};
continue;
}
if ((opcode & 192) == 192) {
reg = opcode & 63;
if (reg < 17) state.registers[reg] = initial.registers[reg];
if (reg < RuleCount) state.registers[reg] = initial.registers[reg];
continue;
}
switch (opcode) {
@ -141,12 +234,12 @@ bool Instructions(const Byte* p, const Byte* end, const Frame& frame, Word targe
case 5: case 17: case 20: case 21: {
reg = Uleb(p);
auto offset = (opcode == 17 || opcode == 21 ? Sleb(p) : std::intptr_t(Uleb(p))) * frame.dataAlign;
if (reg < 17) state.registers[reg] = {unsigned(opcode >= 20 ? 4 : 1), offset};
if (reg < RuleCount) state.registers[reg] = {unsigned(opcode >= 20 ? 4 : 1), offset};
break;
}
case 6: reg = Uleb(p); if (reg < 17) state.registers[reg] = initial.registers[reg]; break;
case 7: case 8: reg = Uleb(p); if (reg < 17) state.registers[reg] = {unsigned(opcode == 7 ? 5 : 0)}; break;
case 9: { reg = Uleb(p); auto other = Uleb(p); if (reg < 17) state.registers[reg] = {2, std::intptr_t(other)}; break; }
case 6: reg = Uleb(p); if (reg < RuleCount) state.registers[reg] = initial.registers[reg]; break;
case 7: case 8: reg = Uleb(p); if (reg < RuleCount) state.registers[reg] = {unsigned(opcode == 7 ? 5 : 0)}; break;
case 9: { reg = Uleb(p); auto other = Uleb(p); if (reg < RuleCount) state.registers[reg] = {2, std::intptr_t(other)}; break; }
case 10: if (depth == 16) return false; saved[depth++] = state; break;
case 11: if (!depth) return false; state = saved[--depth]; break;
case 12: case 18:
@ -157,7 +250,7 @@ bool Instructions(const Byte* p, const Byte* end, const Frame& frame, Word targe
case 15: { state.cfaExpression = p; auto size = Uleb(p); p += size; break; }
case 16: case 22: {
reg = Uleb(p); const Byte* expr = p; auto size = Uleb(p); p += size;
if (reg < 17) state.registers[reg] = {unsigned(opcode == 16 ? 3 : 6), 0, expr};
if (reg < RuleCount) state.registers[reg] = {unsigned(opcode == 16 ? 3 : 6), 0, expr};
break;
}
case 46: Uleb(p); break;
@ -223,6 +316,13 @@ bool GetRules(_Unwind_Context& context, Frame& frame, Rules& rules) {
if (!DecodeFrame(context, frame)) return false;
Rules initial;
if (!Instructions(frame.cieBegin, frame.cieEnd, frame, ~Word(0), initial, {})) return false;
#ifdef _WIN32
if (frame.returnRegister == 32) {
initial.registers[16] = initial.registers[32];
initial.registers[32] = {};
frame.returnRegister = 16;
}
#endif
rules = initial;
if (!Instructions(frame.begin, frame.end, frame, context.registers[16] - !context.signalFrame, rules, initial)) return false;
if (rules.cfaExpression) return Expression(rules.cfaExpression, context, 0, context.cfa);
@ -250,6 +350,17 @@ bool Step(_Unwind_Context& context) {
case 5: next.registers[i] = 0; break;
}
}
#ifdef _WIN32
for (unsigned i = 17; i < RuleCount; ++i) {
const auto& rule = rules.registers[i];
auto* destination = next.vectorRegisters[i - 17];
if (rule.kind == 1) std::memcpy(destination, reinterpret_cast<void*>(context.cfa + rule.value), 16);
else if (rule.kind == 2 && rule.value >= 17 && rule.value < RuleCount)
std::memcpy(destination, context.vectorRegisters[rule.value - 17], 16);
else if (rule.kind == 5) std::memset(destination, 0, 16);
else if (rule.kind != 0) return false;
}
#endif
next.registers[7] = context.cfa;
next.registers[16] = next.registers[frame.returnRegister];
next.signalFrame = frame.signal;
@ -277,7 +388,7 @@ _Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* excepti
if (result != _URC_NO_REASON) return result;
} else if (context.cfa == exception->private_2) actions = _Unwind_Action(actions | _UA_HANDLER_FRAME);
if (frame.personality) {
if (frame.personality != reinterpret_cast<Word>(__gxx_personality_v0_nid_postfix)) return _URC_FATAL_PHASE2_ERROR;
if (!OwnPersonality(frame.personality)) return _URC_FATAL_PHASE2_ERROR;
auto result = __gxx_personality_v0_nid_postfix(1, actions, exception->exception_class, exception, &context);
if (result == _URC_INSTALL_CONTEXT) LibcRestoreRegisters(context.registers);
if (result != _URC_CONTINUE_UNWIND) return _URC_FATAL_PHASE2_ERROR;
@ -306,7 +417,7 @@ _Unwind_Reason_Code APS5_VABI _Unwind_RaiseException_nid_postfix(_Unwind_Excepti
LibcUnwind::Frame frame; LibcUnwind::Rules rules;
if (!LibcUnwind::GetRules(context, frame, rules)) return _URC_END_OF_STACK;
if (frame.personality) {
if (frame.personality != reinterpret_cast<std::uintptr_t>(__gxx_personality_v0_nid_postfix)) return _URC_FATAL_PHASE1_ERROR;
if (!LibcUnwind::OwnPersonality(frame.personality)) return _URC_FATAL_PHASE1_ERROR;
auto result = __gxx_personality_v0_nid_postfix(1, _UA_SEARCH_PHASE, exception->exception_class, exception, &context);
if (result == _URC_HANDLER_FOUND) {
exception->private_2 = context.cfa;

View File

@ -26,6 +26,57 @@ asm(
"movq 104(%r10),%r13\nmovq 112(%r10),%r14\nmovq 120(%r10),%r15\njmp *%r11\n"
".size LibcRestoreRegisters,.-LibcRestoreRegisters\n"
);
#elif defined(_WIN32) && defined(__x86_64__)
asm(
".text\n"
".globl LibcCaptureRegisters\n"
"LibcCaptureRegisters:\n"
"movq %rax,0(%rcx)\nmovq %rdx,8(%rcx)\nmovq %rcx,16(%rcx)\nmovq %rbx,24(%rcx)\n"
"movq %rsi,32(%rcx)\nmovq %rdi,40(%rcx)\nmovq %rbp,48(%rcx)\n"
"leaq 8(%rsp),%rax\nmovq %rax,56(%rcx)\n"
"movq %r8,64(%rcx)\nmovq %r9,72(%rcx)\nmovq %r10,80(%rcx)\nmovq %r11,88(%rcx)\n"
"movq %r12,96(%rcx)\nmovq %r13,104(%rcx)\nmovq %r14,112(%rcx)\nmovq %r15,120(%rcx)\n"
"movdqu %xmm0,136(%rcx)\n"
"movdqu %xmm1,152(%rcx)\n"
"movdqu %xmm2,168(%rcx)\n"
"movdqu %xmm3,184(%rcx)\n"
"movdqu %xmm4,200(%rcx)\n"
"movdqu %xmm5,216(%rcx)\n"
"movdqu %xmm6,232(%rcx)\n"
"movdqu %xmm7,248(%rcx)\n"
"movdqu %xmm8,264(%rcx)\n"
"movdqu %xmm9,280(%rcx)\n"
"movdqu %xmm10,296(%rcx)\n"
"movdqu %xmm11,312(%rcx)\n"
"movdqu %xmm12,328(%rcx)\n"
"movdqu %xmm13,344(%rcx)\n"
"movdqu %xmm14,360(%rcx)\n"
"movdqu %xmm15,376(%rcx)\n"
"movq (%rsp),%rax\nmovq %rax,128(%rcx)\nret\n"
".globl LibcRestoreRegisters\n"
"LibcRestoreRegisters:\n"
"movq %rcx,%r10\nmovq 128(%r10),%r11\n"
"movdqu 136(%r10),%xmm0\n"
"movdqu 152(%r10),%xmm1\n"
"movdqu 168(%r10),%xmm2\n"
"movdqu 184(%r10),%xmm3\n"
"movdqu 200(%r10),%xmm4\n"
"movdqu 216(%r10),%xmm5\n"
"movdqu 232(%r10),%xmm6\n"
"movdqu 248(%r10),%xmm7\n"
"movdqu 264(%r10),%xmm8\n"
"movdqu 280(%r10),%xmm9\n"
"movdqu 296(%r10),%xmm10\n"
"movdqu 312(%r10),%xmm11\n"
"movdqu 328(%r10),%xmm12\n"
"movdqu 344(%r10),%xmm13\n"
"movdqu 360(%r10),%xmm14\n"
"movdqu 376(%r10),%xmm15\n"
"movq 0(%r10),%rax\nmovq 8(%r10),%rdx\nmovq 16(%r10),%rcx\nmovq 24(%r10),%rbx\n"
"movq 32(%r10),%rsi\nmovq 40(%r10),%rdi\nmovq 48(%r10),%rbp\nmovq 56(%r10),%rsp\n"
"movq 64(%r10),%r8\nmovq 72(%r10),%r9\nmovq 96(%r10),%r12\n"
"movq 104(%r10),%r13\nmovq 112(%r10),%r14\nmovq 120(%r10),%r15\njmp *%r11\n"
);
#else
void LibcCaptureRegisters(std::uintptr_t*) { std::abort(); }
[[noreturn]] void LibcRestoreRegisters(const std::uintptr_t*) { std::abort(); }

View File

@ -0,0 +1,32 @@
#include <stdexcept>
#include <cstdio>
#include <cstring>
extern "C" void NotImplemented_nid_no_patch(const char*);
static int destroyed;
struct Guard { ~Guard() { ++destroyed; } };
__attribute__((noinline)) static void ThrowNested() {
Guard guard;
throw std::runtime_error("native own unwind");
}
__attribute__((noinline)) static void Rethrow() {
Guard guard;
try { ThrowNested(); }
catch (const std::runtime_error&) { throw; }
}
int main() {
try {
Rethrow();
return 1;
} catch (const std::runtime_error& error) {
if (std::strcmp(error.what(), "native own unwind") || destroyed != 2) return 2;
}
try { NotImplemented_nid_no_patch("cross DLL"); }
catch (const std::runtime_error& error) {
if (std::strcmp(error.what(), "cross DLL not implemented")) return 3;
std::puts("Own Windows exceptions: catch, rethrow, destructors, cross DLL passed");
return 0;
}
return 4;
}