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

时间:2011-03-11 12:08:38

标签: assembly printf variadic

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

参数的数量显然不会作为(隐藏)参数传递(参见call to printf in asm example here)。

诀窍是什么?

4 个答案:

答案 0 :(得分:10)

诀窍在于你以其他方式告诉他们。对于printf,您必须提供甚至包含类型信息的格式字符串(尽管可能不正确)。提供此信息的方式主要是用户合同,而且往往容易出错。

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

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

答案 1 :(得分:9)

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

答案 2 :(得分:7)

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

如果你打电话:

printf("%s %s", foo, bar);

堆栈最终如下:

  ...
+-------------------+
| bar               |
+-------------------+
| foo               |
+-------------------+
| "%s %s"           |
+-------------------+
| return address    |
+-------------------+
| old frame pointer | <- frame pointer
+-------------------+
  ...

使用从帧指针的偏移量间接加入参数(知道如何从堆栈指针计算内容的智能编译器可以省略帧指针)。第一个参数始终位于此方案中的一个众所周知的地址,该函数访问与其第一个参数相同的参数。

尝试以下方法:

printf("%x %x %x %x %x %x\n");

这会转储部分堆栈。

答案 3 :(得分:3)

  • AMD64 System V ABI(Linux,Mac OS X)确实传递rax中的数字向量(SEE / AVX)变量,与IA-32不同。另见:Why is %eax zeroed before a call to printf?

    TODO为什么需要这个?我认为只是出于性能原因,避免将不需要的SSE寄存器保存到“3.5.7变量参数列表”中提到的“寄存器保存区”。

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

    • 传递一个标记(void *)0来表示execl之类的最后一个参数。

      您需要使用sentinel函数属性来帮助GCC在编译时强制执行该操作:C warning Missing sentinel in function call

    • 将其作为额外的整数参数传递,其数量为varargs

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