spsr 的恢复出错,导致 thumb 指令集的 it 条件运行指令运行异常,清晰的调试思路帮助快速解决问题

本文主要是介绍spsr 的恢复出错,导致 thumb 指令集的 it 条件运行指令运行异常,清晰的调试思路帮助快速解决问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

记一次调试过程

这是一个在 arm 架构上的 RTOS 上的调试过程。问题现象为使用 thumb 指令集的 libgcc 库的情况下,浮点运算随机出错。经过一番追踪调试,逐步缩小问题范围,最后定位问题,成功解决。

场景

在某款的国产 RTOS 上,由于客户应用需要,使用了thumb 指令集编译的 libgcc 的库,导致了同时运行了 arm 指令集和 thumb 指令集的代码。原本的 RTOS 未设计运行 thumb 指令的代码,也没有严格测试过,所以本次暴露了浮点运算偶尔出错的问题。

测试代码

客户精简过的暴露问题代码如下

unsigned long long   ulTa = 0x100;
unsigned long long   ulTb = 0x2001;
double dRea, dReb;
double dRes;while (1) {= (double)ulTa;dReb = (double)ulTb;dRes = dRea / dReb;if ((dRea == 0.0) || (dReab == 0.0) || (c <= 0.01f)) {printf("error!!\r\n");}}

这段代码的逻辑是强转了两个 unsigned long long 类型,然后进行除法运行,最后判断各个变量的结果是否符合预期,否则就报 error。

问题分析

抓取问题中的关键点:

  1. 和浮点运算有关,是不是我们的RTOS对于浮点运算的寄存器没有处理好
  2. 简单的代码反复运行只是偶尔出问题,而不是固定每次出问题,那么考虑其他外部的影响,首先考虑的是中断。

问题调试

bug调试的指导思想是,仔细观察问题剖析问题,提出怀疑一些原因,并修改代码验证

步骤1 剖析问题

靠近问题的实质,分析问题:既然计算出错,那么看看出错的时候的变量是什么样的。经过一些调试打印发现 3 个 double 类型的变量都出错过。出错的时候,打印这些变量对应地址存储的值,发现这些内存的值有 0 存在。因为 double 变量使用 8 bytes 的空间存放的,这些空间里存放的不是实际值,而是分符号位,指数位等。
根据这个现象,我有理由怀疑是某些时候的强转没有成功。

步骤2 证明和中断的相关性

进行关中断测试,在关闭中断的情况下,长时间跑了测试,时间足够长,发现并没有出错。那么证明的确和中断有关系。

步骤3 证明测试代码的逻辑是没有问题

排查其他问题:使用 arm 指令集的 libgcc 进行测试,使用同样的代码,时间足够长,也没有出错。那么证明测试代码的逻辑是没有问题的。

陷入了第一个瓶颈

确定中断有问题,那么推测偶尔出问题肯定是恰好某个时刻的中断踩到了特殊位置。修改代码关注是否是中断返回立即出错,打印分析被中断打断处的指令,观察是否是固定的某个指令。经过漫长的调试,观察到的确有规律。
分析长时间的出错前的 PC 指针的位置,很多是靠近 thumb 指令集里面的 IT block 指令。必须敏锐地抓住这个点。类似下面的 it eq 指令。

001007d4 <__aeabi_ul2d>:1007d4:	ea50 0201 	orrs.w	r2, r0, r11007d8:	bf08      	it	eq1007da:	4770      	bxeq	lr1007dc:	b530      	push	{r4, r5, lr}1007de:	f04f 0500 	mov.w	r5, #01007e2:	e00a      	b.n	1007fa <__aeabi_l2d+0x16>

简化问题

看手册学习 it block 指令。
// 学习过程不表

证明是 it 指令的问题

调试指导思想是:缩小问题的范围
想办法直接运行这个指令,观察是否出错。我使用内联汇编实现,使用伪代码解释代码逻辑

r1 = 0x10;
r2 = 0x10;
r3 = 0x20;
r4 = 0x40;r4 = r4 - r2;  //运行后 r4 = 0x30// itte ge , 上一行指令会改变 cpsr 的 flag 标志位,
// 若 ge: r1 = r1 + (r4 << 1)  //运行后 r1 = 0x70;
// 若 ge: r2 = r2 + 0x10;
// 若 lt: r4 = r4 + 0x10;

代码如下:

__attribute__((target("thumb"))) void thumb_ins(void);
void thumb_ins(void) {int ia = 0;int ib = 0;int ic = 0;int  cnt = 0;while (1) {ia = 0;ib = 0;ic = 0;asm volatile ("mov r1, #0x10");asm volatile ("mov r3, #0x20");asm volatile ("mov r2, #0x10");asm volatile ("mov r4, #0x40");asm volatile ("subs	r4, r4, r2");asm volatile ("itte  ge");asm volatile ("addge.w	r1, r1, r4, lsl #1");asm volatile ("addge r2, r2, #0x10");asm volatile ("addlt  r4, r4, #0x10");asm volatile ("mov %0, r1" : "=r" (ia));asm volatile ("mov %0, r2" : "=r" (ib));asm volatile ("mov %0, r4" : "=r" (ic));if ((ia != 0x70) || (ib != 0x20) || (ic != 0x30)) {printf("error!!!");printf(" r1 [0x%x] r2 [0x%x] r4[0x%x]\r\n", ia, ib, ic);}if (cnt ++ > 9000000) {printf(" r1 [0x%x] r2 [0x%x] r4[0x%x]\r\n", ia, ib, ic);cnt = 0;}}
}

经过运行测试,这个代码运行时,也会打印出 error;关闭中断进行测试,则运行不会打印 error。则完全证明了是 it 指令执行的问题。并且发现了出错时, it block 控制的条件允许代码全部没有执行,包括2条判断条件相反的指令都没有执行。

陷入了第二个瓶颈 分析 it 指令执行出错的原因

arm 手册上关于这个 it 指令从异常返回的描述:
On a branch or an exception return, if PSTATE.IT is set to a value that is not consistent with the instruction stream being branched to or returned to, then instruction execution is CONSTRAINED UNPREDICTABLE.
我们需要关注到异常返回的地址和异常返回时 PSTATE.IT 的标志状态。

  • 经过调试分析,已经确认我们的 RTOS 在异常保存上下文中 cpsr 的值是正确的。使用 msr 将其恢复也是正确的。也确认了我们RTOS配置的返回地址也是正确的。满足手册中提到的注意事项,问题陷入僵局。
  • 试图对比 Linux 源码中的异常保存和恢复过程中,对于 thumb 指令是否有特殊处理,然而并没有收获。

问题突破

经过艰难的调试之后,终于有了新的突破
我们知道,在异常返回时,是由硬件自动将 spsr 寄存器中的值恢复到 cpsr。我在设置PC跳转之前,读出 spsr 的值,保存到内存中,然后有了惊人的发现:此时 spsr 寄存器中的内容和上下文结构体中的值不一致,而且是关键的 IT block 的信息丢失。那么断定是恢复 spsr 的时候出错了。
经过简单调试,和 Linux 进行对比,发现了问题的根源。

问题解决

我们的 RTOS 恢复 spsr 使用的指令为 msr spsr, r1 ,编译生成的指令为 83ce0ae8: e169f001 msr SPSR_fc, r1。而 Linux 使用 msr spsr_cxsf, r1, 编译生成的指令为 83ce0ae8: e16ff001 msr SPSR_fsxc, r1。我的 RTOS 在恢复 spsr 中,丢失了关键的 s 和 x 域的内容:

  • c 指 CPSR中的control field ( PSR[7:0])
  • f 指 flag field (PSR[31:24])
  • x 指 extend field (PSR[15:8])
  • s 指 status field ( PSR[23:16])
    而恰好 IT block 信息存放在 [26:25] + [15:10]
    it block
IT[1:0], bits [26:25] 
IT block state bits for the T32 IT (If-Then) instruction. See IT[7:2] for explanation of this field.
IT[7:2], bits [15:10] 
IT block state bits for the T32 IT (If-Then) instruction. This field must be interpreted in two parts. • IT[7:5] holds the base condition for the IT block. The base condition is the top 3 bits of the 
condition code specified by the first condition field of the IT instruction.
• IT[4:0] encodes the size of the IT block, which is the number of instructions that are to be 
conditionally executed, by the position of the least significant 1 in this field. It also encodes the value of the least significant bit of the condition code for each instruction in the block.
The IT field is 0b00000000 when no IT block is active.

调试经验总结

  1. 分析问题,缩小问题范围
  2. 敏锐观察,抓住灵感
  3. 验证寄存器的值是否正确不能读中间状态,必须紧贴使用之前进行验证
  4. 相信硬件,在计算机的世界没有上帝

这篇关于spsr 的恢复出错,导致 thumb 指令集的 it 条件运行指令运行异常,清晰的调试思路帮助快速解决问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/980070

相关文章

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

通过prometheus监控Tomcat运行状态的操作流程

《通过prometheus监控Tomcat运行状态的操作流程》文章介绍了如何安装和配置Tomcat,并使用Prometheus和TomcatExporter来监控Tomcat的运行状态,文章详细讲解了... 目录Tomcat安装配置以及prometheus监控Tomcat一. 安装并配置tomcat1、安装

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

mysqld_multi在Linux服务器上运行多个MySQL实例

《mysqld_multi在Linux服务器上运行多个MySQL实例》在Linux系统上使用mysqld_multi来启动和管理多个MySQL实例是一种常见的做法,这种方式允许你在同一台机器上运行多个... 目录1. 安装mysql2. 配置文件示例配置文件3. 创建数据目录4. 启动和管理实例启动所有实例

Spring Boot统一异常拦截实践指南(最新推荐)

《SpringBoot统一异常拦截实践指南(最新推荐)》本文介绍了SpringBoot中统一异常处理的重要性及实现方案,包括使用`@ControllerAdvice`和`@ExceptionHand... 目录Spring Boot统一异常拦截实践指南一、为什么需要统一异常处理二、核心实现方案1. 基础组件

IDEA运行spring项目时,控制台未出现的解决方案

《IDEA运行spring项目时,控制台未出现的解决方案》文章总结了在使用IDEA运行代码时,控制台未出现的问题和解决方案,问题可能是由于点击图标或重启IDEA后控制台仍未显示,解决方案提供了解决方法... 目录问题分析解决方案总结问题js使用IDEA,点击运行按钮,运行结束,但控制台未出现http://

解决Spring运行时报错:Consider defining a bean of type ‘xxx.xxx.xxx.Xxx‘ in your configuration

《解决Spring运行时报错:Considerdefiningabeanoftype‘xxx.xxx.xxx.Xxx‘inyourconfiguration》该文章主要讲述了在使用S... 目录问题分析解决方案总结问题Description:Parameter 0 of constructor in x

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时