为什么gcc在显然不需要PLT的情况下生成PLT



考虑以下代码:

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

CCD_ 1是在一个共享对象中实现的。

使用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,PLT仍在生成中。那里存在foo的条目,我们甚至有一个int foo()0重定位,以便在加载时(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。(但可执行文件确实会运行,这与call rel32不可重新定位到整个地址空间的64位不同。(

是的,由于某种原因,ld构建了一个未使用的PLT条目,但链接的方式完全是非标准的。


建立PLT的正常原因是:

当链接非PIE时,ld将把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条目。

不需要运行时重新定位;CCD_ 32部分的这个页面可以停留在;"干净";作为可执行文件的文件支持的私有映射。(运行时重新定位会使其变脏,如果内核想收回该页面,则只能通过交换空间进行备份。(

此外,它使用";正常的";需要提前绑定的GOT条目。这甚至适用于-nostdlib -lc和不明智的-e main,而不是像普通人一样称其为_start。由于它是一个动态链接的可执行文件,因此动态链接器确实会在您的入口点之前运行并设置GOT。

相关内容

最新更新