ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

x86-64 Linux中不再允许32位绝对地址?

2019-09-11 00:50:37  阅读:271  来源: 互联网

标签:relocation linux x86-64 gcc linker-errors


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).

相关:Build compressed x86 kernels as PIE也受更改的默认值的影响.

Why doesn’t Linux randomize the address of the executable code segment?是一个较旧的问题,为什么它不是早期的默认值,或者只是在旧的Ubuntu上启用了几个软件包才能全面启用.

请注意,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

相关:building static / dynamic executables with/without libc, defining _start or main.

检查现有可执行文件是否为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, ...

这也被问到:How to test whether a Linux binary was compiled as position independent code?

半相关(但不是真的):另一个最近的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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有