函数调用--函数栈

函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果,还有大家比较熟悉的一句话,就是函数调用是在栈上发生的,那么在计算机内部到底是如何实现的呢?
对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈
代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写
数据段:保存初始化的全局变量和静态变量,可读可写不可执行
BSS:未初始化的全局变量和静态变量
堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行
栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行
如图所示
寄存器
EAX:累加(Accumulator)寄存器,常用于函数返回值
EBX:基址(Base)寄存器,以它为基址访问内存
ECX:计数器(Counter)寄存器,常用作字符串和循环操作中的计数器
EDX:数据(Data)寄存器,常用于乘除法和I/O指针
ESI:源变址寄存器
DSI:目的变址寄存器
ESP:堆栈(Stack)指针寄存器,指向堆栈顶部
EBP:基址指针寄存器,指向当前堆栈底部
EIP:指令寄存器,指向下一条指令的地址
源代码
int print_out(int begin, int end)
{printf("%d ", begin++);int *p;p = (int*)(int(&begin) - 4);if(begin <= end)*p -= 5;return 1;
}

int add(int a, int b)
{
return a+b;
}

int pass(int a, int b, int c) {
char buffer[4] = {0};
int sum = 0;
int ret;
ret = (int
)(buffer+28);
//(*ret) += 0xA;
sum = a + b + c;
return sum;
}

int main()
{
print_out(0, 2);
printf("\n");
int a = 1;
int b = 2;
int c;
c = add(a, b);
pass(a, b, c);
int __sum;
__asm
{
mov __sum, eax
}
printf("%d\n", __sum);
system(“pause”);
}

函数初始化
  28: int main()29: {
011C1540 push ebp //压栈,保存ebp,注意push操作隐含esp-4
011C1541 mov ebp,esp //把esp的值传递给ebp,设置当前ebp
011C1543 sub esp,0F0h //给函数开辟空间,范围是(ebp, ebp-0xF0)
011C1549 push ebx
011C154A push esi
011C154B push edi
011C154C lea edi,[ebp-0F0h] //把edi赋值为ebp-0xF0
011C1552 mov ecx,3Ch //函数空间的dword数目,0xF0>>2 = 0x3C
011C1557 mov eax,0CCCCCCCCh
011C155C rep stos dword ptr es:[edi]
//rep指令的目的是重复其上面的指令.ECX的值是重复的次数.
//STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址,然后EDI+4
一般所用函数的开头都会有这段命令,完成了状态寄存器的保存,堆栈寄存器的保存,函数内存空间的初始化
函数调用
 30: print_out(0, 2);
013D155E push 2 //第二个实参压栈
013D1560 push 0 //第一个实参压栈
013D1562 call print_out (13D10FAh)//返回地址压栈,本例中是013D1567,然后调用print_out函数
013D1567 add esp,8  //两个实参出栈
//注意在call命令中,隐含操作是把下一条指令的地址压栈,也就是所谓的返回地址
除了VS可能增加一些安全性检查外,print_out的初始化与main函数的初始化完全相同
被调用函数返回
013D141C mov eax,1  //返回值传入eax中
013D1421 pop edi
013D1422 pop esi
013D1423 pop ebx //寄存器出栈
013D1424 add esp,0D0h //以下3条命令是调用VS的__RTC_CheckEsp,检查栈溢出
013D142A cmp ebp,esp
013D142C call @ILT+315(__RTC_CheckEsp) (13D1140h)
013D1431 mov esp,ebp //ebp的值传给esp,也就是恢复调用前esp的值
013D1433 pop ebp //弹出ebp,恢复ebp的值
013D1434 ret  //把返回地址写入EIP中,相当于pop EIP
call指令隐含操作push EIP,ret指令隐含操作 pop EIP,两条指令完全对应起来 
写到这里我们就可以分析一下main函数调用print_out函数前后堆栈(Stack)发生了什么变化,下面用一系列图说明
接下来是返回过程,从上面的013D1431 行代码开始
   
 
  
print_out函数调用前后,main函数的栈帧完全一样,perfect!
下面我们来看看print_out函数到底做了什么事情
int *p;
p = (int*)(int(&begin) - 4);
if(begin <= end)*p -= 5;
根据上面调用print_out函数后的示意图,可以知道p实际上是指向了函数的返回地址addr,然后把addr-5,这又会发生什么?
再回头看一下反汇编的代码,
013D1560 push 0 //第一个实参压栈
013D1562 call print_out (13D10FAh)//返回地址压栈,本例中是013D1567,然后调用print_out函数
013D1567 add esp,8  //两个实参出栈
分析可知,返回地址addr的值是013D1567 ,addr-5为013D1562 ,把返回地址指向了call指令,结果是再次调用print_out函数,
从而print_out函数实现了打印从begin到end之间的所有数字,可以说是循环调用了print_out函数
对于add函数,主要是为了说明返回值存放于寄存器eax中。
另外,VS自身会提供一些安全检查
CheckStackVar安全检查http://blog.csdn.net/masefee/article/details/5630154,通过ecx和edx传递参数, 局部变量有数组时使用
__security_check_cookie返回地址检查, 数组长度大于等于5时使用
__RTC_CheckEsp程序栈检查,printf函数用使用
好文要顶 关注我 收藏该文

小雨淅淅
关注 - 0
粉丝 - 15

+加关注

5
0

« 上一篇:C++中float类型的存储
» 下一篇:KMP算法
 </div><div class="postDesc">posted @ <span id="post-date">2014-03-24 22:38</span> <a href="https://www.cnblogs.com/rain-lei/">小雨淅淅</a> 阅读(<span id="post_view_count">29033</span>) 评论(<span id="post_comment_count">0</span>)  <a href="https://i.cnblogs.com/EditPosts.aspx?postid=3622057" rel="nofollow">编辑</a> <a href="#" "AddToWz(3622057);return false;">收藏</a></div>
</div>
<script type="text/javascript">var allowComments=true,cb_blogId=123908,cb_entryId=3622057,cb_blogApp=currentBlogApp,cb_blogUserGuid='0056b2e5-45d9-e111-aa3f-842b2b196315',cb_entryCreatedDate='2014/3/24 22:38:00';loadViewCount(cb_entryId);var cb_postType=1;</script>

C语言汇编-函数调用栈相关推荐

  1. 第19部分- Linux ARM汇编 函数调用栈使用-阶乘

    第19部分- Linux ARM汇编 函数调用栈使用-阶乘 调用栈我们以阶乘为例.阶乘比较经典. 堆栈定义:堆栈是仅由当前动态激活拥有的内存区域. 我们先来看下阶乘的C代码如下: int factor ...

  2. C 语言 函数调用栈

    From:https://www.cnblogs.com/clover-toeic/p/3755401.html    https://www.cnblogs.com/clover-toeic/p/3 ...

  3. C语言函数调用栈(一)

    以下全文转载自:C语言函数调用栈(一) 程序的执行过程可看作连续的函数调用.当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行.函数调用过程通常使用堆栈实现,每个用户态 ...

  4. 基于GDB-peda汇编调试理解函数调用栈

    一.前言 1.1 叙叙旧 距离上一次写文章已经过去3个月了,当初计划至少一个月一篇,不曾想这一拖就是三个月.一直不写的主要原因是当把一个问题弄清楚了,或者说掌握了一个东西,就觉得没有什么可值得写:另外 ...

  5. c语言中staloc是什么意思,C语言函数调用栈(三)

    6 调用栈实例分析 本节通过代码实例分析函数调用过程中栈帧的布局.形成和消亡. 6.1 栈帧的布局 示例代码如下: //StackReg.c #include //获取函数运行时寄存器%ebp和%es ...

  6. S5PV210开发板用汇编设置栈和调用C语言

    使用C语言前为什么要先用汇编设置栈? C语言程序运行时需要栈,因为C语言中的局部变量都是用栈来实现的,如果没有设置栈就使用C语言,局部变量就会落空,程序就会死掉,所以在使用C语言前,我们需要先在汇编编 ...

  7. c语言数组在栈上的分配,彻底弄懂为什么不能把栈上分配的数组(字符串)作为返回值...

    背景 最近准备从 C 语言零基础到 PHP 扩展开发实战,案例的过程中准备了如下代码碎片,演示解析http scheme #include #include #include char *parse_ ...

  8. 深入理解C语言的函数调用过程

    深入理解C语言的函数调用过程 本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解.     先看一个最简单的程序: 点击(此处)折叠或打开 /*test.c*/ ...

  9. 深入理解 C 语言的函数调用过程

    来源: wjlkoorey 链接:http://blog.chinaunix.net/uid-23069658-id-3981406.html 本文主要从进程栈空间的层面复习一下C语言中函数调用的具体 ...

  10. swap函数_【Golang】图解函数调用栈

    " 不要小瞧函数调用栈哦,它可是理解参数传递.命名匿名返回值.Function Value.defer等面试常客的关键呐~" 我们按照编程语言的语法定义的函数,会被编译器编译为一堆 ...

最新文章

  1. 【收藏】使用Docker搭建MySQL服务
  2. 有序数组中查找数字的范围
  3. Powerdesigner 在线打开 不用安装客户端 访问pdm,ldm文件
  4. 32位CentOS系统安装kernel-PAE支持4g以上内存
  5. CTF【解密】字符串flag被加密成已知新字符串,请解密出flag,可以使用Python解码出WriteUp
  6. 程序员编程艺术第二十六章:基于给定的文档生成倒排索引(含源码下载)
  7. Windows 11正式发布!网友的这波吐槽,太搞笑了。。。
  8. 翻译:swift 5通过使用泛型进行高级异步操作Operation
  9. 深度学习:语义分割网络U-Net
  10. IntelliJ IDEA 汉化包-支持2018和2019版本
  11. Racket编程指南——1 欢迎来到Racket!
  12. Python提取docx格式Word文档中所有尾注
  13. 音视频编解码学习详解h264 ,mpeg4 ,aac 等音视频格式
  14. 车辆网相关政策和法律法规
  15. 空间路面matlab,基于Matlab的三维随机路面联合建模与仿真研究
  16. AirSim学习(1)-介绍,安装,unity测试
  17. 请简述什么是mysql,MySQL之什么是MySQL
  18. 2020年8月试题分析-计算机网络原理
  19. TDS协议解析(转载)
  20. (生活篇)职场饭局生存法则

热门文章

  1. 20172327 2017-2018-2 《程序设计与数据结构》第九周学习总结
  2. Linux串口编程详解(转)
  3. .net中对时间的操作
  4. HTML中构建自动伸缩的表格Table
  5. 踩坑事件:windows操作系统下的eclipse中编写SparkSQL不能从本地读取或者保存parquet文件
  6. DotNetTextBoxV3.0在线编辑器控件Ver3.4.6 Open Source免费开源版
  7. 这项技术曾应用于无人驾驶,荣耀10将其移植到手机上这样操作!
  8. 【Foreign】Weed [线段树]
  9. redis持久化(persistence)
  10. backbond Model实现