栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux函数动态链接简析

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux函数动态链接简析

1. 前言

限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 本文目标
简要分析Linux动态链接过程中,函数的链接过程。
3. 动态链接过程分析 3.1 分析背景
. arm32 cortex a7
. linux 4.14.111
. ubuntu 16.04 arm core
. gcc 4.9
. glibc-2.31
3.2 流程分析 3.2.1 程序代码
#include 

int main(void)
{
	int i;

	scanf("%d", &i);
	
	printf("Hello, World!n");

	return 0;
}

编译:

arm-linux-gcc -o test test.c
3.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.S
3.2.4.1 对函数的调用
00010470 
: ... 1048c: ebffffa7 bl 10330 <__isoc99_scanf@plt> ; 调用 scanf() ... 10498: ebffff9b bl 1030c ; 调用 printf() ...
3.2.4.2 从PLT调用表跳转到_dl_runtime_resolve()

函数调用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. 后记

本文远未深入到动态链接的所有细节,动态链接是一个庞大而复杂的过程,如果全部展开的话应该能成书了。本文仅起到可能的引导作用,有兴趣的读者可自行深入挖掘。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/941309.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号