今天突发奇想想要研究一下C语言的函数调用.
因为以前有个课程设计涉及到C语言函数返回地址的问题,今天看Thinking in C++的时候,看到类似的东西...就想要验证一下.
编译器:gcc4.0.1 on darwin
操作系统:Mac OS X 10.5.8
编辑器:MacVim :)
首先上C语言源程序
#include <stdio.h> void a(int b); int main() { int x = 0x11223344; printf("&x == %p\n", &x); a(x); x = 1; printf("x == %d\n", x); return 0; } void a(int b){ int c = 0x11223344; void *p = &c; int i = 0; printf("&b = %p\n", &b); printf("&c = %p\n", &c); printf("%d\n", &b - &c); for(i = 0; i < 32; ++i){ printf("@%p = %x\n", p + i, *(char * )(p + i)); } }
用gcc -g 编译以后..用gdb进行调试
首先看生成的汇编代码..
输入disas main查看main函数汇编
0x00001ec2 <main+0>: push %ebp 0x00001ec3 <main+1>: mov %esp,%ebp 0x00001ec5 <main+3>: push %ebx 0x00001ec6 <main+4>: sub $0x24,%esp 0x00001ec9 <main+7>: call 0x1ece <main+12> 0x00001ece <main+12>: pop %ebx 0x00001ecf <main+13>: movl $0x11223344,-0xc(%ebp) //注意点1 0x00001ed6 <main+20>: lea -0xc(%ebp),%eax 0x00001ed9 <main+23>: mov %eax,0x4(%esp) 0x00001edd <main+27>: lea 0xfd(%ebx),%eax 0x00001ee3 <main+33>: mov %eax,(%esp) 0x00001ee6 <main+36>: call 0x3005 <dyld_stub_printf> 0x00001eeb <main+41>: mov -0xc(%ebp),%eax //注意点2 0x00001eee <main+44>: mov %eax,(%esp) 0x00001ef1 <main+47>: call 0x1f1d <a> 0x00001ef6 <main+52>: movl $0x1,-0xc(%ebp)//注意点3 0x00001efd <main+59>: mov -0xc(%ebp),%eax 0x00001f00 <main+62>: mov %eax,0x4(%esp) 0x00001f04 <main+66>: lea 0x107(%ebx),%eax 0x00001f0a <main+72>: mov %eax,(%esp) 0x00001f0d <main+75>: call 0x3005 <dyld_stub_printf> 0x00001f12 <main+80>: mov $0x0,%eax 0x00001f17 <main+85>: add $0x24,%esp 0x00001f1a <main+88>: pop %ebx 0x00001f1b <main+89>: leave 0x00001f1c <main+90>: ret
然后输入disas a查看a函数的汇编代码
0x00001f1d <a+0>: push %ebp //注意点4 0x00001f1e <a+1>: mov %esp,%ebp 0x00001f20 <a+3>: push %ebx 0x00001f21 <a+4>: sub $0x24,%esp 0x00001f24 <a+7>: call 0x1f29 <a+12> 0x00001f29 <a+12>: pop %ebx 0x00001f2a <a+13>: movl $0x55667788,-0x14(%ebp) 0x00001f31 <a+20>: lea -0x14(%ebp),%eax 0x00001f34 <a+23>: mov %eax,-0x10(%ebp) 0x00001f37 <a+26>: movl $0x0,-0xc(%ebp) 0x00001f3e <a+33>: lea 0x8(%ebp),%eax 0x00001f41 <a+36>: mov %eax,0x4(%esp) 0x00001f45 <a+40>: lea 0xb5(%ebx),%eax 0x00001f4b <a+46>: mov %eax,(%esp) 0x00001f4e <a+49>: call 0x3005 <dyld_stub_printf> 0x00001f53 <a+54>: lea -0x14(%ebp),%eax 0x00001f56 <a+57>: mov %eax,0x4(%esp) 0x00001f5a <a+61>: lea 0xbe(%ebx),%eax 0x00001f60 <a+67>: mov %eax,(%esp) 0x00001f63 <a+70>: call 0x3005 <dyld_stub_printf> 0x00001f68 <a+75>: lea 0x8(%ebp),%edx 0x00001f6b <a+78>: lea -0x14(%ebp),%eax 0x00001f6e <a+81>: mov %edx,%ecx 0x00001f70 <a+83>: sub %eax,%ecx 0x00001f72 <a+85>: mov %ecx,%eax 0x00001f74 <a+87>: sar $0x2,%eax 0x00001f77 <a+90>: mov %eax,0x4(%esp) 0x00001f7b <a+94>: lea 0xc7(%ebx),%eax 0x00001f81 <a+100>: mov %eax,(%esp) 0x00001f84 <a+103>: call 0x3005 <dyld_stub_printf> 0x00001f89 <a+108>: movl $0x0,-0xc(%ebp) 0x00001f90 <a+115>: jmp 0x1fbf <a+162> 0x00001f92 <a+117>: mov -0xc(%ebp),%eax 0x00001f95 <a+120>: add -0x10(%ebp),%eax 0x00001f98 <a+123>: movzbl (%eax),%eax 0x00001f9b <a+126>: movsbl %al,%edx 0x00001f9e <a+129>: mov -0xc(%ebp),%eax 0x00001fa1 <a+132>: add -0x10(%ebp),%eax 0x00001fa4 <a+135>: mov %edx,0x8(%esp) 0x00001fa8 <a+139>: mov %eax,0x4(%esp) 0x00001fac <a+143>: lea 0xcb(%ebx),%eax 0x00001fb2 <a+149>: mov %eax,(%esp) 0x00001fb5 <a+152>: call 0x3005 <dyld_stub_printf> 0x00001fba <a+157>: lea -0xc(%ebp),%eax 0x00001fbd <a+160>: incl (%eax) 0x00001fbf <a+162>: cmpl $0x1f,-0xc(%ebp) 0x00001fc3 <a+166>: jle 0x1f92 <a+117> 0x00001fc5 <a+168>: add $0x24,%esp 0x00001fc8 <a+171>: pop %ebx 0x00001fc9 <a+172>: leave 0x00001fca <a+173>: ret
果然和书上写的一样..从右往左压参数,再压返回值,再压局部变量。
下面解释一下汇编代码里面的注意点...
注意点1:因为x是main函数的局部变量..所以需要将局部变量压栈.注意压栈的地址是%ebp - c..因为unix下面的栈是从高地址往低地址的方向延伸的.所以用减
注意点2:把a函数的参数压到栈里面...可以想象,在传值过程中,值会被复制一份到栈里面..那么对象也会重新生成一个在栈里面..难怪要搞拷贝构造函数了....这里一个疑问是为什么不用push?
注意点3:这个时候下一条命令的地址是0x00001ef6
注意点4:把栈基址推到栈里面...这里是一个关键哦...
ok...接着按l显示源代码..然后输入 b 8在第8行做了一个断点...
按r执行函数...
到断点的时候..输入i reg看一下寄存器..
esp 0xbffff630 0xbffff630 ebp 0xbffff658 0xbffff658
ok...按s进入函数
再按一下n 执行那条int c的声明语句.
这时候看一下寄存器
esp 0xbffff600 0xbffff600 ebp 0xbffff628 0xbffff628
有变化了..
输入x/20x $esp查看一下esp后面的地址内容
0xbffff600: 0xa054e638 0xa054ade0 0x00001fcb 0xbffff634 0xbffff610: 0x00000004 0x55667788 0xbffff678 0xbffff634 0xbffff620: 0x00001ece 0x00001ece 0xbffff658 0x00001ef6 0xbffff630: 0x11223344 0xbffff64c 0x8fe0154b 0x00001000 0xbffff640: 0x00000000 0x00000000 0xbffff66c 0x11223344
ok..看见我们可爱的 11223344 和55667788了..
而11223344下面的一个地址内容..果然是注意3里面提到的地址..
而注意4里面的..也再他恰当的位置出现了.....
唯一还存在疑问的地方就是....
我们的55667788并没有按照书上所说的..紧紧跟着原来老的$ebp的值后面出现....
中间多了16个字节奇怪的数字...
究竟这些数字是做什么用的....
如果有人知道..可以回帖....
我也会查资料去了解的....
恩...C语言还是很奇妙的东东