为什么 gcc 在显然不需要时生成 PLT?
Why gcc generates a PLT when it is apparently not needed?
考虑这段代码:
int foo();
int main() {
foo();
while(1){}
}
int foo()
在共享对象中实现。
使用 gcc -o main main.c -lfoo -nostdlib -m32 -O2 -e main --no-pic -L./shared
编译此代码会得到以下对话框:
$ objdump -d ./main
./main: file format elf32-i386
Disassembly of section .plt:
00000240 <.plt>:
240: ff b3 04 00 00 00 pushl 0x4(%ebx)
246: ff a3 08 00 00 00 jmp *0x8(%ebx)
24c: 00 00 add %al,(%eax)
...
00000250 <foo@plt>:
250: ff a3 0c 00 00 00 jmp *0xc(%ebx)
256: 68 00 00 00 00 push [=11=]x0
25b: e9 e0 ff ff ff jmp 240 <.plt>
Disassembly of section .text:
00000260 <main>:
260: 8d 4c 24 04 lea 0x4(%esp),%ecx
264: 83 e4 f0 and [=11=]xfffffff0,%esp
267: ff 71 fc pushl -0x4(%ecx)
26a: 55 push %ebp
26b: 89 e5 mov %esp,%ebp
26d: 51 push %ecx
26e: 83 ec 04 sub [=11=]x4,%esp
271: e8 fc ff ff ff call 272 <main+0x12>
276: eb fe jmp 276 <main+0x16>
具有以下搬迁:
$ objdump -R ./main
./main: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
00000272 R_386_PC32 foo
00001ffc R_386_JUMP_SLOT foo
注意:
- 代码是用
--no-pic
编译的,所以不是PIC
- 在
.text
部分(main
函数)对 foo()
的调用 不是 通过 PLT。相反,它只是一个简单的 R_386_PC32
重定位,我假设它将在加载时直接重定位到 foo
函数的地址。这对我来说很有意义,因为代码不是 PIC,因此无需通过 PLT 添加额外的间接寻址。
- 即使不使用,PLT 仍在生成。
foo
的条目在那里,我们甚至有一个 R_386_JUMP_SLOT
重定位以在加载时(PLT 指向)在 GOT 中设置 foo
条目。
我的问题很简单:我没有看到代码中的任何地方使用了 PLT,而且我也没有看到这里有必要,那么 gcc 为什么要创建它?
--no-pic
不像 -no-pie
,它似乎是 -fno-pic
或 -fno-pie
的同义词,影响代码生成但不影响链接。假设您的发行版的 GCC 默认生成一个 PIE,您 是 生成一个 PIE,因此不会将调用转换为 foo@plt
.
我收到链接器警告 /tmp/ccyRsNtd.o: warning: relocation against 'getpid@@GLIBC_2.0' in read-only section '.text.startup'
/ warning: creating DT_TEXTREL in a PIE
。 (但是可执行文件 运行,不像它是 64 位的,其中 call rel32
不能重定位到整个地址 space。)
是的,ld
出于某种原因构建了一个未使用的 PLT 条目,但您的链接方式完全不标准。
构建 PLT 的正常原因是:
ld
链接非 PIE 时会将 call foo
转换为 call foo@plt
而不是在每个调用点都包含文本重定位,每次调用时都需要 运行 时间修正程序加载。
使用 -fno-plt
获得更高效的 asm,特别是对于 64 位模式,即使 PIE 代码也可以直接有效地引用 GOT。
为了做一个更简单的例子,我使用了 libc (getpid
) 中的函数而不是自定义库。使用 gcc -fno-pie -no-pie -m32 -O2 foo.c
正常编译,我得到 5 字节 e8 d5 ff ff ff
调用 rel32: call 8049040 <getpid@plt>
.
但是加上 -fno-plt
,我得到 6 字节 ff 15 f4 bf 04 08 call [disp32]
- call DWORD PTR ds:0x804bff4
。不涉及 PLT,仅涉及用绝对地址引用的 GOT 条目。
不需要运行时间搬迁; .text
部分的这一页可以作为可执行文件的文件支持的私有映射保持“干净”。 (运行时重定位会弄脏它,如果内核想要驱逐该页面,则只能通过交换 space 来支持它。)
此外,它使用需要提前绑定的“正常”GOT 条目。这甚至适用于 -nostdlib -lc
和不明智的 -e main
而不是像普通人那样称呼它 _start
。由于它是一个动态链接的可执行文件,动态链接器会在您的入口点之前 运行 并设置 GOT。
考虑这段代码:
int foo();
int main() {
foo();
while(1){}
}
int foo()
在共享对象中实现。
使用 gcc -o main main.c -lfoo -nostdlib -m32 -O2 -e main --no-pic -L./shared
编译此代码会得到以下对话框:
$ objdump -d ./main
./main: file format elf32-i386
Disassembly of section .plt:
00000240 <.plt>:
240: ff b3 04 00 00 00 pushl 0x4(%ebx)
246: ff a3 08 00 00 00 jmp *0x8(%ebx)
24c: 00 00 add %al,(%eax)
...
00000250 <foo@plt>:
250: ff a3 0c 00 00 00 jmp *0xc(%ebx)
256: 68 00 00 00 00 push [=11=]x0
25b: e9 e0 ff ff ff jmp 240 <.plt>
Disassembly of section .text:
00000260 <main>:
260: 8d 4c 24 04 lea 0x4(%esp),%ecx
264: 83 e4 f0 and [=11=]xfffffff0,%esp
267: ff 71 fc pushl -0x4(%ecx)
26a: 55 push %ebp
26b: 89 e5 mov %esp,%ebp
26d: 51 push %ecx
26e: 83 ec 04 sub [=11=]x4,%esp
271: e8 fc ff ff ff call 272 <main+0x12>
276: eb fe jmp 276 <main+0x16>
具有以下搬迁:
$ objdump -R ./main
./main: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
00000272 R_386_PC32 foo
00001ffc R_386_JUMP_SLOT foo
注意:
- 代码是用
--no-pic
编译的,所以不是PIC - 在
.text
部分(main
函数)对foo()
的调用 不是 通过 PLT。相反,它只是一个简单的R_386_PC32
重定位,我假设它将在加载时直接重定位到foo
函数的地址。这对我来说很有意义,因为代码不是 PIC,因此无需通过 PLT 添加额外的间接寻址。 - 即使不使用,PLT 仍在生成。
foo
的条目在那里,我们甚至有一个R_386_JUMP_SLOT
重定位以在加载时(PLT 指向)在 GOT 中设置foo
条目。
我的问题很简单:我没有看到代码中的任何地方使用了 PLT,而且我也没有看到这里有必要,那么 gcc 为什么要创建它?
--no-pic
不像 -no-pie
,它似乎是 -fno-pic
或 -fno-pie
的同义词,影响代码生成但不影响链接。假设您的发行版的 GCC 默认生成一个 PIE,您 是 生成一个 PIE,因此不会将调用转换为 foo@plt
.
我收到链接器警告 /tmp/ccyRsNtd.o: warning: relocation against 'getpid@@GLIBC_2.0' in read-only section '.text.startup'
/ warning: creating DT_TEXTREL in a PIE
。 (但是可执行文件 运行,不像它是 64 位的,其中 call rel32
不能重定位到整个地址 space。)
是的,ld
出于某种原因构建了一个未使用的 PLT 条目,但您的链接方式完全不标准。
构建 PLT 的正常原因是:
ld
链接非 PIE 时会将 call foo
转换为 call foo@plt
而不是在每个调用点都包含文本重定位,每次调用时都需要 运行 时间修正程序加载。
使用 -fno-plt
获得更高效的 asm,特别是对于 64 位模式,即使 PIE 代码也可以直接有效地引用 GOT。
为了做一个更简单的例子,我使用了 libc (getpid
) 中的函数而不是自定义库。使用 gcc -fno-pie -no-pie -m32 -O2 foo.c
正常编译,我得到 5 字节 e8 d5 ff ff ff
调用 rel32: call 8049040 <getpid@plt>
.
但是加上 -fno-plt
,我得到 6 字节 ff 15 f4 bf 04 08 call [disp32]
- call DWORD PTR ds:0x804bff4
。不涉及 PLT,仅涉及用绝对地址引用的 GOT 条目。
不需要运行时间搬迁; .text
部分的这一页可以作为可执行文件的文件支持的私有映射保持“干净”。 (运行时重定位会弄脏它,如果内核想要驱逐该页面,则只能通过交换 space 来支持它。)
此外,它使用需要提前绑定的“正常”GOT 条目。这甚至适用于 -nostdlib -lc
和不明智的 -e main
而不是像普通人那样称呼它 _start
。由于它是一个动态链接的可执行文件,动态链接器会在您的入口点之前 运行 并设置 GOT。