arm 函数栈回溯

 大概意思就是arm每个函数开始都会将PC、LR、SP以及FP四个寄存器入栈。

 下面我们看一下这四个寄存器里面保存的是什么内存

arm-linux-gnueabi-gcc unwind.c -mapcs -w -g -o unwind(需要加上-mapcs才会严格按照上面说的入栈)

#include <stdio.h>
#include <stdlib.h>struct stackframe {unsigned long fp;//低地址unsigned long sp;unsigned long lr;unsigned long pc;//高地址
};
void backtrace() {struct stackframe *frame = NULL;unsigned long *sp = NULL;asm volatile ("mov %0, ip" : "=g"(sp));//ip里面保存的是还未压栈的spprintf("sp poniter 0x%lx\n", sp);frame = (char*)sp - sizeof(struct stackframe);printf("fp 0x%lx, pc 0x%lx, sp 0x%lx\n", frame->fp,frame->pc, frame->sp);//通过打印栈帧里面的sp确实和ip里面的一样的/* 不知道怎么结束循环.... */for (; frame->fp < 0xdeadbeef; frame = frame->fp - sizeof(struct stackframe) + sizeof(unsigned long)) {printf("Function enter at [<%08x>] from [<%08x>]\n", frame->pc, frame->lr);}
}void f3(int c) {printf("%d\n", c);backtrace();
}
void f2(int b) {f3(b);
}
void f1(int a) {char arr[5] = {0};f2(a);
}int main(int argc, char *argv[]) {printf("programe %s\n", argv[0]);f1(1);return 0;
}

arm-linux-gnueabi-objdump -S unwind > objdump 

void backtrace() {8500:	e1a0c00d 	mov	ip, sp8504:	e92dd810 	push	{r4, fp, ip, lr, pc}8508:	e24cb004 	sub	fp, ip, #4850c:	e24dd00c 	sub	sp, sp, #12
......................................000085c0 <f3>:void f3(int c) {85c0:	e1a0c00d 	mov	ip, sp85c4:	e92dd800 	push	{fp, ip, lr, pc}85c8:	e24cb004 	sub	fp, ip, #485cc:	e24dd008 	sub	sp, sp, #8
........................................85dc:	ebffff6b 	bl	8390 <_init+0x20>backtrace();85e0:	ebffffc6 	bl	8500 <backtrace>
}85e4:	e24bd00c 	sub	sp, fp, #1285e8:	e89da800 	ldm	sp, {fp, sp, pc}85ec:	0000878c 	.word	0x0000878c000085f0 <f2>:
void f2(int b) {85f0:	e1a0c00d 	mov	ip, sp85f4:	e92dd800 	push	{fp, ip, lr, pc}85f8:	e24cb004 	sub	fp, ip, #485fc:	e24dd008 	sub	sp, sp, #88600:	e50b0010 	str	r0, [fp, #-16]f3(b);8604:	e51b0010 	ldr	r0, [fp, #-16]8608:	ebffffec 	bl	85c0 <f3>
}860c:	e24bd00c 	sub	sp, fp, #128610:	e89da800 	ldm	sp, {fp, sp, pc}00008614 <f1>:
void f1(int a) {8614:	e1a0c00d 	mov	ip, sp8618:	e92dd800 	push	{fp, ip, lr, pc}861c:	e24cb004 	sub	fp, ip, #48620:	e24dd018 	sub	sp, sp, #24
..........................................f2(a);8644:	e51b0020 	ldr	r0, [fp, #-32]8648:	ebffffe8 	bl	85f0 <f2>
}864c:	e59f3018 	ldr	r3, [pc, #24]	; 866c <f1+0x58>00008670 <main>:int main(int argc, char *argv[]) {8670:	e1a0c00d 	mov	ip, sp8674:	e92dd800 	push	{fp, ip, lr, pc}8678:	e24cb004 	sub	fp, ip, #4867c:	e24dd008 	sub	sp, sp, #88680:	e50b0010 	str	r0, [fp, #-16]8684:	e50b1014 	str	r1, [fp, #-20]printf("programe %s\n", argv[0]);8688:	e51b3014 	ldr	r3, [fp, #-20]868c:	e5933000 	ldr	r3, [r3]8690:	e59f001c 	ldr	r0, [pc, #28]	; 86b4 <main+0x44>8694:	e1a01003 	mov	r1, r38698:	ebffff3c 	bl	8390 <_init+0x20>f1(1);869c:	e3a00001 	mov	r0, #186a0:	ebffffdb 	bl	8614 <f1>return 0;86a4:	e3a03000 	mov	r3, #0
}

 上面是样例代码对应的汇编代码截取。在函数的最开头都存在如下代码

    8500:	e1a0c00d 	mov	ip, sp8504:	e92dd810 	push	{r4, fp, ip, lr, pc}8508:	e24cb004 	sub	fp, ip, #4

 就是文章最开始说的函数一开始都会将fp、sp、lr以及pc压栈。那这几个寄存器里面的内容是什么呢?

sp即栈顶指针,sp里面记录的是当前函数的栈顶位置;并且从汇编代码里面能看到先是将sp给ip,然后将ip入栈。因此栈中记录的sp位置是压栈之前的

lr用于保存函数的返回地址(若f2调用f3,那在样例代码中对应的位置就是这一行8558:    e89da800     ldm    sp, {fp, sp, pc}

pc指针,程序计数器,用于记录当前执行到哪条指令。但是由于ARM采用流水线机制。当正确读取PC时,该值为当前指令(正在执行的指令)地址+8个字节。即PC执行当前指令的下两条地址。所以这就解释了样例代码的打印是0000850c

void backtrace() {
    8500:    e1a0c00d     mov    ip, sp
    8504:    e92dd810     push    {r4, fp, ip, lr, pc}//执行到这里时,pc里面记录的是下面两条指令
    8508:    e24cb004     sub    fp, ip, #4
    850c:    e24dd00c     sub    sp, sp, #12
......................................

具体可以查看这篇文章

ARM体系结构相关杂记_这个我好像学过的博客-CSDN博客

fp:frame pointer:同样也是这段代码。sub    fp, ip, #4// fp = ip - 4。那fp其实保存的就是上一个函数的函数栈起始位置-4。这也是for循环里面下一个函数栈需要写为

for (;; frame = frame->fp - sizeof(struct stackframe) + sizeof(unsigned long))

即下一个函数栈是fp + 4 - 12

为什么是上一个函数栈呢?

我们看下面的代码f1调用f2。函数f2最开始压入的fp,这个fp寄存器里面记录的是什么值呢。它里面其实就是上一个函数里面的sub    fp, ip, #4得到的啊。ip里面又是上一个函数f1的函数栈开始位置。

000085f0 <f2>:
void f2(int b) {85f0:	e1a0c00d 	mov	ip, sp85f4:	e92dd800 	push	{fp, ip, lr, pc}85f8:	e24cb004 	sub	fp, ip, #485fc:	e24dd008 	sub	sp, sp, #88600:	e50b0010 	str	r0, [fp, #-16]f3(b);8604:	e51b0010 	ldr	r0, [fp, #-16]8608:	ebffffec 	bl	85c0 <f3>
}860c:	e24bd00c 	sub	sp, fp, #128610:	e89da800 	ldm	sp, {fp, sp, pc}00008614 <f1>:
void f1(int a) {8614:	e1a0c00d 	mov	ip, sp8618:	e92dd800 	push	{fp, ip, lr, pc}861c:	e24cb004 	sub	fp, ip, #48620:	e24dd018 	sub	sp, sp, #24
..........................................f2(a);8644:	e51b0020 	ldr	r0, [fp, #-32]8648:	ebffffe8 	bl	85f0 <f2>
}864c:	e59f3018 	ldr	r3, [pc, #24]	; 866c <f1+0x58>

因此最终的函数栈构成了下图所示。那我怎么感觉文章开始的那张图片是错的呢。。。。

 最后样例代码运行结果如下图。由于不知道怎么算回溯结束,所以程序报错了

另外程序打印出来的地址也会汇编代码吻合.具体可以看汇编信息

 

 另外用arm-linux-gnueabi-addr2line解析出来的行号也是准确的

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

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

相关文章

Android 面试题 线程间通信 六

&#x1f525; 主线程向子线程发送消息 Threadhandler&#x1f525; 子线程中定义Handler&#xff0c;Handler定义在哪个线程中&#xff0c;就跟那个线程绑定&#xff0c;在线程中绑定Handler需要调用Looper.prepare(); 方法&#xff0c;主线程中不调用是因为主线程默认帮你调用…

怎么在线修改图片?分享一个图片修改工具

无论是在个人或商业领域&#xff0c;我们都需要使用高质量的图片来传达信息或提高品牌形象。大尺寸的图片也会占据大量的存储空间和带宽&#xff0c;影响网站的加载速度和用户体验。因此&#xff0c;我们需要一种高效的工具来解决这个问题。今天向大家介绍一款非常实用的图片处…

Abaqus 导出单元刚度矩阵和全局刚度矩阵

Abaqus 导出单元刚度矩阵和全局刚度矩阵 首次创建&#xff1a;2023.7.29 最后更新&#xff1a;2023.7.29 如有什么改进的地方&#xff0c;欢迎大家讨论&#xff01; 详细情况请查阅&#xff1a;Abaqus Analysis User’s Guide 一、Abaqus 导出单元刚度矩阵 1.生成单元刚度矩阵…

Linux CentOS快速安装VNC并开启服务

以下是在 CentOS 上安装并开启 VNC 服务的步骤&#xff1a; 安装 VNC 服务器软件包。运行以下命令&#xff1a; sudo yum install tigervnc-server 输出 $ sudo yum install tigervnc-server Loaded plugins: fastestmirror, langpacks Repository epel is missing name i…

教雅川学缠论04-笔

笔由3部分组成&#xff1a; 顶分型K线底分型&#xff0c;或者 底分型K线顶分型 注意&#xff1a;笔加一起至少7根K线&#xff0c;因为一个底分型至少3根&#xff0c;K先至少1个&#xff0c;顶分型至少3根 下图中红色线段就是一个标准的笔&#xff0c;它始于一个底分型&#xff…

Visual C++中的虚函数和纯虚函数(以外观设计模式为例)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来说说Visual C中的虚函数和纯虚函数。该系列帖子全部使用我本人自创的对比学习法。也就是当C学不下去的时候&#xff0c;就用JAVA实现同样的代码&#xff0c;然后再用对比的方法把C学会。 直接说虚函数…

【SpringCloud Alibaba】(四)使用 Feign 实现服务调用的负载均衡

在上一文中&#xff0c;我们实现了服务的自动注册与发现功能。但是还存在一个很明显的问题&#xff1a;如果用户微服务和商品微服务在服务器上部署多份的话&#xff0c;之前的程序无法实现服务调用的负载均衡功能。 本文就带着大家一起实现服务调用的负载均衡功能 1. 负载均衡…

LayUi 树形组件tree 实现懒加载模式,展开父节点时异步加载子节点数据

如题。 效果图&#xff1a; //lazy属性为true&#xff0c;点开时才加载 引用代码&#xff1a; <link href"~/Content/layui-new/css/layui.css" rel"stylesheet" /><form id"form" class"layui-form" style"margin-to…

AC695-按键处理-带UI

AC695-按键修改 消息发出 对应界面处理

【算法和数据结构】257、LeetCode二叉树的所有路径

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;首先看这道题的输出结果&#xff0c;是前序遍历。然后需要找到从根节点到叶子节点的所有路径&#xff…

【图论】树上差分(点差分)

一.题目 输入样例&#xff1a; 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 4 3 4 3 1 3 3 5 5 4 1 5 3 4 输出样例&#xff1a;9 二 .分析 我们可以先建一棵树 但我们发现&#xff0c;这样会超时。 所以&#xff0c;我们想到树上差分 三.代码 /* 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 …

IOS UICollectionView 设置cell大小不生效问题

代码设置flowLayout.itemSize 单元格并没有改变布局大小&#xff0c; 解决办法如下图&#xff1a;把View flow layout 的estimate size 设置为None&#xff0c;上面设置的itemSize 生效了。

物联网阀控水表计量准确度如何?

物联网阀控水表是一种新型的智能水表&#xff0c;它采用了先进的物联网技术&#xff0c;可以通过远程控制和监测水表的运行情况&#xff0c;实现更加精准的水量计量和费用结算。那么&#xff0c;物联网阀控水表的计量准确度如何呢&#xff1f;下面我们将从以下几个方面进行详细…

PHP 3des加解密新旧方法可对接加密

一、旧3des加解密方法 <?php class Encrypt_3DES {//加密秘钥&#xff0c;private $_key;private $_iv;public function __construct($key, $iv){$this->_key $key;$this->_iv $iv;}/*** 对字符串进行3DES加密* param string 要加密的字符串* return mixed 加密成…

【SpringⅢ】Spring 的生命周期

目录 &#x1f96a;1 Bean 的作用域 &#x1f969;1.1 singleton&#xff1a;单例模式 &#x1f359;1.2 prototype&#xff1a;原型模式 &#x1f371;1.3 Bean 的其他作用域 &#x1f35c;2 Spring 生命周期(执行流程) &#x1f958;2.1 启动容器 &#x1f372; 2.2 读…

[小尘送书-第二期]《从零开始读懂量子力学》由浅入深,解释科学原理;从手机到超导,量子无处不在;从微观到宏观,遐想人生的意义!

大家好&#xff0c;我是小尘&#xff0c;欢迎关注&#xff0c;一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; 本文目录 一、前言二、作者简介三、内容简介四、抽奖方式五、名家推介写在最后 一、前…

[containerd] 在Windows上使用IDEA远程调试containerd, ctr, containerd-shim

文章目录 1. containerd安装2. 源码编译3. 验证编译的二进制文件是否含有调试需要的信息3.1. objdump工具验证3.2. file工具验证3.3. dlv工具验证 4. debug 1. containerd安装 [Ubuntu 22.04] 安装containerd 2. 源码编译 主要步骤如下&#xff1a; 1、从github下载containe…

[Java] 单例设计模式详解

模式定义&#xff1a;保证一个类只有一个实例&#xff0c;并且提供一个全局访问点&#xff0c;时一种创建型模式 使用场景&#xff1a;重量级的对象&#xff0c;不需要多个实例&#xff0c;如线程池&#xff0c;数据库连接池 单例设计模式的实现 1.懒汉模式&#xff1a;延迟…

第一次后端复习整理(JVM、Redis、反射)

1. JVM 文章仅为自身笔记 详情查看一篇文章掌握整个JVM&#xff0c;JVM超详细解析&#xff01;&#xff01;&#xff01; 1.1 什么是JVM jvm是Java虚拟机 1.2 Java文件的编译过程 程序员编写代码形成.java文件经过javac编译成.class文件再通过JVM的类加载器进入运行时数据…

【141. 环形链表】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#x…