Skip to content

Instantly share code, notes, and snippets.

@jstaursky
Forked from apsun/c_init_tldr.c
Created January 18, 2021 14:07
Show Gist options
  • Save jstaursky/ae82f546680881b556c4b7b45b16a793 to your computer and use it in GitHub Desktop.
Save jstaursky/ae82f546680881b556c4b7b45b16a793 to your computer and use it in GitHub Desktop.
/*
* tl;dr: C program initialization, written in C!
*
* This applies to an executable dynamically linked with glibc.
* It is current as of glibc 2.26.
*
* A LOT of information has been omitted for simplicity; hell,
* some of it might be flat-out wrong (I wrote this after about
* 3 hours of experimenting with GDB). If you want to know EXACTLY
* what goes on under the covers, I advise you to read the
* actual glibc source.
*/
/*
* glibc/sysdeps/<arch>/start.S
*
* Entry point of the program. Effectively just calls
* __libc_start_main().
*/
void _start(void)
{
__libc_start_main(
&main,
argc,
argv,
init = __libc_csu_init,
fini = __libc_csu_fini,
rtld_fini = _dl_fini,
stack_end = /* current stack pointer */
);
}
/*
* glibc/csu/libc-start.c
*
* The "main" function of libc. Does setup and teardown.
*/
void __libc_start_main(
int (*main)(int, char **, char **),
int argc,
char **argv,
int (*init)(int, char **, char **),
void (*fini)(void),
void (*rtld_fini)(void),
void *stack_end)
{
/*
* Register the dynamic linker's finalizer (_dl_fini)
* to run when exit() is called.
*/
if (rtld_fini != NULL)
atexit(rtld_fini);
/*
* Call the initialization function (__libc_csu_init).
*/
if (init != NULL)
init(argc, argv, envp);
/*
* Call main() (note that the envp array starts right
* after the argv array).
*/
char **envp = &argv[argc + 1];
int result = main(argc, argv, envp);
/*
* And call exit() with the return value of main().
*/
exit(result);
}
/*
* glibc/csu/elf-init.c
*
* Performs initialization. I don't really know how to describe
* it any better.
*/
int __libc_csu_init(int argc, char **argv, char **envp)
{
/*
* Call all the __attribute__((constructor)) functions.
* These symbols are generated by the linker.
*/
size_t num_init = __init_array_end - __init_array_start;
for (size_t i = 0; i < num_init; i++) {
__init_array_start[i](argc, argv, envp);
}
}
/*
* glibc/stdlib/exit.c
*
* Calls all functions registered with atexit() in reverse
* registration order (like a stack), then halts the program.
*/
void exit(int status)
{
/*
* __exit_funcs is a linked list of arrays of functions that
* were registered with atexit(). The order is important;
* the structure looks like this:
*
* __exit_funcs -> [G] -> [D] -> [A] -> NULL
* [H] [E] [B]
* [ ] [F] [C]
*
*/
struct exit_function_list *head = __exit_funcs;
for (struct exit_function_list *curr = head; curr != NULL; curr = curr->next) {
for (int i = curr->idx - 1; i >= 0; i--) {
curr->fns[i]();
}
}
/*
* Do the actual Linux exit syscall.
*/
_exit(status);
}
/*
* glibc/elf/dl-fini.c
*
* The linker's finalizer. Calls the destructor of all loaded
* libraries in the correct order (i.e. if X depends on Y,
* the destructors of X are run before the destructors of Y).
*/
void _dl_fini(void)
{
/*
* Get a list of all the loaded shared objects and sort
* them in the appropriate order. This is essentially
* a DAG topological sort.
*/
int nmaps = /* ... */;
struct link_map *maps[nmaps] = /* ... */;
_dl_sort_fini(maps, nmaps, /* ... */);
/*
* Then run all the destructors for each shared object.
* This calls functions marked with __attribute__((destructor)).
*/
for (int i = 0; i < nmaps; i++) {
struct link_map *map = maps[i];
for (int j = FINI_ARRAY_SZ(map) - 1; j >= 0; j--) {
FINI_ARRAY(map)[j]();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment