作者:手机用户2702936061 | 来源:互联网 | 2023-08-31 13:19
静态链接·重定位
静态链接过程会将汇编器生成的可重定位文件(*.o)合并到一个可执行文件中。当链接器完成符号解析后,就会执行重定位过程。重定位过程分为两步:
- 重定位节及符号定义。相同类型的节会被聚合到可执行文件的同一类型的节中,如下图的步骤一。符号解析过程已经确定了符号被定义的模块及节位置,所以重定位节后,每个符号的运行时地址也已经确定了。
- 重定位节中的符号引用。如下图的步骤二,在编译main.o时,引用的符号func是由其它模块定义的,编译器无法确定该符号的运时地址,此时编译器所做的是为每个引用的位置记录一个重定位条目信息。链接器根据此信息进行符号引用重定位。
程序代码
main.c
#include const static char *s_str = "a static string";
extern int g_num;int func();static int func_st()
{return --g_num;
}int main()
{int ret = func();int ret2 = func_st();printf("str: %s, ret: %d, ret2: %d\n", s_str, ret, ret2); return 0;
}
func.c
static int s_num = 200;
int g_num = 200;int func()
{s_num++;return s_num;
}
链接示意
main.o中引用了三个外部符号,其中g_num、func由func.o定义,使用静态的链接方式。printf是标准库定义的函数,如果其它模块都没定义,链接器会默认从标准库libc.so中查找。可以使用-static选项表明要生成一个完全的可执行文件,这样链接器会选择从libc.a库静态链接相应模块到可执行文件里。
汇编代码及重定位条目
main.o 汇编代码
[guest&#64;linux ~]$ objdump -D main.omain.o: file format elf64-x86-64Disassembly of section .text:0000000000000000 :0: 55 push %rbp1: 48 89 e5 mov %rsp,%rbp4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a a: 83 e8 01 sub $0x1,%eaxd: 89 05 00 00 00 00 mov %eax,0x0(%rip) # 13 13: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 19 19: c9 leaveq 1a: c3 retq 000000000000001b :1b: 55 push %rbp1c: 48 89 e5 mov %rsp,%rbp1f: 53 push %rbx20: 48 83 ec 18 sub $0x18,%rsp24: b8 00 00 00 00 mov $0x0,%eax29: e8 00 00 00 00 callq 2e 2e: 89 45 e8 mov %eax,-0x18(%rbp)31: b8 00 00 00 00 mov $0x0,%eax36: e8 c5 ff ff ff callq 0 3b: 89 45 ec mov %eax,-0x14(%rbp)3e: 48 8b 1d 00 00 00 00 mov 0x0(%rip),%rbx # 45 45: b8 00 00 00 00 mov $0x0,%eax4a: 8b 4d ec mov -0x14(%rbp),%ecx4d: 8b 55 e8 mov -0x18(%rbp),%edx50: 48 89 de mov %rbx,%rsi53: 48 89 c7 mov %rax,%rdi56: b8 00 00 00 00 mov $0x0,%eax5b: e8 00 00 00 00 callq 60 60: b8 00 00 00 00 mov $0x0,%eax65: 48 83 c4 18 add $0x18,%rsp69: 5b pop %rbx6a: c9 leaveq 6b: c3 retq Disassembly of section .data:0000000000000000 :00: 0000000000000000 Disassembly of section .rodata: &#xff08;objdump -s main.o)0000000000000000 <.rodata>:00: 61207374 61746963 20737472 696e6700 a static string.10: 7374723a 2025732c 20726574 3a202564 str: %s, ret: %d20: 2c207265 74323a20 25640a00 , ret2: %d..
可以看出main.o不是一个可执行的文件如&#xff1a;.text节偏移0x29处的指令为" e8 00 00 00 00 callq "&#xff0c; "e8 "表示这是一条函数调用指令&#xff0c;但是并没有给出最终的函数位置&#xff1b;在.data节保存的s_str变量的值为0x00&#xff0c;但在原始代码里s_str为已初始化的指针&#xff0c;初始值为一个常量字符串起始地址。
main.o 重定向位目信息
[guest&#64;linux ~]$ readelf -r main.o Relocation section &#39;.rela.text&#39; at offset 0x6e0 contains 7 entries:Offset Info Type Sym. Value Sym. Name &#43; Addend
000000000006 000b00000002 R_X86_64_PC32 0000000000000000 g_num - 4
00000000000f 000b00000002 R_X86_64_PC32 0000000000000000 g_num - 4
000000000015 000b00000002 R_X86_64_PC32 0000000000000000 g_num - 4
00000000002a 000d00000002 R_X86_64_PC32 0000000000000000 func - 4
000000000041 000300000002 R_X86_64_PC32 0000000000000000 .data - 4
000000000046 00050000000a R_X86_64_32 0000000000000000 .rodata &#43; 10
00000000005c 000e00000002 R_X86_64_PC32 0000000000000000 printf - 4Relocation section &#39;.rela.data&#39; at offset 0x788 contains 1 entries:Offset Info Type Sym. Value Sym. Name &#43; Addend
000000000000 000500000001 R_X86_64_64 0000000000000000 .rodata &#43; 0
.rela.text及.rela.data指示了需要被重定向的位置及重定向方式&#xff0c;关注以下字段&#xff1a;
-
- Offset&#xff1a;重定向项在节中的偏移。
- Type&#xff1a; 重定向类型。
- Sym.Name 、 Addend&#xff1a;重定向的符号及偏移修正。
重定向符号引用
假设链接过程中&#xff0c;已经确定了每个重定向模块的节及其它全局符号在加载时的地址。以s代表当前处理的节&#xff0c;t为重定向的项&#xff0c;Addr(s/sym)为节或符号的地址&#xff0c;那么重定向条目指示的修正值计算方式如下。
foreach sforeach tRelAddr &#61; Addr(s) &#43; t.Offset //重定向的项的地址... if t.Type &#61;&#61; R_X86_64_PC32 then*RelAddr &#61; Addr(t.Sym.Name) - RelAddr &#43; t.Addend //Addr(t.Sym.Name) - PC&#39;endif t.Type &#61;&#61; R_X86_64_32 then*RelAddr &#61; Addr(t.Sym.Name) &#43; t.Addendendend
end
先看.rela.text节中类型为R_X86_64_PC32及R_X86_64_32中的两项。
Relocation section &#39;.rela.text&#39; at offset 0x6e0 contains 7 entries:Offset Info Type Sym. Value Sym. Name &#43; Addend
...
00000000002a 000d00000002 R_X86_64_PC32 0000000000000000 func - 0x4
...
000000000046 00050000000a R_X86_64_32 0000000000000000 .rodata &#43; 0x10
...
对应的main.o中的指令位置。
Disassembly of section .text:0000000000000000 :
...000000000000001b :
...29: e8 00 00 00 00 callq 2e 2e: 89 45 e8 mov %eax,-0x18(%rbp)
...45: b8 00 00 00 00 mov $0x0,%eax
...
假设链接过程中已经知道&#xff1a;main.o中的.text节的起始地址为0x0400534&#xff0c;.rodata 节的起始地址0x4006ac&#xff0c;且符号func的地址为0x04005a0。那么以上的重定向值计算过程如下所示&#xff1a;
R_X86_64_PC32:
RelAddr &#61; Addr(.text) &#43; t.Offset &#61; 0x0400534 &#43; 0x02a &#61; 0x40055e;
*RelAddr &#61; Addr(func) - RelAddr &#43; t.Addend &#61; 0x04005a0 - 0x40055e &#43; (-4) &#61; 0x3e
R_X86_64_32:
RelAddr &#61; Addr(.text) &#43; t.Offset &#61; 0x0400534 &#43; 0x046 &#61; 0x40057a;
*RelAddr &#61; Addr(.rodata) &#43; t.Addend &#61; 0x4006ac &#43; 0x10 &#61; 0x4006bc;
重定向后的结果:
000000000040054f :
...40055d: e8 3e 00 00 00 callq 4005a0 400562: 89 45 e8 mov %eax,-0x18(%rbp)
...400579: b8 bc 06 40 00 mov $0x4006bc,%eax
...
R_X86_64_32类型的重定向&#xff0c;填入的修正值是重定向符号的绝对地址。对于static变量、static函数及字符串常量等&#xff0c;其实是不需要分配全局符号的&#xff0c;使用定义了该本地符号&#xff08;或数据&#xff09;的节&#43;节内偏移&#xff08;Addend&#xff09;的方式即可标识&#xff08;上面就是用了".rodata &#43; 0x10"标识了代码中的字符串常量&#xff09;。
R_X86_64_PC32类型的重定向&#xff0c;填入的修正值是一个相对地址。这是因为修改的地方所对应的指令其实是采用了PC相对寻址的方式取值。在开始执行一条指令的时候&#xff0c;PC计数器总是保存下一条顺序执行的指令地址&#xff0c;如下图所示。利用这一点&#xff0c;就可以用重定向位置(RelAddr)及该位置与PC值的偏差(Addend)得到PC的值&#xff0c;最后计算出的修正值其实就是: *RelAddr &#61; 符号绝对地址- 指令执行时的PC值。