C语言函数调用的汇编实现

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

至此,整个函数的调用分析基本就结束了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值