C语言函数调用栈

栈溢出(stack overflow)是最常见的二进制漏洞,在介绍栈溢出之前,我们首先需要了解函数调用栈。

函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(caller)和被调用函数(callee)根据调用关系堆叠起来。栈在内存区域中从高地址向低地址生长。 每个函数在栈上都有自己的栈帧,用来存放局部变量、函数参数等信息。当caller调用callee时,callee对应的栈帧就会被开辟,当调用结束返回caller时,callee对应的栈帧就会被销毁。

下图展示了栈帧的结构。在32位程序中,寄存器ebp指向栈帧的底部,用来存储当前栈帧的基址,在函数运行过程中不变,可以用来索引函数参数和局部变量的位置。寄存器esp指向栈帧的顶部,当栈生长时,esp的值减少(向低地址生长)。寄存器eip用于存储下一条指令的地址。在64位程序中,三个寄存器分别为rbp、rsp和rip。

当函数调用发生时,首先需要保存caller的状态,以便函数调用结束后进行恢复,然后创建callee的状态。具体来说:

如果是32位程序,将传给callee的参数按照逆序依次压入caller的栈帧中;如果是64位程序,前6个参数分别通过rdi、rsi、rdx、rcx、r8、r9进行传递,剩余参数从后向前压栈。如果callee不需要参数,则这一步骤省略。

对于64位程序,如果只有2个参数:

mov rsi, arg2
mov rdi, arg1

对于64位程序,如果有8个参数:

push arg8
push arg7
mov r9, arg6
mov r8, arg5
mov rcx, arg4
mov rdx, arg3
mov rsi, arg2
mov rdi, arg1
  1. 将caller调用callee后的下一条指令的地址压入栈中,作为callee的返回地址,这样,当函数返回后可以正常执行接下来的指令。

  2. 将当前ebp寄存器的值压入栈中,这是caller栈帧的基址,将ebp更新为当前的esp。

  3. 将callee的局部变量压入栈中。

  4. 函数调用结束后,就是上面过程的逆过程,callee栈帧中数据会出栈,恢复到caller栈帧状态。

上面的第1步由caller完成,第2步在caller执行call指令时完成,第3、4步由callee完成。

下面看一个具体的例子,callerStack.c代码如下:

// callerStack.c
// C语言函数调用栈  

# include <stdio.h>

int func(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8)
{
    int loc1 = arg1 + 1;
    int loc2 = arg8 + 8;
    return loc1 + loc2;
}

int main(void)
{
    int ret = func(1, 2, 3, 4, 5, 6, 7, 8);
    return 0;
}

用命令gcc -m32 callerStack.c -o callerStack32生成32位程序,用gdb反汇编,得到的结果如下:

(这里额外说一下,如果是在64位机器上执行上述命令可能会报错: fatal error: bits/libc-header-start.h: No such file or directory #include <bits/libc-header-start.h>,需要安装multilib库:sudo apt install gcc-multilib

   0x565561dd <main>       endbr32 
   0x565561e1 <main+4>     push   ebp    ; 将ebp入栈,保存caller的基址,esp -= 4
   0x565561e2 <main+5>     mov    ebp, esp    ; 将ebp更新为当前的esp
   0x565561e4 <main+7>     sub    esp, 0x10    ; esp -= 0x10
   0x565561e7 <main+10>    call   __x86.get_pc_thunk.ax                    <__x86.get_pc_thunk.ax>    ; 没看懂
 
   0x565561ec <main+15>    add    eax, 0x2df0    ; 没看懂
   0x565561f1 <main+20>    push   8    ; 参数入栈,esp -= 4
   0x565561f3 <main+22>    push   7
   0x565561f5 <main+24>    push   6
   0x565561f7 <main+26>    push   5
   0x565561f9 <main+28>    push   4
   0x565561fb <main+30>    push   3
   0x565561fd <main+32>    push   2
   0x565561ff <main+34>    push   1
   0x56556201 <main+36>    call   func                    <func>    ; 调用func,返回地址入栈
 
   0x56556206 <main+41>    add    esp, 0x20    ; 恢复栈顶
   0x56556209 <main+44>    mov    dword ptr [ebp - 4], eax    ; eax存放func的返回值
   0x5655620c <main+47>    mov    eax, 0
   0x56556211 <main+52>    leave  
   0x56556212 <main+53>    ret 


   0x565561ad <func>       endbr32 
   0x565561b1 <func+4>     push   ebp    ; 将ebp入栈,保存caller的基址,esp -= 4
   0x565561b2 <func+5>     mov    ebp, esp    ; ebp更新为当前的esp
   0x565561b4 <func+7>     sub    esp, 0x10    ; esp -= 0x10
   0x565561b7 <func+10>    call   __x86.get_pc_thunk.ax                    <__x86.get_pc_thunk.ax>    ; 没看懂
 
   0x565561bc <func+15>    add    eax, 0x2e20                   <func+15>    ; 没看懂
   0x565561c1 <func+20>    mov    eax, dword ptr [ebp + 8]    ; 取出arg1(值为1),放入eax中
   0x565561c4 <func+23>    add    eax, 1    ; arg1 + 1
   0x565561c7 <func+26>    mov    dword ptr [ebp - 8], eax    ; 计算结果(局部变量loc1)放入栈中
   0x565561ca <func+29>    mov    eax, dword ptr [ebp + 0x24]    ; 取出arg8(值为8),放入eax中
   0x565561cd <func+32>    add    eax, 8    ; arg8 + 8
   0x565561d0 <func+35>    mov    dword ptr [ebp - 4], eax    ; 计算结果(局部变量loc8)放入栈中
   0x565561d3 <func+38>    mov    edx, dword ptr [ebp - 8]
   0x565561d6 <func+41>    mov    eax, dword ptr [ebp - 4]
   0x565561d9 <func+44>    add    eax, edx    ; eax = eax (loc8) + edx (loc1),函数返回值存放在eax中
   0x565561db <func+46>    leave      ; mov esp, ebp     pop ebp
   0x565561dc <func+47>    ret     ; pop eip

以上就是C语言函数的调用过程以及栈的情况,但是我还有几点疑问大家可以记录一下:

  1. 为什么在函数刚开始的地方sub esp, 0x10,从后面的代码来看,开辟的空间用于存放局部变量,那为什么不是在局部变量定义的时候将局部变量的值入栈,再移动esp呢?而是一次性先esp -= 0x10,这样不会带来空间的浪费吗?

  2. call __x86.get_pc_thunk.ax是什么意思?

  3. add eax, 0x2e20有什么作用?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/112.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java8使用Lambda表达式(流式)快速实现List转map 、分组、过滤等操作

利用java8新特性&#xff0c;可以用简洁高效的代码来实现一些数据处理。1 数据准备1.1 定义1个Fruit对象package com.wkf.workrecord.work;import org.junit.Test;import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;/*** author wuKeFan* date …

Stable Diffusion加chilloutmixni真人图片生成模型,AI绘图杀疯了

上期图文教程,我们分享过AI绘图大模型Stable Diffusion以及中文版本文心AI绘画大模型的基础知识以及代码实现,截至到目前为止。Stable Diffusion模型已经更新到了V2.1版本,其文生图大模型也越来越火,其在2022年底,由AI绘制的图片被荣为国际大奖,让大家对AI绘画大模型也越…

Node.js-----使用express写接口

使用express写接口 文章目录使用express写接口创建基本的服务器创建API路由模块编写GET接口编写POST接口CROS跨域资源共享1.接口的跨域问题2.使用cros中间件拒绝跨域问题3.什么是cros4.cros的注意事项5.cros请求的分类JSONP接口1.回顾jsonp的概念和特点2.创建jsonp接口的注意事…

请相信总有一扇门为你而开——社科院与杜兰大学金融管理硕士项目

考研人数每年都在递增&#xff0c;考研的竞争压力也逐年增长。考研话题也备受人们关注&#xff0c;初试&#xff0c;国家线&#xff0c;复试&#xff0c;考研的每一个关卡都会冲上热搜&#xff0c;引发热议。国家线公布后&#xff0c;有人欢喜有人忧。祝福成功上岸的学子们&…

【Leetcode——排序的循环链表】

&#x1f60a;&#x1f60a;&#x1f60a; 文章目录一、力扣题之排序循环链表二、解题思路1. 使用双指针法2、找出最大节点&#xff0c;最大节点的下一个节点是最小节点&#xff0c;由此展开讨论总结一、力扣题之排序循环链表 题目如下&#xff1a;航班直达&#xff01;&#…

有什么比较好的bug管理工具?5款热门工具推荐

工具再优秀&#xff0c;适合自己才最重要。 为尽量讲透这个问题&#xff0c;本文的行文结构我先整理如下&#xff1a; 1、为什么需要bug管理工具&#xff1f; 2、好的bug管理工具的标准是什么&#xff1f; 3、好的bug管理工具推荐&#xff08;5款&#xff09; 4、如何挑选适合…

雪花算法(SnowFlake)

简介现在的服务基本是分布式、微服务形式的&#xff0c;而且大数据量也导致分库分表的产生&#xff0c;对于水平分表就需要保证表中 id 的全局唯一性。对于 MySQL 而言&#xff0c;一个表中的主键 id 一般使用自增的方式&#xff0c;但是如果进行水平分表之后&#xff0c;多个表…

【python实操】用python写软件弹窗

文章目录前言组件label 与 多行文本复选框组件Radiobutton单选组件Frame框架组件labelframe标签框架列表框Listboxscrollbar滚动条组件scale刻度条组件spinbox组件Toplevel子窗体组件PanedWindow组件Menu下拉菜单弹出菜单总结针对组件前言 python学习之路任重而道远&#xff0…

chatgpt这么火?前端如何实现类似chatgpt的对话页面

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;…

代码看不懂?ChatGPT 帮你解释,详细到爆!

偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f;网上看到一段代码&#xff0c;不知道是什么含义&#xff1f;输入 ChatGPT 帮我们解释下。生信宝典 1: 下面是一段 Linux 代码&#xff0c;请帮…

Linux命令之nano命令

一、nano命令简介 nano是一个小型、免费、友好的编辑器&#xff0c;旨在取代非免费Pine包中的默认编辑器Pico。nano不仅复制了Pico的外观&#xff0c;还实现了Pico中一些缺失&#xff08;或默认禁用&#xff09;的功能&#xff0c;例如“搜索和替换”和“转到行号和列号”。nan…

【面试题】如何避免使用过多的 if else?

大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库一、引言相信大家听说过回调地狱——回调函数层层嵌套&#xff0c;极大降低代码可读性。其实&#xff0c;if-else层层嵌套&#xff0c;如下图…

iOS-砸壳篇(两种砸壳方式)

CrackerXI砸壳呢&#xff0c;当时你要是使用 frida-ios-dump 也是可以的&#xff1b; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的&#xff1a;手机中的内网ip 密码 等 最后放到我的砸壳路径里&#xff1a; python dump.py -l查看应用…

【答疑现场】我一个搞嵌入式的,有必要学习Python吗?

【答疑现场】我一个搞嵌入式的&#xff0c;有必要学习Python吗&#xff1f; 文章目录1 写在前面2 一个结论3 Python在嵌入式领域能干啥事4 Python是用来干大事的5 友情推荐6 福利活动大家好&#xff0c;我是架构师李肯&#xff0c;一个专注于嵌入式物联网系统架构设计的攻城狮。…

【蓝桥杯嵌入式】ADC模数转换的原理图解析与代码实现(以第十一届省赛为例)——STM32G4

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

Linux--多线程(1)

目录 一、概念 二、理解 三、创建、退出、合并进程 //man pthread_create //Compile and link with -pthread. //1.为什么没有fun函数&#xff1f; //2.加上sleep来改进 //3.线程结束会不会影响主线程运行&#xff1f; //4.那如果主线程比较少呢&#xff1f; 四、如何…

IP协议+以太网协议

在计算机网络体系结构的五层协议中&#xff0c;第三层就是负责建立网络连接&#xff0c;同时为上层提供服务的一层&#xff0c;网络层协议主要负责两件事&#xff1a;即地址管理和路由选择&#xff0c;下面就网络层的重点协议做简单介绍~~ IP协议 网际协议IP是TCP/IP体系中两…

RecyclerView流程学习

RecyclerView流程学习模块划分绘制流程onMeasuremLayout为nullmLayout开启自动测量未开启自动测量onLayoutonDrawonLayoutChildren缓存预加载滚动和fling模块划分 RecyclerView中根据其功能可以分为以下几个模块&#xff1a; Recycler mRecycler // 缓存管理者&#xff0c;fi…

yolov5的基本配置

yolov5的基本配置train.pydata.yaml数据集标签文件格式:总结train.py def parse_opt(knownFalse):parser argparse.ArgumentParser()parser.add_argument(--weights, typestr, defaultROOT / yolov5s.pt, helpinitial weights path)parser.add_argument(--cfg, typestr, defau…

uniCloud在线升级APP配置教程

app在线升级背景实现思路流程流程背景 因用户需要添加手机h5页面来进数据操作实现思路流程 实现流程图流程 相关文档&#xff1a;帮助文档 https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html 注册服务空间 https://unicloud.dcloud.net.cn/pages/login/login uni升级…