• 每个程序都有的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

     

     

  • 相关阅读:
    char varchar nchar nvarchar text ntext区别
    Bindable 使用
    ShareObject离线存储相关
    as CPU 优化【转】
    as类收集(转)
    TweenLite 使用简介
    34个有用的ActionScript 3.0的API【转】
    as类库
    【转】A*寻路
    IOS学习笔记34—EGOTableViewPullRefresh实现下拉刷新
  • 原文地址:https://www.cnblogs.com/aspirs/p/15865739.html
Copyright © 2020-2023  润新知