Calling Global Constructors: Difference between revisions

Go through article and sanity check things
[unchecked revision][unchecked revision]
m (Add warnings against using the wrong link order)
(Go through article and sanity check things)
Line 1:
This tutorial discusses how to correct invoke global constructors, such as those on global C++ objects. These are supposed to have run before your main function, which is why the program entry point is normally a function called _start. This function has the responsibility of parsing the command line arguments, initializing the standard library (mallocmemory allocation, signals, ...), running the global constructors and finally exit(main(argc, argv)). Things may be different on your operating system if you change your compiler, but if you are using the GNU Compiler Collection (gcc) it may be wise to follow the System V ABI.
 
On most platforms, the global constructors/destructors are stored in a stored array of function pointers and invoking these is as simple as traversing the array and running each element. However, the compiler does not always give access to this list, and some compilers considers this implementation details. In this case you will have to cooperate with the compiler - fighting the compiler will only cause trouble.
Line 5:
= GNU Compiler Collection - System V ABI =
 
The System V ABI (as used by <tt>i586-elf-gcc</tt>, <tt>x86_64-elf-gcc</tt>, and other ELF platforms) specifies use of five different object files that together handle program initialization. These are traditionally called <tt>crt0.o</tt>, <tt>crti.o</tt>, <tt>crtbegin.o</tt>, <tt>crtend.o</tt>, and <tt>crtn.o</tt>. Together these object files implement two special functions: '<tt>_init'</tt> which runs the global constructors and other initialization tasks, and '<tt>_fini'</tt> that runs the global destructors and other termination tasks.
 
This scheme allows the compiler great control in program initialization and makes things easy for you, but you have to cooperate with the compiler or bad things will happen. Your cross-compiler will provide you with <tt>crtbegin.o</tt> and <tt>crtend.o</tt>. These files contain the internals that the compiler wish to hide from you, but wants you to use. To get access to this information, you will need to provide your own implementation of <tt>crti.o</tt> and <tt>crtn.o</tt>. Fortunately, this is easy and described in detail in this tutorial. The fifth file <tt>crt0.o</tt> contains the program entry point (normally <tt>_start</tt>) and calls the special <tt>_init</tt> function that runs the "program initialization tasks" that <tt>crti.o</tt>, <tt>crtbegin.o</tt>, <tt>crtend.o</tt>, and <tt>crtn.o</tt> together form, and your exit function will normally call the <tt>_fini</tt> function made by these objects. However, <tt>crt0</tt>.o is out of scope of this article. (Note that the object file that contains <tt>_start</tt> acts as <tt>crt0.o</tt> in a kernel.)
 
To understand this apparent complexity, consider a program consisting of <tt>foo.o</tt> and <tt>bar.o</tt> that is being linked:
 
<source lang="bash">i585-elf-gcc foo.o bar.o -o program</source>
 
The compiler will rewrite the command line and pass it to the linker as:
 
<source lang="bash">i585-elf-ld crt0.o crti.o crtbegin.o foo.o bar.o crtend.o crtn.o</source>
 
The idea is that the these files toggethertogether form the <tt>_init</tt> and <tt>_fini</tt> functions during the linking. This is done by storing the <tt>_init</tt> function in the <tt>.init</tt> section, and the <tt>_fini</tt> function in the <tt>.fini section</tt>. Each file then contributes a bit to these sections and the linker makes glues together the fragments in the code specified on the command line. <tt>crti.o</tt> provides the function header, <tt>crtbegin.o</tt> and <tt>crtend.o</tt> provide the body, and <tt>crtn.o</tt> provide the footer (return statement). It is important to understand that the link order matters and strange things may happen if the objects is not exactly linked in this order.
 
== Using global constructors from C ==
Line 35:
 
== Using crti.o, crtbegin.o, crtend.o, and crtn.o in a Kernel ==
In a kernel, you are not using a user-space C library. You may be using a special kernel "C library", or none at all. The compiler always supplies crtbegin.o and crtend.o, but normally the C library supplies crti.o and crtn.o, but not in this case. The kernel should supply its own crti.o and crtn.o implementation (even if it would be otherwise identical to the user-space libc version). A kernel is linked with -nostdlib (which is the same as passing -nodefaultlibs and -nostartfiles) which disables the "start files" crt*.o that is normally automatically added to the link command line. By passing -nostartfiles, we promise to the compiler that we take on the responsibility ourselves to call the "program initialization tasks" in the crtbegin.o and crtend.o files. This means as we need to manually add crti.o, crtbegin.o, crtend.o, and crtn.o to the command line. Since we provide crti.o and crtn.o ourselves, that is trivial to add to the kernel command line. However, crtbegin.o and crtend.o is installed inside a compiler-specific directory we'll need to figure out the path. Luckily, gcc offers an option just to do this. If i586-elf-gcc is your cross-compiler and $CFLAGS is the flags you would normally provide to your compiler, then
 
In a kernel, you are not using a user-space C library. You may be using a special kernel "C library", or none at all. The compiler always supplies <tt>crtbegin.o</tt> and <tt>crtend.o</tt>, but normally the C library supplies <tt>crti.o</tt> and <tt>crtn.o</tt>, but not in this case. The kernel should supply its own <tt>crti.o</tt> and <tt>crtn.o</tt> implementation (even if it would be otherwise identical to the user-space libc version). A kernel is linked with <tt>-nostdlib<tt> (which is the same as passing <tt>-nodefaultlibs</tt> and <tt>-nostartfiles</tt>) which disables the "start files" <tt>crt*.o</tt> that is normally automatically added to the link command line. By passing <tt>-nostartfiles</tt>, we promise to the compiler that we take on the responsibility ourselves to call the "program initialization tasks" in the <tt>crtbegin.o</tt> and <tt>crtend.o</tt> files. This means as we need to manually add <tt>crti.o</tt>, <tt>crtbegin.o</tt>, <tt>crtend.o</tt>, and <tt>crtn.o</tt> to the command line. Since we provide <tt>crti.o</tt> and <tt>crtn.o</tt> ourselves, that is trivial to add to the kernel command line. However, <tt>crtbegin.o</tt> and <tt>crtend.o</tt> is installed inside a compiler-specific directory we'll need to figure out the path. Luckily, gcc offers an option just to do this. If <tt>i586-elf-gcc</tt> is your cross-compiler and <tt>$CFLAGS</tt> is the flags you would normally provide to your compiler, then
i586-elf-gcc $CFLAGS -print-file-name=crtbegin.o
 
<source lang="bash">i586-elf-gcc $CFLAGS -print-file-name=crtbegin.o</source>
will make the compiler print the path to the correct crtbegin.o file (that is ABI compatible with the $CFLAGS options) to the standard output. The same works with crtend.o. If you are using GNU Make, you can do it easily in your makefile assuming $(CC) is your cross-compiler and $(CFLAGS) is the flags you would normally pass it:
 
will make the compiler print the path to the correct <tt>crtbegin.o</tt> file (that is ABI compatible with the $CFLAGS options) to the standard output. The same works with <tt>crtend.o</tt>. If you are using GNU Make, you can do it easily in your makefile assuming <tt>$(CC)</tt> is your cross-compiler and <tt>$(CFLAGS)</tt> is the flags you would normally pass it:
 
<source lang="make">
Line 59 ⟶ 60:
INTERNAL_OBJS:=$(CRTI_OBJ) $(OBJS) $(CRTN_OBJ)
 
mykernelmyos.binkernel: $(OBJ_LINK_LIST)
$(CC) -o mykernelmyos.binkernel $(OBJ_LINK_LIST) -nostdlib -lgcc
 
clean:
rm -f mykernelmyos.binkernel $(INTERNAL_OBJS)
</source>
 
It is important to remember that the objects must be linked in this exact order, or you will experience strange bugs.
 
Your kernel will then have an <tt>_init</tt> and a <tt>_fini</tt> function linked in, which can be called from your <tt>boot.o</tt> (or what your kernel entry point object is called) before passing control to kmain<tt>kernel_main</tt> (or what your kernel main routine is called). Please note that the kernel may not be initialized, at all, at this point in time and you can only do trivial things from your global constructors. In addition, <tt>_fini</tt> may not ever get called because your operating system will remain running, and when it is time to shut down, there is little worth doing that a processor reset won't do. It may be worth setting up a kmain_early<tt>kernel_early_main</tt> function that initializes the heap, the log, and other core kernel features. Then your <tt>boot.o</tt> can call kmain_early<tt>kernel_early_main</tt>, then call <tt>_init</tt>, and then finally pass control to the real kmain<tt>kernel_main</tt>. This is analogous to how things work in user-space, where <tt>crt0.o</tt> calls <tt>_initialize_c_library</tt> (or what you call it), then <tt>_init</tt>, and finally <tt>exit(main(argc, argv))</tt>.
 
== Using crti.o, crtbegin.o, crtend.o, and crtn.o in User-Space ==
{{Main|Creating a C Library}}
It is very easy to use these object files in user-space, as the cross-compiler automatically will link them in the right order into the final program. The compiler will, as always, provide crtbegin.o and crtend.o. Your C library will then provide crt0.o (program entry point file), crti.o, and crtn.o. If you have a [[OS Specific Toolchain]], you can change the name of the program entry point (normally _start), the path where the compiler searches for the crt{0,i,n}.o files, and what files is even used (possibly with other names) and what order, by modifying STARTFILE_SPEC and ENDFILE_SPEC. When you start creating a user-space, it may well be worth creating an OS Specific Toolchain because it allows you great control over exactly how all this works. For more information, see [[Creating a C Library]].
 
It is very easy to use these object files in user-space, as the cross-compiler automatically will link them in the right order into the final program. The compiler will, as always, provide <tt>crtbegin.o</tt> and <tt>crtend.o</tt>. Your C library will then provide <tt>crt0.o</tt> (program entry point file), <tt>crti.o</tt>, and <tt>crtn.o</tt>. If you have a [[OS Specific Toolchain]], you can change the name of the program entry point (normally _start), the path where the compiler searches for the <tt>crt{0,i,n}.o</tt> files, and what files is even used (possibly with other names) and what order, by modifying <tt>STARTFILE_SPEC</tt> and <tt>ENDFILE_SPEC</tt>. When you start creating a user-space, it may well be worth creating an OS Specific Toolchain because it allows you great control over exactly how all this works. For more information, see [[Creating a C Library]].
 
== x86 (32-bit) ==
It is very simple to implement this under x86. You simply have to define the header of two functions in <tt>crti.o</tt> and the footer in <tt>crtn.o</tt> and use these objects in your C library or kernel. You can then simply call <tt>_init</tt> to perform the initialization tasks and call <tt>_fini</tt> to perform the termination tasks (normally done from a <tt>crt0.o</tt> or <tt>my-kernel-boot-object.o</tt>).
 
<source lang="asm">
Line 101 ⟶ 104:
popl %ebp
ret
.size _init, . - _init
 
.section .fini
Line 106 ⟶ 110:
popl %ebp
ret
.size _fini, . - _fini
</source>
 
== x86_64 (64-bit) ==
 
The system ABI on x86_64 is similar to its 32-bit counterpart and we also just need to provide function headers and function footers and the compiler will insert the rest of the <tt>_init</tt> and <tt>_fini</tt> functions through <tt>crtbegin.o</tt> and <tt>crtend.o</tt>.
 
<source lang="asm">
Line 137 ⟶ 142:
popq %rbp
ret
.size _init, . - _init
 
.section .fini
Line 142 ⟶ 148:
popq %rbp
ret
.size _fini, . - _fini
</source>
 
== ARM (BPABI) ==
 
In this case things are slightly different. The system ABI mandates the use of special sections called <tt>.init_array</tt> and <tt>.fini_array</tt>, rather than the common <tt>.init</tt> and <tt>.fini</tt> sections. This means that <tt>crtbegin.o</tt> and <tt>crtend.o</tt>, as provided by your cross-compiler, does not insert instructions into the <tt>.init</tt> and <tt>.fini</tt> sections. The result is that if you follow the method from Intel/AMD systems, your <tt>_init</tt> and <tt>_fini</tt> functions will do nothing. Your cross-compiler may actually come with default <tt>crti.o</tt> and <tt>crtn.o</tt> objects, however they also suffer from this ABI decision, and their <tt>_init</tt> and <tt>_fini</tt> functions will also do nothing.
 
The solution is to provide your own <tt>crti.o</tt> object that inserts a symbol at the start of <tt>.init_array</tt> and <tt>.fini_array</tt> sections, as well as your own <tt>crtn.o</tt> that inserts a symbol at the end of the sections. In this case it is actually possible to write <tt>crti.o</tt> and <tt>crtn.o</tt> in C, because we are not writing incomplete functions. These files should be compiled like the rest of your kernel and otherwise used normally as <tt>crti.o</tt> and <tt>crtn.o</tt>.
 
<source lang="c">
Line 181 ⟶ 188:
</source>
 
Additionally, if you use constructor/desctructor priorities, the compiler will append these priorities to the section name. The linker script is expected to expected to sort these, so you will have to add the following to your linker script. Note that we have to treat the <tt>crti.o</tt> and <tt>crtn.o</tt> objects specially because we need to put the symbols in the right order. Alternatively, you can emit the <tt>_init_array_start</tt>, <tt>_init_array_end</tt>, <tt>_fini_array_start</tt>, <tt>_fini_array_end</tt> symbols yourself from the linker script.
 
<pre>
/* Include the list of initialization functions sorted. */
.init_array :
{
{
crti.o(.init_array)
KEEP (*(SORT(EXCLUDE_FILE(crti.o crtn.o) (.init_array.*)))
KEEP (*(SORT(EXCLUDE_FILE(crti.o crtn.o) .init_array.*)))
KEEP (*(EXCLUDE_FILE(crti.o crtn.o() .init_array))
crticrtn.o(.init_array)
}
}
 
/* Include the list of termination functions sorted. */
.fini_array :
{
{
crti.o(.fini_array)
KEEP (*(SORT(EXCLUDE_FILE(crti.o crtn.o) .fini_array.*)))
KEEP (*(EXCLUDE_FILE(crti.o crtn.o) .fini_array))
crtn.o(.fini_array)
}
}
</pre>
 
= Clang =
Anonymous user