.got
和.plt
部分。我想知道静态链接二进制文件需要
got
和plt
部分吗?有人吗?#1 楼
程序员对于ELF
二进制文件在内部的工作方式有很多不清楚的地方。而且,不幸的是,除了广泛涵盖该主题的两个或三个之外,几乎没有可靠的参考文献。许多工具(链接器,加载器,汇编器,调试器等)对于大多数人来说还是个谜。关于链接器和加载器,主要参考文献是John R. Levine的链接器和加载器(http://linker.iecc.com/)。另一个可靠的信息来源是官方的ELF
二进制格式文档。但是,这些仅仅是对某种或大多数技术的工作方式的介绍。现在,这是您的问题的答案(为什么
GOT
和PLT
部分仍包含在静态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
指令的返回值(4187d6
和4187f7
)来标识CPU(更准确地说是微体系结构和其他功能,例如缓存大小)。 ..)正在运行您的ELF
二进制文件,然后确定哪种配置最适合该配置。这样,无论您使用的是哪种体系结构,上述C代码中调用的strcpy
函数始终将是最快的方法(英特尔:Nehalem,Sandy Bridge,Ivy Bridge,Haswell,...; AMD:Phenom,Opteron等。 。; ...)。请记住,这些快速实现已针对每种可能的目标体系结构进行了手动优化和微调。 所以这就是静态
PLT
二进制文件中GOT
和ELF
部分的用途。现在,如果您想自己研究一下,应该编译
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
评论
他们根本不应该有这些部分。我自己尝试了一下,并且确认即使将-static提供给编译器,.got和.got.plt节仍然存在。但是,.dynamic节不存在。我的猜测是,给定-static时,它们只是空白部分。静态已死,一个相关的堆栈溢出问题:stackoverflow.com/questions/3430400/…
@Nirlzr:有趣,我不知道。因此,实际上,摆脱get / plt表的唯一方法是使用-nostdlib选项。
@perror:请先尝试-nodefaultlibs。但是您可能必须提供编译器可能需要的任何标准功能的自己实现(memcpy等)。
@Nirtlzr:是的,使用此选项,您将必须重建整个libc(没有printf,scanf等)。