ARM Linux 手写实现栈回溯

自己动手实现arm函数栈帧回溯 阅读详情

        在开发中遇到这样一个问题,程序要跑很久才会出现一次崩溃,由于系统有硬件看门狗,因此程序在崩溃时coredump文件都没生成就已经重启了,这对定位程序bug代价严重。gdb可以对程序错误有很好的定位,因此为了模仿gdb这种从出错函数回溯的功能,于是想实现能否程序中也能实现这个。

       一开始,想看gdb源码,但是真的太多太复杂了(不是一般人看的,神人看的)。后来就探究Linux内核是如何判断程序已经跑飞然后产生信号的,我能不能像gdb那样截取这个信号,并从拿到出错的函数的地址。因此经过一番栈帧的探究,最后发现程序中的函数关系是如下图示的。

上图所示是程序函数的调用关系(fp指针模式)下的示意图,在编译程序时我们一般用-O2进行编译,此时fp指针模式将不存在,因此我们需要手动添加编译、链接条件。

-rdynamic -funwind-tables -ffunction-sections -fno-omit-frame-pointer

如果不添加以上编译条件的话,将不能使用该fp模式进行栈回溯。

在ARM Linux中,当前函数的指令地址、函数返回地址、栈顶、FP指针,其分别由PC(R15)、LR(R14)、SP(R13)、FP(R11)寄存器分别保存,因此如果想要获取出错函数的地址,获取当前出错函数的R0-R15一组寄存器值便可得到对应的调用关系的中起始函数调用地址。其中当前函数栈的大小为:size = SP-FP;FP寄存器的值指向存放上一个函数PC(也即是上一个函数的入口),因此可通过这种关系我们就能一步一步的进行函数追溯了。

当前PC所指向的是当前函数入口地址,fp值所指向的是上一函数入口地址,fp值-4是上一个函数的fp,再由上一个fp往上一层追溯,直到超过线程栈最大8M的地址空间停止,这样就完成栈的追溯了。

现在问题来了,在程序发生段错误或其它信号时,该如何截获该信号?又如何获取上述一组寄存器信息呢?

回答:我的解决思路是,稍微修改内核,然后程序通过内核模块接口读出寄存器组信息。

首先在内核信号处理文件signal.c中的handle_signal函数内,在内核检测到程序段错误或其它错误时,在未切换寄存器组信息时,保存当前出错函数对应的PC、SP、FP、LR信息,如下示。

struct pt_regs gStackEnvs;
EXPORT_SYMBOL(gStackEnvs);

static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
	sigset_t *oldset = sigmask_to_save();
	int ret;
    /*过滤信号,只保存设定信号时的寄存器组信息*/
	if(ksig->info.si_signo == SIGSEGV ||ksig->info.si_signo == SIGABRT ||
		ksig->info.si_signo == SIGFPE ||ksig->info.si_signo == SIGILL)
	{		
		/*
		printk(KERN_EMERG "[kernel] ===> getSigno= %d\n",ksig->info.si_signo);
		printk(KERN_EMERG "---------------> r0= 0x%x\n",regs->ARM_ORIG_r0);
		printk(KERN_EMERG "---------------> cpsr= 0x%x\n",regs->ARM_cpsr);
		printk(KERN_EMERG "---------------> pc= 0x%x\n",regs->ARM_pc);
		printk(KERN_EMERG "---------------> lr= 0x%x\n",regs->ARM_lr);
		printk(KERN_EMERG "---------------> sp= 0x%x\n",regs->ARM_sp);
		printk(KERN_EMERG "---------------> ip= 0x%x\n",regs->ARM_ip);
		printk(KERN_EMERG "---------------> fp= 0x%x\n",regs->ARM_fp);
		*/
		memcpy(&gStackEnvs,regs,sizeof(struct pt_regs));
		gStackEnvs.uregs[15] = kernel_text_address(regs->ARM_pc)
			 ? regs->ARM_pc : regs->ARM_lr;
	}
	/*
	 * Set up the stack frame
	 */
	if (ksig->ka.sa.sa_flags & SA_SIGINFO)
		ret = setup_rt_frame(ksig, oldset, regs);
	else
		ret = setup_frame(ksig, oldset, regs);

	/*
	 * Check that the resulting registers are actually sane.
	 */
	ret |= !valid_user_regs(regs);

	signal_setup_done(ret, ksig, 0);
}

然后编写一个内核模块以读取该寄存器组信息。如下示:

extern struct pt_regs gStackEnvs;  //函数堆栈信息
static int fp_frameInfoRead(struct file * file, const char __user * buffer, size_t count, loff_t * ppos)
{
	size_t cnt = 0;
	memcpy(&myStackEnvsInfo,&gStackEnvs,sizeof(gStackEnvs));

	if(count > sizeof(myStackEnvsInfo))
		cnt = sizeof(myStackEnvsInfo);
	else
		cnt = count;

	if(!copy_to_user((char *)buffer, (char*)&myStackEnvsInfo, count))
		return cnt;
	else
		return -1;
}

然后在应用层main函数中,注册对应的信号处理函数,当程序发生错误时,内核将执行用户注册的信号处理函数,我们可以在此获取寄存器组信息,然后完成栈回溯。

signal(SIGSEGV, sigroutine);//在main函数注册信号
void sigroutine(int dunno) 
{
	unsigned char ucLog[256] = {0};

	/*进行栈回溯*/
	fpDumpFrameInfo(ucLog,sizeof(ucLog));

    //打印log或写log

}

以下为栈回溯实现函数,回溯结束约束条件为线程栈大小最大8M,回溯的地址超过8M就会结束回溯。

static int  fpUnwindFrame(struct stackframe *frame)
{
	unsigned long high, low;
	unsigned long fp = frame->fp;
	low = frame->sp;
	high = T_ALIGN(low, 8192*1024); //Linux stack default size is 8M
	if (fp < low + 4 || fp > high - 4)
		return -EINVAL;
	frame->pc = *(unsigned long *)(fp - 0);	
	frame->sp = frame->fp + 4;
	frame->fp = *(unsigned long *)(fp - 4);	
	return 0;
}
static int fpDumpFrameInfo(char *pOutBuf,int inbufSize)
{
	int urc = 0;
	int ni = 0;
	int fd = -1;
	int nCnt = 8;
	int nBufSize = 0;
	unsigned long where = 0;
	unsigned long tmpValue = 0;
	unsigned long addr = 0;
	unsigned char addrInfo[16] = {0};
	unsigned long high, low;
	struct stackframe frame;
	if(NULL == pOutBuf)
		return -1;
	fd = open(DEVNAME, O_RDWR);
	if(fd == -1)
	{
		printf("open file %s failed!\n", DEVNAME);
		return-1;
	}
	ni = read(fd, uregs, sizeof(uregs));
	if(ni < sizeof(uregs))
	{
		printf("get frametraceInfo failed\n");
		return-1;
	}
	frame.fp	= uregs[11];
	frame.sp	= uregs[13];
	frame.lr	= uregs[14];
	frame.pc	= uregs[15];
	addr		= uregs[11];
	memset(pOutBuf,0,inbufSize);
	memset(addrInfo,0,sizeof(addrInfo));
	sprintf(addrInfo,"[PC=0x%x ",uregs[15]);
	strcat(pOutBuf,addrInfo);
	memset(addrInfo,0,sizeof(addrInfo));
	sprintf(addrInfo,"LR=0x%x ",uregs[14]);
	strcat(pOutBuf,addrInfo);
	memset(addrInfo,0,sizeof(addrInfo));
	sprintf(addrInfo,"SP=0x%x ",uregs[13]);
	strcat(pOutBuf,addrInfo);
	memset(addrInfo,0,sizeof(addrInfo));
	sprintf(addrInfo,"FP=0x%x]",uregs[11]);
	strcat(pOutBuf,addrInfo);
	nBufSize = strlen(pOutBuf); //计算已写入长度

	while (nCnt>0) {
		nCnt--;
		where = frame.pc;
		low = frame.sp;
		high = T_ALIGN(low, 8192*1024); //Linux stack default size is 8M
		if (addr < low || addr > high -4)
			break;
		if(nBufSize > 225)  //如果已写入长度大于225,不再追溯
			break;
		memcpy(&tmpValue,(char*)addr,sizeof(tmpValue));
		memset(addrInfo,0,sizeof(addrInfo));
		sprintf(addrInfo,"addr=0x%x ",tmpValue);
		strcat(pOutBuf,addrInfo);
		nBufSize += strlen(addrInfo); //更新已写入长度
		addr-=4;
		memcpy(&tmpValue,(char*)addr,sizeof(tmpValue));
		addr = tmpValue;
		urc = fpUnwindFrame(&frame);
		if (urc < 0)
			break;
	}
	
	return 0;
}

 

arm平台的调用栈回溯(backtrace) title: arm平台的调用栈回溯(backtrace) date: 2018-09-19 16:07:47 tags: --- 介绍 arm平台的调用栈与x86平台的调用栈大致相同,稍微有些区别,主要在于栈帧的压栈内容和传参方式不同。在arm平台的不同程序,采用的编译选项不同,程序运行期间的栈帧也会不同。有些工具在对arm的调用栈回溯时,可能会遇到无法回溯的情况。例如gdb在使用bt查看co... 阅读详情

相关推荐

ARM平台(海思)unwind栈回溯的实现

本文在撰写前尝试了约三四天的时间来实现海思平台的栈回溯,最终仅以此文作为记录,帮助更多的人。

Callon_H的博客 9853

linux arm无符号表栈回溯,【嵌入式Linux+ARM】ARM体系结构与编程(ARM汇编指令)

自己的一些简单的总结,也是最常用的ARM汇编指令,之后也会不断的补充完善。1.汇编系统预定义的段名.text @代码段.data @初始化数据段.bss @未初始化数据段需要注意的是,源程序中.bss段应该在.text之前。2.定义入口点汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。.text.global _start_start...

weixin_31155097的博客 411

ARM Backtrace实战:从帧指针到unwind table的栈回溯全解析

在嵌入式开发中,栈回溯(Stack Backtrace)是定位程序崩溃与异常现场的关键技术。x86环境下通过帧指针(Frame Pointer)即可高效遍历调用栈,而ARM架构因编译器优化、过程调用标准(APCS/ATPCS)的灵活性,导致传统基于fp的回溯方案失效。为解决这一难题,ARM生态引入了基于.ARM.exidx/.ARM.extab的异常展开表(Unwind Table)机制,通过查询元数据精确恢复每一帧寄存器。同时,Cortex-M处理器在异常入口会硬件自动压栈,结合EXC_RETURN与FP

weixin_33925615的博客 308

linux 64位 函数栈帧回溯,自己动手实现arm函数栈帧回溯

自己动手实现arm函数栈帧回溯内核版本:2.6.14glibc版本:2.3.6CPU平台:armglic中其实有这些函数,当时用的uclib版本较低,没有这些函数,但又需要,只能自己实现了(较高的版本应该有这些函数,换版本很麻烦),而且可以加深自己对这方面的理解.原理性的东西就不深入讲解了,直接上例子!#include #include #include #include #include voi...

weixin_39645306的博客 401

linux内核中打印栈回溯信息 - dump_stack()函数分析

当内核出现比较严重的错误时,例如发生Oops错误或者内核认为系统运行状态异常,内核就会打印出当前进程的栈回溯信息,其中包含当前执行代码的位置以及相邻的指令、产生错误的原因、关键寄存器的值以及函数调用关系等信息,这些信息对于调试内核错误非常有用。

落尘纷扰的专栏 2万+

一文解析Arm64 栈回溯

发生异常的函数可以根据pc寄存器得到,该函数是栈回溯的第一个函数。sp寄存器指向了第一个栈帧中的FP1寄存器,即0xffffffc0ee823b80地址,FP1向高地址偏移8字节得到LR1寄存器,即0xffffff80087369e4地址,该地址位于dwc3_ep0_stall_and_restart函数内,该函数是栈回溯的第二个函数。每产生一次函数调用,就会在栈中形成一个栈帧,该栈总共保存了4个栈帧(Stack Frame),每个栈帧由FP、LR及栈参数(函数参数、函数局部变量等)组成。

youzhangjing_的博客 2267

Linux 栈回溯

日常工作中,我们在开发软件程序时,经常会遇到程序奔溃的问题,导致程序奔溃的原因有很多,我们一般都是定位到相关代码,再去查询具体原因。而定位到bug相关代码往往需要依赖栈回溯这个功能,知道程序是在哪里挂掉的。在Linux系统中,栈回溯(stack trace)是用于跟踪程序执行期间函数调用的一种技术,记录了程序在出现错误或异常时,从当前位置开始追溯回到程序的执行起点,包括每个函数的调用关系和相应的返回地址。栈回溯可以帮助开发人员快速定位问题所在。

小嵌同学的博客 3245

ARM64栈帧回溯:从FP与LR寄存器解析函数调用链

本文深入解析ARM64架构中栈帧回溯的核心原理,重点探讨帧指针(FP)与链接寄存器(LR)在函数调用链中的关键作用。通过实际代码示例和GDB调试技巧,详细展示如何利用FP和LR寄存器解析函数调用关系,并提供异常情况处理指南与性能优化建议,帮助开发者高效进行ARM64平台调试。

weixin_29197051的博客 716

arm linux 栈帧,arm 栈帧/栈回溯/栈帧回溯 相关

arm平台的调用栈回溯(backtrace)title: arm平台的调用栈回溯(backtrace)date: 2018-09-19 16:07:47tags:介绍arm平台的调用栈与x86平台的调用栈大致相同,稍微有些区别,主要在于栈帧的压栈内容和传参方式不同。在arm平台的不同程序,采用的编译选项不同,程序运行期间的栈帧也会不同。有些工具在对arm的调用栈回溯时,可能会遇到无法回溯的情况。例...

weixin_30491017的博客 841

CTP开发实战:如何高效处理预埋单与委托单的完整流程(附代码示例)

本文深入探讨了CTP开发中预埋单与委托单的完整处理流程,从订单生命周期理解、核心代码实现到异步事件处理与系统健壮性构建。通过详细的代码示例和状态机分析,为开发者提供了构建高可靠订单处理系统的实战指南,重点解决了状态同步、异常恢复等关键工程挑战。

weixin_29234239的博客 116

STM32F407 HAL库定时器触发ADC采集与DMA数据传输(定时器TIM+ADC+DMA)

在STM32F407系列微控制器的开发中,结合定时器、ADC(模数转换器)与DMA(直接存储器访问)控制器,能够显著提升数据采集与传输的效率。本文将指导你如何使用STM32 HAL库,通过定时器触发ADC1的单通道采集,利用DMA传输数据,最终通过串口将电压值打印出来。具体实现中,我们将读取ADC1的通道5(对应引脚PA5),并将转换得到的电压值发送到串口助手上进行显示。

jmlinux的博客 7012

(重磅原创)冬之焱: 谈谈Linux内核的栈回溯与妙用

作者简介:冬之焱,杭州某公司linux内核工程师,4年开发经验,对运用linux内核的某些原理解决实际问题很感兴趣。版权声明:本文最先发表于"Linux阅码场"微信公众号...

Linux阅码场 3209

基于Jeecgboot3.9.0的文档管理系统(四)

基于Jeecgboot3.9.0的文档管理系统(四)

宁波阿成的博客 251

linux dump_stack 栈回溯原理

linux arm架构下的栈回溯主要有两种方式,一种是基于fp栈帧寄存器的栈回溯,还有一种是unwind形式的栈回溯。 1 基于fp栈帧寄存器的栈回溯 在介绍栈回溯原理前,先贴一下arm的基本寄存器: 寄存器 含义 r0-r3 用作函数传参,例如函数A调用函数B,如果A需要向B传递参数,则将参数放到寄存器r0-r3中,如果参数个数大于4,则需要借用函数的栈空间。 r4-r11 变量寄存器,在函数中可以用来保存临时变量。 r9(SB) 静态基址寄存器。 r10(S

oqqYuJi12345678的博客 3406

基于 Qt C++ 的智能工厂数字孪生系统

你想要实现一个基于 Qt C++ 的智能工厂数字孪生系统,核心是 **Qt 3D** 场景搭建 + **实时数据驱动** 设备模型。下面我将为你提供一个完整的解决方案,包含 **核心思路、实现步骤、完整代码示例** 和 **注意事项**。| **渲染层** | 3D 场景搭建、设备模型加载、实时动画渲染、用户交互。| **业务逻辑层** | 数据处理(滤波、转换)、设备状态管理、动画逻辑控制。| **数据层** | 实时采集设备数据(如传感器、PLC、MES 系统),提供数据接口。

寒寒豆豆软件公司的博客 193

在最新版eclipse中配置使用AI AIcoder _AIcoder配置GPUSTACK使用_配置OLLAMA本地AI模型利用_集合---AI场景应用工作笔记

本文介绍了三款实用的编程辅助工具:一款便捷的反编译插件,可直接在插件市场安装使用;GitHub Copilot提供每月2000次代码补全和50条聊天记录的免费额度,支持智能代码提示功能;aicoder支持配置本地模型运行。这些工具都能有效提升开发效率,其中GitHub Copilot的智能补全功能尤为突出。

添柴程序猿的专栏 1244

arm32栈回溯原理学习以及示例代码

简单介绍下传统栈回溯原理,方便理解。

深海 1495

四足狗机器人硬件开发技术栈详解

本文系统介绍了四足狗机器人的硬件开发技术,重点分析了五大核心模块:机械结构(骨架设计)、动力系统(关节驱动)、感知系统(环境监测)、控制系统(决策中枢)和电源系统(能源管理)。文章详细阐述了各模块的选型标准、技术要点和协同设计原则,提供了从需求定位到组装调试的完整开发流程,并针对常见问题给出解决方案。随着材料轻量化、模块标准化和AI集成化的发展趋势,四足机器人硬件将向更灵活、更智能的方向演进,为工业巡检、应急救援等场景提供更强大的移动平台支持。

start_up_go的博客 284

谈谈Linux的栈回溯与妙用

原标题:谈谈Linux的栈回溯与妙用 作者简介:冬之焱,杭州某公司linux内核工程师,4年开发经验,对运用linux内核的某些原理解决实际问题很感兴趣。 版权声明:本文最先发表于"Linux阅码场"微信公众号,转载请在文章的最开头,保留本声明。 1 前言 说起linux内核的栈回溯功能,我想这对每个Linux内核或驱动开发人员来说,太常见了。如下演示...

zhuyong006的博客 3829

【A_Star三维路径规划】基于matlab A_Star算法结合B样条平滑三维地形路径规划(含雷达威胁)【含Matlab源码 3527期】

A_Star算法结合B样条平滑三维地形路径规划(含雷达威胁) 完整代码,包运行;可提供运行操作视频!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 741

基于Python的网贷平台数据爬虫实战:Scrapy+Selenium+反反爬策略

python复制下载# items.py本文详细介绍了如何使用Python构建一个完整的网贷平台数据爬虫系统,涵盖了从基础爬取到高级反反爬策略的全套解决方案。我们使用了Scrapy框架作为基础,结合Selenium处理动态内容,实现了多种反反爬策略,并探讨了分布式爬虫的实现方式。机器学习识别验证码基于深度学习的网页结构变化检测自动化行为模拟的更精细控制基于Kubernetes的弹性爬虫集群。

2201_76125261的博客 1560

反汇编与栈回溯

利用反汇编手段解析C语言函数 https://blog.youkuaiyun.com/songjinshi/article/details/8450419?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1&utm_source=distribute.pc_relevant.none-task-...

happylzs2008的专栏 903

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

ARM64位CPU下linux crash手动恢复函数调用栈

注:本文转载自知乎:https://zhuanlan.zhihu.com/p/50768351

qq_34127958的博客 1980

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈

破获ARM64位CPU下linux crash要案之神技能:手动恢复函数调用栈(一) 1案情引言前不久瓶子哥的一位刚入职的同事小马在调试基于三星S6手机平台的7420 SoC(ARM64位处理器)芯片Android驱动的时候遇到了一个linux crash问题,但是panic出来后没有打印出backtrace(函数的调用关系),作为新手的他看到如下的界面,我想大概率都是懵逼的状态吧。为了彰显我老司机的地位,也为了刷一波存在感,后来瓶子哥帮忙他分析了下ARM64 linux的函数调用关系以及AAPCS64规则

maimang1001的专栏 1660

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

上一篇: Linux 时间戳localtime函数多线程编程的一些坑
下一篇: stm32收发 wiegand 韦根协议开发详解
Chasing_Chasing
博客等级 码龄10年 233粉丝 87原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值