一、一次函数调用分析

c代码:

// function_example.c
#include <stdio.h>
int static add(int a, int b)
{
return a+b;
} int main()
{
int x = 5;
int y = 10;
int u = add(x, y);
}

编译并objdump:

$ gcc -g -c function_example.c
$ objdump -d -M intel -S function_example.o
int static add(int a, int b)
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 89 7d fc mov DWORD PTR [rbp-0x4],edi
7: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
return a+b;
a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
d: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
10: 01 d0 add eax,edx
}
12: 5d pop rbp
13: c3 ret
0000000000000014 <main>:
int main()
{
14: 55 push rbp
15: 48 89 e5 mov rbp,rsp
18: 48 83 ec 10 sub rsp,0x10
int x = 5;
1c: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5
int y = 10;
23: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa
int u = add(x, y);
2a: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
2d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
30: 89 d6 mov esi,edx
32: 89 c7 mov edi,eax
34: e8 c7 ff ff ff call 0 <add>
39: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
3c: b8 00 00 00 00 mov eax,0x0
}
41: c9 leave
42: c3 ret

分析:

  1. 可以看到main方法和add方法的入口处14行和0行都有一个push操作,紧接着是一个mov操作将rsp赋值给rbp,rbp正是上一步push的参数;
  2. 同时可以看到在add方法的结尾有一个pop指令,参数正是函数开头push的参数rbp;
  3. add函数和main函数结尾都有一个ret指令;
  4. main方法中1c行到32行
    1. 首先对两个内存地址分别赋值5和10
    2. 将刚刚赋值过的两个内存地址的值再次赋值给edx和eda
    3. 将dex和eda的值分别赋给esi和edi
  5. main函数34行执行了call指令,参数是add函数;

    实际上PUSH指令就表示压栈,POP就表示出栈。其实上述的调用过程和if/else的指令都进行了指令地址跳转,区别在于通过if/else跳转后,指令顺序执行到下一行,而call命令执行的跳转函数完成后还会调回call命令下一行,这是如何实现的呢?

程序栈说明:

我们自己思考这个功能该如何实现呢?

第一种方法,我们可以将add方法的指令直接拼接到main方法指令行的对应位置,这个方法的问题是,假如main方法和add相互调用(循环调用)那么不断拼接无穷无尽,显然不行。

第二种方法,我们可以单独设置一个程序执行寄存器,每次函数执行都记录该函数执行需要返回的位置,这样等该函数执行完成,直接返回到记录的返回位置即可,但是在多级函数调用情况下,只记录一个跳转机制远远不够,因此计算机科学家实际使用了一种更好的办法;

实际栈结构:

    1. 在内存中开辟一块区域保存先进后出的栈结构来记录函数调用的返回地址
    2. A函数调用B函数时,在进入B函数后,首先将A函数调用B函数的下一行指令地址放入栈中,此之谓压栈,假若B函数又调用了C函数,就又在C函数一开始将B调用C的下一行指令地址压入栈顶
    3. C函数执行完成,从栈顶拿出要返回的地址并将指令跳转回该地址,此之谓出栈,随之B函数执行完成,此时又从栈中取出栈顶存放的指令地址并跳转执行;
    4. 如此,调用层级越深的函数所记录的跳转地址越接近于栈顶,如此弹栈过程遍类似于反向遍历;

汇编代码的表现:

  1. add方法0行,rbp表示的是当前栈的指针,此时push rbp表示将main函数栈帧的栈底地址压入了程序栈顶;
  2. 紧接着1行,mov rbp,rsp 将rsp的值赋给rbp,而rsp是一个始终指向栈顶的参数,此时rbp指向栈顶;
  3. add末尾pop rbp表示弹栈,将当前的栈顶出栈,由此维护好了栈;
  4. 由于call调用时PC寄存器将返回地址压栈,最后执行ret指令时将call指令压入栈顶的返回地址弹栈并替换到PC寄存器中,以此完成指令跳转。

二、构造一次Stack Overflow

由于栈的大小有限,因此当调用层级太多时,会由于压栈造成栈空间不足以致溢出,典型场景为不加限制的递归调用:

int a()
{
return a();
} int main()
{
a();
return 0;
}

三、使用函数内联进行性能优化

程序编译时将实际函数调用产生的指令直接插入到调用位置,来替换对应的函数调用指令,称为函数内联。

这样做的好处是:减少了指令数、减少了函数调用时的压栈弹栈开销;

但是当目标函数被引用很多次时会多次展开,使得程序占用空间变得很大。

(八)函数调用为何会发生“Stack Overflow”的更多相关文章

  1. 重学计算机组成原理(六)- 函数调用怎么突然Stack Overflow了!

    用Google搜异常信息,肯定都访问过Stack Overflow网站 全球最大的程序员问答网站,名字来自于一个常见的报错,就是栈溢出(stack overflow) 从函数调用开始,在计算机指令层面 ...

  2. 函数调用堆栈及活动记录 堆栈溢出 stack overflow

    小结: 1.当被调函数返回主调函数时,被调函数的 活动记录-activation record / 堆栈帧-stack frame 被 弹出-popping 程序执行栈-program executi ...

  3. Stack Overflow上59万浏览量的提问:为什么会发生ArrayIndexOutOfBoundsException?

    在逛 Stack Overflow 的时候,发现了一些访问量像昆仑山一样高的问题,比如说这个:为什么会发生 ArrayIndexOutOfBoundsException?这样看似简单到不值得一问的问题 ...

  4. Stack的三种含义(数据超过栈的大小,就发生stack overflow)

    非常典型的基础知识,转自http://www.ruanyifeng.com/blog/2013/11/stack.html 学习编程的时候,经常会看到stack这个词,它的中文名字叫做"栈& ...

  5. [转] log4j-over-slf4j与slf4j-log4j12共存stack overflow异常分析

    [From] http://www.tuicool.com/articles/INveIf 注:下文中的“桥接”.“转调”.“绑定”等词基本都是同一个概念. log4j-over-slf4j和slf4 ...

  6. Stack overflow 编译能通过,运行时出现Stack overflow

    Stack overflow 编译能通过,运行时出现Stack overflow 大家都知道,Windows程序的内存机制大概是这样的,全局变量(局部的静态变量本质也属于此范围)存储于堆内存,该段内存 ...

  7. Stack Overflow是如何做应用缓存的

    首先要说下缓存是什么?缓存,就是在取出数据结果后,暂时将数据存储在某些可以快速存取的位置(例如各种NoSQL如Redis,HBase,又或MemoryCache等等),于是就可以让这些耗时的数据结果多 ...

  8. Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder

    Stack Overflow 排错翻译  - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder 转自:ht ...

  9. Stack Overflow: The Architecture - 2016 Edition(Translation)

    原文: https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/ 作者:Nick Cra ...

随机推荐

  1. graph generation model

    Generative Graph Models 第八章传统的图生成方法> The previous parts of this book introduced a wide variety of ...

  2. 基于YOLO-V2的行人检测(自训练)附pytorch安装方法

    声明:本文是别人发表在github上的项目,并非个人原创,因为那个项目直接下载后出现了一些版本不兼容的问题,故写此文帮助解决.(本人争取在今年有空的时间,自己实现基于YOLO-V4的行人检测) 项目链 ...

  3. OpenCV计算机视觉学习(11)——图像空间几何变换(图像缩放,图像旋转,图像翻转,图像平移,仿射变换,镜像变换)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 图像 ...

  4. 内网渗透 day12-免杀框架2

    免杀框架2 目录 1. IPC管道连接 2. 查看wifi密码 3. Phantom-Evasion免杀框架的运用 4. 自解压(sfx) 5. 数字签名 6. 资源替换 1. IPC管道连接 命名管 ...

  5. 它是世界上最好的语言,吊打PHP那种

    Scratch Scratch是麻省理工媒体实验室终身幼稚园组开发的一套电脑程序开发平台,旨在让程序设计语言初学者不需先学习语言语法便能设计产品.开发者期望通过学习Scratch,启发和激励用户在愉快 ...

  6. ubuntu 文件编码格式 转换

    正在学习jquery,之前在windows下弄的编码到了 ubuntu下,乱码: 找到一个方法: iconv : 源文件:a.htm 格式:gbk: 目标:    a.html 格式:utf8: ic ...

  7. Elasticsearch(6):文档查询

      为方便后续查询演示,我们先创建一个索引.创建索引请求如下:  

  8. Kubernetes+Promethues+Cloud Alert实践分享

    前言 容器集群管理系统 Kubernetes(简称K8s),为容器化的应用提供部署运行.容器编排.负载均衡.服务发现和动态伸缩等一系列完整功能,Prometheus 对 K8s 支持非常棒,能够自动发 ...

  9. ixgbe 驱动 为xxx驱动做准备1

    网卡都是pci设备,因此这里每个网卡驱动其实就是一个pci驱动.并且intel这里是把好几个万兆网卡(82599/82598/x540)的驱动做在一起的.V4L2 一样几个类型摄像头合并在一起 先说一 ...

  10. linux文件增删拷(touch/mkdir/cp/mv/rm)

    touch或>命令创建普通文件: [root@localhost test]# touch a  ---创建单个文件 [root@localhost test]# ls a [root@loca ...