函数调用--函数栈

函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果,还有大家比较熟悉的一句话,就是函数调用是在栈上发生的,那么在计算机内部到底是如何实现的呢?
对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈
代码段:保存程序文本,指令指针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. 从60多场技术面试中,我总结了这份面试经验
  2. linux实战专家为你梳理网站集群安全基本框架思路!
  3. java 之 运算符
  4. Restful设计相关
  5. objloader使用方法
  6. Intel Realsense D435 测试摄像头在不同曝光值下的帧生成时间(防止曝光时间过长导致fps下降)auto_exposure_priority(没成功)
  7. 虚拟机的分类_虚拟化精华问答 | 虚拟化技术分类
  8. 设计模式——单例模式(懒汉模式,饿汉模式)
  9. mfc如何删除lineto画的_有哪些好用的板绘软件?衣服上的花纹怎么画?
  10. P4175 [CTSC2008]网络管理(整体二分)
  11. 从零开始实现数据结构(二) 有序数组
  12. mongodb的条件查询笔记
  13. 详细讲解Spring中的@Bean注解
  14. 微信小程序云开发教程-WXML入门-数据绑定
  15. Nifi 常用Processor
  16. 【python第三方库】playwright简要入门
  17. 【概率论与数理统计】1.5 独立性
  18. 人像抠图软件哪个好?这些软件助你实现人像抠图
  19. JS流程控制语句 反反复复(while循环) 和for循环有相同功能的还有while循环, while循环重复执行一段代码,直到某个条件不再满足。...
  20. css中overflow属性失效,页面始终不能滚动显示溢出的内容

热门文章

  1. type与instance区别
  2. Android UI 调试常用工具(Dump view UI hierarchy for Automator)
  3. 今天8月5号 2011-08-05
  4. 高性能服务器架构 的几个注意点 (High-Performance Server Architecture)
  5. 成熟有家男人与24岁女孩的精彩对白[推荐]
  6. pc工具不支持stb的加密方式_微信协议分析 pc端记录实现不死号
  7. zk监控集群几点变化 给管理员发邮件
  8. 什么是.NET应用程序域
  9. Python零基础学习笔记(三十九)—— time
  10. 设计模式学习与应用——单例模式