共计 1854 个字符,预计需要花费 5 分钟才能阅读完成。
一、概述
在如下示例程序 print_banner 中,调用了 glibc 动静库中的函数 printf,在编译和链接阶段,链接器无奈晓得过程运行起来之后 printf 函数的加载地址,所以示例中 call printf 的地址只有在过程运行起来当前能力确定。
080483cc <print_banner>:
80483cc: push %ebp
80483cd: mov %esp, %ebp
80483cf: sub $0x8, %esp
80483d2: sub $0xc, %esp
80483d5: push $0x80484a8
80483da: call **<printf 函数的地址 >**
80483df: add $0x10, %esp
80483e2: nop
80483e3: leave
80483e4: ret
那么过程运行起来之后,glibc 动静库也装载了,printf 函数地址也确定了,上述 call 指令中的地址是如何取得的呢。call 地址次要是在运行时 / 链接时进行重定位。运行时重定位和链接时重定位相干常识见背景常识模块。
背景常识
1、古代操作系统不容许批改代码段,只能批改数据段
2、编译阶段和运行阶段汇编的变动
参考文档:https://blog.csdn.net/linyt/a…
编译阶段 是将.c 源码翻译成汇编指令的中间件.o
查看应用 objdump -d test.o,能够看到 printf 的地址临时应用 fc ff ff ff 代替,这个地址在链接 / 运行时会进行修改。
00000000 <print_banner>:
0: 55 push %ebp
1: 89 e5 mov %esp, %ebp
3: 83 ec 08 sub $0x8, %esp
6: c7 04 24 00 00 00 00 movl $0x0, (%esp)
d: e8 fc ff ff ff call e <print_banner+0xe>
12: c9 leave
13: c3 ret
链接阶段 是将一个或多个两头文件 (.o) 通过链接器链接成一个可执行文件,链接次要实现
各个两头文件之间的同名 section 合并
对代码段,数据段以及各符号进行地址调配
链接时重定位修改
3、运行时重定位和链接时重定位
- 链接时重定位:如果函数在其余.o 文件中定义,则链接时 printf 地址即可确定,间接重定位
-
运行时重定位:如果函数在动静库内(链接阶段是能够晓得 printf 在哪定义的,只是如果定义在动静库内不晓得它的地址而已),则会在运行时进行重定位。运行时重定位是无奈批改代码段的,只能将 printf 重定位到数据段。那在编译阶段就已生成好的 call 指令,怎么感知这个已重定位好的数据段内容呢。
答案是:链接器生成一段额定的小代码片段,通过这段代码支获取 printf 函数地址,并实现对它的调用。伪代码如下。
链接阶段发现 printf 定义在动静库时,链接器生成一段小代码 print_stub,而后 printf_stub 地址取代原来的 printf。因而转化为链接阶段对 printf_stub 做链接重定位,而运行时才对 printf 做运行时重定位。.text ... // 调用 printf 的 call 指令 call printf_stub ... printf_stub: mov rax, [printf 函数的贮存地址] // 获取 printf 重定位之后的地址 jmp rax // 跳过去执行 printf 函数 .data ... printf 函数的贮存地址:这里贮存 printf 函数重定位后的地址
-
PLT 和 GOT 表
动静链接次要有 2 个因素须要寄存内部函数的数据段 获取数据段寄存函数地址的一小段额定代码
如果可执行文件中调用多个动静库函数,那每个函数都须要这两样货色,这样每样货色就造成一个表,每个函数应用中的一项。
总不能每次都叫这个表那个表,于是得正名。寄存函数地址的数据表,称为重局偏移表(GOT, Global Offset Table),而那个额定代码段表,称为程序链接表(PLT,Procedure Link Table)。它们两姐妹各司其职,联结出手演出这一出运行时重定位好戏。
那么 PLT 和 GOT 长得什么样子呢?后面已有一些阐明,上面以一个例子和简略的示意图来阐明 PLT/GOT 是如何运行的。
假如最开始的示例代码 test.c 减少一个 write_file 函数,在该函数外面调用 glibc 的 write 实现写文件操作。依据后面探讨的 PLT 和 GOT 原理,test 在运行过程中,调用方(如 print_banner 和 write_file)是如何通过 PLT 和 GOT 穿针引线之后,最终调用到 glibc 的 printf 和 write 函数的?
上面是 PLT 和 GOT 雏形图,供参考。