Zhixiao Zhang

Author: Brooks Davis

Before calling main()

Execve

What's its role?

The execve() system call's job:

  • open the file to be executed;

  • clear the memory image of the current process;

  • map the file to be executed into the process space;

  • set up a stack with argument and environment vectors embedded;

  • return to user space with argument and program counter registers set up to call the __start() function.

How does it accomplish these things?

  1. copyies the program name, argument vector, and environment vector into kernel memory by exec_copyin_args

  2. calls do_execve via a wrapper kern_execve()

    1. resolves the path of the executable into a file by namei()

    2. verifies the caller's permission by exec_check_permissions

  3. allows the program headers to be parsed and the binary format determined by exec_map_first_page

  4. updates the credentials of the new process if the binary is marked setuid or getuid

  5. parse and configure the process address space by calling image activator (a binary-specific function)

    • the use of exec_map_first_page requires that any information required to set up the address space be stored in the first page of the binary, which is not necessary.

  6. copies out the argument array, environmenta array, and ELF auxiliary arguments by exec_copyout_strings

  7. initialize the state of the registers when execve() returns to the process by exec_setregs

  8. does a bit of cleanup and then returns to userspace finally.

__start

The __start()= function and related infrastructure account for much of the size of a minimal hello-world program.

Overview of the internal of =__start()=:

  1. calls _init_tls(), which sets up thread-local storage and cost so much time

  2. calls main()

  3. calls exit()

_init_tls()

job: allocate and initialize the TLS area for the first thread.

It spends most of its time in malloc initialization code. In fact, every program linked against libc initializes the malloc() subsystem before main().

  • the majority of thread-local variables are initialized to 0, the other's value is stored in the executable at a localtion accessible via the ELF auxiliary arguments.

How to find the specific value?

  1. locates the auxiliary arguments vector

Elf_Addr *sp;
sp = (Elf_Addr *) environ;
while (*sp++ != 0) ;
aux = (Elf_Auxinfo *) sp;

Actually this depends on a UB.

  1. finds the ELF program headers in the auxiliary argument vector

  2. finds the PT_TLS sement, copies the value to the beginning of the previous allocated TLS entry and sets the TLS pointer.

After TLS initialization

handle_static_init() calls various initialization functions:

  1. functions in the .pre_init_array section;

  2. _init() comprises all .init section in the program;

Calling main()

printf() is implemented as a call to vfprintf(), which in turn retrieves the current thread’s locale via __get_locale() and passes it to the function vfprintf_l(). vfprintf_l() finally calls __vfprintf() to do the actual work.

The majority of time in printf() is actually spent allocating an internal buffer in __swsetup().

Exiting

Processes exit either by being killed or by calling the _exit() system call. Programs call the exit() libc function to do this.

_cleanup called by _exit uses a buffered file stream to traverse buffered streams and flush any with outstanding data.

Dynamic linking

Difference with statical linked programs:

  • statically linked programs include all code they may call;

  • statically linked programs are non-relocable;

  • for dynamically linked programs, the kernel loads both the program and the runtime linker.

    • when execve() returns to userspace, it returns calling rtld_start() in the linker rather than =_start()__ in the program.

Looking upan external symbol is expensive at the first time, which need to search all of userspace functions. It will then being stored in the Global Offset Table and being called.

Each undefined function has an entry in the GOT.

Summary

A few things of the low-level design are influenced by the need to support legacy code. However, most of them are there currently for good reasons.