int fun2()
{
return 2;
}
int fun1(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k)
{
int aa;
int bb;
aa = 0x11;
bb = 0x22;
fun2();
aa = h;
bb = i;
return 1;
}
int main()
{
fun1(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
return 0;
}
00000000004004b4 <fun2>:
4004b4: 55 push %rbp
4004b5: 48 89 e5 mov %rsp,%rbp
4004b8: b8 02 00 00 00 mov $0x2,%eax
4004bd: 5d pop %rbp
4004be: c3 retq
00000000004004bf <fun1>:
4004bf: 55 push %rbp
4004c0: 48 89 e5 mov %rsp,%rbp
4004c3: 48 83 ec 28 sub $0x28,%rsp
4004c7: 89 7d ec mov %edi,-0x14(%rbp)
4004ca: 89 75 e8 mov %esi,-0x18(%rbp)
4004cd: 89 55 e4 mov %edx,-0x1c(%rbp)
4004d0: 89 4d e0 mov %ecx,-0x20(%rbp)
4004d3: 44 89 45 dc mov %r8d,-0x24(%rbp)
4004d7: 44 89 4d d8 mov %r9d,-0x28(%rbp)
4004db: c7 45 f8 11 00 00 00 movl $0x11,-0x8(%rbp)
4004e2: c7 45 fc 22 00 00 00 movl $0x22,-0x4(%rbp)
4004e9: b8 00 00 00 00 mov $0x0,%eax
4004ee: e8 c1 ff ff ff callq 4004b4 <fun2>
4004f3: 8b 45 18 mov 0x18(%rbp),%eax
4004f6: 89 45 f8 mov %eax,-0x8(%rbp)
4004f9: 8b 45 20 mov 0x20(%rbp),%eax
4004fc: 89 45 fc mov %eax,-0x4(%rbp)
4004ff: b8 01 00 00 00 mov $0x1,%eax
400504: c9 leaveq
400505: c3 retq
0000000000400506 <main>:
400506: 55 push %rbp
400507: 48 89 e5 mov %rsp,%rbp
40050a: 48 83 ec 28 sub $0x28,%rsp
40050e: c7 44 24 20 0b 00 00 movl $0xb,0x20(%rsp)
400515: 00
400516: c7 44 24 18 0a 00 00 movl $0xa,0x18(%rsp)
40051d: 00
40051e: c7 44 24 10 09 00 00 movl $0x9,0x10(%rsp)
400525: 00
400526: c7 44 24 08 08 00 00 movl $0x8,0x8(%rsp)
40052d: 00
40052e: c7 04 24 07 00 00 00 movl $0x7,(%rsp)
400535: 41 b9 06 00 00 00 mov $0x6,%r9d
40053b: 41 b8 05 00 00 00 mov $0x5,%r8d
400541: b9 04 00 00 00 mov $0x4,%ecx
400546: ba 03 00 00 00 mov $0x3,%edx
40054b: be 02 00 00 00 mov $0x2,%esi
400550: bf 01 00 00 00 mov $0x1,%edi
400555: e8 65 ff ff ff callq 4004bf <fun1>
40055a: b8 00 00 00 00 mov $0x0,%eax
40055f: c9 leaveq
400560: c3 retq
以上是在ubuntu12.04 + gcc4.6.3 + intel i7上测试的C代码和反汇编结果。
fun1函数之所以设置那么多的参数,主要是因为传递参数在汇编中有两种不同的方法。
一种是用寄存器传递,一种是压栈传递。
当参数数量比较少时,编译器就可能完全使用寄存器传递的方法。
因此设置了这些参数,可以保证编译器必须会用到压栈的传递方法。
我们先看main函数:
400535: 41 b9 06 00 00 00 mov $0x6,%r9d
40053b: 41 b8 05 00 00 00 mov $0x5,%r8d
400541: b9 04 00 00 00 mov $0x4,%ecx
400546: ba 03 00 00 00 mov $0x3,%edx
40054b: be 02 00 00 00 mov $0x2,%esi
400550: bf 01 00 00 00 mov $0x1,%edi
从这几句汇编可以看出,前6个参数是通过寄存器传递的。
40050a: 48 83 ec 28 sub $0x28,%rsp
40050e: c7 44 24 20 0b 00 00 movl $0xb,0x20(%rsp)
400515: 00
400516: c7 44 24 18 0a 00 00 movl $0xa,0x18(%rsp)
40051d: 00
40051e: c7 44 24 10 09 00 00 movl $0x9,0x10(%rsp)
400525: 00
400526: c7 44 24 08 08 00 00 movl $0x8,0x8(%rsp)
40052d: 00
40052e: c7 04 24 07 00 00 00 movl $0x7,(%rsp)
而后5个则是通过压栈传递的。
但是这里并没有用到push指令,而是通过mov把变量写进堆栈。
因为前面那句
40050a: 48 83 ec 28 sub $0x28,%rsp
已经把这些变量的空间都预留出来了,所以可以直接写进去了。
参数都准备好了,就可以调用函数fun1了。
400555: e8 65 ff ff ff callq 4004bf <fun1>
call这个指令,其实隐藏着一个压栈动作.
压进去的是0x40055a,也就是call指令的下一条指令的地址。
作用就是为了调用完fun1之后回到main函数继续执行。
要执行的指令自然就是call指令的顺序下一条指令了。
进入fun1之后,首先出现的是
4004bf: 55 push %rbp
4004c0: 48 89 e5 mov %rsp,%rbp
这两句汇编基本出现在所有的函数的开头,
作用就是把bp保存起来,再把bp设置成sp,这样的话,sp在后面就能够随意的修改了。
直到函数的末尾,把bp赋值给sp,再pop出bp不就解决了。
事实上,fun1里也确实是这么实现的,只是它用了一条更简洁的指令
400504: c9 leaveq
leave指令其实就相当与bp->sp, pop bp这两句的作用。
那这样之后,堆栈不就和刚进去函数时一样了啊。
最后再通过
400505: c3 retq
把刚才call压栈的返回地址pop到eip寄存器,就此就回到了main函数。
让我们继续看fun1的实现
4004c3: 48 83 ec 28 sub $0x28,%rsp
4004c7: 89 7d ec mov %edi,-0x14(%rbp)
4004ca: 89 75 e8 mov %esi,-0x18(%rbp)
4004cd: 89 55 e4 mov %edx,-0x1c(%rbp)
4004d0: 89 4d e0 mov %ecx,-0x20(%rbp)
4004d3: 44 89 45 dc mov %r8d,-0x24(%rbp)
4004d7: 44 89 4d d8 mov %r9d,-0x28(%rbp)
4004db: c7 45 f8 11 00 00 00 movl $0x11,-0x8(%rbp)
4004e2: c7 45 fc 22 00 00 00 movl $0x22,-0x4(%rbp)
函数内的零时变量aa,bb被存放在堆栈的偏上位置,而后是存放刚才用寄存器传递进来的其他变量。
但是可以发现,-0xc(%rbp)和-0x10(%rbp)并没有被用到,直接就被跳掉了,有点奇怪。
之后是调用fun2函数
4004ee: e8 c1 ff ff ff callq 4004b4 <fun2>
接下来的两句赋值语句则被翻译成
4004f3: 8b 45 18 mov 0x18(%rbp),%eax
4004f6: 89 45 f8 mov %eax,-0x8(%rbp)
4004f9: 8b 45 20 mov 0x20(%rbp),%eax
4004fc: 89 45 fc mov %eax,-0x4(%rbp)
变量h是通过压栈传递进来的,所以需要通过ebp来寻找。
0x20(%rbp) = i
0x18(%rbp) = h
0x10(%rbp) = g
0x8(%rbp) = 返回地址0x40055a
0x0(%rbp) = bp
然后把函数返回值放入eax寄存器中,就可以返回了。
4004ff: b8 01 00 00 00 mov $0x1,%eax
至此,整个函数的调用分析基本就结束了。