前言
分析linker相关,系统为android8.0
got-plt
参考:https://www.jianshu.com/p/e2a529e72d84
https://blog.csdn.net/L173864930/article/details/40507359
dynamic
|
|
DT_HASH -> .hash
DT_SYMTAB & DT_SYMENT -> .dynsym
DT_STRTAB & DT_STRSZ -> .dynstr
PLTREL(决定REL还是RELA) &(DT_REL | DT_RELA) & (DT_RELSZ | DT_RELASZ ) & (DT_RELENT | DT_RELAENT ) -> .rel.dyn
DT_JMPREL & DT_PLTRELSZ & (DT_RELENT | DT_RELAENT) -> .rel.plt
FINI_ARRAY & FINI_ARRAYSZ -> .fini_array
INIT_ARRAY & INIT_ARRAYSZ -> .init_array
意义
64位下GOT的表中每项的大小是8字节,正存放一个一个地址,其作用是提出跨模块的数据访问与函数调用地址放于数据段,数据段每个进程都有一份,从而使代码地址无关,可以进程共享。
plt是为了延迟绑定,防止linker工作过多耗费在修改got上。在代码与got间再加一层跳转,代码先跳到plt,再通过plt引用got寄存器跳转。plt存放于代码段,其与地址无关
实现简单来说就是连接器初始化时先把plt中jmp的下一条指令地址放于got中,第一次执行时代码跳回plt,plt统一跳到开头共同的处理代码,其获取got前三项的内容跳转(本ELF动态段的装载地址、本ELF的link-map数据结构描述符地址、dl-runtime-resolve函数的地址)
之后修改got为目标函数。第二次使用时经过plt引用got直接跳转到目标。
android下
android下.rel.plt是重定位got的,Android函数重定位并未采用延迟绑定,ida会解析got把got上存上自己定的extern段上的东西。plt上也没有书中精巧的指令,直接就取了got的内容之后寄存器跳转。其got前3个8字节也不用
.rela.dyn是需要重定位的数据的引用。重定位got的
.rela.plt存放需要重定位的函数引用。重定位got.plt的
R_AARCH64_JUMP_SLOT: 重定位类型,通过PLT找到目标符号的地址
以上主要决定导入问题,导出由动态连接里的SYMTAB决定
linker源码分析
着重寻找so的加载,解析过程
参考:https://zhuanlan.zhihu.com/p/31632620
linker自举
之前分析了execve在内核中装载elf文件与linker,之后由linker负责加载依赖的动态库。从这里开始进入用户态,是运行库负责的工作。
start
linker入口代码:
bionic\linker\arch\arm64\begin.S
在start函数中,栈顶指针寄存器被赋值给r0寄存器作为参数调用init。init做完linker的初始化和依赖库的加载后,通过r0返回了可执行文件入口函数,接下来的bx r0 指令就会将控制权移交给可执行文件。
初始的栈顶指向命令行参数、环境变量、ELF辅助向量。这些再execve时就初始好了,基本结构:
init
bionic\linker\linker_main.cpp
linker-init-post-relocation
dlopen
流程图
图库挂了….寻找稳定图库中
源码分析
入口部分
bionic\libdl\libdl.c
导出并代理了dlopen
bionic\linker\dlfcn.cpp
bionic\linker\linker.cpp
调用find_libraries
参数:ns、dlopen的caller、库名、1、返回的info、null、0、flag、null、false、true
|
|
主要为三个部分:
通过load_tasks栈广度优先搜索依赖库。
调用find_library_internal依次载入库。
link_image依次连接,链接与加载的顺序相反。
加载库
find_library_internal
load_library
创建 soinfo 对象,解析elf并将该.so 文件的依赖库添加到待加载的队列中。
加载详见ElfReader
基本过程为:
校验头部。
遍历段头表,找到可加载段,并通过 mmap 将可加载段从文件映射到内存。
- ReadElfHeader():从.so 文件中读取 ELF 头;
- VerifyElfHeader():校验 ELF 头;
- ReadProgramHeader():将.so 文件的 program header table 映射到内存;
- ReserveAddressSpace():开辟匿名内存空间;
- LoadSegments():将可加载段加载到 ReserveAddressSpace 开辟的空间中;
- FindPhdr():校验 program header table 是否在内存中。
之后解析dynamic获取DT_HASH、DT_STRTAB、 DT_STRSZ、DT_SYMTAB、DT_NEEDED,用于链接。
链接库
link_image
基本流程为:
通过重定位表的r_offset找到需要重定位的地址,通过r_info确定重定位类型与符号表索引。
之后用符号表索引找到符号,根据该符号去已加载的符号表中寻找,找到后即得到该符号地址等信息,根据重定位类型在重定位地点重定位。