feat(libc): implement exception unwinding + array cleanup

This commit is contained in:
boykopovar 2026-09-05 19:54:11 +03:00
parent 6fcbf1f829
commit 36c874e296
5 changed files with 176 additions and 63 deletions

View File

@ -1,7 +1,5 @@
#include "exception/Unwind.cpp"
#include "exception/Runtime.cpp"
#include "exception/Personality.cpp"
#include "exception/StandardExceptions.cpp"
#include "exception/Runtime.hpp"
#include <thread>
#include <cstddef>
#include <cxxabi.h>
#include <cstdint>
@ -17,57 +15,44 @@
#include <functional>
#include <unwind.h>
namespace LibcGuard {
thread_local std::uint64_t* active[64] {};
thread_local std::size_t depth {};
}
extern "C" {
int __cxa_guard_acquire_nid_postfix(std::uint64_t* guardObject) {
(void)guardObject;
return 0;
auto* bytes = reinterpret_cast<unsigned char*>(guardObject);
std::atomic_ref<unsigned char> initialized(bytes[0]);
std::atomic_ref<unsigned char> lock(bytes[1]);
if (initialized.load(std::memory_order_acquire)) return 0;
for (std::size_t i = 0; i < LibcGuard::depth; ++i)
if (LibcGuard::active[i] == guardObject) LibcException::Terminate();
for (;;) {
unsigned char expected = 0;
if (lock.compare_exchange_weak(expected, 1, std::memory_order_acquire)) break;
if (initialized.load(std::memory_order_acquire)) return 0;
std::this_thread::yield();
}
if (initialized.load(std::memory_order_acquire)) { lock.store(0, std::memory_order_release); return 0; }
if (LibcGuard::depth == 64) LibcException::Terminate();
LibcGuard::active[LibcGuard::depth++] = guardObject;
return 1;
}
void __cxa_guard_release_nid_postfix(std::uint64_t* guardObject) { (void)guardObject; }
void __cxa_guard_abort_nid_postfix(std::uint64_t* guardObject) { (void)guardObject; }
void* __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*)) {
(void)n; (void)s; (void)p; (void)ctor; (void)dtor;
return nullptr;
void __cxa_guard_release_nid_postfix(std::uint64_t* guardObject) {
if (!LibcGuard::depth || LibcGuard::active[LibcGuard::depth - 1] != guardObject) LibcException::Terminate();
--LibcGuard::depth;
auto* bytes = reinterpret_cast<unsigned char*>(guardObject);
std::atomic_ref<unsigned char>(bytes[0]).store(1, std::memory_order_release);
std::atomic_ref<unsigned char>(bytes[1]).store(0, std::memory_order_release);
}
void* __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*)) {
(void)n; (void)s; (void)p; (void)ctor; (void)dtor; (void)alloc; (void)dealloc;
return nullptr;
}
void* __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*, std::size_t)) {
(void)n; (void)s; (void)p; (void)ctor; (void)dtor; (void)alloc; (void)dealloc;
return nullptr;
}
void __cxa_vec_ctor_nid_postfix(void* arr, std::size_t n, std::size_t s, void (*ctor)(void*), void (*dtor)(void*)) {
(void)arr; (void)n; (void)s; (void)ctor; (void)dtor;
}
void __cxa_vec_dtor_nid_postfix(void* arr, std::size_t n, std::size_t s, void (*dtor)(void*)) {
(void)arr; (void)n; (void)s; (void)dtor;
}
void __cxa_vec_cleanup_nid_postfix(void* arr, std::size_t n, std::size_t s, void (*dtor)(void*)) {
(void)arr; (void)n; (void)s; (void)dtor;
}
void __cxa_vec_delete_nid_postfix(void* arr, std::size_t s, std::size_t p, void (*dtor)(void*)) {
(void)arr; (void)s; (void)p; (void)dtor;
}
void __cxa_vec_delete2_nid_postfix(void* arr, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*)) {
(void)arr; (void)s; (void)p; (void)dtor; (void)dealloc;
}
void __cxa_vec_delete3_nid_postfix(void* arr, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*, std::size_t)) {
(void)arr; (void)s; (void)p; (void)dtor; (void)dealloc;
}
void __cxa_vec_cctor_nid_postfix(void* dst, void* src, std::size_t n, std::size_t s, void (*cctor)(void*, void*), void (*dtor)(void*)) {
(void)dst; (void)src; (void)n; (void)s; (void)cctor; (void)dtor;
void __cxa_guard_abort_nid_postfix(std::uint64_t* guardObject) {
if (!LibcGuard::depth || LibcGuard::active[LibcGuard::depth - 1] != guardObject) LibcException::Terminate();
--LibcGuard::depth;
auto* bytes = reinterpret_cast<unsigned char*>(guardObject);
std::atomic_ref<unsigned char>(bytes[1]).store(0, std::memory_order_release);
}
void* __cxa_demangle_nid_postfix(const char* mangled, char* buf, std::size_t* len, int* status) {

View File

@ -0,0 +1,102 @@
#include "Runtime.hpp"
#include <limits>
extern "C" {
void __cxa_vec_cleanup_nid_postfix(void* array, std::size_t count, std::size_t size, void (*destroy)(void*)) {
if (!destroy) return;
try {
while (count) destroy(static_cast<unsigned char*>(array) + --count * size);
} catch (...) { LibcException::Terminate(); }
}
void __cxa_vec_ctor_nid_postfix(void* array, std::size_t count, std::size_t size, void (*construct)(void*), void (*destroy)(void*)) {
if (!construct) return;
std::size_t complete = 0;
try {
for (; complete < count; ++complete) construct(static_cast<unsigned char*>(array) + complete * size);
} catch (...) {
__cxa_vec_cleanup_nid_postfix(array, complete, size, destroy);
throw;
}
}
void __cxa_vec_cctor_nid_postfix(void* destination, void* source, std::size_t count, std::size_t size, void (*copy)(void*, void*), void (*destroy)(void*)) {
if (!copy) return;
std::size_t complete = 0;
try {
for (; complete < count; ++complete)
copy(static_cast<unsigned char*>(destination) + complete * size, static_cast<unsigned char*>(source) + complete * size);
} catch (...) {
__cxa_vec_cleanup_nid_postfix(destination, complete, size, destroy);
throw;
}
}
void __cxa_vec_dtor_nid_postfix(void* array, std::size_t count, std::size_t size, void (*destroy)(void*)) {
if (!destroy) return;
bool unwinding = LibcException::globals.uncaught != 0;
try {
while (count) destroy(static_cast<unsigned char*>(array) + --count * size);
} catch (...) {
if (unwinding) LibcException::Terminate();
__cxa_vec_cleanup_nid_postfix(array, count, size, destroy);
throw;
}
}
}
namespace LibcException {
std::size_t ArraySize(std::size_t count, std::size_t size, std::size_t padding) {
if ((padding && padding < sizeof(std::size_t)) || (size && count > (std::numeric_limits<std::size_t>::max() - padding) / size))
__cxa_throw_bad_array_new_length_nid_postfix();
return count * size + padding;
}
void* ArrayAllocate(std::size_t size) {
void* result = std::malloc(size ? size : 1);
if (!result) _ZSt11_Xbad_allocv_nid_postfix();
return result;
}
template<class Deallocate>
void* NewArray(std::size_t count, std::size_t size, std::size_t padding, void (*construct)(void*), void (*destroy)(void*), void* (*allocate)(std::size_t), Deallocate deallocate) {
std::size_t total = ArraySize(count, size, padding);
auto* storage = static_cast<unsigned char*>(allocate(total));
if (!storage) return nullptr;
auto* array = storage + padding;
if (padding) std::memcpy(array - sizeof(count), &count, sizeof(count));
try { __cxa_vec_ctor_nid_postfix(array, count, size, construct, destroy); }
catch (...) { deallocate(storage, total); throw; }
return array;
}
template<class Deallocate>
void DeleteArray(void* array, std::size_t size, std::size_t padding, void (*destroy)(void*), Deallocate deallocate) {
if (!array) return;
auto* storage = static_cast<unsigned char*>(array) - padding;
std::size_t count = 0;
if (padding) {
if (padding < sizeof(count)) Terminate();
std::memcpy(&count, static_cast<unsigned char*>(array) - sizeof(count), sizeof(count));
}
std::size_t total = ArraySize(count, size, padding);
try { if (padding) __cxa_vec_dtor_nid_postfix(array, count, size, destroy); }
catch (...) { deallocate(storage, total); throw; }
deallocate(storage, total);
}
}
extern "C" {
void* __cxa_vec_new_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*)) {
return LibcException::NewArray(n, s, p, ctor, dtor, LibcException::ArrayAllocate, [](void* ptr, std::size_t) { std::free(ptr); });
}
void* __cxa_vec_new2_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*)) {
return LibcException::NewArray(n, s, p, ctor, dtor, alloc, [dealloc](void* ptr, std::size_t) { dealloc(ptr); });
}
void* __cxa_vec_new3_nid_postfix(std::size_t n, std::size_t s, std::size_t p, void (*ctor)(void*), void (*dtor)(void*), void* (*alloc)(std::size_t), void (*dealloc)(void*, std::size_t)) {
return LibcException::NewArray(n, s, p, ctor, dtor, alloc, dealloc);
}
void __cxa_vec_delete_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*)) {
LibcException::DeleteArray(array, s, p, dtor, [](void* ptr, std::size_t) { std::free(ptr); });
}
void __cxa_vec_delete2_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*)) {
LibcException::DeleteArray(array, s, p, dtor, [dealloc](void* ptr, std::size_t) { dealloc(ptr); });
}
void __cxa_vec_delete3_nid_postfix(void* array, std::size_t s, std::size_t p, void (*dtor)(void*), void (*dealloc)(void*, std::size_t)) {
LibcException::DeleteArray(array, s, p, dtor, dealloc);
}
}

View File

@ -0,0 +1,18 @@
#include "Unwind.cpp"
#include "Runtime.cpp"
#include "Personality.cpp"
#include "StandardExceptions.cpp"
#include "ArraySupport.cpp"
asm(
".local __cxa_allocate_exception\n.set __cxa_allocate_exception,__cxa_allocate_exception_nid_postfix\n"
".local __cxa_free_exception\n.set __cxa_free_exception,__cxa_free_exception_nid_postfix\n"
".local __cxa_throw\n.set __cxa_throw,__cxa_throw_nid_postfix\n"
".local __cxa_begin_catch\n.set __cxa_begin_catch,__cxa_begin_catch_nid_postfix\n"
".local __cxa_end_catch\n.set __cxa_end_catch,__cxa_end_catch_nid_postfix\n"
".local __cxa_rethrow\n.set __cxa_rethrow,__cxa_rethrow_nid_postfix\n"
".local __gxx_personality_v0\n.set __gxx_personality_v0,__gxx_personality_v0_nid_postfix\n"
".local _Unwind_Resume\n.set _Unwind_Resume,_Unwind_Resume_nid_postfix\n"
".local _ZSt9terminatev\n.set _ZSt9terminatev,_ZSt9terminatev_nid_postfix\n"
".local __cxa_call_terminate\n.set __cxa_call_terminate,__cxa_call_terminate_nid_postfix\n"
);

View File

@ -5,7 +5,7 @@ namespace LibcException {
inline std::atomic<void(*)()> terminateHandler {std::abort};
[[noreturn]] void Terminate() {
auto handler = terminateHandler.load(std::memory_order_acquire);
if (handler) handler();
try { if (handler) handler(); } catch (...) {}
std::abort();
}
@ -14,13 +14,15 @@ void Release(void* object) {
auto* header = FromObject(object);
auto* allocation = AllocationOf(header);
if (allocation->references.fetch_sub(1, std::memory_order_acq_rel) == 1) {
if (header->destructor) header->destructor(object);
try { if (header->destructor) header->destructor(object); }
catch (...) { Terminate(); }
allocation->~Allocation();
std::free(allocation);
}
}
void Cleanup(_Unwind_Reason_Code, _Unwind_Exception* exception) {
void Cleanup(_Unwind_Reason_Code reason, _Unwind_Exception* exception) {
if (reason != _URC_FOREIGN_EXCEPTION_CAUGHT) Terminate();
auto* header = FromUnwind(exception);
if (exception->exception_class == DependentClass) {
Release(reinterpret_cast<void*>(header->type));
@ -233,6 +235,10 @@ void* __dynamic_cast_nid_postfix(const void* source, const __cxxabiv1::__class_t
return publicSource.count == 1 && publicSource.found == source ? target.found : nullptr;
}
[[noreturn]] void __cxa_call_terminate_nid_postfix(void* exception) {
__cxa_begin_catch_nid_postfix(exception);
LibcException::Terminate();
}
[[noreturn]] void __cxa_pure_virtual_nid_postfix() { LibcException::Terminate(); }
[[noreturn]] void __cxa_deleted_virtual_nid_postfix() { LibcException::Terminate(); }
}

View File

@ -284,8 +284,7 @@ bool Step(_Unwind_Context& context) {
return true;
}
_Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* exception, bool resume) {
bool skip = resume;
_Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* exception) {
for (unsigned depth = 0; depth < 65536; ++depth) {
Frame frame; Rules rules;
if (!GetRules(context, frame, rules)) {
@ -295,10 +294,7 @@ _Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* excepti
}
return _URC_END_OF_STACK;
}
if (skip) {
if (context.cfa == exception->private_2 && !exception->private_1) skip = false;
else if (exception->private_1 && context.cfa == context.registers[0]) skip = false;
} else {
{
auto actions = _UA_CLEANUP_PHASE;
if (exception->private_1) {
actions = _Unwind_Action(actions | _UA_FORCE_UNWIND);
@ -312,7 +308,13 @@ _Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* excepti
if (result != _URC_CONTINUE_UNWIND) return _URC_FATAL_PHASE2_ERROR;
}
}
if (!Step(context)) return _URC_END_OF_STACK;
if (!Step(context)) {
if (exception->private_1) {
auto stop = reinterpret_cast<_Unwind_Stop_Fn>(exception->private_1);
return stop(1, _Unwind_Action(_UA_FORCE_UNWIND | _UA_CLEANUP_PHASE | _UA_END_OF_STACK), exception->exception_class, exception, &context, reinterpret_cast<void*>(exception->private_2));
}
return _URC_FATAL_PHASE2_ERROR;
}
}
return _URC_FATAL_PHASE2_ERROR;
}
@ -332,7 +334,7 @@ _Unwind_Reason_Code _Unwind_RaiseException_nid_postfix(_Unwind_Exception* except
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;
return LibcUnwind::PhaseTwo(start, exception, false);
return LibcUnwind::PhaseTwo(start, exception);
}
if (result != _URC_CONTINUE_UNWIND) return _URC_FATAL_PHASE1_ERROR;
}
@ -344,8 +346,8 @@ _Unwind_Reason_Code _Unwind_RaiseException_nid_postfix(_Unwind_Exception* except
[[noreturn]] void _Unwind_Resume_nid_postfix(_Unwind_Exception* exception) {
_Unwind_Context context;
LibcCaptureRegisters(context.registers);
if (!LibcUnwind::Step(context) || !LibcUnwind::Step(context)) std::abort();
LibcUnwind::PhaseTwo(context, exception, false);
if (!LibcUnwind::Step(context)) std::abort();
LibcUnwind::PhaseTwo(context, exception);
std::abort();
}
@ -382,7 +384,7 @@ _Unwind_Reason_Code _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception* exceptio
if (!LibcUnwind::Step(context)) return _URC_FATAL_PHASE2_ERROR;
exception->private_1 = reinterpret_cast<std::uintptr_t>(stop);
exception->private_2 = reinterpret_cast<std::uintptr_t>(argument);
return LibcUnwind::PhaseTwo(context, exception, false);
return LibcUnwind::PhaseTwo(context, exception);
}
_Unwind_Reason_Code _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn trace, void* argument) {