Android Linker分析笔记

本文详细探讨了Android系统的加载器/链接器——linker的工作原理,包括linker的启动、__linker_init函数、重定位过程以及执行主体功能。重点介绍了如何通过find_linker_base获取linker地址,以及在重定位阶段的动态链接库加载和重定位操作。通过对linker源码的分析,揭示了Android应用程序加载过程的关键细节。
摘要由CSDN通过智能技术生成

这篇笔记做过一次,但是做的不好,现在又重新做一遍,其中有几处借鉴了同学的笔记文档,对此表示感谢。不足之处欢迎交流,我来进行改进,希望能对其他学习这方面东西的同学有所帮助。

linker是Android系统的加载器/链接器,当上层加载.so文件时,具体的工作就是由linker实际完成的。

linker的启动

因为linker本身也是一个.so文件,它也需要链接器的装载和链接,而这个链接器就是它本身。

首先,linker的入口在Android/bionic/linker/arch/arm/begin.S:

_start:
    mov r0, sp
    mov r1, #0
    bl  __linker_init

    /* linker init returns the _entry address in the main image */
    mov pc, r0

    .section .ctors, "wa"
    .globl __CTOR_LIST__
__CTOR_LIST__:
    .long -1

首先将堆栈指针的值传给r0,并将r1置零。接下来跳转到函数__linker_init,传入的参数就是r0。最后返回的值为程序镜像的入口地址,传给pc,开始正式地执行程序。

__linker_init

接下来,看一下函数__linker_init的内容,首先看一下该函数的注释:

/*
 * This is the entry point for the linker, called from begin.S. This
 * method is responsible for fixing the linker's own relocations, and
 * then calling __linker_init_post_relocation().
 *
 * Because this method is called before the linker has fixed it's own
 * relocations, any attempt to reference an extern variable, extern
 * function, or other GOT reference will generate a segfault.
 */

这段注释的意思是,该函数是linker的入口,负责对linker本身进行重定位,然后调用函数__linker_init_post_relocation。由于在这个函数调用之前,linker还没有自举,也就没法重定位自身,所以任何试图引用外部变量、函数或者其他GOT的操作都会导致错误,因为这些操作都需要链接器(linker)来完成。然后看一下函数的具体内容。

extern "C" unsigned __linker_init(unsigned **elfdata) {
    unsigned linker_addr = find_linker_base(elfdata);
    Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *) linker_addr;
    Elf32_Phdr *phdr =
        (Elf32_Phdr *)((unsigned char *) linker_addr + elf_hdr->e_phoff);

    soinfo linker_so;
    memset(&linker_so, 0, sizeof(soinfo));

    linker_so.base = linker_addr;
    linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
    linker_so.load_bias = get_elf_exec_load_bias(elf_hdr);
    linker_so.dynamic = (unsigned *) -1;
    linker_so.phdr = phdr;
    linker_so.phnum = elf_hdr->e_phnum;
    linker_so.flags |= FLAG_LINKER;

    if (soinfo_link_image(&linker_so)) {
        // It would be nice to print an error message, but if the linker
        // can't link itself, there's no guarantee that we'll be able to
        // call write() (because it involves a GOT reference).
        //
        // This situation should never occur unless the linker itself
        // is corrupt.
        exit(-1);
    }

    // We have successfully fixed our own relocations. It's safe to run
    // the main part of the linker now.
    return __linker_init_post_relocation(elfdata, linker_addr);
}

首先通过函数find_linker_base()获取linker的地址,因为linker本身是一个Elf文件,所以可以分别获取它的文件header和程序段header(这是在可执行视图下,具体的详见Elf文件格式的相关资料)。创建一个soinfo结构体,获取linker文件的相关信息,填入该soinfo结构体的对应字段。最后调用函数soinfo_link_image()完成linker的重定位,执行函数__linker_init_post_relocation()继续完成linker的主体部分。

find_linker_base

这个函数是用来寻找链接器linker本身的装载地址。

/*
 * Find the value of AT_BASE passed to us by the kernel. This is the load
 * location of the linker.
 */
static unsigned find_linker_base(unsigned **elfdata) {
    int argc = (int) *elfdata;
    char **argv = (char**) (elfdata + 1);
    unsigned *vecs = (unsigned*) (argv + argc + 1);
    while (vecs[0] != 0) {
        vecs++;
    }

    /* The end of the environment block is marked by two NULL pointers */
    vecs++;

    while(vecs[0]) {
        if (vecs[0] == AT_BASE) {
            return vecs[1];
        }
        vecs += 2;
    }

    return 0; // should never happen
}

当内核启动动态链接器的时候,内核会把一个指针传递给linker,也就是上面提到的r0。这个指针指向一块这样的内容:这块内容包括 argc、argv 数组、环境变量数组和动态链接器所需的信息辅助数组,这些事先保存在进程堆栈的初 始化信息都是由操作系统完成的。上述 find_linker_base 函数的参数 elfdata 便是内核传递给 Linker 的指针, 利用该指针 find_linker_base 首先获取参数个数、参数指针,然后准备跳过环境变量字符串,argv + argc + 1中的 +1 是因为参数字符串地址结束是以0结束的,即有 4 个字节是 0。后面的 while 循环便是跳过环境变量字符串,跳过环境变量字符串之后,便是动态链接器中的辅助信息数组,其结构如下:

typedef struct
{
    uint32_t a_type; /* Entry type */ 
    union
    {
        uint32_t a_val; /* Integer value */
        /* We use to have pointer elements added here. We cannot do that, though, since it does not work when using 32-bit definitions on 64-bit platforms and vice versa. */
    } a_un;
} Elf32_auxv_t;

前面的a_type表示类型,后面的a_val表示对应的值,这里要找的是AT_BASE类型,其值表示的是链接器的装载地址,然后返回这个地址。

由于链接器本身就是一个Elf文件,知道了它的装载地址,可以根据Elf文件格式获取它的文件头和段表。

现在还有一个需要搞清楚的就是结构体soinfo。

struct soinfo
{
    char name[SOINFO_NAME_LEN];
    const Elf32_Phdr *phdr;
    int phnum;
    unsigned entry;
    unsigned base;
    unsigned size;

    int unused;  // DO NOT USE, maintained for compatibility.

    unsigned *dynamic;

    unsigned unused2; // DO NOT USE, maintained for compatibility
    unsigned unused3; // DO NOT USE, maintained for compatibility

    soinfo *next;
    unsigned flags;

    const char *strtab;
    Elf32_Sym *symtab;

    unsigned nbucket;
    unsigned nchain;
    unsigned *bucket;
    unsigned *chain;

    unsigned *plt_got;

    Elf32_Rel *plt_rel;
    unsigned plt_rel_count;

    Elf32_Rel *rel;
    unsigned rel_count;

    unsigned *preinit_array;
    unsigned preinit_array_count;

    unsigned *init_array;
    unsigned init_array_count;
    unsigned *fini_array;
    unsigned fini_array_count;

    void (*init_func)(void);
    void (*fini_func)(void);

#if defined(ANDROID_ARM_LINKER)
    /* ARM EABI section used for stack unwinding. */
    unsigned *ARM_exidx;
    unsigned ARM_exidx_count;
#elif defined(ANDROID_MIPS_LINKER)
#if 0
     /* not yet */
     unsigned *mips_pltgot
#endif
     unsigned mips_symtabno;
     unsigned mips_local_gotno;
     unsigned mips_gotsym;
#endif /* ANDROID_*_LINKER */

    unsigned refcount;
    struct link_map linkmap;

    int constructors_called;

    /* When you read a virtual address from the ELF file, add this
     * value to get the corresponding address in the process' address space */
    Elf32_Addr load_bias;
    int has_text_relocations;
};

内容十分多,从函数__linker_init的源码中可以看到它分别填充了soinfo结构体中的几个字段,分别是:base、size、load_bias、dynamic、phdr、phnum和flags。其中,base表示linker的装载地址,size表示所有可加载的段的总大小,load_bias是为了将虚拟地址映射到物理地址的偏移值,dynamic表示.dynamic的地址(初始化为-1),phdr表示段表头,phnum表示段的个数,flags表示装载的类型(FLAG_LINKER表示链接器的自举)。这些字段基本上都可以直接得到,除了size和load_bias两个是需要计算的。


先看如何计算size。

/* Compute the extent of all loadable segments in an ELF program header
 * table. This corresponds to the page-aligned size in bytes that needs to be
 * reserved in the process' address space
 *
 * This returns 0 if there are no loadable segments.
 */
Elf32_Addr phdr_table_get_load_size(const Elf32_Phdr* phdr_table,
                                    size_t phdr_count)
{
    Elf32_Addr min_vaddr = 0xFFFFFFFFU;
    Elf32_Addr max_vaddr = 0x00000000U;

    for (size_t i = 0; i < phdr_count; ++i) {
        const Elf32_Phdr* phdr = &phdr_table[i];

        if (phdr->p_type != PT_LOAD) {
            continue;
        }

        if (phdr->p_vaddr < min_vaddr) {
            min_vaddr = phdr->p_vaddr;
        }

        if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
            max_vaddr = phdr->p_vaddr + phdr->p_memsz;
        }
    }

    if (min_vaddr > max_vaddr) {
        return 0;
    }

    min_vaddr = PAGE_START(min_vaddr);
    max_vaddr = PAGE_END(max_vaddr);

    return max_vaddr - min_vaddr;
}

传入的参数是第一个段表头的地址和段表头的数目,从第一个开始便利,若其类型为PT_LOAD,则表示可加载的,min_vaddr最后保存的是这些可加载的段的最小地址,max_vaddr保存的是最大地址。再通过两个宏处理一下,将两者的差作为返回值。这两个宏的具体内容如下:

/* Returns the address of the page starting at address 'x' */
#define PAGE_START(x)  ((x) & ~PAGE_MASK)

/* Returns the address of the next page after address 'x', unless 'x' is
 * itself at the start of a page. Equivalent to:
 *
 *  (x == PAGE_START(x)) ? x : PAGE_START(x)+PAGE_SIZE
 */
#define PAGE_END(x)    PAGE_START((x) + (PAGE_SIZE-1))

PAGE_START(x)通过将虚拟地址与页表的掩码相与,可以得出是从哪一页开始的;PAGE_END(x)就是在当前地址的基础上加上一个页的大小减1,这样做是为了页的对齐,最后返回的大小并不是恰好所占用的虚拟空间大小,而是页的整数倍。

然后计算当前可执行文件的偏移值,这个函数只能用于通过内核加载的可执行文件或者共享目标文件。

/* Compute the load-bias 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值