C++: Difference between revisions
Jump to navigation
Jump to search
[unchecked revision] | [unchecked revision] |
Content added Content deleted
Line 79: | Line 79: | ||
</source> |
</source> |
||
<tt>f</tt> is a function-pointer to the destructor, <tt>p</tt> is the parameter for the destructor and <tt>d</tt> is the "home DSO" (DSO = dynamic shared object). This function should save all three parameters and if successful return zero, on failure nonzero. When your kernel exits you should call |
<tt>f</tt> is a function-pointer to the destructor, <tt>p</tt> is the parameter for the destructor and <tt>d</tt> is the "home DSO" (DSO = dynamic shared object). This function should save all three parameters and if successful return zero, on failure nonzero. When your kernel exits you should call <tt>__cxa_finalize(0)</tt>. According to the ABI specification, calling this with (int) 0 as the parameter instead of the address of a function to be called and removed from the list causes <em>all</em> destructors in the list to be called and removed from the list. |
||
Since you will be calling this function from your assembly source, right after your kernel exits, you could use the following code to do so: |
|||
<source lang="cpp"> |
|||
void __cxa_finalize(void *d); |
|||
<source lang="asm"> |
|||
; This is NASM source, mind you. |
|||
sub esp, 4 |
|||
mov [esp], dword 0x0 |
|||
call __cxa_finalize |
|||
add esp, 4 |
|||
</source> |
</source> |
||
The following is tested, working, fully commented source that gives a more detailed explanation than the source previously found here. It also hilights what improvements can be implemented on itself, where they can be inserted. |
|||
with d = 0 in order to destroy all with <tt>__cxa_atexit</tt> registered objects. Objects, which were registered first with <tt>__cxa_atexit</tt>, must be destroyed last by <tt>__cxa_finalize</tt>. You must provide the symbol <tt>__dso_handle</tt> in your executable. Only the address of this symbol is needed, because GCC calls <tt>__cxa_atexit</tt> with <tt>&__dso_handle</tt>. |
|||
'''File: icxxabi.h''' |
|||
<source lang="cpp"> |
<source lang="cpp"> |
||
#ifndef _ICXXABI_H |
|||
extern "C" |
|||
#define _ICXXABI_H |
|||
{ |
|||
int __cxa_atexit(void (*f)(void *), void *p, void *d); |
|||
void __cxa_finalize(void *d); |
|||
}; |
|||
#define ATEXIT_MAX_FUNCS 128 |
|||
void *__dso_handle; /*only the address of this symbol is taken by gcc*/ |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
typedef unsigned uarch_t; |
|||
struct |
struct atexit_func_entry_t |
||
{ |
{ |
||
/* |
|||
void (*f)(void*); |
|||
* Each member is at least 4 bytes large. Such that each entry is 12bytes. |
|||
void *p; |
|||
* 128 * 12 = 1.5KB exact. |
|||
void *d; |
|||
**/ |
|||
} object[32] = {0}; |
|||
void (*destructor_func)(void *); |
|||
unsigned int iObject = 0; |
|||
void *obj_ptr; |
|||
void *dso_handle; |
|||
}; |
|||
int __cxa_atexit(void (*f)(void *), void * |
int __cxa_atexit(void (*f)(void *), void *objptr, void *dso); |
||
void __cxa_finalize(void *f); |
|||
#ifdef __cplusplus |
|||
}; |
|||
#endif |
|||
#endif |
|||
</source> |
|||
'''File: icxxabi.cpp''' |
|||
<source lang="cpp"> |
|||
#include "./icxxabi.h" |
|||
atexit_func_entry_t __atexit_funcs[ATEXIT_MAX_FUNCS]; |
|||
uarch_t __atexit_func_count = 0; |
|||
void *__dso_handle = 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; |
|||
object[iObject].f = f; |
|||
__atexit_funcs[__atexit_func_count].obj_ptr = objptr; |
|||
object[iObject].p = p; |
|||
__atexit_funcs[__atexit_func_count].dso_handle = dso; |
|||
object[iObject].d = d; |
|||
__atexit_func_count++; |
|||
++iObject; |
|||
return 0; /*I would prefer if functions returned 1 on success, but the ABI says...*/ |
|||
return 0; |
|||
} |
}; |
||
void __cxa_finalize(void *f) |
|||
/* This currently destroys all objects */ |
|||
void __cxa_finalize(void *d) |
|||
{ |
{ |
||
uarch_t i = __atexit_func_count; |
|||
unsigned int i = iObject; |
|||
vga_boot icxxabi; |
|||
for (; i > 0; --i) |
|||
if (!f) |
|||
{ |
|||
{ |
|||
--iObject; |
|||
/* |
|||
object[iObject].f(object[iObject].p); |
|||
* 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; |
|||
}; |
|||
for ( ; i >= 0; ) |
|||
{ |
|||
/* |
|||
* 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. |
|||
**/ |
|||
}; |
|||
}; |
|||
}; |
|||
</source> |
</source> |
||