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?
copyies the program name, argument vector, and environment vector into kernel memory by
exec_copyin_args
calls
do_execve
via a wrapperkern_execve()
resolves the path of the executable into a file by
namei()
verifies the caller's permission by
exec_check_permissions
allows the program headers to be parsed and the binary format determined by
exec_map_first_page
updates the credentials of the new process if the binary is marked
setuid
orgetuid
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.
copies out the argument array, environmenta array, and ELF auxiliary arguments by
exec_copyout_strings
initialize the state of the registers when
execve()
returns to the process byexec_setregs
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()=
:
calls
_init_tls()
, which sets up thread-local storage and cost so much timecalls
main()
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?
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.
finds the ELF program headers in the auxiliary argument vector
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:
functions in the
.pre_init_array
section;_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 callingrtld_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.