赞
踩
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位绝对地址?
解决方法:
您的发行版使用–enable-default-pie配置gcc,因此它默认情况下使位置无关的可执行文件(允许可执行文件的ASLR以及库).如今,大多数发行版都在这样做.
你实际上是在创建一个共享对象:PIE可执行文件是一种使用具有入口点的共享对象的hack.动态链接器已经支持此功能,ASLR非常适合安全性,因此这是为可执行文件实现ASLR的最简单方法.
ELF共享对象中不允许32位绝对重定位;这将阻止它们被加载到低2GiB之外(对于符号扩展的32位地址).允许使用64位绝对地址,但通常只需要跳转表或其他静态数据,而不是指令的一部分
使用-fPIC重新编译错误消息的部分是手写asm的伪造;它是为人们使用gcc -c进行编译,然后尝试使用gcc链接gcc -shared -o foo.so * .o而使用gcc编写的,其中-fPIE不是默认值.错误消息可能应该更改,因为许多人在链接手写asm时遇到此错误.
如何使用RIP相对寻址:基础知识
对于没有缺点的简单情况,始终使用RIP相对寻址.另请参见下面的脚注1和this answer for syntax.只考虑使用32位绝对寻址,它实际上对代码大小有用而不是有害.例如NASM默认rel位于文件顶部.
AT& T foo(%rip)或GAS .intel_syntax noprefix使用[rip foo].
禁用PIE模式以使32位绝对寻址工作
使用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 compat,但是clang4.0.1没有它.)
PIE对64位(次要)或32位代码(主要)的性能成本
只有-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]寻址模式的一部分保存指令和寄存器vs一个RIP相对LEA,用于将地址输入寄存器.另外5字节的mov r32,imm32而不是7字节的lea r64,[rel symbol]用于将静态地址放入寄存器,这对于将字符串文字或其他静态数据的地址传递给函数来说是很好的.
-fPIE仍然假设没有全局变量/函数的符号插入,不像-fPIC用于必须通过GOT访问全局变量的共享库(这是为任何可以限制为文件范围的变量使用静态的另一个原因)全球).见The sorry state of dynamic libraries on Linux.
因此,对于64位代码,-fPIE比-fPIC要差得多,但对于32位仍然不好,因为RIP相对寻址不可用.请参阅some examples on the Godbolt compiler explorer.平均而言,-fPIE在64位代码中具有非常小的性能/代码大小的缺点.特定循环的最坏情况可能只有几个百分点.但是32位PIE会更糟糕.
这些-f code-gen选项在链接时没有任何区别,
或者在组装时.S手写的asm. gcc -fno-pie -no-pie -O3 main.c nasm_output.o是你想要两个选项的情况.
检查GCC配置
如果你的GCC是这样配置的,gcc -v |& grep -o -e'[^] * pie’打印–enable-default-pie.在0700中向gcc添加了对此配置选项的支持.Ubuntu在16.10中启用了它,而Debian在gcc 6.2.0-7中同时启用了它(导致内核生成错误:https://lkml.org/lkml/2016/10/21/904).
请注意,ld本身并未更改其默认值.它仍然可以正常工作(至少在Arch Linux上使用binutils 2.28).更改是gcc默认将-pie作为链接器选项传递,除非您明确使用-static或-no-pie.
在NASM源文件中,我使用a32 mov eax,[abs buf]来获取绝对地址. (我正在测试编码小绝对地址的6字节方式(地址大小mov eax,moffs:67 a1 40 f1 60 00)是否在Intel CPU上有LCP停顿.It does.)
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
检查现有可执行文件是否为PIE
file和readelf说PIE是“共享对象”,而不是ELF可执行文件.静态可执行文件不能是PIE.
$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, ...
## Or with a more recent version of file:
a.out: ELF 64-bit LSB pie executable, ...
半相关(但不是真的):另一个最近的gcc功能是gcc -fno-plt.最后调用共享库可以只调用[rip symbol @ GOTPCREL](AT& T call * puts @ GOTPCREL(%rip)),没有PLT蹦床.
发行版有望很快开始启用它,因为它还避免了需要可写的可执行内存页面.对于进行大量共享库调用的程序来说,这是一个显着的加速,例如,在任何硬件the patch author tested on上,x86-64 clang -O2 -g编译tramp3d从41.6s到36.8s.(clang可能是共享库调用的最坏情况.)
它确实需要早期绑定而不是延迟动态链接,因此对于立即退出的大型程序来说速度较慢. (例如clang –version或编译hello.c).显然,通过预链接可以减少这种放缓.
但是,这不会消除共享库PIC代码中外部变量的GOT开销. (参见上面的godbolt链接).
脚注1
Linux ELF共享对象中实际允许64位绝对地址,text relocations允许在不同地址(ASLR和共享库)加载.这允许你在.rodata节中有跳转表,或者静态const int * foo =& bar;没有运行时初始值设定项.
所以mov rdi,qword msg工作(10字节mov r64, imm64的NASM / YASM语法,又名AT& T语法movabs,唯一可以使用64位立即数的指令).但这比lea rdi更大,通常更慢,[rel msg],如果你决定不禁用-pie,你应该使用它.根据Agner Fog’s microarch pdf,从Sandybridge系列CPU上的uop缓存中获取64位立即数较慢.(是的,同一个人问这个问题.:)
您可以使用NASM的默认rel而不是在每个[rel symbol]寻址模式中指定它.有关避免32位绝对寻址的更多描述,另请参见Mach-O 64-bit format does not support 32-bit absolute addresses. NASM Accessing Array. OS X根本不能使用32位地址,因此RIP相对寻址也是最佳方式.
在位置相关的代码(-no-pie)中,当你想要一个寄存器中的地址时,你应该使用mov edi,msg; 5字节的mov r32,imm32甚至比RIP相对LEA小,并且更多的执行端口可以运行它.
标签:relocation,linux,x86-64,gcc,linker-errors
来源: https://codeday.me/bug/20190911/1802729.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。