当我在x86-64架构的Linux上使用libc的system()函数时,我注意到了一个非常奇怪的行为,有时调用system()函数会失败并导致段错误,下面是我在使用gdb进行调试后得到的信息。
我发现这个段错误是在以下代码行中引发的:
=> 0x7ffff7a332f6 <do_system+1094>: movaps XMMWORD PTR [rsp+0x40],xmm0
根据手册,这就是SIGSEGV的原因:
进一步深入查看后,我发现我的当源操作数或目标操作数是内存操作数时,操作数必须在16字节边界上对齐,否则会生成通用保护异常(#GP)。
rsp
值确实没有16字节填充(即其十六进制表示不以0
结尾)。在调用system
之前手动修改rsp
的值确实使一切正常。所以我编写了以下程序:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
register long long int sp asm ("rsp");
printf("%llx\n", sp);
if (sp & 0x8) /* == 0x8*/
{
printf("running system...\n");
system("touch hi");
}
return 0;
}
使用gcc 7.3.0编译 观察输出结果:
sha@sha-desktop:~/Desktop/tda$ ltrace -f ./o_sample2
[pid 26770] printf("%llx\n", 0x7ffe3eabe6c87ffe3eabe6c8
) = 13
[pid 26770] puts("running system..."running system...
) = 18
[pid 26770] system("touch hi" <no return ...>
[pid 26771] --- SIGSEGV (Segmentation fault) ---
[pid 26771] +++ killed by SIGSEGV +++
[pid 26770] --- SIGCHLD (Child exited) ---
[pid 26770] <... system resumed> ) = 139
[pid 26770] +++ exited (status 0) +++
因此,使用这个程序,我不能执行
system()
。另外还有一件小事,我无法确定它是否与问题有关,几乎所有的运行都以错误的
rsp
值和被SEGSEGV杀死的子进程结束。这让我想到了一些问题:
system
为什么要干扰xmm
寄存器?- 这是正常的行为吗?或者我在正确使用
system()
函数方面可能遗漏了一些基本的东西?
register long long int sp asm("rsp");
并将其作为变量访问而不使用扩展内联汇编是未定义的行为。只有幸运才能工作。我想看看原始代码,看看它为什么失败了。你能给我们展示一下你调用系统时失败的原始代码吗?这听起来像一个XY问题。 - Michael Petchsystem
前维护对齐。像你建议的将 RSP 四舍五入到最近的 16 字节边界可以解决问题。我猜你是用and rsp, -16
这样的方法实现的吧?ABI 规定,在函数调用时,栈指针需要 16 字节(某些情况下为 32 字节)对齐。使用对齐向量指令来提高性能(这很正常)也没有问题,比如system
函数。 - Michael Petchrsp
没有对齐是因为asm
的原因吗?我认为这是一个已知的指令对于gcc
,除了编译我的程序之外,它不应该也不会“搞乱”我的rsp
,对吗? - shaqedint foo asm("rsp")
不应该导致你的代码编译错误,特别是如果你从未对该变量进行赋值。如果gcc允许自己生成错误的代码而没有警告,那么这是一个bug。手册说唯一支持的用途是使"r"(var)
约束选择你想要的寄存器(https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html),但它并不意味着仅仅使用这样的变量其他时间会有问题。 - Peter Cordes