ARM 栈和函数调用

news2024/9/22 7:11:19

阅读本文前,可以先阅读下述文档,对函数栈、栈帧等的概念会有所了解,会对本文章的理解大有益处
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, #0
   1039c:	bf00      	nop
   1039e:	46bd      	mov	sp, r7
   103a0:	bc80      	pop	{r7}
   103a2:	4770      	bx	lr

000103a4 <func2>:
   103a4:	b580      	push	{r7, lr}
   103a6:	b084      	sub	sp, #16
   103a8:	af00      	add	r7, sp, #0
   103aa:	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, r3
   103b4:	68bb      	ldr	r3, [r7, #8]
   103b6:	b2db      	uxtb	r3, r3
   103b8:	fb12 f303 	smulbb	r3, r2, r3
   103bc:	b2da      	uxtb	r2, r3
   103be:	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, r3
   103ce:	4618      	mov	r0, r3
   103d0:	3710      	adds	r7, #16
   103d2:	46bd      	mov	sp, r7
   103d4:	bd80      	pop	{r7, pc}

000103d6 <main>:
   103d6:	b580      	push	{r7, lr}
   103d8:	b082      	sub	sp, #8
   103da:	af00      	add	r7, sp, #0
   103dc:	1cfb      	adds	r3, r7, #3
   103de:	461a      	mov	r2, r3
   103e0:	2102      	movs	r1, #2
   103e2:	2001      	movs	r0, #1
   103e4:	f7ff ffde 	bl	103a4 <func2>
   103e8:	6078      	str	r0, [r7, #4]
   103ea:	2300      	movs	r3, #0
   103ec:	4618      	mov	r0, r3
   103ee:	3708      	adds	r7, #8
   103f0:	46bd      	mov	sp, r7
   103f2:	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 + 0
   1039c:	bf00      	nop
   1039e:	46bd      	mov	sp, r7					# sp = r7
   103a0:	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 - 16
   103a8:	af00      	add	r7, sp, #0				# r7 = sp + 0
   103aa:	60f8      	str	r0, [r7, #12]			# (r7 + 12) = r0
   103ac:	60b9      	str	r1, [r7, #8]			# (r7 + 8) = r1
   103ae:	607a      	str	r2, [r7, #4]			# (r7 + 4) = r2
   103b0:	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 * r3
   103bc:	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 = r7
   103d4:	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.coloradmin.cn/o/2154523.html

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

相关文章

WebRTC编译后替换libwebrtc.aar时提示找不到libjingle_peerconnection_so.so库

Loading native library: jingle_peerconnection_so 问题原因&#xff1a;编译的时候只编译了armeabi-v7a的版本&#xff0c;但是应用程序是arm64-v8a&#xff0c;所以无法运行 解决方法&#xff1a;更新编译脚本&#xff0c;加上arm64-v8a进行编译 ./tools_webrtc/android/bu…

OpenAI GPT o1技术报告阅读(5)-安全性对齐以及思维链等的综合评估与思考

✨继续阅读报告&#xff1a;使用大模型来学习推理(Reason) 原文链接&#xff1a;https://openai.com/index/learning-to-reason-with-llms/ 编码 我们训练了一个模型&#xff0c;在2024年国际信息学奥林匹克竞赛&#xff08;IOI&#xff09;中得分213分&#xff0c;排名在第…

Arthas sysenv(查看JVM的环境变量)

文章目录 二、命令列表2.1 jvm相关命令2.1.5 sysenv&#xff08;查看JVM的环境变量&#xff09;举例1&#xff1a;sysenv 查看所有环境变量举例2&#xff1a;sysenv java.version 查看单个属性&#xff0c;支持通过tab补全 二、命令列表 2.1 jvm相关命令 2.1.5 sysenv&#x…

saas收银系统源码

1. 线下门店多样化收银 ①门店有社区小店、也会有大店&#xff0c;甚至还会有夫妻店&#xff0c;同时还要有Windows版和安卓版&#xff0c;需满足不同门店的收银需求。 ②支持Windows收银、安卓收银、无人自助收银、聚合码收银等&#xff0c;支持ai智能称重、收银称重一体机等…

Unity3D入门(二) :Unity3D实现视角的丝滑过渡切换

1. 前言 上篇文章&#xff0c;我们已经初步了解了Unity3D&#xff0c;并新建并运行起来了一个项目&#xff0c;使相机视角自动围绕着立方体旋转。 这篇文章&#xff0c;我们来讲一下Unity3D怎么过渡地切换视角。 我们继续是我上篇文章中的项目&#xff0c;但是需要向把Camera…

Qt Debugging帮助文档

Qt中给断点添加条件&#xff1a; 示例1&#xff1a; 当i10时&#xff0c;程序中断 但不知道为什么&#xff0c;46行的条件没有生效&#xff0c;47行的条件生效了 给断点添加忽略次数&#xff1a; 在程序停止之前忽略该断点200次。 Breakpoints (Debugging with GDB)

AI 时代的网络危机沟通计划

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Go-知识recover

Go-知识recover 1. 介绍2. 工作机制2.1 recover 定义2.2 工作流程2.3 总结 3. 原理3.1 recover函数的真正逻辑3.2 恢复逻辑3.3 生效条件 4. 总结4.1 recover的返回值是什么&#xff1f;4.2 执行recover之后程序将从哪里继续运行&#xff1f;4.3 recover为什么一定要在defer中使…

2024年信息学奥赛CSP-J1入门组初赛真题试卷

2024年信息学奥赛CSP-J1入门组初赛真题试卷 题目总数&#xff1a;20 总分数&#xff1a;100 选择题 第 1 题 单选题 32位int类型的存储范围是&#xff08; &#xff09; A. -2147483647 ~ 2147483647 B. -2147483647 ~ 2147483648 C. -2147483648 ~ 2147483647…

如何使用 React、TypeScript、TailwindCSS 和 Vite 创建 Chrome 插件

创建一个 Chrome 插件是一个有趣的项目&#xff0c;特别是当结合使用强大的工具如 React、TypeScript、TailwindCSS 和 Vite 时 在这篇文章中&#xff0c;我们将逐步引导完成整个过程&#xff0c;了解如何在 2024 年构建自己的 Chrome 插件。无论是经验丰富的开发者还是刚刚起…

C++ | Leetcode C++题解之第423题从英文中重建数字

题目&#xff1a; 题解&#xff1a; class Solution { public:string originalDigits(string s) {unordered_map<char, int> c;for (char ch: s) {c[ch];}vector<int> cnt(10);cnt[0] c[z];cnt[2] c[w];cnt[4] c[u];cnt[6] c[x];cnt[8] c[g];cnt[3] c[h] - …

JavaScript使用leaflet库显示信息窗口

前言 我们可千万不能忘记我们之前花的流程图哦&#xff0c;我们所有的计划都按照我们的流程图来去构建&#xff1b; 我们已经完成了&#xff0c;页面的加载&#xff0c;也已经完成获取用户当前的位置坐标&#xff0c;并且我们通过地图的API将当前的位置在地图中渲染出来&…

【每日刷题】Day128

【每日刷题】Day128 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 606. 根据二叉树创建字符串 - 力扣&#xff08;LeetCode&#xff09; 2. LCR 194. 二叉树的最近公…

TryHackMe 第3天 | Pre Security (中)

该学习路径讲解了网络安全入门的必备技术知识&#xff0c;比如计算机网络、网络协议、Linux命令、Windows设置等内容。上一篇中简短介绍了计算机网络相关的知识&#xff0c;本篇博客将记录 网络协议 部分。 How the web works? DNS in detail DNS (Domain name system&…

【数据结构】排序算法系列——快速排序(附源码+图解)

快速排序 接下来我们将要介绍的是排序中最为重要的算法之一——快速排序。 快速排序&#xff08;英语&#xff1a;Quicksort&#xff09;&#xff0c;又称分区交换排序&#xff08;partition-exchange sort&#xff09;&#xff0c;最早由东尼霍尔提出。快速排序通常明显比其…

XXL-JOB环境搭建

2.快速入门 2.1 下载源码 a.源码下载地址: github地址 gitee地址 2.2.环境搭建&#xff1a; a.初始化调度数据库: 1.请下载项目源码并解压&#xff0c;获取 “调度数据库初始化SQL脚本” 并执行即可 b.编译源码: 1.解压源码,按照maven格式将源码导入IDE, 使用maven进行…

【Python】使用国内镜像安装conda并创建python环境

conda介绍&#xff1a; Conda 是一个开源的包管理系统和环境管理系统&#xff0c;由 Continuum Analytics 开发。它的主要作用是简化科学计算中软件包和依赖的安装和升级&#xff0c;并允许用户轻松地在不同的环境中切换。Conda 的设计初衷是为了简化 Python 环境的搭建和管理&…

海洋大地测量基准与水下导航系列之二国外海底大地测量基准和海底观测网络发展现状(上)

海底大地控制网建设构想最先由美国斯克里普斯海洋研究所(Scripps Institution of Oceanography,SIO)提出&#xff0c;目前仅有少数发达国家具备相应技术条件。美国、日本、俄罗斯和欧盟等发达国家通过布测先进的海底大地控制网&#xff0c;不断完善海洋大地测量基准基础设施&am…

go 运行报错missing go.sum entry for module providing package

运行&#xff1a; #清理go.mod中不再需要的模块&#xff0c;并且会添加缺失的模块条目到go.sum中 go mod tidy

【全网最全】2024华为杯数学建模C题高质量成品查看论文!【附带全套代码+数据】

题 目&#xff1a; ___基于数据驱动下磁性元件的磁芯损耗建模 完整版获取&#xff1a; 点击链接加入群聊【2024华为杯数学建模助攻资料】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kxtS4vwn3gcv8oCYYyrqd0BvFc7tNfhV7&authKeyedQFZne%2BzvEfLEVg2v8FOm%…