x64下的可变数量的参数WITHOUT <stdarg.h>

时间:2015-06-08 03:39:24

标签: c++ abi

可以使用var_list中的stdarg.h来完成变量数参数,但如何在64位环境中使用stdarg.h来实现这一点。

在32位环境中,函数参数使用堆栈从右到左传递,并且是4字节对齐的。因此可以使用地址访问变量参数。

void func(int arg1, ...) {
    int arg2 = *(&arg1 + 1);
    int arg3 = *(&arg1 + 2);
}

但是,在64位环境(System V ABI)中,使用寄存器传递参数。如果寄存器不能保存所有寄存器,则额外的使用堆栈传递。

所以我想知道如何在64位环境下访问没有stdarg.h的所有参数。感谢。

修改

我正在编写自定义内核,因此无法使用标准C库。

stdarg.hva_startva_endva_arg定义为内置类型,它们似乎是特定于编译器的功能或扩展:

...
#define va_start(ap, param) __builtin_va_start(ap, param)
#define va_end(ap)          __builtin_va_end(ap)
#define va_arg(ap, type)    __builtin_va_arg(ap, type)
...

2 个答案:

答案 0 :(得分:0)

您可以使用足够的参数定义函数,因此至少有一个在堆栈上传递:

void func(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7 ...) {
    int64_t* stack = reinterpret_cast<int64_t*>(&arg7);
    int arg8 = (int)stack[1];
    int arg9 = (int)stack[2];
}

请注意,堆栈上的地址以8为步长递增(&#34;自然&#34;用于访问堆栈的类型sizeof等于8)。

答案 1 :(得分:0)

在实现libc syscall函数的玩具版本时,我最近遇到了这个问题。如果可移植性不是问题,您总是可以使用程序集来执行此操作。以下是我的表现方式:

ssize_t syscall(size_t sysno, ...) {
  // The x86_64 calling convention uses rdi, rsi, rdx, rcx, r8 and r9 as args
  // The kernel interface uses rdi, rsi, rdx, r10, r8 and r9 for arg and rax for sysno

  asm ("mov %%rdi,%%rax;" // System call number (sysno)
       "mov %%rsi,%%rdi;" // first syscall arg and va_arg
       "mov %%rdx,%%rsi;" // second syscall arg and va_arg
       "mov %%rcx,%%rdx;" // third syscall arg and va_arg
       "mov %%r8,%%r10;"  // fourth syscall arg and va_arg
       "mov %%r9,%%r8;"   // fifth syscall arg and va_arg
       "syscall;"
       :
       :
       :

  register ssize_t ret asm("rax");
  return ret;
}

就我而言,我只是在汇编中编写了整个函数,但你可以做一些不那么极端的事情,例如:

register long arg1 asm("rdi");
register long arg2 asm("rsi");
...