本文分析了在main()之前的ELF程式流程,试图让您更清楚的把握程式的流程的脉络走向。
从而更深入的了解ELF。不正确之处,还请斧正。

★ 综述

ELF的可执行文档和共享库在结构上很类似,他们具备一张程式段表,用来描述这些段如何映射到进程空间.
对于可执行文档来说,段的加载位置是固定的,程式段表中如实反映了段的加载地址.对于共享库来说,段的加
载位置是浮动的,位置无关的,程式段表反映的是以0作为基准地址的相对加载地址.尽管共享库的连接是不
充分的,为了便于测试动态链接器,Linux允许直接加载共享库运行.假如应用程式具备动态链接器的描述段,
内核在完成程式段加载后,紧接着加载动态链接器,并且启动动态链接器的入口.假如没有动态链接器的描述段,
就直接交给用户程式入口。
上述这部分请参考:linuxforum论坛上opera写的《分析ELF的加载过程》

在控制权交给动态链接器的入口后,首先调用_dl_start函数获得真实的程式入口(注:该入口地址
不是main的地址,也就是说一般程式的入口不是main),然后循环调用每个共享object的初始化函数,
接着跳转到真实的程式入口,一般为_start(程式中的_start)的一个例程,该例程压入一些参数到堆栈,
就直接调用__libc_start_main函数。在__libc_start_main函数中替动态连接器和自己程式安排
destructor,并运行程式的初始化函数。然后才把控制权交给main()函数。



★ main()之前流程

下面就是动态链接器的入口。
/* Initial entry point code for the dynamic linker.
The C function `_dl_start' is the real entry point;
its return value is the user program's entry point. */

#define RTLD_START asm ("\
.text\n\
.globl _start\n\
.globl _dl_start_user\n\
_start:\n\
pushl %esp\n\
call _dl_start\n\/*该函数返回时候,陎中存放着user entry point address*/
popl 離\n\/*離放着是esp的内容*/
_dl_start_user:\n\
# Save the user entry point address in 韎.\n\
movl 陎, 韎\n\/*入口地址放在韎*/

# Point 離 at the GOT.
call 0f\n\
0: popl 離\n\
addl $_GLOBAL_OFFSET_TABLE_ [.-0b], 離\n\

# Store the highest stack address\n\
movl __libc_stack_end@GOT(離), 陎\n\
movl %esp, (陎)\n\/*把栈顶%esp放到GOT的__libc_stack_end中*/

# See if we were run as a command with the executable file\n\
# name as an extra leading argument.\n\
movl _dl_skip_args@GOT(離), 陎\n\
movl (陎), 陎\n\

# Pop the original argument count.\n\
popl 靫\n\

# Subtract _dl_skip_args from it.\n\
subl 陎, 靫\n\

# Adjust the stack pointer to skip _dl_skip_args words.\n\
leal (%esp,陎,4), %esp\n\

# Push back the modified argument count.\n\
pushl 靫\n\

# Push the searchlist of the main object as argument in\n\
# _dl_init_next call below.\n\
movl _dl_main_searchlist@GOT(離), 陎\n\
movl (陎), %esi\n\
0: movl %esi,陎\n\

# Call _dl_init_next to return the address of an initializer\n\
# function to run.\n\
call _dl_init_next@PLT\n\/*该函数返回初始化函数的地址,返回地址放在陎中*/

# Check for zero return, when out of initializers.\n\
testl 陎, 陎\n\
jz 1f\n\

# Call the shared object initializer function.\n\
# NOTE: We depend only on the registers (離, %esi and 韎)\n\
# and the return address pushed by this call;\n\
# the initializer is called with the stack just\n\
# as it appears on entry, and it is free to move\n\
# the stack around, as long as it winds up jumping to\n\
# the return address on the top of the stack.\n\
call *陎\n\/*调用共享object初始化函数*/

# Loop to call _dl_init_next for the next initializer.\n\
jmp 0b\n\

1: # Clear the startup flag.\n\
movl _dl_starting_up@GOT(離), 陎\n\
movl , (陎)\n\

# Pass our finalizer function to the user in 韝, as per ELF ABI.\n\
movl _dl_fini@GOT(離), 韝\n\

# Jump to the user's entry point.\n\
jmp *韎\n\
.previous\n\
");


sysdeps\i386\start.s中
user's entry也就是下面的_start例程

/* This is the canonical entry point, usually the first thing in the text
segment. The SVR4/i386 ABI (pages 3-31, 3-32) says that when the entry
point runs, most registers' values are unspecified, except for:

韝 Contains a function pointer to be registered with `atexit'.
This is how the dynamic linker arranges to have DT_FINI
functions called for shared libraries that have been loaded
before this code runs.

%esp The stack contains the arguments and environment:
0(%esp) argc
4(%esp) argv[0]
...
(4*argc)(%esp) NULL
(4*(argc 1))(%esp) envp[0]
...
NULL
*/

.text
.globl _start

文章整理:西部数码--专业提供域名注册虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!