x86_64:强制gcc在堆栈上传递参数
我正在为x86-64系统开发一个x86_64:强制gcc在堆栈上传递参数,c,gcc,parameter-passing,x86-64,calling-convention,C,Gcc,Parameter Passing,X86 64,Calling Convention,我正在为x86-64系统开发一个setjmp/longjmp自定义实现,它保存了CPU的整个上下文(即,所有xmm、fpu堆栈等;而不仅仅是被调用方保存寄存器)。这是直接在汇编中编写的 #include <stdio.h> static void foo(void) { int i; asm volatile ("mov 16(%%rbp), %0" : "=g" (i)); printf("%d\n", i); } #define
setjmp
/longjmp
自定义实现,它保存了CPU的整个上下文(即,所有xmm、fpu堆栈等;而不仅仅是被调用方保存寄存器)。这是直接在汇编中编写的
#include <stdio.h>
static void foo(void)
{
int i;
asm volatile ("mov 16(%%rbp), %0" : "=g" (i));
printf("%d\n", i);
}
#define foo(x) ({ int _i = (x); \
asm ("push %0\ncall %P1\nadd $8, %%rsp\n" : : "g"(_i), "i"(foo)); })
int main(int argc, char *argv[])
{
foo(argc-1);
return 0;
}
该代码在极少的示例中运行良好(直接从程序集源调用时)。将其与C代码一起使用时会出现问题,这是由于参数传递给自制setjmp
/longjmp
函数的方式造成的。事实上,x64_64系统的SysV ABI规定参数应该通过寄存器传递(如果它们最多为6)。我的职能签字如下:
long-long-set_jmp(exec_-context_-t*env)代码>
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu代码>
当然,这不能像现在这样起作用。事实上,当我输入set_jmp
时,rdi
和rsi
已经被删除,以保持指向env
和val
的指针。对于rdi
,这同样适用于long_jmp
是否有任何方法可以强制GCC(例如,通过依赖某个属性)强制通过堆栈传递参数?这比使用一些define来包装set_jmp
和long_jmp
要优雅得多,define可以手动将被破坏的寄存器推送到堆栈上,以便以后检索它们。您可以通过使用内联汇编调用函数来避免覆盖寄存器
#include <stdio.h>
static void foo(void)
{
int i;
asm volatile ("mov 16(%%rbp), %0" : "=g" (i));
printf("%d\n", i);
}
#define foo(x) ({ int _i = (x); \
asm ("push %0\ncall %P1\nadd $8, %%rsp\n" : : "g"(_i), "i"(foo)); })
int main(int argc, char *argv[])
{
foo(argc-1);
return 0;
}
#包括
静态void foo(void)
{
int i;
asm挥发性(“mov 16(%%rbp),%0”:“=g”(i));
printf(“%d\n”,i);
}
#定义foo(x)({int_i=(x))\
asm(“推送%0\n调用%P1\n添加$8,%%rsp\n):“g”(_i),“i”(foo));})
int main(int argc,char*argv[])
{
foo(argc-1);
返回0;
}
这里,在堆栈上推送一个整数,并调用函数foo。foo使该值在其局部变量i中可用。返回后,堆栈指针将调整回其原始值。这将中断PCS/ABI。你从错误的方向靠近。汇编代码必须遵循ABI。最好是直接将C函数与内联汇编程序结合使用,或者将其作为实际代码的包装。这样,您就可以指定哪些寄存器/内存是您的代码阻塞器,并将保存/恢复留给gcc。setjmp
不需要保存rdi
和rsi
——正如您所说,它们在setjmp
中被阻塞,那么为什么要保存它们呢?事实上,只有标记为“被调用方已保存”的寄存器(即rbp、ebx、r12、r13、r14、r15,当然还有rsp)必须保留。我同意我不尊重ABI,但这是有原因的。使用setjmp
和longjmp
一切正常,因为setjmp
未保存的内容实际上由调用方保存,以备需要,因为它们是调用方保存寄存器。在我的应用程序中,我与Linux内核进行了交互,在给定的中断时,Linux内核将控制返回到用户级代码的不同部分,而用户级代码又调用setjmp
。通过这种构造,最初执行的代码不知道正在调用setjmp
,因此也应该保存调用方保存寄存器。C标准将setjmp
定义为宏,因此如果使用宏,则不受任何调用约定的约束。因为您似乎无论如何都在使用gcc,所以可以通过使用类似于({exec\u context\u t*env=(env);…})
的东西来检查参数。您可以使用GNU C函数属性来指定函数使用的ABI。不过,我认为x86-64没有堆栈调用ABI,因此可能没有任何属性可以满足您的需要。我使用C++从Windows或Linux调用一个手写ASM函数(通过声明函数使用Sysv调用约定,即使不是默认调用约定)。因此,如果没有最新的gcc,您将得到不正确的代码:如果调用函数main()
没有将%rbp
用作帧指针,而是将其用作额外的整数寄存器,或者根本不使用它以避免推送/弹出开销,那么这当然会非常糟糕。如果您使用-O2
@NateEldredge编译此代码,则实际会发生后者:只有foo
的设计很糟糕,但它只是他的示例的一个占位符。生成自定义ABI调用的宏看起来不错。OP将使用用ASM手工编写的自定义setjmp
实现,它不会做出任何这些假设。它首先按下一些寄存器来获取一些临时寄存器,然后保存所有调用方的状态。IDK ifxsave
在用户空间中运行良好。该asm语句不安全:。您需要在推送之前添加$128,%rsp
,然后在推送之后添加$128+8。对于asm函数具有的任何自定义调用约定,您通常还需要在所有未保留调用的寄存器上声明clobber。e、 g.RAX、RCX、RDX、RDI、RSI、R8-R11、xmm0-15(或带有AVX-512的0-31)和x87 st0..7。