c++ - R_X86_64_PLT32的地址是怎么计算的?
问题描述
我试图了解链接是如何工作的。我有一个简单的 C++ 代码
#include "a.h"
int Other() {
return 1;
}
int SomeFunction() {
Other();
Other();
Other();
return 0;
}
这会生成以下重定位表。
$ readelf -r a.o
Relocation section '.rela.text' at offset 0x240 contains 3 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000018 000900000004 R_X86_64_PLT32 0000000000000000 _Z5Otherv - 4
00000000001d 000900000004 R_X86_64_PLT32 0000000000000000 _Z5Otherv - 4
000000000022 000900000004 R_X86_64_PLT32 0000000000000000 _Z5Otherv - 4
Relocation section '.rela.eh_frame' at offset 0x288 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + f
看起来 Other() 函数的调用点都标记为 R_X86_64_PLT32 类型。当我检查 R_X86_64_PLT32 的定义时,它显示 L + A - P,其中L
表示符号的过程链接表条目的位置(节偏移或地址),A
表示用于计算可重定位字段值的加数并P
表示被重定位的存储单元的位置(部分偏移或地址)(使用 r_offset 计算)。
我不确定 PLT 的符号位置是什么意思。我的问题是
在这种情况下,L、A、P 的值是多少?
据我了解,PLT 被动态对象使用。为什么即使这只是一个静态函数,Other() 也被标记为 PLT32。
如何找到ELF文件的PLT以及如何在其中找到Other()?
解决方案
我不是专家,也有点晚了,但据我了解,较新的 GCC 版本使用 R_X86_64_PLT32 而不是 R_X86_64_PC32 来标记 32 位 PC 相关分支。
“在 x86-64 上,对于 32 位 PC 相关分支,我们可以生成 PLT32 重定位,而不是 PC32 重定位,它也可以用作 32 位 PC 相关分支的标记。链接器始终可以将 PLT32 重定位减少到PC32 如果函数是在本地定义的。本地函数应该使用 PC32 重定位。(相关提交)
因此,尽管重定位条目的类型是 R_X86_64_PLT32,链接器仍将使用 R_X86_64_PC32 计算 (S + A - P) 来修改正在修改的重定位目标,其中:
S
是符号 (st_value
ofElf64_Sym
)的值A
是加数(-4
在你的情况下)P
是被重定位的内存位置的地址( to 的地址的call
开始Other
)
因为Other
是局部函数。因此,PLT 实际上在您的情况下根本没有发挥作用。
如果您想了解所有节标题(包括 .plt、.got、.got.plt、...)的概览,您可以readelf -S
在 EXEC/DYN elf 文件(已编译和链接)上使用。或者只是使用objdump -d <file
which 还与转储一起显示不同的部分。
我希望这能解释一下。
为了完整起见,类似于您的简短示例(我使用了 C,但这不应该有所作为):
我有一个rel.c
文件Other
在other.c
. 首先我编译这两个文件gcc -c -o <name>.o <name>.c
。
readelf -r rel.o
:
Relocation section '.rela.text' at offset 0x198 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000015 000500000004 R_X86_64_PLT32 0000000000000000 Other - 4
如您所见,A
(加数)是-4
最终地址S
,P
尚不知道。
在使用链接所有内容后,gcc -o main rel.o other.o
我曾经objdump -M intel -d main
获得二进制文件的完整转储。有趣的部分如下所示。
...
0000000000001139 <main>:
1139: 55 push rbp
113a: 48 89 e5 mov rbp,rsp
113d: 48 83 ec 10 sub rsp,0x10
1141: 89 7d fc mov DWORD PTR [rbp-0x4],edi
1144: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi
1148: b8 00 00 00 00 mov eax,0x0
114d: e8 07 00 00 00 call 1159 <Other>
1152: b8 00 00 00 00 mov eax,0x0
1157: c9 leave
1158: c3 ret
0000000000001159 <Other>:
1159: 55 push rbp
115a: 48 89 e5 mov rbp,rsp
...
可以看到DWORD
at保存了可以与跳转到子例程地址 ( = )一起使用0x114e
的偏移量。0x7
IP
Other
0x1152 + 0x7
0x1159
这是使用S + A - P
= 0x1159 - 0x4 - 0x114e
=计算的7
。
推荐阅读
- html - CSS Checkbox 样式独特的类
- embedded - 执行 pow(2, ((m - 69.0f) / 12.0f)) 时出现 DeepSleepLock 下溢错误 - MBed OS
- c# - Unity Error building Player 因为脚本有编译器错误
- oracle - 查找所有需要重建的索引
- mysql - gorm.Open() 每次调用时都会创建一个新的连接池吗?
- python - 使用python通过outlook发送excel附件
- python - 如果是 .pdf 文件,为什么页面会返回文本/html?
- python - 在结构化流 PySpark 中动态扩展 Arraytype() 列
- c++ - 比较文件中的最佳平均成绩并打印学生的姓名
- excel - 使用 Excel 内置函数从自定义函数调用 Microsoft Excel API?