一. 函数调用过程:
1. 32位传参是通过栈来完成的 在调用之前 将参数放在栈顶 然后再执行call指令.
2. 相对于32位传参, 对于x86_64位寄存器, 字母”r”用作名称前缀,即指示各寄存器大小为64位。形数是通过寄存器来传递 一般使用寄存器 esi edi, 此时,编译器无须再为形参分配栈空间。
3. 返回值都是用eax来传递。
4. 示例一个有意思的小程序:
程序代码请查阅funPointer2.c
基本思路: 在main函数中调用plus函数,正常情况下,plus函数执行完成之后就会通过return返回到主函数中去(这里的return实质是leav ret两步来完成,其中leav做了如下内容:A. mov ebp,esp B. pop ebp(当然 这里esp也就自加了1))。这里我们通过对栈做一些小的修改,使我们的plus不再返回到主函数中去,而去执行test函数。如果只这样做的话,还不够,再通过修改test中栈的内容,使我们程序在test当中返回到main函数中去。
5. 通过以上的例子,ebp 和 esp的配合对栈的操作使我们函数的调用得以无误执行。当然这里的修改已经使原来的栈减小,因为相对正常的调用,少了一次压栈过程或者说多了一次弹栈的过程。
二. gdb的使用观察:
1. gcc -g funPointer2.c -0 funPointer2 ;加入调试信息
2. gdbfunPointer2 ;正式开始调试
这里停到了下一个函数调用前。
main函数中程序执行到这,实际已经完成了对所有变量的栈空间的分配,在一个函数体内,编译器对栈空间的分配是一次性完成的。我们记录一下在main函数当中栈顶esp的值:
info registers eip esp
得到 esp : 0xbffff1e0
当然,我这里顺便把eip值看了一下,也就知道了在main函数中执行调用plus之后的下一条指令的地址,0x0804843a
发现了没有, 我们定义的两个全局变量的地址分别为:
p: 0x804a020
pOfaddr: 0x804a024
保存了下一条指令地址入栈,程序由main函数转移到plus函数中时,首先将ebp入栈。此时我们的esp已经减小两次4字节,把上次mian中的栈顶+4字节的地址即保存了返回main地址。这里打印看了一下,是0x0804843a
将返回main的地址保存在了全局变量p中,以便我们修改test中的栈,使test执行完返回到main中。
leav ret一执行 我们修改的地址就被弹出,从而执行我们的修改的地址,程序转到test中去。
在这里做的操作是:首先是将esp打印了一下,可以证实 栈的内容已经恢复到了main调用之前的情况。
test函数中在此基础上,对栈做了如下操作:
首先明确,由于我们不是通过函数调用call来转到的test函数中去,因此地址并未压入到栈中去。
其次,和其他函数调用相同,首先将ebp压栈,然后分配栈空间。当然,因为这是最后一个被调用的函数,程序无需再返回,栈顶完全可以不再减小(分配栈空间一般是指栈顶减小,作为最后一个返回函数情况特殊在这一点)。
这里我们已经将保存在全局变量p中main的返回地址赋给了test函数返回时要弹出的栈。因此当test执行完,返回时,栈中地址被弹出,程序转回到main程序中,接着执行。这里修改的栈地址是:0xbffff1e0 = 0x0804843a;
对比一下与正常情况下栈的不同,在main中的栈空间小了一个,即栈顶地址高了4。但由于栈空间减小的是传参的参数,因此不会对main函数造成任何的影响(当然这里是在32位下!)。
源代码参考在下一篇文章中。
说明:以上内容均为原创,转载请标明来源或联系作者。