我正在看静态链接的Linux x86剥离二进制文件。我注意到有.got.plt部分。

我想知道静态链接二进制文件需要gotplt部分吗?有人吗?

评论

他们根本不应该有这些部分。我自己尝试了一下,并且确认即使将-static提供给编译器,.got和.got.plt节仍然存在。但是,.dynamic节不存在。我的猜测是,给定-static时,它们只是空白部分。

静态已死,一个相关的堆栈溢出问题:stackoverflow.com/questions/3430400/…

@Nirlzr:有趣,我不知道。因此,实际上,摆脱get / plt表的唯一方法是使用-nostdlib选项。

@perror:请先尝试-nodefaultlibs。但是您可能必须提供编译器可能需要的任何标准功能的自己实现(memcpy等)。

@Nirtlzr:是的,使用此选项,您将必须重建整个libc(没有printf,scanf等)。

#1 楼

程序员对于ELF二进制文件在内部的工作方式有很多不清楚的地方。而且,不幸的是,除了广泛涵盖该主题的两个或三个之外,几乎没有可靠的参考文献。许多工具(链接器,加载器,汇编器,调试器等)对于大多数人来说还是个谜。关于链接器和加载器,主要参考文献是John R. Levine的链接器和加载器(http://linker.iecc.com/)。另一个可靠的信息来源是官方的ELF二进制格式文档。但是,这些仅仅是对某种或大多数技术的工作方式的介绍。

现在,这是您的问题的答案(为什么GOTPLT部分仍包含在静态ELF二进制文件中?):性能。

更多说明...假设您拥有以下C代码:

    #include <stdio.h>
    #include <string.h>

    int main(int argc, char **argv)
    {
        char str[1024];

        strcpy(str, argv[1]);
        printf("%s\n", str);

        return 0;
    }


无需天才就能弄清所有内容这样做是将命令行参数复制到字符串中并打印出来。以下是汇编中的main函数:

     000000000040105e <main>:
     40105e:   55                      push   rbp
     40105f:   48 89 e5                mov    rbp,rsp
     401062:   48 81 ec 10 04 00 00    sub    rsp,0x410
     401069:   89 bd fc fb ff ff       mov    DWORD PTR [rbp-0x404],edi
     40106f:   48 89 b5 f0 fb ff ff    mov    QWORD PTR [rbp-0x410],rsi
     401076:   48 8b 85 f0 fb ff ff    mov    rax,QWORD PTR [rbp-0x410]
     40107d    48 83 c0 08             add    rax,0x8
     401081:   48 8b 10                mov    rdx,QWORD PTR [rax]
     401084:   48 8d 85 00 fc ff ff    lea    rax,[rbp-0x400]
     40108b:   48 89 d6                mov    rsi,rdx
     40108e:   48 89 c7                mov    rdi,rax
     401091:   e8 3a f2 ff ff          call   4002d0 <__rela_iplt_end+0x38>
     401096:   48 8d 85 00 fc ff ff    lea    rax,[rbp-0x400]
     40109d:   48 89 c7                mov    rdi,rax
     4010a0:   e8 fb 09 00 00          call   401aa0 <_IO_puts>
     4010a5:   b8 00 00 00 00          mov    eax,0x0
     4010aa:   c9                      leave
     4010ab:   c3                      ret
     4010ac:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]


请注意,在地址401091处,您将调用存储在PLT中的函数(标签更具表现力)。令人惊讶的是,在此地址4002d0处,您会发现跳转到GOT中存储的内容(见下文)。

     4002d0:   ff 25 f2 2f 2c 00       jmp QWORD PTR [rip+0x2c2ff2] # 6c32c8 <_GLOBAL_OFFSET_TABLE_+0x20>


GOT中的确切位置,您ll找到对存储在以下部分中的函数的调用:

    00000000004187d0 <handle_amd>:
    4187d0:    53                      push   rbx
    4187d1:    b8 00 00 00 80          mov    eax,0x80000000
    4187d6:    0f a2                   cpuid
    4187d8:    81 ff c4 00 00 00       cmp    edi,0xc4
    4187de:    7f 40                   jg     418820 <handle_amd+0x50>
    4187e0:    31 d2                   xor    edx,edx
    4187e2:    81 ff bf 00 00 00       cmp    edi,0xbf
    4187e8:    0f 9d c2                setge  dl
    4187eb:    81 ea fb ff ff 7f       sub    edx,0x7ffffffb
    4187f1:    39 c2                   cmp    edx,eax
    4187f3:    77 2b                   ja     418820 <handle_amd+0x50>
    4187f5:    89 d0                   mov    eax,edx
    4187f7:    0f a2                   cpuid
    4187f9:    81 ff bb 00 00 00       cmp    edi,0xbb
    4187ff:    7e 27                   jle    418828 <handle_amd+0x58>
    418801:    81 ef bc 00 00 00       sub    edi,0xbc
    418807:    83 ff 08                cmp    edi,0x8
    41880a:    0f 87 48 01 00 00       ja     418958 <handle_amd+0x188>
    418810:    48 8d 35 c9 0b 08 00    lea    rsi,[rip+0x80bc9]        # 4993e0 <__PRETTY_FUNCTION__.4767+0x20>
    418817:    48 63 04 be             movsxd rax,DWORD PTR [rsi+rdi*4]
    41881b:    48 01 c6                add    rsi,rax
    41881e:    ff e6                   jmp    rsi
    418820:    31 c0                   xor    eax,eax
    418822:    5b                      pop    rbx
    418823:    c3                      ret


首先,查看该部分的名称。其次,如果仔细看一下代码,您会发现该函数通过剖析cpuid指令的返回值(4187d64187f7)来标识CPU(更准确地说是微体系结构和其他功能,例如缓存大小)。 ..)正在运行您的ELF二进制文件,然后确定哪种配置最适合该配置。这样,无论您使用的是哪种体系结构,上述C代码中调用的strcpy函数始终将是最快的方法(英特尔:Nehalem,Sandy Bridge,Ivy Bridge,Haswell,...; AMD:Phenom,Opteron等。 。; ...)。请记住,这些快速实现已针对每种可能的目标体系结构进行了手动优化和微调。

所以这就是静态PLT二进制文件中GOTELF部分的用途。

现在,如果您想自己研究一下,应该编译C代码上面使用的是GCC 4.9版(这是我使用的版本),它使用了-static-g3(调试符号)标志。然后,使用objdump-D开关反汇编二进制文件,以获取所有ELF部分。然后,您可以遍历所有部分并浏览汇编代码。您还可以使用gdb运行二进制文件,并在关键位置设置断点,并逐步运行程序。

评论


有没有一种方法可以避免检查CPU的开销,而只是让其承担特定的CPU实现?

–约瑟夫·加文
19-09-28在17:36

@JosephGarvin是的!您可以在编译时使用C宏进行cpu调度。但这意味着程序员必须提供优化的函数的代码或库。另一种方法是使用编译器标志。使用gcc和icc可以设置-march = native和-mtune = native。编译器/语言文档应涵盖此类情况。

– Yaspr
7月1日7:03

#2 楼

@yaspr的答案很好,因为这个问题有很多“寻找可靠和/或官方来源的答案。”,让我在这里尝试提供一些参考。

由于性能问题,此处需要.PLT.GOT表。

BinCFI在去年的前2大计算机安全会议上发布。


由于PLT存根的目的是调度交叉
模块调用,所以看来目标只能从其他模块中导出符号。但是,最新的gcc版本
支持称为
gnu间接函数的新函数类型,该函数类型允许函数具有许多不同的实现,而最合适的实现是在运行时选择的。基于诸如CPU
类型的因素。当前,许多glibc低级功能(例如
memcpy,strcmp和strlen)都使用此功能。为了支持此功能,库导出一个选择器函数,该函数在运行时选择要使用的许多实现中的哪个。这些实现功能可能根本不会导出。


此处列出了有关如何利用此功能的其他一些参考。


Agner的
GCC bugzilla
GCC文档


评论


好吧,我避免引用这些参考文献,因为它们可能会造成混乱。它们以ifunc属性为中心,该属性不是此问题的主题,而是静态链接的二进制文件中存在GOT和PLT(Agner的CPU博客是有价值的信息来源)。但是,当然,如果明智地使用附加信息,那总是好的!问题是,我在修补/插入静态ELF二进制文件以进行性能评估时发现的所有内容,都早于发布的参考文献。

– Yaspr
2014年10月8日在9:14