From 5494d8d2acfcd41393c92e4ec2079c1ffc34d84a Mon Sep 17 00:00:00 2001 From: Bananymous Date: Tue, 13 Dec 2022 00:52:32 +0200 Subject: [PATCH] Kernel: We can now use destructors in global objects --- kernel/icxxabi.cpp | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 kernel/icxxabi.cpp diff --git a/kernel/icxxabi.cpp b/kernel/icxxabi.cpp new file mode 100644 index 00000000..8cdb147e --- /dev/null +++ b/kernel/icxxabi.cpp @@ -0,0 +1,114 @@ +#define ATEXIT_MAX_FUNCS 128 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned uarch_t; + +struct atexit_func_entry_t +{ + /* + * Each member is at least 4 bytes large. Such that each entry is 12bytes. + * 128 * 12 = 1.5KB exact. + **/ + void (*destructor_func)(void *); + void *obj_ptr; + void *dso_handle; +}; + +atexit_func_entry_t __atexit_funcs[ATEXIT_MAX_FUNCS]; +uarch_t __atexit_func_count = 0; + +int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) +{ + if (__atexit_func_count >= ATEXIT_MAX_FUNCS) {return -1;}; + __atexit_funcs[__atexit_func_count].destructor_func = f; + __atexit_funcs[__atexit_func_count].obj_ptr = objptr; + __atexit_funcs[__atexit_func_count].dso_handle = dso; + __atexit_func_count++; + return 0; /*I would prefer if functions returned 1 on success, but the ABI says...*/ +}; + +void __cxa_finalize(void *f) +{ + uarch_t i = __atexit_func_count; + if (!f) + { + /* + * According to the Itanium C++ ABI, if __cxa_finalize is called without a + * function ptr, then it means that we should destroy EVERYTHING MUAHAHAHA!! + * + * TODO: + * Note well, however, that deleting a function from here that contains a __dso_handle + * means that one link to a shared object file has been terminated. In other words, + * We should monitor this list (optional, of course), since it tells us how many links to + * an object file exist at runtime in a particular application. This can be used to tell + * when a shared object is no longer in use. It is one of many methods, however. + **/ + //You may insert a prinf() here to tell you whether or not the function gets called. Testing + //is CRITICAL! + while (i--) + { + if (__atexit_funcs[i].destructor_func) + { + /* ^^^ That if statement is a safeguard... + * To make sure we don't call any entries that have already been called and unset at runtime. + * Those will contain a value of 0, and calling a function with value 0 + * will cause undefined behaviour. Remember that linear address 0, + * in a non-virtual address space (physical) contains the IVT and BDA. + * + * In a virtual environment, the kernel will receive a page fault, and then probably + * map in some trash, or a blank page, or something stupid like that. + * This will result in the processor executing trash, and...we don't want that. + **/ + (*__atexit_funcs[i].destructor_func)(__atexit_funcs[i].obj_ptr); + }; + }; + return; + }; + + while (i--) + { + /* + * The ABI states that multiple calls to the __cxa_finalize(destructor_func_ptr) function + * should not destroy objects multiple times. Only one call is needed to eliminate multiple + * entries with the same address. + * + * FIXME: + * This presents the obvious problem: all destructors must be stored in the order they + * were placed in the list. I.e: the last initialized object's destructor must be first + * in the list of destructors to be called. But removing a destructor from the list at runtime + * creates holes in the table with unfilled entries. + * Remember that the insertion algorithm in __cxa_atexit simply inserts the next destructor + * at the end of the table. So, we have holes with our current algorithm + * This function should be modified to move all the destructors above the one currently + * being called and removed one place down in the list, so as to cover up the hole. + * Otherwise, whenever a destructor is called and removed, an entire space in the table is wasted. + **/ + if (__atexit_funcs[i].destructor_func == f) + { + /* + * Note that in the next line, not every destructor function is a class destructor. + * It is perfectly legal to register a non class destructor function as a simple cleanup + * function to be called on program termination, in which case, it would not NEED an + * object This pointer. A smart programmer may even take advantage of this and register + * a C function in the table with the address of some structure containing data about + * what to clean up on exit. + * In the case of a function that takes no arguments, it will simply be ignore within the + * function itself. No worries. + **/ + (*__atexit_funcs[i].destructor_func)(__atexit_funcs[i].obj_ptr); + __atexit_funcs[i].destructor_func = 0; + + /* + * Notice that we didn't decrement __atexit_func_count: this is because this algorithm + * requires patching to deal with the FIXME outlined above. + **/ + }; + }; +}; + +#ifdef __cplusplus +}; +#endif