Calling Global Constructors: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(→‎Clang: add my observations for dealing with clang)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(2 intermediate revisions by 2 users not shown)
Line 10:
 
To understand this apparent complexity, consider a program consisting of <tt>foo.o</tt> and <tt>bar.o</tt> that is being linked:
<sourcesyntaxhighlight lang="bash">i686-elf-gcc foo.o bar.o -o program</sourcesyntaxhighlight>
 
The compiler will rewrite the command line and pass it to the linker as:
 
<sourcesyntaxhighlight lang="bash">i686-elf-ld crt0.o crti.o crtbegin.o foo.o bar.o crtend.o crtn.o</sourcesyntaxhighlight>
 
The idea is that the these files together 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.
Line 21:
As a special extension, GCC allows C programs to run functions as global constructors. For more information, consult the compiler documentation. This is normally used as:
 
<sourcesyntaxhighlight lang="c">
__attribute__ ((constructor)) void foo(void)
{
Line 31:
printf("%s: main is running with argc=%i\n", argv[0], argc);
}
</syntaxhighlight>
</source>
 
=== Using crti.o, crtbegin.o, crtend.o, and crtn.o in a Kernel ===
Line 37:
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 are 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, since <tt>crtbegin.o</tt> and <tt>crtend.o</tt> are 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>i686-elf-gcc</tt> is your cross-compiler and <tt>$CFLAGS</tt> is the flags you would normally provide to your compiler, then
 
<sourcesyntaxhighlight lang="bash">i686-elf-gcc $CFLAGS -print-file-name=crtbegin.o</sourcesyntaxhighlight>
 
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:
 
<sourcesyntaxhighlight lang="make">
CRTBEGIN_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtbegin.o)
CRTEND_OBJ:=$(shell $(CC) $(CFLAGS) -print-file-name=crtend.o)
</syntaxhighlight>
</source>
 
You can then use them as such (adapted to your real build system):
 
<sourcesyntaxhighlight lang="make">
OBJS:=foo.o bar.o
 
Line 64:
clean:
rm -f myos.kernel $(INTERNAL_OBJS)
</syntaxhighlight>
</source>
 
It is important to remember that the objects must be linked in this exact order, or you will experience strange bugs.
Line 152:
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>.
 
<sourcesyntaxhighlight lang="c">
/* crti.c for ARM - BPABI - use -std=c99 */
typedef void (*func_ptr)(void);
Line 173:
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)))) = { };
</syntaxhighlight>
</source>
 
<sourcesyntaxhighlight lang="c">
/* crtn.c for ARM - BPABI - use -std=c99 */
typedef void (*func_ptr)(void);
Line 181:
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)))) = { };
</syntaxhighlight>
</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.
Line 209:
Another way to execute the global constructors / destructors is to execute the .ctors / .dtors symbols manually (assuming you have your own ELF loader, see [[ELF_Tutorial]]). Once you have loaded each ELF file into memory, and all of the symbols have been resolved and relocated you can use .ctors / .dtors to execute the global constructors / destructors manually (apparently, the same applies to .init_array and .fini_array). To do this, you must first locate the .ctors / .dtors section headers:
 
<sourcesyntaxhighlight lang="c">
for (i = 0; i < ef->ehdr->e_shnum; i++)
{
Line 235:
}
}
</syntaxhighlight>
</source>
 
Now that you have the .ctors / .dtors section headers, you can resolve each constructor using the following. Note that .ctors / .dtors is a table of pointers (32bit for ELF32 and 64bit for ELF64). Each pointer is a function that must be executed.
 
<sourcesyntaxhighlight lang="c">
typedef void(*ctor_func)(void);
 
Line 254:
/* elf->exec is the char * that stores the location in memory that the ELF file have been loaded to, and and reloacted */
}
</syntaxhighlight>
</source>
 
Don't be surprised if you only have one entry in .ctors / .dtors. At least on x86_64, GCC appears to add a single entry to a set of functions called _GLOBAL__sub_I_XXX and _GLOBAL__sub_D_XXX which call _Z41__static_initialization_and_destruction_0ii that actually call each constructor for you. Adding more globally defined constructors / destructors will cause this function to grow, and not .ctors / .dtors.
Line 262:
If you don't call the constructors / destructors that GCC provides, GCC will generate code that will segfault under certain conditions with x86_64. Take this for example:
 
<sourcesyntaxhighlight lang="cpp">
class A
{
Line 276:
p_a->anything(); // <---- segfault
}
</syntaxhighlight>
</source>
 
It appears that GCC is using the constructor / destructor initialization routines to do more than simply call the constructors / destructors of each globally defined class. Executing the functions defined in .ctors / .dtors not only initializes all of the constructors / destructors, but resolves these types of segfaults (the above is only one example of many that are resolved). From what I can tell, when globally defined objects exist, GCC might also create ".data.rel.ro" which is another relocation table that GCC needs to process. It is marked as PROGBITS and not REL/RELA, which means that the ELF loader will not do the relocations for you. Instead, executing the functions defined in .ctors will execute _Z41__static_initialization_and_destruction_0ii which appears to perform the relocations for us. See the following for more info: [https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68738]
Line 294:
=== External links ===
* [https://gcc.gnu.org/onlinedocs/gccint/Initialization.html How Initialization Functions Are Handled] in the GCC Documentation.
 
 
[[Category:C]]
[[Category:C++]]