bionic linker代码分析(1) - linker自举

研究bionic是两年前做symbol hook时,分析了linker解析ELF文件的相关代码;去年底Android 7发布之后,为了解决dlopen系统目录下动态库的限制,研究了系统库访问权限的相关代码,彻底把linker自举和加载动态库的流程理清楚。
摘要由CSDN通过智能技术生成

Android在启动一个新的进程的时候,是由execv函数族trap到内核,由kernel去检查和加载可执行文件;kernel做完可执行文件的加载同时会加载/system/bin/linker,然后由linker去加载依赖的动态库,并调用可执行文件的入口函数,完成控制权的转移。

linker本身也是一个ELF格式的动态库文件,它的入口代码位于${bionic}/linker/arch/arm/begin.S文件中

#include <private/bionic_asm.h>
ENTRY(_start)
  mov r0, sp
  bl __linker_init

  /* linker init returns the _entry address in the main image */
  bx r0
END(_start)

在_start函数中,栈顶指针寄存器被赋值给r0寄存器作为参数调用__linker_init。__linker_init做完linker的初始化和依赖库的加载后,通过r0返回了可执行文件入口函数,接下来的bx r0 指令就会将控制权移交给可执行文件。

__linker_init() 位于${bionic}/linker/linker_main.cpp文件中:

492 extern "C" ElfW(Addr) __linker_init(void* raw_args) {
493   KernelArgumentBlock args(raw_args);
494 
495   // AT_BASE is set to 0 in the case when linker is run by iself
496   // so in order to link the linker it needs to calcuate AT_BASE
497   // using information at hand. The trick below takes advantage
498   // of the fact that the value of linktime_addr before relocations
499   // are run is an offset and this can be used to calculate AT_BASE.
500   static uintptr_t linktime_addr = reinterpret_cast<uintptr_t>(&linktime_addr);
501   ElfW(Addr) linker_addr = reinterpret_cast<uintptr_t>(&linktime_addr) - linktime_addr;

493行的KernelArgumentBlock类在 ${bionic}/libc/private/KernelArgumentBlock.h文件中定义。kernel在加载linker时,已经在堆栈中初始化好了命令行参数、环境变量以及后面的ELF辅助向量(Auxiliary Vector)。raw_args即_start中传入的栈顶指针寄存器,通过args对象只是对上述信息进行封装,提供一系列读写接口而已。堆栈的内存布局如下:

position            content                     size (bytes)  comment
  ------------------------------------------------------------------------
stack pointer ->  [ argc = number of args ]     4      
                  [ argv[0] (pointer) ]         4      
                  [ argv[1] (pointer) ]         4      
                  [ argv[..] (pointer) ]        4 * n  
                  [ argv[n - 1] (pointer) ]     4      
                  [ argv[n] (pointer) ]         4           = NULL

                  [ envp[0] (pointer) ]         4     
                  [ envp[1] (pointer) ]         4      
                  [ envp[..] (pointer) ]        4      
                  [ envp[term] (pointer) ]      4           = NULL

                  [ auxv[0] (Elf32_auxv_t) ]    8      
                  [ auxv[1] (Elf32_auxv_t) ]    8
                  [ auxv[..] (Elf32_auxv_t) ]   8 
                  [ auxv[term] (Elf32_auxv_t) ] 8           = AT_NULL vector

                  [ padding ]                   0 - 16     

                  [ argument ASCIIZ strings ]   >= 0   
                  [ environment ASCIIZ str. ]   >= 0   

(0xbffffffc)      [ end marker ]                4          = NULL 结束

(0xc0000000)       < bottom of stack >          0          (virtual)

500行定义的linker_addr变量就是linker文件在内存中实际映射的基地址,在Android 7之前,linker_addr是通过是直接从ELF辅助向量中读取AT_BASE获得。Android 8之后,通过定义静态变量linktime_addr是来计算linker_addr。这里通过对linker反汇编,来理解这两行代码的trick,是如何计算linker_addr,

先找到__linker_init函数的实现

0xf57ed的指令r2 = r2(0x78c2e) + pc(linker_addr + 0xf582) = linker_addr + 0x881B0; 因为实际在内存运行的时候指令寄存器pc的值是基地址+偏移地址,所以实际当前的pc = linker_addr + 0xf582,armv7三级流水线pc等于取指地址0xf582。

接来下的指令ldr r3, [r2]也就是将linker_addr+0x881B0中的内容赋值给r3,图2中0x881B0地址的值等于0x881B0,所以r3的值等于0x881B0。

然后在地址0xf588的指令,r5 = r2(linker_addr+0x881B0) - r3(0x881B0) = linker_addr

这里写图片描述

图1 计算linker_addr汇编指令

这里写图片描述

图2 计算linktime_addr内存值

503 #if defined(__clang_analyzer__)
504   // The analyzer assumes that linker_addr will always be null. Make it an
505   // unknown value so we don't have to mark N places with NOLINTs.
506   //
507   // (`+=`, rather than `=`, allows us to sidestep a potential "unused stor
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值