R_X86_64_PLT32的地址是如何计算出来的?
How does the address of R_X86_64_PLT32 computed?
我正在尝试了解链接的工作原理。我有一个简单的 C++ 代码
#include "a.h"
int Other() {
return 1;
}
int SomeFunction() {
Other();
Other();
Other();
return 0;
}
生成以下重定位 table。
$ 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
表示符号的过程链接 Table 条目的位置(部分偏移或地址),A
表示用于计算relocatable字段值的加数,P
表示被重定位的存储单元的位置(段偏移或地址)(使用r_offset计算) ).
我不确定符号的 PLT 位置是什么意思。我的问题是
在这种情况下,L、A、P 的值是多少?
据我了解,PLTs是动态对象使用的。为什么 Other() 被标记为 PLT32,即使这只是一个静态函数。
如何找到ELF文件的PLT以及如何在其中找到Other()?
我不是专家,有点晚了,但据我了解,较新的 GCC 版本使用 R_X86_64_PLT32 而不是 R_X86_64_PC32 来标记 32 位 PC-relative 分支.
"在x86-64上,对于32位PC-relative分支,我们可以生成PLT32重定位,而不是PC32重定位,也可以作为32位PC-relative的标记分支。如果函数是在本地定义的,链接器总是可以将 PLT32 重定位减少到 PC32。本地函数应该使用 PC32 重定位。” (Related Commit)
因此,尽管重定位条目的类型是 R_X86_64_PLT32,但链接器仍将对正在修改的重定位目标使用 R_X86_64_PC32 计算 (S + A - P),其中:
S
是符号的值(st_value
of Elf64_Sym
)
A
是加数(-4
在你的例子中)
P
是被重定位的内存位置的地址(call
到Other
的地址开始)
因为Other
是局部函数。所以 PLT 实际上对你的情况根本不起作用。
如果您想了解所有部分 headers(包括 .plt、.got、.got.plt、...),您可以使用 readelf -S
EXEC/ DYN elf 文件(已编译和链接)。或者只使用 objdump -d <file
,它还显示了不同的部分以及转储。
我希望这能稍微解释一下。
为了完整起见,与您的示例类似的简短示例(我使用了 C,但这应该没什么区别):
我有一个文件 rel.c
,它调用 other.c
中的函数 Other
。首先我编译两个文件 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
...
可以看到 0x114e
处的 DWORD
保存偏移量 0x7
可以与 IP
一起使用以跳转到 Other
子程序 (0x1152 + 0x7
= 0x1159
).
这是使用S + A - P
= 0x1159 - 0x4 - 0x114e
= 7
计算得出的。
我正在尝试了解链接的工作原理。我有一个简单的 C++ 代码
#include "a.h"
int Other() {
return 1;
}
int SomeFunction() {
Other();
Other();
Other();
return 0;
}
生成以下重定位 table。
$ 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
表示符号的过程链接 Table 条目的位置(部分偏移或地址),A
表示用于计算relocatable字段值的加数,P
表示被重定位的存储单元的位置(段偏移或地址)(使用r_offset计算) ).
我不确定符号的 PLT 位置是什么意思。我的问题是
在这种情况下,L、A、P 的值是多少?
据我了解,PLTs是动态对象使用的。为什么 Other() 被标记为 PLT32,即使这只是一个静态函数。
如何找到ELF文件的PLT以及如何在其中找到Other()?
我不是专家,有点晚了,但据我了解,较新的 GCC 版本使用 R_X86_64_PLT32 而不是 R_X86_64_PC32 来标记 32 位 PC-relative 分支.
"在x86-64上,对于32位PC-relative分支,我们可以生成PLT32重定位,而不是PC32重定位,也可以作为32位PC-relative的标记分支。如果函数是在本地定义的,链接器总是可以将 PLT32 重定位减少到 PC32。本地函数应该使用 PC32 重定位。” (Related Commit)
因此,尽管重定位条目的类型是 R_X86_64_PLT32,但链接器仍将对正在修改的重定位目标使用 R_X86_64_PC32 计算 (S + A - P),其中:
S
是符号的值(st_value
ofElf64_Sym
)A
是加数(-4
在你的例子中)P
是被重定位的内存位置的地址(call
到Other
的地址开始)
因为Other
是局部函数。所以 PLT 实际上对你的情况根本不起作用。
如果您想了解所有部分 headers(包括 .plt、.got、.got.plt、...),您可以使用 readelf -S
EXEC/ DYN elf 文件(已编译和链接)。或者只使用 objdump -d <file
,它还显示了不同的部分以及转储。
我希望这能稍微解释一下。
为了完整起见,与您的示例类似的简短示例(我使用了 C,但这应该没什么区别):
我有一个文件 rel.c
,它调用 other.c
中的函数 Other
。首先我编译两个文件 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
...
可以看到 0x114e
处的 DWORD
保存偏移量 0x7
可以与 IP
一起使用以跳转到 Other
子程序 (0x1152 + 0x7
= 0x1159
).
这是使用S + A - P
= 0x1159 - 0x4 - 0x114e
= 7
计算得出的。