GCC促进了奇怪的重新定位R_X86_64_32S错误,但在手动链接中没有



我正试图在Linux环境下,在C文件中具有内联汇编的x86_64机器上,通过syscall指令调用sys_write。为了实现这一点,我编写了以下代码:

[tjm@ArchPad poc]$ cat poc.c 
const char S[] = "abcdn";
int main() {
__asm __volatile(" 
movq $1, %%rax; 
movq $1, %%rdi; 
movq %0, %%rsi; 
movq $5, %%rdx; 
syscall;"
:
: "i"(S)
);
return 0;
}

当我用GCC编译它时,出现了以下错误:

[tjm@ArchPad poc]$ gcc -O0 poc.c -o poc
/usr/bin/ld: /tmp/ccWYxmSb.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status

然后我将-fPIE添加到编译器命令中。不幸的是,一切都没有改变:

[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -o poc 
/usr/bin/ld: /tmp/ccdK36lx.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status

这太奇怪了,以至于我很好奇手动编译和链接它:

[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -S
[tjm@ArchPad poc]$ as -O0 poc.s -o poc.o
[tjm@ArchPad poc]$ ld -O0 -melf_x86_64 --dynamic-linker /usr/lib/ld-linux-x86-64.so.2 /usr/lib/crt{1,i,n}.o -lc poc.o -o poc

令人惊讶的是,在上述尝试过程中没有出现错误。然后我测试了可执行文件的功能,它似乎可以工作:

[tjm@ArchPad poc]$ ./poc 
abcd

关于为什么会发生这种情况,以及如何防止GCC助长上述错误,有什么想法吗?

立即数移动指令movq $S, %rsi尽管使用64位寄存器,但只使用32位带符号的立即数。当构建要使用ASLR运行的与位置无关的可执行文件时,就像大多数Linux系统上的默认设置一样,您的程序通常不位于虚拟内存的低或高31位中,因此像S这样的全局或静态对象的地址不适合带符号的32位立即数。

一个修复方法是使用movabs,它确实需要一个完整的64位立即数。用movabs %0, %%rsi替换movq %0, %%rsi可以构建代码。然而,更好的方法是使用RIP相对寻址lea S(%rip), %rsi,这是一条较短的指令,并且避免了在加载时重新定位的需要。在内联asm中这样做有点尴尬,所以您可以让编译器使用"S" (S)这样的输入操作数将地址加载到寄存器中(令人困惑的是,rsi寄存器的约束是S,它恰好与您为数组变量选择的名称一致(。

当您手动链接时,您构建了一个非位置独立的可执行文件,这意味着将地址视为常量是有效的。将-no-pie传递到gcc也可以获得相同的效果。

您的代码还有另一个严重的问题,因为它没有声明它撞击的寄存器——不仅是那些您显式mov的寄存器,还有syscall隐式修改的rcxr11。您应该看看如何在内联程序集中通过syscall或sysenter调用系统调用?其具有正确的示例。仔细研究这些例子是明智的——GCC内联asm功能强大,但也很容易以编译器无法帮助您检测的微妙方式出错,而且可能在简单的程序中工作,但在更复杂的程序中不可预测地失败。

GCC支持ASM指令的多种方言,默认值可能有所不同。点击此处阅读更多:

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Extended-Asm

当然,不同的方言是不兼容的。

相关内容

  • 没有找到相关文章

最新更新