变量参数列表的使用是“C”语言的标准特性,因此必须在任何存在 C 编译器的机器上强制执行。
当我们说任何机器时,我们的意思是独立于用于参数传递、寄存器、堆栈或两者的方式,我们必须具有该功能。
实际上,实现功能真正需要的是过程的确定性。参数是否以堆栈、寄存器、两者或其他 MCU 自定义方式传递无关紧要,重要的是其完成方式定义明确且始终相同。
如果尊重此属性,我们确信我们始终可以遍历参数列表,并访问它们中的每一个。
实际上,用于为每台机器或系统传递参数的方法,在ABI(A应用程序Binary I接口,见https://en.wikipedia.org/wiki/Application_binary_interface),遵循规则,反之,可以随时回溯参数。
无论如何,在大多数系统上,ABI 的简单逆向工程不足以恢复参数,即与标准 CPU 寄存器/堆栈大小不同的参数大小,在这种情况下,您需要有关您的参数的更多信息正在寻找:操作数大小。
让我们回顾一下 C 中的可变参数处理。首先,您声明一个具有单个整数类型参数的函数,保存作为可变参数传递的参数的计数,以及可变部分的 3 个点:
int foo(int cnt, ...);
要正常访问变量参数,您可以按以下方式使用 <stdarg.h> 标头中的定义:
int foo(int cnt, ...)
{
va_list ap; //pointer used to iterate through parameters
int i, val;
va_start(ap, cnt); //Initialize pointer to the last known parameter
for (i=0; i<cnt; i++)
{
val = va_arg(ap, int); //Retrieve next parameter using pointer and size
printf("%d ", val); // Print parameter, an integer
}
va_end(ap); //Release pointer. Normally do_nothing
putchar('\n');
}
在基于堆栈的机器(即 x86-32 位)上,参数按顺序推送,上面的代码或多或少如下工作:
int foo(int cnt, ...)
{
char *ap; //pointer used to iterate through parameters
int i, val;
ap = &cnt; //Initialize pointer to the last known parameter
for (i=0; i<cnt; i++)
{
/*
* We are going to update pointer to next parameter on the stack.
* Please note that here we simply add int size to pointer because
* normally the stack word size is the same of natural integer for
* that machine, but if we are using different type we **must**
* adjust pointer to the correct stack bound by rounding to the
* larger multiply size.
*/
ap = (ap + sizeof(int));
val = *((int *)ap); //Retrieve next parameter using pointer and size
printf("%d ", val); // Print parameter, an integer
}
putchar('\n');
}
请注意,如果我们访问不同于int e/o 且大小与本机堆栈字长不同的类型,必须调整指针以始终增加堆栈字长的倍数。
现在考虑一台使用寄存器传递参数的机器,为简单起见,我们认为任何操作数都不能大于寄存器大小,并且分配是使用寄存器顺序进行的(另请注意伪汇编指令mov val, rx用寄存器rx的内容加载变量val):
int foo(int cnt, ...)
{
int ap; //pointer used to iterate through parameters
int i, val;
/*
* Initialize pointer to the last known parameter, in our
* case the first in the list (see after why)
*/
ap = 1;
for (i=0; i<cnt; i++)
{
/*
* Retrieve next parameter
* The code below obviously isn't real code, but should give the idea.
*/
ap++; //Next parameter
switch(ap)
{
case 1:
__asm mov val, r1; //Get value from register
break;
case 2:
__asm mov val, r2;
break;
case 3:
__asm mov val, r3;
break;
.....
case n:
__asm mov val, rn;
break;
}
printf("%d ", val); // Print parameter, an integer
}
putchar('\n');
}
希望这个概念现在已经足够清楚了。