函数栈调用解析复习
由于时间过长,中间有些遗忘的,在此总结归纳一下知识点并进行一个回顾
复习参考资料
1 共性
一个函数都可以分为三部分
- prologue: 这部分负责相关栈和寄存器的初始化
- body: 这部分负责函数运算主题部分
- Epilogue: 这部分负责对函数栈的清理和恢复工作
2 x86的函数调用
1 | .486 |
注意:
ebp
保存栈帧
esp
保存栈底函数参数都在栈上,使用
ebp
作为基准进行调用
call func
等于push eip
+jump func
ret
等于pop eip
2 ARM
1 | .global main |
注意:
r11(fp)
保存栈帧lr
保存返回地址sp
保存栈底- 参数前四个存在
r0-r3
,后面存在栈bl func
等于mov lr,PC+4
b func
- PC 指向当前正在运行的指令,编码,译码,执行导致pc的改变
3 动态加载
动态链接处理共享库的时候非常高效,当一个程序被加载进内存的时候,动态链接器会将需要的共享库,加载并绑定到进程的地址空间,在调用某个函数的时候,对函数地址进行解析,达到对函数调用的目的
3.1 两个表
3.1.1 PLT(Procedure Linkage Table)
过程连接表,在程序中以
.plt
节表示,表在代码段,每个表项表示了一个需要重定位的函数的相关若干条指令,每个表项长度为16字节,存储用于延迟绑定的代码可执行
1 |
|
3.1.2 GOT(Global Offset Table)
全局偏移表,在程序中以
.gpt
和.got.plt
节区表示,存放于数据段,可读可写,每一个表项存储的都是一个地址,每个表项的长度都是当前程序对应的需要寻址的长度(32位程序:4个字节,64位程序8个字节)
1 | GOT[0] --> 此处存放的是 .dynamic 的地址;该节(段)的作用会在下文讨论 |
3.1.3 表与表之间的关系
1 | GOT[0]: .dynamic 地址 PLT[0]: 与每个函数第一次链接相关指令 |
3.2 三个节
3.2.1 .dynamic
加载的时候
.dynamic
节区也是.dynamic
段,这部分主要与动态链接的整个过程有关,保存的是与动态链接相关的信息。通过
.dynamic
能够找到与动态链接相关的其他节区(.dynsym
,.dynstr
,.rel.plt
等节区)
结构:
1 | struct Elf64_Dyn { |
3.2.2 .dynsym
动态符号表,存储着在动态链接中需要的每个函数的符号信息,每个结构体对应一个符号,结构体数组,
d_tag = DT_SYMTAB(0x06)
.symtab
在运行时会将运行时需要的符号表复制一份到.dynsym
,而不需要的并不需要加载进内存,因此可以优化- 同理
strtab
也可以优化
结构:
1 | typedef struct { |
3.2.3 .dynstr
动态字符串表,存放了一系列的字符串,表示了符号的名称,是一组字符串数组,
d_tag = DT_STRTAB(0x05)
strtab
在运行的时候会将运行时需要的字符串赋值到.dynstr
,所以strtab
可以优化
3.2.4 .rel.plt(.rela.plt)
重定位表,保存了重定位相关的信息,描述了如何在链接或者运行时,对ELF目标文件的某部分内容或者进程镜像进行补充和修改
每个结构体也对应一个需要重定位的函数,
d_tag = DT_REL(0x11)/DT_RELA(0x7)
结构:
1 | typedef struct { |
3.3 链接过程
主要函数是
dl_runtime__resolve(link_map_obj, reloc_arg)
第一个参数是一个link_map
第二个参数是一个重定位参数,即在运行PLT代码时push进去的n
这个函数主要是调用了一个
dl_fixup(link_map_obj, reloc_arg)
结构完成主要功能第一个参数的作用是获得重定位函数所在的library的基址地址,以及或许在library中寻找重定位函数时所需要的Section。
第二个函数主要是确定需要解析的函数名,以及解析完之后写回的地址。
因此整个过程大概可以理解为,dl_fixup,通过reloc_arg参数确定当前正在解析的函数名
然后拿着函数名,利用link_map找到
.dynsym
,然后进行函数名匹配,如果成功,则从.dynsym
中获取对应符号的函数地址
1 | reloc_arg : 函数名A |
如果存在一个函数put,在第一次执行的时候绑定过程如下:
进入puts@plt,执行2中的case2:
1
2
3
4
5
6case1:
jump GOT[x] # address
case2:
push n
jump PLT[0] # dynmic1
2
3PLT[0]:
push GOT[1] # link_map
jump GOT[2] # call dl_runtime_resolve(栈上的参数为 n 以及 link_map)上面
dl_runtime_resolve
函数得到的puts
函数的真实地址写入GOT[x]