64位Linux默认使用小内存模型,它将所有代码和静态数据置于2GB地址限制之下。这可确保您可以使用32位绝对地址。较旧版本的gcc使用静态数组的32位绝对地址,以便为相对地址计算保存额外的指令。但是,这不再有效。如果我尝试在汇编中创建一个32位的绝对地址,我会收到链接器错误: "对`.data'重新定位R_X86_64_32S制作共享对象时不能使用;用-fPIC"重新编译。 当然,此错误消息具有误导性,因为我没有制作共享对象,-fPIC也没有帮助。 到目前为止我发现的是:gcc版本4.8.5使用静态数组的32位绝对地址,gcc版本6.3.0没有。版本5可能也没有。 binutils 2.24中的链接器允许32位绝对地址,而2.28则不允许。
此更改的结果是必须重新编译旧库并破坏旧版程序集代码。
现在我想问一下:这个改变是什么时候做的?它在某处记录了吗?是否有一个链接器选项使其接受32位绝对地址?
答案 0 :(得分:28)
您的发行版配置了带--enable-default-pie
的gcc,因此默认情况下它会创建与位置无关的可执行文件(允许可执行文件的ASLR以及库)。如今,大多数发行版都在这样做。
你实际上正在制作一个共享对象:PIE可执行文件是一种使用具有入口点的共享对象的hack。动态链接器已经支持此功能,ASLR非常适合安全性,因此这是为可执行文件实现ASLR的最简单方法。
ELF共享对象中不允许32位绝对重定位;这将阻止它们被加载到低2GiB之外(对于符号扩展的32位地址)。允许使用64位绝对地址,但通常只需要跳转表或其他静态数据,而不是指令的一部分。 1
错误消息的recompile with -fPIC
部分是手写的asm伪造的;它是针对使用gcc -c
进行编译然后尝试与gcc -shared -o foo.so *.o
进行关联的情况编写的,其中gcc是-fPIE
而不是的默认值。错误消息可能应该更改,因为许多人在链接手写asm时遇到此错误。
使用gcc -fno-pie -no-pie
将此值重写为旧行为。 -no-pie
是链接器选项-fno-pie
is the code-gen option。仅使用-fno-pie
时,gcc会生成与mov eax, offset .LC0
类似的代码,但不会与仍然启用的-pie
相关联。
( clang 也可以默认启用PIE:使用 clang -fno-pie -nopie
。July 2017 patch为-no-pie
添加了别名-nopie
,与gcc兼容,但clang4.0.1没有。)
仅使用-no-pie
,(但仍然是-fpie
)编译器生成的代码(来自C或C ++源代码)将稍微慢一点且大于必要,但仍然会被链接到一个依赖于位置的可执行文件,它不会受益于ASLR。 "太多PIE对性能不利" reports an average slowdown of 3% for x86-64 on SPEC CPU2006 (我没有这份文件的副本,所以IDK上有什么硬件:/)。但在32位代码中,平均减速为10%,最差情况为25%(在SPEC CPU2006上)。
PIE可执行文件的代价主要用于索引静态数组,正如Agner在问题中描述的那样,使用静态地址作为32位立即数或作为[disp32 + index*4]
寻址模式的一部分保存指令和寄存器与RIP相关的LEA,以获取寄存器中的地址。用于将静态地址输入寄存器的5字节mov r32, imm32
而不是7字节lea r64, [rel symbol]
也很适合将字符串文字或其他静态数据的地址传递给函数。
-fPIE
仍假设没有全局变量/函数的符号插入,与-fPIC
不同,共享库必须通过GOT才能访问全局变量(这是使用{{1的另一个原因)对于任何可以限制为文件范围而不是全局的变量。请参阅The sorry state of dynamic libraries on Linux。
因此,对于64位代码,static
比-fPIE
要差得多,但对于32位仍然不好,因为RIP相对寻址不可用 。见some examples on the Godbolt compiler explorer。平均而言,-fPIC
在64位代码中具有非常小的性能/代码大小的缺点。特定循环的最坏情况可能只有几个百分点。但是32位PIE可能会更糟糕。
这些-fPIE
代码生成选项在链接时没有任何区别,
或者在汇编-f
手写的asm时。 .S
是您需要两个选项的情况。
如果您的GCC是这样配置的,gcc -fno-pie -no-pie -O3 main.c nasm_output.o
会打印gcc -v |& grep -o -e '[^ ]*pie'
。在early 2015中向gcc添加了对此配置选项的支持。 Ubuntu在16.10中启用了它,而Debian在gcc --enable-default-pie
中大约同时启用它(导致内核构建错误:https://lkml.org/lkml/2016/10/21/904)。
相关:Build compressed x86 kernels as PIE也受更改默认值的影响。
Why doesn't Linux randomize the address of the executable code segment?是一个较旧的问题,为什么它不是之前的默认值,或者只是在旧版Ubuntu上启用了一些软件包之后才启用它。
请注意, 6.2.0-7
本身并未更改其默认。它仍然可以正常工作(至少在Arch Linux上使用binutils 2.28)。更改是ld
默认为传递gcc
作为链接器选项,除非您明确使用-pie
或-static
。
在NASM源文件中,我使用-no-pie
获取绝对地址。 (我正在测试编码小绝对地址的6字节方式(地址大小+ mov eax,moffs:a32 mov eax, [abs buf]
)在Intel CPU上是否有LCP停顿。It does。
67 a1 40 f1 60 00
相关:building static / dynamic executables with/without libc, defining _start
or main
。
nasm -felf64 -Worphan-labels -g -Fdwarf testloop.asm &&
ld -o testloop testloop.o # works: static executable
gcc -v -nostdlib testloop.o # doesn't work
...
..../collect2 ... -pie ...
/usr/bin/ld: testloop.o: relocation R_X86_64_32 against `.bss' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: error: ld returned 1 exit status
gcc -v -no-pie -nostdlib testloop.o # works
gcc -v -static -nostdlib testloop.o # also works: -static implies -no-pie
和file
表示PIE是"共享对象",而不是ELF可执行文件。静态可执行文件不能是PIE。
readelf
此问题也在How to test whether a Linux binary was compiled as position independent code?
上提出半相关(但不是真的):另一个最近的gcc功能是 $ gcc -fno-pie -no-pie -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB executable, ...
$ gcc -O3 hello.c
$ file a.out
a.out: ELF 64-bit LSB shared object, ...
。最后,对共享库的调用只能是gcc -fno-plt
(AT& T call [rip + symbol@GOTPCREL]
),没有PLT蹦床。
Distros有望很快开始启用它,因为它还避免了需要可写的+可执行内存页面。对于进行大量共享库调用的程序来说,这是一个显着的加速,例如,在任何硬件the patch author tested on上,x86-64 call *puts@GOTPCREL(%rip)
编译tramp3d从41.6s到36.8s。 (clang可能是共享库调用的最坏情况。)
它确实需要早期绑定而不是延迟动态链接,因此对于立即退出的大型程序来说速度较慢。 (例如clang -O2 -g
或编译clang --version
)。显然,使用预链接可以减少这种放缓。
但是,这并没有消除共享库PIC代码中外部变量的GOT开销。 (参见上面的godbolt链接。)
脚注1
Linux ELF共享对象实际上允许使用64位绝对地址,text relocations允许在不同地址(ASLR和共享库)加载。这允许您在没有运行时初始化程序的情况下在hello.c
或section .rodata
中设置跳转表。
所以static const int *foo = &bar;
有效(10字节mov r64, imm64
的NASM / YASM语法,又称AT& T语法mov rdi, qword msg
,唯一可以使用64位立即数的指令)。但这比较大且通常慢于 movabs
,这是您决定不禁用lea rdi, [rel msg]
时应该使用的内容。根据{{3}},从Sandybridge系列CPU上的uop缓存中获取64位立即数较慢。 (是的,提出这个问题的同一个人。)
您可以使用NASM -pie
,而不是在每个default rel
寻址模式中指定它。另请参阅Agner Fog's microarch pdf以获取有关避免32位绝对寻址的更多描述。 OS X根本不能使用32位地址,因此RIP相对寻址也是最佳方式。
在与位置相关的代码([rel symbol]
)中,如果需要寄存器中的地址,则应使用-no-pie
; 5字节mov edi, msg
甚至小于RIP相对LEA,更多执行端口可以运行它。