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 leaveqleave指令其实就相当与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
至此,整个函数的调用分析基本就结束了。