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里面保存的是还未压栈的sp
    printf("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, sp
    8504:	e92dd810 	push	{r4, fp, ip, lr, pc}
    8508:	e24cb004 	sub	fp, ip, #4
    850c:	e24dd00c 	sub	sp, sp, #12
......................................

000085c0 <f3>:

void f3(int c) {
    85c0:	e1a0c00d 	mov	ip, sp
    85c4:	e92dd800 	push	{fp, ip, lr, pc}
    85c8:	e24cb004 	sub	fp, ip, #4
    85cc:	e24dd008 	sub	sp, sp, #8
........................................
    85dc:	ebffff6b 	bl	8390 <_init+0x20>
	backtrace();
    85e0:	ebffffc6 	bl	8500 <backtrace>
}
    85e4:	e24bd00c 	sub	sp, fp, #12
    85e8:	e89da800 	ldm	sp, {fp, sp, pc}
    85ec:	0000878c 	.word	0x0000878c

000085f0 <f2>:
void f2(int b) {
    85f0:	e1a0c00d 	mov	ip, sp
    85f4:	e92dd800 	push	{fp, ip, lr, pc}
    85f8:	e24cb004 	sub	fp, ip, #4
    85fc:	e24dd008 	sub	sp, sp, #8
    8600:	e50b0010 	str	r0, [fp, #-16]
	f3(b);
    8604:	e51b0010 	ldr	r0, [fp, #-16]
    8608:	ebffffec 	bl	85c0 <f3>
}
    860c:	e24bd00c 	sub	sp, fp, #12
    8610:	e89da800 	ldm	sp, {fp, sp, pc}

00008614 <f1>:
void f1(int a) {
    8614:	e1a0c00d 	mov	ip, sp
    8618:	e92dd800 	push	{fp, ip, lr, pc}
    861c:	e24cb004 	sub	fp, ip, #4
    8620:	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, sp
    8674:	e92dd800 	push	{fp, ip, lr, pc}
    8678:	e24cb004 	sub	fp, ip, #4
    867c:	e24dd008 	sub	sp, sp, #8
    8680:	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, r3
    8698:	ebffff3c 	bl	8390 <_init+0x20>
	f1(1);
    869c:	e3a00001 	mov	r0, #1
    86a0:	ebffffdb 	bl	8614 <f1>
	return 0;
    86a4:	e3a03000 	mov	r3, #0
}

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

    8500:	e1a0c00d 	mov	ip, sp
    8504:	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, sp
    85f4:	e92dd800 	push	{fp, ip, lr, pc}
    85f8:	e24cb004 	sub	fp, ip, #4
    85fc:	e24dd008 	sub	sp, sp, #8
    8600:	e50b0010 	str	r0, [fp, #-16]
	f3(b);
    8604:	e51b0010 	ldr	r0, [fp, #-16]
    8608:	ebffffec 	bl	85c0 <f3>
}
    860c:	e24bd00c 	sub	sp, fp, #12
    8610:	e89da800 	ldm	sp, {fp, sp, pc}

00008614 <f1>:
void f1(int a) {
    8614:	e1a0c00d 	mov	ip, sp
    8618:	e92dd800 	push	{fp, ip, lr, pc}
    861c:	e24cb004 	sub	fp, ip, #4
    8620:	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解析出来的行号也是准确的

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

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

相关文章

Flutter 开发者工具 Android Studio 开发Flutter应用

Flutter 开发者工具 在 Android Studio 开发Flutter应用 &#x1f525; Android Studio 版本更新 &#x1f525; Android Studio Check for Update Connection failed ​ 解决方案 如果是运行的是32位的android studio需要在andriod studio的启动目录下找到studio.exe.vmoptio…

Flutter-基础Widget

Flutter页面-基础Widget 文章目录 Flutter页面-基础WidgetWidgetStateless WidgetStateful WidgetState生命周期 基础widget文本显示TextRichTextDefaultTextStyle 图片显示FlutterLogoIconImageIamge.assetImage.fileImage.networkImage.memory CircleAvatarFadeInImage 按钮R…

抖音账号矩阵系统开发源码

一、技术自研框架开发背景&#xff1a; 抖音账号矩阵系统是一种基于数据分析和管理的全新平台&#xff0c;能够帮助用户更好地管理、扩展和营销抖音账号。 部分源码分享&#xff1a; ic function indexAction() { //面包屑 $breadcrumbs [ [tit…

【雕爷学编程】MicroPython动手做(13)——掌控板之RGB三色灯2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

Java另一种debug方法(not remote jmv debug),类似python远程debug方式

这种Debug类似python的debug方式&#xff0c;是运行时将业务代码及依赖推送到Linux并使用Linux的java运行运行程。只要本地能运行&#xff0c;就能自动将代码推送到Linux运行&#xff0c;不需打包及设置远程debug jvm参数&#xff0c;适合一些项目Debug调试 运行时会推送一些依…

67. 二进制求和

题目链接&#xff1a;力扣 解题思路&#xff1a;模拟十进制中的列竖式方法进行计算&#xff0c;逢二进一&#xff0c;因为高位在前&#xff0c;低位在后&#xff0c;两个二进制长度不一定相等&#xff0c;可以取两者长度的较大值&#xff0c;从后面开始遍历两个字符串&#xff…

【算法基础:动态规划】5.3 计数类DP(整数拆分、分拆数)

文章目录 例题&#xff1a;900. 整数划分解法1——完全背包解法2——分拆数⭐⭐⭐ 例题&#xff1a;900. 整数划分 https://www.acwing.com/problem/content/902/ 解法1——完全背包 容量是 n&#xff0c;物品的大小和价值是 1 ~ n 中的所有数字。 import java.util.*;pub…

Echarts 文字太长用省略号代替

xAxis: [{type: category,data: [materialUserEchartsDate.value[0] ? materialUserEchartsDate.value[0].name : ,materialUserEchartsDate.value[1] ? materialUserEchartsDate.value[1].name : ,materialUserEchartsDate.value[2] ? materialUserEchartsDate.value[2].na…

RabbitMQ 集群部署

RabbiMQ 是用 Erlang 开发的,集群非常方便,因为 Erlang 天生就是一门分布式语言,但其本身并不支持负载均衡。 RabbitMQ 的集群节点包括内存节点、磁盘节点。RabbitMQ 支持消息的持久化,也就是数据写在磁盘上,最合适的方案就是既有内存节点,又有磁盘节点。 RabbitMQ 模式大…

Kibana+Prometheus+node_exporter 监控告警部署

下载好三个软件包 一、prometheus安装部署 1、解压 linxxubuntu:~/module$ tar -xvf prometheus-2.45.0-rc.0.linux-amd64.tar.gz 2、修改配置文件的IP地址 # my global config global:scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is ever…

Eclipse memory analyzer 分析GC dump日志定位代码问题

1、问题描述&#xff1a; 使用命令 jstat -gcutil [pid] 查看JVM GC日志&#xff0c;发现生产系统频繁FullGC&#xff0c;大概几分钟一次&#xff0c;而且系统响应速度变慢很多 再使用 free -g 查看服务器内存全部占用&#xff0c;猜测是内存溢出了 2、导出dump日志 jmap -du…

修改整数(有点坑,所以发出来了)

问题描述 小贝给了小聪一个正整数 x&#xff0c;但是小聪决定把这个数改掉。她可以把整数 x 每个位置上的数 t 改成 9-t。 请你帮助小聪来计算一下&#xff0c;如何把 x 改成一个最小的正整数&#xff0c;注意&#xff0c;不能出现首位为 0 的情况。 输入格式 输入一个正整数…

Flowable-中间事件-消息中间抛出事件

定义 消息中间事件指在流程中将一个消息事件作为独立的节点来运行。它是一种抛出事件&#xff0c;当流程 执行到消息中间事件时就会中断在这里&#xff0c;一直等待被触发&#xff0c;直接到该事件接收到相应的消息后&#xff0c;流 程沿后继路线继续执行。消息事件是一种引用…

6门新兴语言,小众亦强大

编码语言在塑造我们创建软件的方式方面起着至关重要的作用。多年来&#xff0c;我们观察到Python&#xff0c;Java和C等成熟语言的流行。然而&#xff0c;如今一波新的编码语言浪潮已经出现&#xff0c;提出了创造性的解决方案&#xff0c;并推动了软件工程领域所能完成的极限。…

Redis学习路线(6)—— Redis的分布式锁

一、分布式锁的模型 &#xff08;一&#xff09;悲观锁&#xff1a; 认为线程安全问题一定会发生&#xff0c;因此在操作数据之前先获取锁&#xff0c;确保线程串行执行。例如Synchronized、Lock都属于悲观锁。 优点&#xff1a; 简单粗暴缺点&#xff1a; 性能略低 &#x…

如何在3ds max中创建可用于真人场景的巨型机器人:第 5 部分

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. After Effects 中的项目设置 步骤 1 打开“后效”。 打开后效果 步骤 2 我有真人版 我在After Effects中导入的素材。这是将 用作与机器人动画合成的背景素材。 实景镜头 步骤 3 有背景 选定的素材…

MybatisPlus拓展篇

文章目录 逻辑删除通用枚举字段类型处理器自动填充功能防全表更新与删除插件MybatisX快速开发插件插件安装逆向工程常见需求代码生成 乐观锁问题引入乐观锁的使用效果测试 代码生成器执行SQL分析打印多数据源 逻辑删除 逻辑删除的操作就是增加一个字段表示这个数据的状态&…

uni-app点击按钮弹出提示框(以弹窗的形式显示),选择确定和取消

学习目标&#xff1a; 学习目标如下所示&#xff1a; uni-app点击提交按钮后弹出提示框&#xff0c;&#xff08;以弹窗的形式显示&#xff09;,提示用户是否确认提交&#xff08;即确定和取消&#xff09;&#xff0c;点击确定后调用真正的提交方法&#xff0c;将数据传给后端…

【基于矢量射线的衍射积分 (VRBDI)】基于矢量射线的衍射积分 (VRBDI) 和仿真工具(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

文心一言大数据模型-文心千帆大模型平台

官网&#xff1a; 文心千帆大模型平台 (baidu.com) 文心千帆大模型 (baidu.com) 模型优势 1、模型效果优&#xff1a;所需标注数据少&#xff0c;在各场景上的效果处于业界领先水平 2、生成能力强&#xff1a;拥有丰富的AI内容生成&#xff08;AIGC&#xff09;能力 3、应用…