每个程序都有的main函数是谁调用的?

每个程序都有的main函数是谁调用的?

程序有动态链接程序和静态链接程序,这两类程序开始执行的flow有些差异,下面将对这两类程序开始执行的flow进行说明

 

动态链接程序开始执行流程

1. 链接器执行阶段,执行加载依赖的动态链接库

动态链接程序需要在开始执行前先链接依赖的动态库,这个链接的工作由链接器来完成,通过readelf -l [elf_program]来查看一个elf program的linker是什么,这个cmd结果里会有一项如下:

.INTERP

requesting program interpreter: /system/bin/linker64

表明这个程序的linker是/system/bin/linker64

以aarch64为例,先执行__linker_init(),这个函数返回程序的entry point,保存在x0,然后br x0跳转到程序entry point开始执行:

bionic/linker/arch/arm64/begin.S

31 ENTRY(_start)
32   // Force unwinds to end in this function.
33   .cfi_undefined x30
34 
35   mov x0, sp
36   bl __linker_init
37 
38   /* linker init returns the _entry address in the main image */
39   br x0
40 END(_start)

 

2. 转入程序entry point位置执行

程序入口

程序的入口是_start,其定义在crtbegin.c里,用asm定义:

* 关于这个_start入口,用readelf -h查看程序entry point,结果里的entry point,如果用llvm-symbolizer -e [program_name] [entry_point]将这个地址定位出来也是_start label

* crtbegin.o是直接被链接进可执行程序里面了的,每个可执行程序都是这样

 

bionic/libc/arch-common/bionic/crtbegin.c

48  #define PRE ".text; .global _start; .type _start,%function; _start:"
49  #define POST "; .size _start, .-_start"
50  
51  #if defined(__aarch64__)
52  __asm__(PRE "mov x0,sp; b _start_main" POST);
53  #elif defined(__arm__)
54  __asm__(PRE "mov r0,sp; b _start_main" POST);
55  #elif defined(__i386__)
56  __asm__(PRE "movl %esp,%eax; andl $~0xf,%esp; subl $12,%esp; pushl %eax; calll _start_main" POST);
57  #elif defined(__x86_64__)
58  __asm__(PRE "movq %rsp,%rdi; andq $~0xf,%rsp; callq _start_main" POST);
59  #else
60  #error unsupported architecture
61  #endif

 

 

上面会跳转到_start_main,在这个函数里会传递main函数指针,没错,这个main函数就是每个程序都有的main函数:

39  __used static void _start_main(void* raw_args) {
40    structors_array_t array;
41    array.preinit_array = &__PREINIT_ARRAY__;
42    array.init_array = &__INIT_ARRAY__;
43    array.fini_array = &__FINI_ARRAY__;
44  
45    __libc_init(raw_args, NULL, &main, &array);
46  }

 

__libc_init()里的slingshot参数即指向main函数,调用main函数,其返回值作为exit()调用的参数:

137  __noreturn void __libc_init(void* raw_args,
138                              void (*onexit)(void) __unused,
139                              int (*slingshot)(int, char**, char**),
140                              structors_array_t const * const structors) {
141    BIONIC_STOP_UNWIND;
142  
143    KernelArgumentBlock args(raw_args);
144  
145    // Several Linux ABIs don't pass the onexit pointer, and the ones that
146    // do never use it.  Therefore, we ignore it.
147  
148    // The executable may have its own destructors listed in its .fini_array
149    // so we need to ensure that these are called when the program exits
150    // normally.
151    if (structors->fini_array) {
152      __cxa_atexit(__libc_fini,structors->fini_array,nullptr);
153    }
154  
155    exit(slingshot(args.argc - __libc_shared_globals()->initial_linker_arg_count,
156                   args.argv + __libc_shared_globals()->initial_linker_arg_count,
157                   args.envp));
158  }

 

 在调用__libc_init()时,如果一个程序是动态链接的,则对应libc_init_dynamic.cpp里的__libc_init();如果是静态链接的,则调用libc_init_static.cpp里的__libc_init():

 

静态链接程序开始执行流程

静态链接程序没有链接器执行的部分,直接是从程序的entry point位置处(_start,同上述动态链接程序)开始执行,然后会调用libc_init_static.cpp里的__libc_init()

 

动态链接程序和静态链接程序.preinit_array/.init_array函数是谁来执行的?

动态链接程序的.preinit_array/.init_array函数集由动态链接器来调用执行,是在动态链接器阶段完成的,所以在跳转到程序入口地址/main函数之前就执行完成了;

静态链接程序的.preinit_array/.init_array函数集由程序自己调用执行,具体调用位置在__real_libc_init(),它也是在main函数之前执行的。

所以无论是动态链接还是静态链接的程序,.preinit_array/.init_array函数集均在main函数执行之前执行

* 有constructor attribute的函数是被放置在.init_array段

168  __noreturn static void __real_libc_init(void *raw_args,
169                                          void (*onexit)(void) __unused,
170                                          int (*slingshot)(int, char**, char**),
171                                          structors_array_t const * const structors,
172                                          bionic_tcb* temp_tcb) {
173    BIONIC_STOP_UNWIND;
174  
175    // Initialize TLS early so system calls and errno work.
176    KernelArgumentBlock args(raw_args);
177    __libc_init_main_thread_early(args, temp_tcb);
178    __libc_init_main_thread_late();
179    __libc_init_globals();
180    __libc_shared_globals()->init_progname = args.argv[0];
181    __libc_init_AT_SECURE(args.envp);
182    layout_static_tls(args);
183    __libc_init_main_thread_final();
184    __libc_init_common();
185  
186    call_ifunc_resolvers();
187    apply_gnu_relro();
188  
189    // Several Linux ABIs don't pass the onexit pointer, and the ones that
190    // do never use it.  Therefore, we ignore it.
191  
192    call_array(structors->preinit_array);
193    call_array(structors->init_array);
194  
195    // The executable may have its own destructors listed in its .fini_array
196    // so we need to ensure that these are called when the program exits
197    // normally.
198    if (structors->fini_array != nullptr) {
199      __cxa_atexit(__libc_fini,structors->fini_array,nullptr);
200    }
201  
202    exit(slingshot(args.argc, args.argv, args.envp));
203  }

 

android gcc link script

如下文件,可以看到link script里define的程序入口是_start:

prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/aarch64-linux-android/lib/ldscripts/aarch64elf.x

6  OUTPUT_FORMAT("elf64-littleaarch64", "elf64-bigaarch64",
7            "elf64-littleaarch64")
8  OUTPUT_ARCH(aarch64)
9  ENTRY(_start)

* 至于使用clang时ld.lld,没有找到有相关的link script,听说是没有指定link script时是链接器内部code class里自行处理了,链接如下,可以使用-T参数指定link script

是的,在不显示指定script的情况下,是按照类里的实现来安排各个segment和section的    ---fromhttps://www.zhihu.com/question/353341442/answer/949594559

 

 

本文基于android Q源码进行写作,compiler为clang,linker为ld.lld

 

 

 

 

 

posted @ 2022-02-06 15:33  aspirs  阅读(535)  评论(0编辑  收藏  举报