ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

C&Golang函数调用过程详解(二)

2021-10-09 14:58:40  阅读:163  来源: 互联网

标签:sum mov 函数调用 rbp Golang eax 详解 指令 main


上篇文章聊到在main中执行了调用sum函数的call指令。

这时CPU跳到sum开始执行如下命令:

0x0000000000400526 <+0>:push   %rbp          0x0000000000400527 <+1>:mov   %rsp,%rbp 0x000000000040052a <+4>:mov   %edi,-0x14(%rbp)  0x000000000040052d <+7>:mov   %esi,-0x18(%rbp)  0x0000000000400530 <+10>:mov   -0x14(%rbp),%edx0x0000000000400533 <+13>:mov   -0x18(%rbp),%eax0x0000000000400536 <+16>:add   %edx,%eax0x0000000000400538 <+18>:mov   %eax,-0x4(%rbp)0x000000000040053b <+21>:mov   -0x4(%rbp),%eax0x000000000040053e <+24>:pop   %rbp0x000000000040053f <+25>:retq

sum前两条指令与main的一样。

0x0000000000400526 <+0>:push   %rbp            # sum函数序言,保存调用者的rbp0x0000000000400527 <+1>:mov   %rsp,%rbp   # sum函数序言,调整rbp寄存器指向自己的栈帧起始位置

它们都是在保存调用者的rbp然后设置新值来指向当前函数栈帧起始地址,这时sum保存了main的rbp的值(0x7fffffffe510),并将rbp的值修改为sum自己的栈帧的起始位置(0x7fffffffe4e0)。

通过上述指令可以看到,sum的函数序言并没有像main的序言一样,通过调整rsp的值,给sum的局部变量和临时变量预留栈空间。

这是不是说明sum没有使用栈来存储局部变量呢?

从后文的分析中可以看到,sum局部变量s还是存在栈上的,没有预留也可以使用的原因之前也提到过,栈上的内存不需要在应用层代码中进行分配,操作系统已经分配好了,直接使用就可以了。

main之所以还要调整rsp的值来预留局部变量和临时变量使用的栈空间,是因为main还需要使用call调用sum,而call会自动将rsp的值减去8,然后将函数的返回地址存到rsp所指的栈内存位置,如果main不调整rsp的值,则call保存函数返回地址的值时就会覆盖main的局部变量或临时变量的值,而sum中没有任何指令会自动使用rsp来保存数据到栈上,所以不需要调整rsp的值。

看下紧接着执行的四条指令。

0x000000000040052a <+4>:mov   %edi,-0x14(%rbp)  # 把第1个参数a放入临时变量0x000000000040052d <+7>:mov   %esi,-0x18(%rbp)  # 把第2个参数b放入临时变量0x0000000000400530 <+10>:mov   -0x14(%rbp),%edx # 从临时变量中读取第1个到edx寄存器0x0000000000400533 <+13>:mov   -0x18(%rbp),%eax # 从临时变量中读取第2个到eax寄存器

上述指令通过rbp加偏移量的方式将main传递给sum的两个参数保存在当前栈帧的合适位置,然后又取出来放入寄存器,看着有点儿多此一举,这是因为在编译时未给gcc指定优化级别,而gcc编译程序时,默认不做任何优化,所以看起来比较啰嗦。

再来看紧接着的三条指令。

0x0000000000400536 <+16>:add   %edx,%eax            # 执行a + b并把结果保存到eax寄存器0x0000000000400538 <+18>:mov   %eax,-0x4(%rbp)  # 把加法结果赋值给变量s0x000000000040053b <+21>:mov   -0x4(%rbp),%eax  # 读取s变量的值到eax寄存器

上述第一条指令负责执行加法运算并将并将结果存入eax中,第二条指令将eax中的值存入局部变量s所在的内存,第三条指令将局部变量s的值读取到eax中,可以看到,局部变量s被编译器安排到了rbp  -0x4这个地址对应的内存中。

到这里,sum主要功能已运行完毕,来看下当前栈和寄存器的状态图:

需要说明的是,sum的两个参数和返回值都是int,在内存中只占4个字节,而图中每个栈内存单元都是8个字节且按8字节地址边界进行了对齐,所以才是上图这个样子。

接下来继续执行pop %rbp这个指令,它包含以下两个操作:

  1. 将当前rsp所指的栈内存中的值放到rbp,如此rbp就恢复到未执行sum的第一条指令时的值,也就是重新指向了main栈帧的起始位置。

  2. 将rsp的值加8,如此rsp就指向了包含0x40055e这个值的栈内存,而这个栈单元中的值是当初main调用sum时call放入的,放入的这个值就是紧跟在call后面的下一条指令的值。

状态图如下:

继续执行retq指令,上述指令将rsp指向的栈单元中0x40055e取出存入rip,同时将rsp的值加8,这样一来,rip的值就变成main调用sum的call指令的下一条指令,于是就返回到main中继续执行。

此时eax中的值是3,也就是sum执行后返回的值,来看下状态图:

继续执行下面这条指令:

mov   %eax,-0x4(%rbp)  # 把sum函数的返回值赋给变量n

上述指令将eax中的值(3)放入rbp  -0x4所指的内存中,这里也是main的局部变量n所在位置,所以此指令的含义就是将sum返回值赋值给局部变量n,此时状态图如下:

再往后的指令如下:

0x0000000000400561 <+33>:mov   -0x4(%rbp),%eax0x0000000000400564 <+36>:mov   %eax,%esi0x0000000000400566 <+38>:mov   $0x400604,%edi0x000000000040056b <+43>:mov   $0x0,%eax0x0000000000400570 <+48>:callq 0x400400 <printf@plt>0x0000000000400575 <+53>:mov   $0x0,%eax

上述指令首先为printf准备参数,然后调用printf,具体过程和调用sum的过程相似,让CPU直接执行到main倒数第二条leaveq指令处,此时栈和寄存器状态如下:

leaveq指令的上一条mov $0x0, %eax指令作用是将main返回值0放到寄存器eax,等main返回后调用main可拿到这个值。

执行leaveq指令相当于执行如下两天指令:

mov %rbp, %rsppop %rbp

leaveq指令首先将rbp的值复制给rsp,如此rsp就指向rbp所指的栈单元,之后leaveq指令将该栈单元的值pop给rbp,如此rsp和rbp就恢复成刚进入main时的状态。如下:

此时main就只剩下retq指令了,之前分析sum时介绍过,此指令执行完后会完全返回到调用main的函数中继续执行。

到此,关于C的函数调用过程就介绍完毕了,下文接着聊一下Go函数调用过程。

好啦,到这里本文就结束了,喜欢的话就来个三连击吧。 

扫码关注公众号,获取更多优质内容。

  

标签:sum,mov,函数调用,rbp,Golang,eax,详解,指令,main
来源: https://blog.csdn.net/luyaran/article/details/120671104

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有