【问题标题】:Why gcc generates a PLT when it is apparently not needed?为什么 gcc 在显然不需要时会生成 PLT?
【发布时间】:2021-04-09 18:23:38
【问题描述】:

考虑这段代码:

int foo();
int main() {
    foo();
    while(1){}
}

int foo() 在共享对象中实现。

gcc -o main main.c -lfoo -nostdlib -m32 -O2 -e main --no-pic -L./shared 编译这段代码会得到以下diasm:

$ 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   $0x0
 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    $0xfffffff0,%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    $0x4,%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

注意:

  1. 代码是用--no-pic编译的,所以不是PIC
  2. .text 部分(main 函数)中对foo() 的调用通过 PLT。相反,它只是一个简单的R_386_PC32 重定位,我假设它会在加载时直接重定位到foo 函数的地址。这对我来说很有意义,因为代码不是 PIC,因此无需通过 PLT 添加额外的间接。
  3. 即使没有使用,PLT 仍在生成中。那里有一个foo 的条目,我们甚至有一个R_386_JUMP_SLOT 重定位来在加载时(PLT 指向)在GOT 中设置foo 条目。

我的问题很简单:我没有看到代码中的任何地方都使用了 PLT,我也没有看到这里有必要,那么为什么 gcc 会创建它?

【问题讨论】:

  • "我没有看到 PLT 在任何地方使用" -- foo()定义 来自哪里?如果它来自libfoo.so(反汇编暗示),那么PLT 必要的。
  • @EmployedRussian 是的,foo() 的定义来自libfoo.so。我不明白为什么在这种情况下需要 PLT。对foo() 的调用未指向PLT。这是直接R_386_PC32 搬迁。如果我用-fpic 编译main.c,那么是的,我看到调用指向其相应的PLT 条目,然后我看到一个R_386_JUMP_SLOT 重定位
  • 另外,GOT 指针在调用foo 之前没有准备好。这似乎是未使用 PIC 编译模块时的行为。原始的main.o 目标文件只准备GOT 指针并在使用-fpic 编译时请求R_386_PLT32 重定位。如果使用--no-pic 编译,则不再设置GOT 指针,然后要求进行简单的R_386_PC32 重定位,如问题所示。基于此,在我看来,在这种情况下,动态链接器只会将重定位直接添加到调用中,我不明白它将如何使用 PLT

标签: linux assembly x86 elf dynamic-linking


【解决方案1】:

--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 不能重定位到整个地址空间。)

是的,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 call rel32:call 8049040 &lt;getpid@plt&gt;

但是添加-fno-plt,我得到6字节ff 15 f4 bf 04 08 call [disp32] - call DWORD PTR ds:0x804bff4。不涉及 PLT,只是用绝对地址引用的 GOT 条目。

无需运行时重定位; .text 部分的此页面可以作为可执行文件的文件支持的私有映射保持“干净”。 (运行时重定位会弄脏它,如果内核想要驱逐该页面,则使其仅由交换空间支持。)

此外,它使用需要早期绑定的“普通”GOT 条目。即使与-nostdlib -lc 和不明智的-e main 一起使用也可以,而不是像普通人那样称呼它_start。由于它是一个动态链接的可执行文件,动态链接器会在您的入口点之前运行并设置 GOT。

【讨论】:

  • 感谢您的回答。这对我来说很有意义,但是当我用--no-pic 编译main.c 时,我没有看到这种行为。我不认为ld 正在将呼叫转换为foo@plt。看起来调用只是被动态链接器直接重定位。为了确认我什至调试了正在运行的程序,发现call 指令是一个近调用,它直接指向动态库中的过程。但是,如果我用-fpic 编译main.c 并调试正在运行的程序,那么call 会通过PLT,我会看到额外的间接。
  • "ld 链接非 PIE 时会将调用 foo 转换为调用 foo@plt,而不是在每次程序加载时都需要运行时修复的每个调用点包含文本重定位。" - 就我而言,ld 似乎没有这样做。我在问题中发布的最终二进制文件显示有一个动态重定位 R_386_PC32 直接绑定到 call 指令。如果它正在使用 PLT,AFAIK 它将已经指向 PLT(不需要重定位),然后将有一个重定位绑定到 PLT 条目本身。
  • “只有在链接非 PIE 可执行文件时 ld 才会处理 PLT 生成”:非常有趣,我认为这解释了我所看到的奇怪行为。如果我用-fno-pie 生成main.o,则重定位不会通过PLT。而且,事实上,如果我用-no-pie 链接二进制文件,链接器会生成PLT 并使call 指令指向PLT,即使重定位不是R_386_PLT32。如果我在没有no-pie(默认为PIE)的情况下链接二进制文件,链接器会使call 指令直接指向共享对象中的函数,但是...
  • ...仍然出于某种奇怪的原因创建 PLT。这是我最初的问题,但是有很多标志误导了我的理解。我没有意识到即使将--no-pic 传递给ld,最终的可执行文件仍被编译为 PIE。事实上,我认为ld 在这种情况下并不关心将 PLT 留在那里,因为在没有 PIE 的情况下编译目标文件然后使用 PIE 生成二进制文件并不常见。非常感谢!
  • @felipeek:是的,听起来不错。如果您想发布包含我在快速回答中遗漏的任何详细信息的答案,既然您已经对其进行了更多整理,请随意。否则,这些 cmets 有望满足任何未来的读者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-12
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
  • 1970-01-01
相关资源
最近更新 更多