每个程序都有的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的 ---from: https://www.zhihu.com/question/353341442/answer/949594559
本文基于android Q源码进行写作,compiler为clang,linker为ld.lld