重定位应该如何在静态 PIE 二进制文件中工作? [英] How are relocations supposed to work in static PIE binaries?
问题描述
考虑这个用于 AMD64 Linux 的 GNU 汇编程序:
Consider this GNU Assembler program for AMD64 Linux:
.globl _start
_start:
movl $59, %eax # SYS_execve
leaq .pathname(%rip), %rdi # position-independent addressing
leaq .argv(%rip), %rsi
movq (%rsp), %rdx
leaq 16(%rsp,%rdx,8), %rdx
syscall
movl $60, %eax # SYS_exit
movl $1, %edi
syscall
.section .data
.argv:
.quad .argv0 # Absolute address as static data
.quad .argv1
.quad 0
.pathname:
.ascii "/bin/"
.argv0:
.asciz "echo"
.argv1:
.asciz "hello"
当我使用 gcc -nostdlib -static-pie
构建它并运行它时,它失败了,strace 告诉我发生了这种情况:
When I build it with gcc -nostdlib -static-pie
and run it, it fails, and strace shows me that this happens:
execve("/bin/echo", [0x301d, 0x3022], 0x7fff9bbe5a08 /* 28 vars */) = -1 EFAULT (Bad address)
不过,如果我将其构建为静态非 PIE 二进制文件或动态 PIE 二进制文件,它就可以正常工作.问题似乎是没有处理重定位.
It works fine if I build it as a static non-PIE binary or as a dynamic PIE binary, though. It looks like the problem is that relocations aren't getting processed.
在动态 PIE 二进制文件中,动态链接器会这样做,而在非 PIE 静态二进制文件中,您不需要运行时重定位;静态地址是链接时间常数.
In dynamic PIE binaries, the dynamic linker does that, and in non-PIE static binaries, you don't need runtime relocations; static addresses are link time constants.
但是静态 PIE 二进制文件应该如何工作?他们是根本不应该有任何重定位,还是应该有其他东西来处理它们?
But how are static PIE binaries supposed to work? Are they just not supposed to have any relocations at all, or is something else supposed to process them?
推荐答案
显然静态 PIE 仍然将运行时重定位到用户空间.如果您省略 CRT 启动代码(使用 -nostdlib
),则它根本不会发生.这大概就是 gcc -nostdlib
默认不制作静态 PIE 的原因.
Apparently static-PIE still leaves runtime relocation to user-space. If you omit CRT startup code (with -nostdlib
), it doesn't happen at all. That's presumably why gcc -nostdlib
doesn't make a static-PIE by default.
如果您确实链接了 glibc 的 CRT 启动代码,它会使用专门用于此目的的代码为您处理.
测试用例:将带有 _start
的代码更改为 main
,或者来自注释的 Nate 的 C 示例.(我将全局变量名称更改为长且易于在搜索 readelf
或 nm
输出时找到.)
Test case: your code with _start
changed to main
, or Nate's C example from comments. (I changed the global var names to be long and easy to find in searching readelf
or nm
output.)
#include <stdio.h>
int global_static_a = 7;
int *static_ptr = &global_static_a;
int main(void) {
printf("%d\n", *static_ptr); // load and deref the statically-initialized pointer
}
使用
gcc -g -fpie -static-pie print.c
编译(我在 Arch GNU/Linux for x86-64 上使用 gcc 10.1.0 和 glibc 2.31-5)Compile with
gcc -g -fpie -static-pie print.c
(I used gcc 10.1.0 with glibc 2.31-5 on Arch GNU/Linux for x86-64)运行
gdb ./a.out
.在 GDB 中:Run
gdb ./a.out
. In GDB:starti
(我想确保 GDB 可以在设置观察点之前看到正确的地址,以防万一)starti
(I wanted to make sure GDB could see the correct addresses before setting watch points, in case that's necessary)观看 static_ptr
继续
观察点被
_dl_relocate_static_pie+540 mov QWORD PTR [rcx],rdx
命中.The watchpoint was hit by
_dl_relocate_static_pie+540 mov QWORD PTR [rcx],rdx
.Hardware watchpoint 2: static_ptr Old value = (int *) 0xb7130 New value = (int *) 0x7ffff7ffb130 <global_static_a>
一个名为
_dl_relocate_static_pie
的函数链接到我的可执行文件中这一事实非常清楚地证明 glibc 提供了该代码.The fact that a function called
_dl_relocate_static_pie
got linked into my executable is pretty clear evidence that glibc provided that code.这篇关于重定位应该如何在静态 PIE 二进制文件中工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!