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

A F*_*Fog 21 linux gcc x86-64 linker-errors relocation

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位绝对地址?

Pet*_*des 31

您的发行版配置了gcc --enable-default-pie,因此默认情况下它与位置无关的可执行文件(允许可执行文件的ASLR以及库).如今,大多数发行版都在这样做.

你实际上是在创建一个共享对象:PIE可执行文件是一种使用具有入口点的共享对象的hack.动态链接器已经支持此功能,ASLR非常适合安全性,因此这是为可执行文件实现ASLR的最简单方法.

ELF共享对象中不允许32位绝对重定位; 这将阻止它们被加载到低2GiB之外(对于符号扩展的32位地址).允许使用64位绝对地址,但通常只需要跳转表或其他静态数据,而不是指令的一部分.1

recompile with -fPIC错误信息的一部分是手写asm的伪造; 它是人与编译的情况下写的gcc -c,然后试图与链接gcc -shared -o foo.so *.o,与海湾合作委员会,其中-fPIE不是默认.错误消息可能应该更改,因为许多人在链接手写asm时遇到此错误.


用于default rel将此重写回旧行为. foo(%rip)是链接器选项,.intel_syntax noprefix是code-gen选项.仅使用[rip + foo],gcc将使代码gcc -fno-pie -no-pie不会与仍然启用的链接-no-pie.

(可以在默认情况下,也已启用PIE:使用-fno-pie一种2017年7月补丁-fno-pie一个别名mov eax, offset .LC0,为COMPAT用gcc,但clang4.0.1没有它.)


仅使用-pie(但仍然clang -fno-pie -nopie)编译器生成的代码(来自C或C++源代码)将稍微慢一些,并且仍然大于必要,但仍将链接到位置相关的可执行文件,这将不会受益于ASLR. "太多的PIE对性能有害" 报道了SPEC CPU2006上x86-64的平均速度下降了3%(我没有这份文件的副本,所以IDK上的硬件是什么:/).但在32位代码中,平均减速为10%,最差情况为25%(在SPEC CPU2006上).

PIE可执行文件的代价主要用于索引静态数组,正如Agner在问题中描述的那样,使用静态地址作为32位立即数或作为-no-pie寻址模式的一部分保存指令和寄存器而不是RIP相对LEA将地址输入寄存器.用于将静态地址放入寄存器的5字节-nopie而不是7字节-no-pie也很适合将字符串文字或其他静态数据的地址传递给函数.

-fpie仍然假设没有全局变量/函数的符号插入,[disp32 + index*4]这与必须通过GOT访问全局变量的共享库不同(这是mov r32, imm32用于任何可以限制为文件范围而不是全局的变量的另一个原因).请参阅Linux上的动态库的抱歉状态.

因此lea r64, [rel symbol]-fPIE64位代码要糟糕得多,但对于32位仍然不好,因为RIP相对寻址不可用.请参阅Godbolt编译器资源管理器中的一些示例.平均而言,-fPIC64位代码具有非常小的性能/代码大小的缺点.特定循环的最坏情况可能只有几个百分点.但是32位PIE会更糟糕.

这些static代码生成选项在链接或汇编-fPIE手写asm 时都没有任何区别. -fPIC是你想要两个选项的情况.


如果你的GCC是这样配置的,-fPIE 打印-f.2015年初,gcc已添加对此配置选项的支持.Ubuntu在16.10中启用了它,而Debian在gcc中同时启用了它.S(导致内核构建错误:https://lkml.org/lkml/2016/10/21/904).

相关:构建压缩的x86内核,因为PIE也受更改的默认值的影响.

为什么Linux没有随机化可执行代码段的地址?是一个较旧的问题,为什么它不是早期的默认值,或者只是在旧的Ubuntu上启用了几个软件包才能全面启用.


请注意,gcc -fno-pie -no-pie -O3 main.c nasm_output.o它本身并未更改其默认值.它仍然可以正常工作(至少在Arch Linux上使用binutils 2.28).更改是gcc -v |& grep -o -e '[^ ]*pie'默认为--enable-default-pie作为链接器选项传递,除非您明确使用6.2.0-7ld.

在NASM源文件中,我曾经gcc获得一个绝对地址.(我正在测试编码小绝对地址的6字节方式(地址大小+ mov eax,moffs :) -pie在Intel CPU上是否有LCP停顿. 确实如此.)

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
Run Code Online (Sandbox Code Playgroud)

相关:使用/不使用libc构建静态/动态可执行文件,定义-static-no-pie.


检查现有可执行文件是否为PIE

a32 mov eax, [abs buf]67 a1 40 f1 60 00说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, ...
Run Code Online (Sandbox Code Playgroud)

这也被问到:如何测试Linux二进制文件是否被编译为位置无关代码?


半相关(但不是真的):另一个最近的gcc功能是_start.最后调用共享库可以只是main(AT&T file),没有PLT蹦床.

发行版有望很快开始启用它,因为它还避免了需要可写的+可执行内存页面.对于进行大量共享库调用的程序来说,这是一个显着的加速,例如x86-64 readelf编译tramp3d 在补丁作者测试的任何硬件上从41.6s到36.8s .(clang可能是共享库调用的最坏情况.)

它确实需要早期绑定而不是延迟动态链接,因此对于立即退出的大型程序来说速度较慢.(例如gcc -fno-plt或编译call [rip + symbol@GOTPCREL]).显然,通过预链接可以减少这种放缓.

但是,这不会消除共享库PIC代码中外部变量的GOT开销.(参见上面的godbolt链接).


脚注1

Linux ELF共享对象实际上允许使用64位绝对地址,文本重定位允许在不同地址(ASLR和共享库)加载.这允许您具有跳转表call *puts@GOTPCREL(%rip),或者clang -O2 -g没有运行时初始值设定项.

因此clang --version工作(NASM/YASM语法为10字节hello.c,又称AT&T语法section .rodata,唯一可以使用64位立即数的指令).但是这个比较大,通常比较慢static const int *foo = &bar;,如果您决定不禁用,那就应该使用mov rdi, qword msg.根据Agner Fog的microarch pdf,64位立即数从Sandybridge系列CPU的uop缓存中获取的速度较慢.(是的,问这个问题的同一个人.:)

您可以使用NASM mov r64, imm64而不是在每种movabs寻址模式中指定它.另请参见Mach-O 64位格式不支持32位绝对地址.NASM访问阵列有关避免32位绝对寻址的更多描述.OS X根本不能使用32位地址,因此RIP相对寻址也是最佳方式.

在位置相关的代码(lea rdi, [rel msg])中,您应该-pie在需要寄存器中的地址时使用; 5字节default rel甚至比RIP相对LEA小,并且更多执行端口可以运行它.

  • 对于`-m32`来说这真的很糟糕,因为它浪费了一个珍贵的寄存器来保存PC.我的64位发行版也启用了标志,所以`-m32`也使用了它. (2认同)
  • @Trey:仅对于 32 位代码,是的,您可以在没有 `-fPIE` 的情况下进行编译,但我认为仍然与 `-pie` 链接。然后,您将获得嵌入代码中各处的所有 32 位绝对地址的运行时重定位。这在 64 位模式下不起作用,因为编译器在没有 `-fPIE` 的情况下编译时会正确选择有效的 32 位绝对立即数和 disp32 寻址模式,这会阻止链接。64 位绝对地址可以获得运行时重定位,但您只会在跳转表或 `static int *foo = &bar` 静态数据中找到;代码不会使用 `mov r64, imm64` 作为地址。 (2认同)

归档时间:

查看次数:

5787 次

最近记录:

6 年,1 月 前