Calling Global Constructors: Difference between revisions

More work
[unchecked revision][unchecked revision]
(draft of global constructors explaination)
 
(More work)
Line 5:
= GNU Compiler Collection - System V ABI =
 
The System V ABI (as used by i586-elf-gcc, x86_64-elf-gcc, and other ELF platforms) specifies use of five different object files that together handle program initialization. These are traditionally called crt0.o, crti.o, crtbegin.o, crtend.o, and crtn.o. Together these object files implement two special functions: 'init_init' which runs the global constructors and other initialization tasks, and 'fini_fini' 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 crtbegin.o and crtend.o. 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 crti.o and crtn.o. Fortunately, this is easy and described in detail in this tutorial. The fifth file crt0.o contains the program entry point (normally _start) and calls the special _init function that runs the "program initialization tasks" that crti.o, crtbegin.o, crtend.o, and crtn.o together form, and your exit function will normally call the _fini function made by these objects. However, crt0.o is out of scope of this article. (Note that the object file that contains _start acts as crt0.o in a kernel.)
Line 17:
i585-elf-ld crt0.o crti.o crtbegin.o foo.o bar.o crtend.o crtn.o
 
The idea is that the these files toggether form _init and _fini functions during the linking. This is done by storing the _init function in the .init section, and the _fini function in the .fini section. 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. crti.o provides the function header, crtbegin.o and crtend.o provide the body, and crtn.o provide the footer (return statement).
The idea is that the
 
== 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
 
i586-elf-gcc $CFLAGS -print-file-name=crtbegin.o
 
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:
 
<source lang="make">
CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtbegin.o)
CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtend.o)
</source>
 
You can then use them as such (adapted to your real build system):
 
<source lang="make">
OBJS:=foo.o bar.o
 
CRTI_OBJ=crti.o
CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtbegin.o)
CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtend.o)
CRTN_OBJ=crtn.o
 
OBJ_LINK_LIST:=$(CRTI_OBJ) $(CRTBEGIN_OBJ) $(OBJS) $(CRTEND_OBJ) $(CRTN_OBJ)
INTERNAL_OBJS:=$(CRTI_OBJ) $(OBJS) $(CRTN_OBJ)
 
mykernel.bin: $(OBJ_LINK_LIST)
$(CC) -o mykernel.bin $(OBJ_LINK_LIST) -lgcc
 
clean:
rm -f mykernel.bin $(INTERNAL_OBJS)
</source>
 
Your kernel will then have an _init and a _fini function linked in, which can be called from your boot.o (or what your kernel entry point object is called) before passing control to kmain (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, _fini 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 function that initializes the heap, the log, and other core kernel features. Then your boot.o can call kmain_early, then call _init, and then finally pass control to the real kmain. This is analogous to how things work in user-space, where crt0.o calls _initialize_c_library (or what you call it), then _init, and finally exit(main(argc, argv)).
 
== Using crti.o, crtbegin.o, crtend.o, and crtn.o in User-Space ==
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]].
 
== x86 (32-bit) ==
Anonymous user