重定位应该如何在静态 PIE 二进制文件中工作?

How are relocations supposed to work in static PIE binaries?

考虑这个用于 AMD64 的 GNU 汇编程序 Linux:

.globl _start
_start:
    movl , %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 , %eax # SYS_exit
    movl , %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 告诉我发生了这种情况:

execve("/bin/echo", [0x301d, 0x3022], 0x7fff9bbe5a08 /* 28 vars */) = -1 EFAULT (Bad address)

不过,如果我将它构建为静态非 PIE 二进制文件或动态 PIE 二进制文件,它工作正常。看起来问题是没有处理重定位。

在动态 PIE 二进制文件中,动态 linker 会执行此操作,而在非 PIE 静态二进制文件中,您不需要 运行time 重定位;静态地址是 link 时间常数。

但是静态 PIE 二进制文件应该如何工作?他们只是根本不应该有任何搬迁,还是应该有其他东西来处理它们?

显然静态 PIE 仍然将运行时重定位留给用户-space。如果您省略 CRT 启动代码(使用 -nostdlib),它根本不会发生。这大概就是为什么 gcc -nostdlib 默认情况下不制作静态 PIE 的原因。

如果您 link 使用 glibc 的 CRT 启动代码,它将使用专门用于该目的的代码为您处理它。

测试用例:您的代码 _start 更改为 main,或来自注释的 Nate 的 C 示例。 (我将全局变量名称更改为很长且易于在搜索 readelfnm 输出时找到。)

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

  • 运行gdb ./a.out。在 GDB 中:

  • starti(我想确保 GDB 可以在 设置观察点之前看到正确的地址,以防万一)

  • watch static_ptr

  • continue

监视点被_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 的函数被 link 编辑到我的可执行文件中这一事实非常清楚地证明 glibc 提供了该代码。

问题是您使用的是 -nostdlib,因此您缺少在可执行文件本身中执行重定位的代码。如果您使用 gcc -static-pie:

构建一个略有不同的示例,它将起作用
.globl main
main:
    movl , %eax # SYS_execve
    leaq .pathname(%rip), %rdi     # position-independent addressing
    leaq .argv(%rip), %rsi
    syscall
    movl , %eax # SYS_exit
    movl , %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"