当前位置: 首页 > news >正文

从汇编看函数调用

文章目录

    • 函数调用流程
    • 栈相关寄存器及的作用简介
      • 寄存器功能
      • 指令功能
    • 函数的括号{}
      • 正括号
      • 反括号
    • 参数传递
        • 传值,变量不可改
        • 传指针,变量可改
        • C++ 传引用
    • 函数调用实例
    • 函数返回值

函数调用流程

目标:函数调用前后栈保持不变
在这里插入图片描述

  1. 保存main函数的寄存器上下文
  2. 移动栈指针,到新栈
  3. 调用新函数:新函数会开辟内存然后操作
  4. 恢复栈指针

栈相关寄存器及的作用简介

寄存器功能

在这里插入图片描述ESP/RSP:堆栈指针寄存器,指向栈顶。栈顶指针
EBP/RBP:栈底指针,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量

rax:通常用于存储函数调用返回值
rdi:第一个入参
rsi:第二个入参
rdx:第三个入参
rcx:第四个入参
r8:第五个入参
r9:第六个入参

寄存器ebp作为当前函数的“栈帧”基地址,配合一定的偏移,就可以读、写函数体的临时变量。如果一个变量是通过ebp寄存器间接访问的,那么它往往是临时变量,也叫“栈”变量。

指令功能

在这里插入图片描述
在这里插入图片描述

push rbp 保存栈桢,保存rbp值
1. rbp里面的值放到当前rsp指向的位置,保存当前栈底指针的值
2. 然后rsp–,栈顶指针向上移动

pop eax 恢复栈帧
1. 栈顶指针向下移动,这里的值保存的是原函数的栈底位置
2. ebp指向esp里面值的位置,移动栈底指针到原函数位置

在这里插入图片描述
call
1. 会把下一条指令的地址也就是当前函数要执行的下一条指令的地址,保存到栈中。
2.将func函数地址填入程序计数器来执行。

ret 指令,是把保存在栈中的地址设定到程序计数器中,让CPU进行执行
在这里插入图片描述

栈是存储临时数据的区域,在普通内存中,它的特点是通过push指令和pop指令进行数据的存储和读出。往栈中存储数据称为“入栈”,从栈中读出数据称为“出栈”。32位x86系列的CPU中,进行1次push或pop,即可处理32位(4字节)的数据。push指令和pop指令中只有一个操作数。该操作数表示的是“push的是什么及pop的是什么”,而不需要指定“对哪一个地址编号的内存进行push或pop”。
这是因为,对栈进行读写的内存地址是由esp寄存器(栈指针)进行管理的。push指令和pop指令运行后,esp寄存器的值会自动进行更新(push指令是-4, pop命令是+4),因而程序员就没有必要指定内存地址了。

栈是由大地址向小地址递减,而堆和普通内存是小地址到大地址递增

操作系统会为每个任务(进程或线程)分配一段内存当作任务“堆栈”;CPU则提供两个寄存器esp、ebp,用来标识当前函数对“堆栈”的使用情况。随着函数的逐层调用,函数的“栈帧”会逐次堆叠,互不重合;随着函数的逐层返回,函数的“栈帧”会被就地放弃,但不会清理内存

函数的括号{}

其实函数的调用主要部分就是正反括号的内容
正负括号都对应两条指令。
在这里插入图片描述

正括号

先看正括号,作用是保存原栈

  1. push rbp :
    1. rbp里面的值放到当前rsp指向的位置,保存当前栈底指针的值
    2. 然后rsp–,栈顶指针向上移动。至此,main函数的“栈帧”保护工作完成。
  2. mov rbp, rsp,更新一下“栈帧”基准线,让ebp指向esp,这里就是新的func的栈了

在这里插入图片描述

反括号

然后看反括号两条指令:反括号作用是恢复栈

  1. pop, 把事先压入“栈顶”的ebp值返还给CPU寄存器ebp。这样蓝色基准线就恢复到了最开始的位置。然后esp红色水位线也随之下降。esp和ebp的值就都恢复了。
  2. ret指令,把“栈顶”处的返回值传给CPU寄存器rip,这样,CPU就可以跳转到主调函数main被打断的地方0x401105e继续执行了。

参数传递

先看下传递参数的汇编:
在这里插入图片描述

  1. 传值调用和传指针其实都是将值传递到函数中,只不过这个值含义不同指针是一个地址的值。
  2. 还可以看出用作传参的寄存器是哪几个。
传值,变量不可改

我们接着看函数中,对参数赋值的汇编:
在这里插入图片描述1. 这里会将参数寄存器中的值,放入栈中。然后释放参数寄存器。
2. 然后将内存地址数据赋值。
3. 这也就说明原来参数的值被复制了一份到内存中,修改当前形参的值,实际是修改栈中内存的值,原变量不会被修改

传指针,变量可改

在这里插入图片描述

  1. 首先还是将参数的值放入内存中,释放寄存器
  2. 然后将参数x的内存地址传给寄存器,寄存器当前存储的是该地址
  3. 然后向该寄存器中存储的地址中,写入0.
    这也就直接修改了内存中原变量的值,这里的寄存器rax起到了一个中间过渡作用。

Q:为什么传递参数是通过CPU寄存器,而不是直接压入堆栈呢?
A:传递参数,也可以不通过CPU寄存器,而通过压入堆栈的方式,一些老版本的编译器,也是如此操作的。但通过寄存器传递,可以避免一些内存操作,一定程度上有利于提高函数的执行效率。

C++ 传引用

C++ 传引用和传指针的汇编相同,所以传引用只是一个语法糖
在这里插入图片描述

函数调用实例

在这里插入图片描述

  1. push, mov 保存栈帧,移动栈指针到新栈
  2. 因为要传递参数,所以将4和3存入到寄存器esi和edi
  3. 调用func函数
    1. push mov保存栈帧,移动栈指针到新栈
    2. 将参数寄存器edi和esi的值放到栈内存中
    3. 1和2的值放到栈内存中
    4. 将1的值放到eax寄存器中当作返回值
    5. 恢复栈桢,跳转回函数执行
  4. 将0放到eax中当作返回值

函数返回值

函数返回值对应的指令其实就是 mov eax 1
实际上就是把值放入eax寄存器。
那么函数返回值有以下几个注意点:

  1. 函数返回值无论是存变量还是指针变量,都是把一个值放到rax寄存器中。
  2. 函数返回值放到rax寄存器中所以最大64位,想通过寄存器rax返回超过8字节的数据是不可能的。例如,我们想写一个函数,让其返回数组、字符串时,编译器一定会百般阻挠。
  3. 函数返回值不要放入栈中的临时对象,虽然有时候栈中该变量不会被立即清理,但是还是有风险,所以只能返回提前申请malloc申请的堆中内存。

相关文章:

  • 008 CSS盒子模型
  • 如何成为一名嵌入式C语言高手?
  • 突破编程_前端_SVG(概述)
  • 通俗易懂的理解 ADC(2)
  • zabbix绑定钉钉进行通知,网页端添加JavaScript,无脑式操作
  • sharo反序列化漏洞
  • 算法| ss 双指针
  • CentOS7安装Tomcat
  • 如何在plesk面板安装域名付费SSL证书
  • 云原生架构(微服务、容器云、DevOps、不可变基础设施、声明式API、Serverless、Service Mesh)
  • 大语言模型中常见小模型LLM垂直领域应用微调数据集
  • C++20 semaphore(信号量) 详解
  • 摄影杂记一
  • MyBatis 解决上篇的参数绑定问题以及XML方式交互
  • Pytest教程:一文了解如何使用 pytest_runtest_makereport 修改 Pytest 测试报告内容
  • CSS 三角实现
  • Effective Java 笔记(一)
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JAVA多线程机制解析-volatilesynchronized
  • js数组之filter
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • laravel with 查询列表限制条数
  • Python_网络编程
  • STAR法则
  • swift基础之_对象 实例方法 对象方法。
  • vue-router 实现分析
  • windows下mongoDB的环境配置
  • 百度小程序遇到的问题
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 欢迎参加第二届中国游戏开发者大会
  • 简单实现一个textarea自适应高度
  • 三分钟教你同步 Visual Studio Code 设置
  • 树莓派 - 使用须知
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • #pragma data_seg 共享数据区(转)
  • #vue3 实现前端下载excel文件模板功能
  • (WSI分类)WSI分类文献小综述 2024
  • (第27天)Oracle 数据泵转换分区表
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • .bat批处理(六):替换字符串中匹配的子串
  • .java 9 找不到符号_java找不到符号
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .Net+SQL Server企业应用性能优化笔记4——精确查找瓶颈
  • .net6 webapi log4net完整配置使用流程
  • [20140403]查询是否产生日志
  • [BZOJ2208][Jsoi2010]连通数
  • [BZOJ3223]文艺平衡树
  • [C puzzle book] types
  • [C#] 我的log4net使用手册
  • [CCIE历程]CCIE # 20604
  • [CSS]CSS 的背景
  • [CTSC2014]企鹅QQ
  • [Django 0-1] Core.Checks 模块