重定位应该如何在静态 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 示例。 (我将全局变量名称更改为很长且易于在搜索 readelf
或 nm
输出时找到。)
#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"
考虑这个用于 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 示例。 (我将全局变量名称更改为很长且易于在搜索 readelf
或 nm
输出时找到。)
#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"