vararg函数如何找出机器码中的参数数量?

mas*_*ilo 11 assembly printf variadic abi calling-convention

printf这样的可变函数如何找出它们得到的参数数量?

显然,参数的数量不会作为(隐藏)参数传递(请参阅asm示例中的printf调用).

有什么诀窍?

col*_*fix 12

诀窍是你以其他方式告诉他们.因为printf你必须提供一个甚至包含类型信息的格式字符串(尽管可能不正确).提供此信息的方式主要是用户合同,并且通常容易出错.

至于调用约定:通常将参数从左到右推入堆栈,然后最后推送到backjump地址.调用例程清除堆栈.因此,调用例程不需要知道参数的数量.

编辑:在C++ 0x中有一种安全的方式(甚至类型安全!)来调用可变参数函数!


Ruu*_*oot 9

隐含地,来自格式字符串.请注意,stdarg.h不包含任何宏来检索传递的参数总数"变量".这也是C调用约定要求调用者清理堆栈的原因之一,即使这会增加代码大小.


nin*_*alj 9

这就是为什么在C调用约定上按相反顺序推送参数的原因,例如:

如果你打电话:

printf("%s %s", foo, bar);
Run Code Online (Sandbox Code Playgroud)

堆栈最终如下:

  ...
+-------------------+
| bar               |
+-------------------+
| foo               |
+-------------------+
| "%s %s"           |
+-------------------+
| return address    |
+-------------------+
| old frame pointer | <- frame pointer
+-------------------+
  ...
Run Code Online (Sandbox Code Playgroud)

使用其与帧指针的偏移量间接地接受参数(知道如何从堆栈指针计算事物的智能编译器可以省略帧指针).第一个参数始终位于此方案中的一个众所周知的地址,该函数访问尽可能多的参数,因为它的第一个参数告诉它.

请尝试以下方法:

printf("%x %x %x %x %x %x\n");
Run Code Online (Sandbox Code Playgroud)

这将转储部分堆栈.


Cir*_*四事件 5

  • 与任何标准IA-32调用约定不同,AMD64 System V ABI(Linux,Mac OS X)确实在al(RAX的低字节)中传递了数字矢量(SSE / AVX)变量参数。另请参阅:为什么在调用printf之前将%eax调零?

    但最多只能有8个(要使用的最大寄存器数)。和IIRC的ABI允许al更大的比XMM / YMM / ZMM args来实际数目,但一定不能少。因此,它通常不会总是告诉您FP args的数量。您无法知道数量多于8个,并且al可以超算。

    仅出于性能考虑,可以跳过将不需要的向量寄存器保存到“ 3.5.7变量参数列表”中提到的“寄存器保存区域”的操作。例如,GCC进行测试al!=0,然后将XMM0..7转储到堆栈中,或者什么都没有。(或者,如果该函数VA_ARG__m256任何地方一起使用,则为YMM0..7。)

  • 在C级别上,除了解析其他人提到的格式字符串外,还有其他技术。您还可以:

    • 传递哨兵(void *)0以指示最后一个参数,例如execl一样。

      您将要使用sentinelfunction属性来帮助GCC在编译时执行该操作:C警告函数调用中缺少前哨

    • 将其作为带有可变参数个数的额外整数参数传递

    • 使用formatfunction属性来帮助GCC强制执行已知类型的格式字符串,例如printfstrftime

相关:如何在gcc中实现变量参数?