972
edits
[unchecked revision] | [unchecked revision] |
(Removed old link) |
m (Bot: Replace deprecated source tag with syntaxhighlight) |
||
Line 21:
Below you will find an example of an implementation in respectively GCC.
<
extern "C" void __cxa_pure_virtual()
{
// Do nothing or print an error message.
}
</syntaxhighlight>
Or, if you happen to use Visual Studio:
<
int __cdecl _purecall()
{
// Do nothing or print an error message.
}
</syntaxhighlight>
If, during runtime, your kernel detects that a call to a pure virtual function couldn't be made, it calls the above functions. These functions should actually never be called, because without hacks, or through undefined behaviour of your kernel, it is not possible to instantiate a class that doesn't define all pure virtual functions.
Line 47:
Global objects must have their constructors called before they are used. Usually, they are called by the start-up code (which you just disabled). So, in order to be able to use them, you have to write your own start-up code for them. All objects have a constructor and a destructor. When an executable is loaded into memory and the program jumps straight to the entry point, the constructors of global objects will not have been called. One solution is to do this manually. You could put this code first when your C++ entry point is called:
<
object1.object1();
object2.object2();
object3.object3();
// ...
</syntaxhighlight>
Global or static objects have to be constructed by the environment before they are available to C++. Care should be taken if global/static objects need '''new''' and '''delete''' in their constructors. In this case it is best to construct these objects only after your kernel heap is ready for use (and you have access to dynamic memory allocation). Not doing so can cause an object to attempt to allocate memory via the non-working '''new''' operator. This also simplifies the storing of the destructor functions in '''__cxa_atexit''', because you don't have to use a static and fixed-size structure.
Line 64:
In the example implementation of '''__cxa_atexit''', the '''__atexit_funcs[ATEXIT_MAX_FUNCS]''' array acts as the table. This is why the '''__cxa_atexit''' function is defined as:
<
int __cxa_atexit(void (*destructor) (void *), void *arg, void *__dso_handle);
</syntaxhighlight>
So that the '''destructor''' function pointer is the handle for a destructor function and '''arg''' is the single argument it may take. Finally, '''__dso_handle''' is a handle for the DSO (Dynamic Shared Object).
Line 72:
So summarized, you are required to define two functions and one symbol in order to use global objects in your C++ files:
<
void *__dso_handle;
int __cxa_atexit(void (*destructor) (void *), void *arg, void *dso);
void __cxa_finalize(void *f);
</syntaxhighlight>
After you have called the objects constructor GCC automatically calls the function
<
int __cxa_atexit(void (*destructor) (void *), void *arg, void *dso);
</syntaxhighlight>
This function should save all three parameters and if successful return zero, on failure non-zero. When your kernel exits you should call '''__cxa_finalize(0)'''. According to the ABI specification, calling this with 0 as the parameter instead of the address of a function (to be called and removed from the list) causes ''all'' destructors in the list to be called and removed from the list.
Line 89:
Since you will be calling this function from your Assembly source right after your kernel exits, you could use the following code:
<
; This is NASM source, mind you.
sub esp, 4
Line 97:
add esp, 4
</syntaxhighlight>
The following is tested, working, fully commented source that gives a more detailed explanation than the source previously found here. It also highlights what improvements can be implemented and where they can be inserted. To use it, just include '''icxxabi.h''' in any '''one''' file of your C++ kernel source (preferably the file where your kernel's main statements begin).
'''File: icxxabi.h'''
<
#ifndef _ICXXABI_H
#define _ICXXABI_H
Line 133:
#endif
</syntaxhighlight>
'''File: icxxabi.cpp'''
<
#include "./icxxabi.h"
Line 242:
};
#endif
</syntaxhighlight>
=== Visual C++ ===
Line 355:
''Note that these are only stubs to get the code compiled, and you should implement them yourself. Simply add a mutex-like guard with a test-and-set primitive.''
<
namespace __cxxabiv1
{
Line 382:
}
}
</syntaxhighlight>
The actual code emitted by GCC to call a local static variable's constructor looks something like this:
Line 458:
In order to use placement new, you need special overloads of the new and delete operators defined in scope. Fortunately, the required definitions are simple and can be inlined in a header file (the C++ standard puts them in a header called '''new''').
<
inline void *operator new(size_t, void *p) throw() { return p; }
inline void *operator new[](size_t, void *p) throw() { return p; }
inline void operator delete (void *, void *) throw() { };
inline void operator delete[](void *, void *) throw() { };
</syntaxhighlight>
The above implementation can potentially be unsafe for allocating memory since your kernel does not mark the memory that was allocated as being used. Placement new is hardly ever used, and if you wish to read an object from a specified address in memory, it is usually easier to create a pointer to that address.
Line 469:
You never call placement delete explicitly (it's only required for certain implementation detail reasons). Instead, you simply invoke your object's destructor explicitly.
<
apic->~APIC();
</syntaxhighlight>
== RTTI (Run-Time Type Information) ==
|