限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 本文目标简要分析Linux动态链接过程中,函数的链接过程。3. 动态链接过程分析 3.1 分析背景
. arm32 cortex a7 . linux 4.14.111 . ubuntu 16.04 arm core . gcc 4.9 . glibc-2.313.2 流程分析 3.2.1 程序代码
#includeint main(void) { int i; scanf("%d", &i); printf("Hello, World!n"); return 0; }
编译:
arm-linux-gcc -o test test.c3.2.2 从程序执行开始说起
当在控制台输入:
./test
开始,bash会调用 fork() 创建一个新的子进程,然后在新进程中调用 execve() 加载test程序,我们的分析从 execve() 系统调用开始:
sys_execve(filename, argv, envp)
...
do_execveat_common(AT_FDCWD, filename, argv, envp, 0)
...
search_binary_handler(bprm)
fmt->load_binary(bprm) = load_elf_binary(bprm)
...
elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
for (i = 0; i < loc->elf_ex.e_phnum; i++) {
if (elf_ppnt->p_type == PT_INTERP) {
...
elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
kernel_read(bprm->file, elf_interpreter, elf_ppnt->p_filesz, &pos);
...
interpreter = open_exec(elf_interpreter);
...
}
}
...
elf_entry = load_elf_interp(&loc->interp_elf_ex,
interpreter, &interp_map_addr,
load_bias, interp_elf_phdata);
interp_load_addr = elf_entry;
elf_entry += loc->interp_elf_ex.e_entry;
start_thread(regs, elf_entry, bprm->p)
3.2.3 程序的动态链接初始化
_start
_dl_start()
bootstrap_map.l_addr = elf_machine_load_address ();
elf_get_dynamic_info (&bootstrap_map, NULL);
...
bootstrap_map.l_relocated = 1;
...
entry = _dl_start_final (arg)
...
start_addr = _dl_sysdep_start (arg, &dl_main)
...
user_entry = (ElfW(Addr)) ENTRY_POINT;
...
(*dl_main) (phdr, phnum, &user_entry, GLRO(dl_auxv)) = dl_main()
main_map = _dl_new_object ((char *) "", "", lt_executable, NULL,
__RTLD_OPENEXEC, LM_ID_BASE);
...
main_map->l_entry = *user_entry;
...
elf_get_dynamic_info (main_map, NULL);
...
unsigned i = main_map->l_searchlist.r_nlist;
while (i-- > 0) {
...
_dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
consider_profiling)
...
ELF_DYNAMIC_RELOCATE (l, lazy, consider_profiling, skip_ifunc)
if (l->l_info[DT_JMPREL] && lazy) {
got[1] = (Elf32_Addr) l;
got[2] = (Elf32_Addr) &_dl_runtime_resolve;
}
...
}
return user_entry;
...
return start_addr;
...
在这一小节,我们只需要重点关注,程序的 GOT 表项: GOT[1] 和 GOT[2] 赋值。
3.2.4 函数的动态链接过程初次调用触发对函数的动态链接过程,我们需从汇编层次来观察这些细节。反汇编程序:
arm-linux-objdump -D test > test.S3.2.4.1 对函数的调用
000104703.2.4.2 从PLT调用表跳转到_dl_runtime_resolve(): ... 1048c: ebffffa7 bl 10330 <__isoc99_scanf@plt> ; 调用 scanf() ... 10498: ebffff9b bl 1030c ; 调用 printf() ...
函数调用PLT表:
Disassembly of section .plt: 000102f8: ; 所有函数初次调用的PLT表项 ... (3) 102f8: e52de004 push {lr} ; 将函数返回地址压入堆栈 (返回main()函数) 102fc: e59fe004 ldr lr, [pc, #4] ; lr = 0x00010328 10300: e08fe00e add lr, pc, lr ; lr = 0x10308 + 0x00010328 = 0x20630 10304: e5bef008 ldr pc, [lr, #8]! ; pc = [0x20630 + 8] = [0x20638] = 0x00000000, lr = 0x20638 10308: 00010328 andeq r0, r1, r8, lsr #6 ... 00010330 <__isoc99_scanf@plt>: ; 函数 scanf 的PLT表项 ... (1) 10330: e28fc600 add ip, pc, #0, 12 ; ip = 0x10338 10334: e28cca10 add ip, ip, #16, 20 ; ip = 0x10338 + 0x10000 = 0x20338 10338: e5bcf310 ldr pc, [ip, #784]! ; pc = [0x20338 + 0x310] = [0x20648] = 0x000102f8, ip = 0x20648
函数调用GOT表:
Disassembly of section .got: 00020630 <_GLOBAL_OFFSET_TABLE_>: 20630: 00020548 andeq r0, r2, r8, asr #10 ; .dynamic 段地址 (_DYNAMIC[]) 20634: 00000000 ; 3.2.3 小节中填充为 link_map 指针 20638: 00000000 ; 3.2.3 小节中填充为函数 _dl_runtime_resolve() 地址 2063c: 000102f8 strdeq r0, [r1], -r8 ; printf()函数GOT表项:静态链接时填充为PLT首表项地址; 首次调用后 _dl_runtime_resolve() 修正此表项为printf()函数的正确地址 20640: 000102f8 strdeq r0, [r1], -r8 20644: 000102f8 strdeq r0, [r1], -r8 20648: 000102f8 strdeq r0, [r1], -r8 ; scanf()函数GOT表项:静态链接时填充为PLT首表项地址; 首次调用后 _dl_runtime_resolve() 修正此表项为 scanf() 函数的正确地址 ... (2) 2064c: 000102f8 strdeq r0, [r1], -r8 20650: 00000000 andeq r0, r0, r0
首次调用函数,跳转到 _dl_runtime_resolve() 函数的流程总结:
main() __isoc99_scanf@plt puts@plt-0x14 _dl_runtime_resolve()
ARM32架构小知识
arm32 pc寄存器的值为:当前执行指令所在地址+4。3.2.4.3 修正函数调用GOT表项,并跳转到函数执行
_dl_runtime_resolve:
push {r0-r4}
...
ldr r0, [lr, #-4] @ 程序动态链接管理数据指针 (link_map *),即 GOT[1]
sub r1, ip, lr
sub r1, r1, #4
add r1, r1, r1
bl _dl_fixup @ 修正被调用函数对应的 GOT 表项
const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
const ElfW(Sym) *refsym = sym;
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
...
value = DL_FIXUP_MAKE_VALUE (result,
SYMBOL_ADDRESS (result, sym, false));
return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
mov ip, r0 @ ip = 返回被调用函数的地址
pop {r0-r4,lr} @ 恢复参数寄存器以及调用的返回地址
BX(ip) @ 跳转到被调用函数 scanf() 或 printf()
3.2.5 动态链接相关数据段
.dynsym: 动态链接符号段 .dynstr: 动态链接字符串表 .rel.dyn: 动态链接重定位段 .rel.plt: 动态链接仅PLT相关的重定位段 .plt: 调用PLT(Prodedure Linkage Table)表 .dynamic: 动态链接段(_DYNAMIC[]) .got: 动态链接函数地址表4. 后记
本文远未深入到动态链接的所有细节,动态链接是一个庞大而复杂的过程,如果全部展开的话应该能成书了。本文仅起到可能的引导作用,有兴趣的读者可自行深入挖掘。



