原因是由于可变长参数。
如今GCC已经支持按声明顺序进栈,这里分析传统的函数调用入栈。
前提
了解函数调用,所涉及栈帧分配,见下图1:
这个图2也有意思,把函数调用之间的关系展示了出来。
理论分析
假设现在有函数f,固定参数m个,可变参数n(未知)个,假设所有参数都是32位整数,如果不是整数,也可以根据参数类型,推出参数地址,为了简单画图,在此使用整数。
自右向左(逆变量声明顺序)
那么调用栈图3:
虽然不知道可变参数n的大小,但是,依然可以根据固定参数的大小m,找到可变参数的开始位置,然后去访问就可以了。至于访问第n+1个可变参数,会造成什么问题,我们一会讨论。
自左向右(按变量声明顺序)
那么调用栈图4:
在只知道ebp的情况下,而不知道可变参数n的大小,不能确定可变参数和参数的分解,怎么确定参数1,参数m的地址呢?不确定他们怎么能访问呢?真相就是这样子,你无法确定各参数的地址。
如何实现自左向右入栈
使用另外的寄存器指向参数1。那么就可以访问固定参数,已经可变参数的区域也就确定了。
自右向左访问参数超过范围
未定义。
以printf为例:
printf("%d %d\n", 1);
这条语句输出另个整数,但可变参数只给了一个整数1,那么访问第二个整数的时候必然也就“越界”了。
假设1的地址为ptr,那么会把(char *)ptr+sizeof(int),这个地址上的数据解析为一个整数输出。如果这个区域是它不能访问的,还会造成非法访问,如果可以访问,数据也是错误的。