Calling Global Constructors

From OSDev.wiki
Revision as of 01:36, 24 February 2013 by osdev>Sortie (draft of global constructors explaination)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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 (malloc, 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.

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' which runs the global constructors and other initialization tasks, and '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.)

To understand this apparent complexity, consider a program consisting of foo.o and bar.o that is being linked:

i585-elf-gcc foo.o bar.o -o program

The compiler will rewrite the command line and pass it to the linker as:

i585-elf-ld crt0.o crti.o crtbegin.o foo.o bar.o crtend.o crtn.o

The idea is that the

x86 (32-bit)

/* x86 crti.s */
.section .init
.global _init
.type _init, @function
_init:
	push %ebp
	movl %esp, %ebp
	/* gcc will nicely put the contents of crtbegin.o's .init section here. */

.section .fini
.global _fini
.type _fini, @function
_fini:
	push %ebp
	movl %esp, %ebp
	/* gcc will nicely put the contents of crtbegin.o's .fini section here. */
/* x86 crtn.s */
.section .init
	/* gcc will nicely put the contents of crtend.o's .init section here. */
	popl %ebp
	ret

.section .fini
	/* gcc will nicely put the contents of crtend.o's .fini section here. */
	popl %ebp
	ret

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 _init and _fini functions through crtbegin.o and crtend.o.

/* x86_64 crti.s */
.section .init
.global _init
.type _init, @function
_init:
	push %rbp
	movq %rsp, %rbp
	/* gcc will nicely put the contents of crtbegin.o's .init section here. */

.section .fini
.global _fini
.type _fini, @function
_fini:
	push %rbp
	movq %rsp, %rbp
	/* gcc will nicely put the contents of crtbegin.o's .fini section here. */
/* x86_64 crtn.s */
.section .init
	/* gcc will nicely put the contents of crtend.o's .init section here. */
	popq %rbp
	ret

.section .fini
	/* gcc will nicely put the contents of crtend.o's .fini section here. */
	popq %rbp
	ret

ARM (BPABI)

In this case things are slightly different. The system ABI mandates the use of special sections called .init_array and .fini_array, rather than the common .init and .fini sections. This means that crtbegin.o and crtend.o, as provided by your cross-compiler, does not insert instructions into the .init and .fini sections. The result is that if you follow the method from Intel/AMD systems, your _init and _fini functions will do nothing. Your cross-compiler may actually come with default crti.o and crtn.o objects, however they also suffer from this ABI decision, and their _init and _fini functions will also do nothing.

The solution is to provide your own crti.o object that inserts a symbol at the start of .init_array and .fini_array sections, as well as your own crtn.o that inserts a symbol at the end of the sections. In this case it is actually possible to write crti.o and crtn.o 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 crti.o and crtn.o.

/* crti.c for ARM - BPABI - use -std=c99 */
typedef void (*func_ptr)(void);

extern func_ptr _init_array_start[0], _init_array_end[0];
extern func_ptr _fini_array_start[0], _fini_array_end[0];

void _init(void)
{
	for ( func_ptr* func = _init_array_start; func != _init_array_end; func++ )
		(*func)();
}

void _fini(void)
{
	for ( func_ptr* func = _fini_array_start; func != _fini_array_end; func++ )
		(*func)();
}

func_ptr _init_array_start[0] __attribute__ ((used, section(".init_array"), aligned(sizeof(func_ptr)))) = { };
func_ptr _fini_array_start[0] __attribute__ ((used, section(".fini_array"), aligned(sizeof(func_ptr)))) = { };
/* crtn.c for ARM - BPABI - use -std=c99 */
typedef void (*func_ptr)(void);

func_ptr _init_array_end[0] __attribute__ ((used, section(".init_array"), aligned(sizeof(func_ptr)))) = { };
func_ptr _fini_array_end[0] __attribute__ ((used, section(".fini_array"), aligned(sizeof(func_ptr)))) = { };

Clang

Since Clang attemps to be largely compatible with GCC, the information listed there can possibly be adapted easily. If you give it a try, please document the findings here.

Other Compilers / Platforms

If your compiler or system ABI is not listed here, you will need to consult the appropriate documentation manually and hopefully document the information here if it is relevant.