ARM 栈和函数调用

阅读本文前,可以先阅读下述文档,对函数栈、栈帧等的概念会有所了解,会对本文章的理解大有益处
X86_64 栈和函数调用

1、调试环境

Ubuntu:

liangjie@liangjie-virtual-machine:~/Desktop$ cat /proc/version
Linux version 6.5.0-35-generic (buildd@lcy02-amd64-079) 
(x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, 

交叉编译器使用:gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabi

2、调试原码

/* proc.c */
void func1()
{
}int func2(int a, long b, char *c)
{*c = a * b;func1();return a * b;
}int main()
{char value;int rc = func2(1, 2, &value);
}

3、反汇编结果

3.1 ARM 基础

  ARM 是精简指令集(RISC:Reduced Instruction set Computing)处理器,拥有更简单的指令集(少于100个)和更多的通用寄存器。与 X86 不同,ARM 指令只操作寄存器,且只能使用 Load/Stroe(取/存) 命令来读取和写入内存。也就是说,如果增加某个地址处的 32 位数据的值,你起码需要三个指令(取,加,存):首先将该地址处的数据加载到寄存器(取),然后增加寄存器里的值(加),最后再将寄存器里的值存储到原来的地址处(存)。

ARM 在任何时候都可以看到 16 个通用寄存器,具体取决于当前的处理器模式。它们是 R0-R12、SP、LR、PC (R15)

  • R0-R12:可用于常见操作期间存储临时值、指针(内存位置)等等。例如 R0,在算术运算期间可以称为累加器,或用于存储调用的函数时返回的结果。R7 在进行系统调用时非常有用,因为它存储了系统号,R11(FP 栈底)可帮助我们跟踪作为帧指针的堆栈上的边界。此外,ARM上的函数调用约定函数的前四个参数存储在寄存器 r0-r3 中(关于栈底,不同的编译器实现可能不同,有些编译器将 R7 作为栈底,而有些则是 R11 作为栈底
  • SP(或 R13)是堆栈指针。C 和 C++ 编译器始终使用 SP 作为堆栈指针。不鼓励将 SP 用作通用寄存器。在 Thumb 中,SP 被严格定义为堆栈指针。汇编程序参考中的说明页描述了何时可以使用 SP 和 PC
  • 在用户模式下,LR(或R14)用作链接寄存器,用于在调用子程序时存储返回地址。如果返回地址存储在堆栈中,它也可以用作通用寄存器。在异常处理模式中,LR 保存异常的返回地址,或者如果在异常内执行子例程调用,则保存子例程返回地址。如果返回地址存储在堆栈中,LR 可以用作通用寄存器
  • CPSR:状态寄存器:在它下面你可以看到工作状态标志,用户模式,中断标志,溢出标志,进位标志,零标志位,符号标志。这些标志代表了CPSR寄存器中特定的位,并根据CPSR的值进行设置,如果标志位有效则会进行加粗。N、Z、C 和 V 位与 x86 上的 EFLAG 寄存器中的 SF、ZF、CF 和 OF 位相同
  • SPSR:程序保存状态寄存器(saved program status register)SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用 SPSR 来恢复 CPSR
    在这里插入图片描述

3.2 源码讲解

00010398 <func1>:10398:	b480      	push	{r7}1039a:	af00      	add	r7, sp, #01039c:	bf00      	nop1039e:	46bd      	mov	sp, r7103a0:	bc80      	pop	{r7}103a2:	4770      	bx	lr000103a4 <func2>:103a4:	b580      	push	{r7, lr}103a6:	b084      	sub	sp, #16103a8:	af00      	add	r7, sp, #0103aa:	60f8      	str	r0, [r7, #12]103ac:	60b9      	str	r1, [r7, #8]103ae:	607a      	str	r2, [r7, #4]103b0:	68fb      	ldr	r3, [r7, #12]103b2:	b2da      	uxtb	r2, r3103b4:	68bb      	ldr	r3, [r7, #8]103b6:	b2db      	uxtb	r3, r3103b8:	fb12 f303 	smulbb	r3, r2, r3103bc:	b2da      	uxtb	r2, r3103be:	687b      	ldr	r3, [r7, #4]103c0:	701a      	strb	r2, [r3, #0]103c2:	f7ff ffe9 	bl	10398 <func1>103c6:	68fb      	ldr	r3, [r7, #12]103c8:	68ba      	ldr	r2, [r7, #8]103ca:	fb02 f303 	mul.w	r3, r2, r3103ce:	4618      	mov	r0, r3103d0:	3710      	adds	r7, #16103d2:	46bd      	mov	sp, r7103d4:	bd80      	pop	{r7, pc}000103d6 <main>:103d6:	b580      	push	{r7, lr}103d8:	b082      	sub	sp, #8103da:	af00      	add	r7, sp, #0103dc:	1cfb      	adds	r3, r7, #3103de:	461a      	mov	r2, r3103e0:	2102      	movs	r1, #2103e2:	2001      	movs	r0, #1103e4:	f7ff ffde 	bl	103a4 <func2>103e8:	6078      	str	r0, [r7, #4]103ea:	2300      	movs	r3, #0103ec:	4618      	mov	r0, r3103ee:	3708      	adds	r7, #8103f0:	46bd      	mov	sp, r7103f2:	bd80      	pop	{r7, pc}

  r7 对应于 x86 下的 bp 寄存器,相对与 sp,r7 就是栈底,在进入一个新栈帧之后先把原来的 r7 压栈,然后 r7 保存当前 bp。Linux 下,r7 大部分情况用来保存系统调用号(syscall number)

3.2.1 func1

  func1 函数比较简单,由 func1 先讲起。 func1 是叶子函数,主要关注其函数调用关系流程。

  ARM 中的 bl 指令为相对跳转指令,在跳转之前,会先将当前指令的下一条指令地址保存到 lr 寄存器中,然后才跳转到标号执行。
  所以当进入一个函数时,如果该函数可能会修改 lr 寄存器,则 lr 需要入栈保存;如果该函数是叶子函数,则不需要保存 lr 寄存器值

103c2:	f7ff ffe9 	bl	10398 <func1>

再看函数 func1,

00010398 <func1>:10398:	b480      	push	{r7}				# r7 入栈保存值1039a:	af00      	add	r7, sp, #0				# r7 = sp + 01039c:	bf00      	nop1039e:	46bd      	mov	sp, r7					# sp = r7103a0:	bc80      	pop	{r7}					# r7 出栈103a2:	4770      	bx	lr						# 返回
  • push {r7},将 r7 压栈的,保存原来的栈底 r7
  • add r7, sp, #0,原来的栈底(r7 指向的位置)成为了新的栈顶(sp 指向的位置)
    在这里插入图片描述
  • mov sp, r7,恢复原来的栈顶
  • pop {r7},恢复原来的栈底
    在这里插入图片描述
3.2.2 func2

我们再看看 func2,func2 则主要关注函数传参以及局部变量的存储。

000103a4 <func2>:103a4:	b580      	push	{r7, lr}         	# r7、lr 入栈保存值103a6:	b084      	sub	sp, #16				 	# sp = sp - 16103a8:	af00      	add	r7, sp, #0				# r7 = sp + 0103aa:	60f8      	str	r0, [r7, #12]			# (r7 + 12) = r0103ac:	60b9      	str	r1, [r7, #8]			# (r7 + 8) = r1103ae:	607a      	str	r2, [r7, #4]			# (r7 + 4) = r2103b0:	68fb      	ldr	r3, [r7, #12]			# r3 = (r7 + 12)103b2:	b2da      	uxtb	r2, r3				# r2 = r3 低 8 位103b4:	68bb      	ldr	r3, [r7, #8]			# r3 = (r7 + 8)103b6:	b2db      	uxtb	r3, r3				# r3 = r3 低 8 位103b8:	fb12 f303 	smulbb	r3, r2, r3			# r3 = r2 * r3103bc:	b2da      	uxtb	r2, r3				# r2 = r3 低 8 位103be:	687b      	ldr	r3, [r7, #4]			# r3 = (r7 + 4)103c0:	701a      	strb	r2, [r3, #0]		# (r3 + 0) = r2 (8位)103c2:	f7ff ffe9 	bl	10398 <func1>103c6:	68fb      	ldr	r3, [r7, #12]			# r3 = (r7 + 12)103c8:	68ba      	ldr	r2, [r7, #8]			# r2 = (r7 + 8)103ca:	fb02 f303 	mul.w	r3, r2, r3			# r3 = r2 * r3 (16位乘法)103ce:	4618      	mov	r0, r3					# r0 = r3 (返回值)103d0:	3710      	adds	r7, #16				# r7 = (r7 + 16) 103d2:	46bd      	mov	sp, r7					# sp = r7103d4:	bd80      	pop	{r7, pc}				# r7 、lr 出栈,lr 出栈赋值给 pc
  • push {r7, lr},保存 lr 和 r7
  • sub sp, #16,开辟 16 字节空间,即 func2 的参数大小 (sp = sp - 16)
  • add r7, sp, #0,更新 r7 栈底(r7 = sp + 0)

到这里,函数栈情况如下图:
在这里插入图片描述

TG : func2 参数为 12 字节,但是实际开辟大小为 16 字节,这与不同编译器对栈对齐、调用约定等原因有关

其中,恢复 func1 栈顶 sp

  • adds r7, #16
  • mov sp, r7

其中,恢复 func1 栈底 r7、pc(r7 = r7,pc = lr)

  • pop {r7, pc}

恢复后栈帧如下:
在这里插入图片描述
总的函数调用关系如下图:
在这里插入图片描述

4、关于编译器版本

较新版本的 gcc 编译器,编译出的栈帧逻辑和老版本的 gcc 编译器可能不同,例如:
针对 r7 寄存器,他不再是保存新栈帧的栈顶,而是调用者栈帧的栈底 r7 的值,这么做的原因是方便栈回溯。

    push {r7}          		#保存 r7 到栈上add  r7, sp, #0    		#将当前栈指针 sp 的值复制到 r7,作为帧指针sub  sp, sp, #16   		#为函数的局部变量分配 16 字节的栈空间

使用 gcc-7.3 默认选项编译,GNU 说使用 unwind 方法回溯,这里暂时不会介绍 unwind 方法。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

c++9月20日

1.思维导图 2.顺序表 头文件 #ifndef RECTANGLE_H #define RECTANGLE_H#include <iostream>using namespace std;using datatype int ;//类型重定义class Seqlist { private://私有权限datatype *ptr; //指向堆区申请空间的起始地址int size;//堆区空间的长度int len …

leetcode第十二题:整数转罗马数字

七个不同的符号代表罗马数字&#xff0c;其值如下&#xff1a; 符号值I1V5X10L50C100D500M1000 罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则&#xff1a; 如果该值不是以 4 或 9 开头&#xff0c;请选择可以从输入中减去的…

利用LRZ压缩与Base64编码实现高效文件上传

引言 在当今互联网时代&#xff0c;文件上传已成为众多在线服务不可或缺的一部分&#xff0c;尤其是在社交媒体平台上的照片分享和云存储服务中的文档管理等场景&#xff0c;高效且安全的文件上传机制对于保障用户体验至关重要。 为此&#xff0c;本文将介绍一种结合了LRZ压缩…

使用vite+react+ts+Ant Design开发后台管理项目(三)

前言 本文将引导开发者从零基础开始&#xff0c;运用、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈&#xff0c;构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导&#xff0c;文章旨在为开发者揭示如何利用这些技术工具…

VSCode环境下连接 MySQL 8.0 数据库 (C++)

前言 时隔了不知道多久&#xff0c;笔者需要在Windows环境下通过VSCode重新搭建一个简单的数据库连接的Cpp工程。由于VSCode和MySQL的版本和之前连通时发生了一些变化&#xff0c;无需用MySQL Connector&#xff0c;环境配置的细节和之前也不尽相同&#xff0c;因此笔者找了一…

简单有效关于msvcp140.dll丢失的解决方法,msvcp140.dll修复的方法原理及步骤

这篇文章将和大家分享几种msvcp140.dll丢失的解决方法&#xff0c;分析解决方法为什么能够通过这种方法进行修复成功&#xff0c;有效的将丢失的msvcp140.dll文件进行修复完成。 msvcp140.dll丢失&#xff1f;简单有效的解决途径 一、重新安装相关软件 原理 许多应用程序在安…

掌握Android开发新趋势:Jetpack与现代架构技术全解析

随着Android开发技术的不断进步&#xff0c;Jetpack和现代架构技术已成为构建高效、可维护应用的关键。本文将为您介绍一套全面的学习资料&#xff0c;包括大纲、PDF文档、源代码以及学习视频&#xff0c;帮助您深入理解Jetpack核心库、架构组件以及现代开发工具。 内容&#…

【C++】—— string模拟实现

前言&#xff1a; 学习了string的使用&#xff0c;总感觉了解不是很深厚&#xff1b;自己模拟实现string类来帮助自己理解。 这里只是实现了一部分内容&#xff08;并没有实现完整的string类&#xff09;。 先来实现string类里面的成员变量&#xff1a; #include<iostream…

草莓团队创造了o1 - Building OpenAI o1 (Extended Cut) 观后笔记

美妙的事物往往需要世界去创造&#xff0c;商业希望大模型越来越快给出回答。或许花费几个月几年的时间持续思考&#xff0c;大模型能够解决更复杂的问题&#xff0c;而不只是回答42 刚发现凌晨OpenAI发布了一个22多分钟的采访&#xff0c;将构建出O1的整个团队拉到一个小屋子&…

让Tkinter更美观:教你同步Tkinter窗口与弹窗图标(Tkinter同步主窗口与Messagebox的图标)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 步骤1:主窗口图标📝 步骤2:messagebox 图标📝 示例代码📝 实现原理与代码解释⚓️ 相关链接 ⚓️📖 介绍 📖 你有没有注意到,在开发软件图形界面时,会需要弹出一些提示框,而这些提示框的图标总…

695. 岛屿的最大面积

思路&#xff1a; 只有当前是陆地&#xff0c;才会构成岛屿 当前是陆地&#xff0c;进入回溯 往当前的上、下、左、右位置分别找陆地位置&#xff0c;为陆地 1>标记为2:代表已经遍历过的陆地 2>记录当前方向的陆地总数 以当前陆地组成的岛屿面积当前陆地面积向上的…

11----mtk芯片专用解锁工具 解除FRP 很小的工具 去除屏幕锁 免授权等等 工具预览与步骤解析

机型的FRP锁是谷歌账号锁。工具是mtk芯片使用 。可以去除当前机型的FRP和米账号重置。操作非常简单。但前提是联机驱动要装好。任何的工具联机驱动是关键。 工具功能选项 ★★★★★工具开发者说明功能与选项操作与资源下载 ★★★★★具体工具操作使用指南 工具联机界面与…

Type-C协议(CC检测原理)-CC1和CC2接电阻-数字和模拟耳机兼容

Type-C协议&#xff08;CC检测原理&#xff09;-CC1和CC2接电阻-数字和模拟耳机兼容 - Sean_hn - 博客园 (cnblogs.com)

4.变量与函数

作业系统链接 变量是Python中用于存储数据的命名标签&#xff0c;通过赋值&#xff0c;新值可以覆盖旧值&#xff0c;且数据类型不必相同。变量命名规则包括必须以字母或下划线开头&#xff0c;由字母、数字和下划线组成&#xff0c;大小写敏感&#xff0c;并避免使用保留字。函…

精选写作技巧!分享4款ai写毕业论文可以写出公式表格的软件

在撰写毕业论文时&#xff0c;AI写作工具可以极大地提高效率和质量。以下是四款值得推荐的AI软件&#xff0c;它们不仅能够帮助生成高质量的论文初稿&#xff0c;还能处理公式和表格等复杂内容。 传送门&#xff1a;https://www.aipaperpass.com?piclLGw 千笔-AIPassPaper是一…

Rx Strategist:智能体实现处方验证的方方面面,如适应症、剂量、药物相互作用

Rx Strategist&#xff1a;智能体实现处方验证的方方面面&#xff0c;如适应症、剂量、药物相互作用 秒懂大纲提出背景&#xff1a;拆解解法分析全流程分析创意 秒懂大纲 ├── 处方验证系统【主题】 │ ├── 背景和问题【问题描述】 │ │ ├── 现代药物复杂性对严…

2024年华为杯研究生数学建模竞赛研赛C题超详细解题思路+数据预处理代码分享

研赛题目由当年负责赛区【23年为东南大学&#xff0c;24年为山东大学】的相关老师每年独立命题&#xff0c;因此每年的题目在涉及的模型和知识点上都有很大差异。数模竞赛是百分比获奖&#xff0c;选择人数最多的也就意味着题目简单&#xff0c;会有很多新手、小白选择。我们只…

python有main函数吗

python和C/Java不一样&#xff0c;没有主函数一说&#xff0c;也就是说python语句执行不是从所谓的主函数main开始的。 当运行单个python文件时&#xff0c;如运行a.py&#xff0c;这个时候a的一个属性__name__是__main__。 当调用某个python文件时&#xff0c;如b.py调用a.p…

华为HarmonyOS地图服务 13 - 如何实现地图数据聚合效果?

场景介绍 本章节将向您介绍如何根据地图数据实现聚合效果。 您可以通过比例尺缩放自适应聚合效果,聚合图标可点击。聚合支持功能: 支持按距离聚合ClusterItem。支持绘制聚合Overlay的默认图标。支持绘制聚合Overlay的自定义图标。支持监听聚合Overlay的点击事件。支持添加单…

华为为什么要做三折叠屏手机?

前些天我做了一条视频&#xff0c;关于讲华W的新的三折叠屏手机。我说我有点失望&#xff0c;结果引起了华W的同事的一些关注。于是&#xff0c;华W几位高管都跑过来&#xff0c;跟我解释为什么会出现这样的一个状态。 我才知道&#xff0c;这款手机他们其实是亏着钱在卖的。因…