嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 我的AUTOSAR程序在S32G上运行,跑进异常了该怎么办?

我的AUTOSAR程序在S32G上运行,跑进异常了该怎么办?


最近几个Classic AUTOSAR项目并行在做,无论我怎么切来切去,怎么好像每个项目都是S32G啊!我都快不认识别的芯片了....

好吧╮(-)╭,绕不开就绕不开,谁叫S32G这么火呢---- 关键是打工人也没得选不是,咱不能挑。

但是吧,在S32G上调试的代码越来越多之后,代码跑着跑着进入异常的情况也非常多(似乎暴露了我的编程水平??),调试过嵌入式的都懂,一旦进入错误或者异常,往往要面对一堆异常处理函数,而且还是汇编!虽说咱也不是不懂汇编,但是调起来肯定不如调试C代码直观。

那么问题来了,怎么能够快速解决这个问题呢,对吧?现在也不知道是怎么了,嵌入式软件也开始搞敏捷开发,两周一个sprint(没有说敏捷开发不对的意思,杠就是你对),老有人催我啊!不信看看我的身后,Scrum Master,项目经理,还有客户爸爸一直哔哔叨叨的,烦的3次幂!

这个时候牢记曼联老大哥C罗,啊不,B费的“别慌,冷静”!我们不要怕问题,想想可以怎么解决它。


请你现在回答我几个问题:

S32G是什么公司的芯片?

NXP!(可以给我打点广告费吗??)

S32G适用场景是什么?

网关!(现在我快网关PTSD了。。。)

S32G是基于什么架构的?

Cortex-M7

OK,这个三连回答的非常好,既然知道它是Cortex-M7架构的,那么就好办了。

什么,Cortex-M是什么?我给你指个路:Arm Product Filter Arm®

Cortex-M还分了很多系列,其他我们暂且不表,主要还是先来了解下S32G对应的M7

一般来说,芯片是有相应的寄存器来告诉我们发生了什么样的异常/错误,对于Cortex-M,有什么寄存器可以提供这些信息呢?


从第一步分析的角度来说,相关的寄存器有两个,Configurable Fault Status Register(CSFR)HardFault Status Register(HFSR)

可配置故障状态寄存器 Configurable Fault Status Registers (CFSR)

这一个32位寄存器包含了何种故障导致进入异常的信息,一共和三种状态有关:

  • UFSR -     UsageFault Status Register

  • BFSR -     BusFault Status Register

  • MMFSR -     MemManage Status Register

使用故障状态寄存器 UsageFault Status Register (UFSR)

UFSR2个字节长度,代表和内存访问无关的故障,例如尝试执行非法指令 ,或者尝试进入非法状态。

  • DIVBYZERO - 发生了除零操作。可通过CCR寄存器配置是否开启此故障检测。

  • UNALIGNED - 发生了未对其地址访问操作,例如访问了一个没有8字节对齐的uint64_t类型变量。对于4字节及以下的非对其地址访问,也可以通过CCR寄存器配置是否开启检测。

  • NOCP - 在协处理器不存在或者未使能的情况下,执行了Cortex-M协处理器指令。一个普遍的场景是在编译选项当中使用-mfloat-abi=hard-mfpu=fpv4-sp-d16进行浮点操作,但在程序启动/执行时并未使能协处理器。

  • INVPC - EXC_RETURN一致性检查错误。

  • INVSTATE - 处理器尝试使用非法的Execution Program Status Register (EPSR)     值执行指令。ESPR寄存器追踪处理器是否在thumb状态。写汇编代码时容易有这种错误。

  • UNDEFINSTR - 执行了未定义指令,如果在异常处理推出时栈损坏,会触发这个异常。或者,代码路径应该无法访问到的时候,编译器也可能会生成未定义指令。

总线故障状态寄存器 BusFault Status Register (BFSR)

BFSR1个字节长度,代表预取指令或者内存访问错误。

  • BFARVALID - 代表位于0xE000ED38地址的32位寄存器Bus Fault Address Register (BFAR),     存储了触发这个故障的地址。

  • LSPERR & STKERR - floating-point     lazy state错误或者异常入口时stack错误

  • UNSTKERR - 从异常返回时发生故障,比如异常处理时栈损坏,或者SP指针被改变而内容没有被正确初始化。

  • IMPRECISERR - 硬件是否可以决定故障的确切位置。

  • PRECISERR - 是否进入异常前的指令导致错误

IMPRECISERR对于调试bus error非常重要,毕竟它代表了我们是否能够直接获取导致异常的指令在何处。指令获取和数据载入,在Cortex-M设备上总是会产生同步故障,而存储操作则可能产生异步故障,因为有的时候写入数据会先被缓存起来,然后才会写进数据,以避免流水线停留,所以PC指针先于数据存储完毕之前移动,执行其他指令。

所以,当出现imprecise bus error时,你可能需要在报告的故障地址附近看看是否有可能导致异常的存储操作。如果MCU支持ARMEmbedded Trace MacrocellETM),那么也可以通过调试器查看最近被执行的指令历史。

Auxiliary Bus Fault Status Register (ABFSR)

IMPRECISE error发生时,我们可以利用仅在Cortex-M7设备上才有的ABFSR寄存器,查看何种内存总线产生的故障。

这里的AXIMEPPBAHBPDTCMITCM都是接口类型,这里不再做展开。

内存故障寄存器MemManage Status Register (MMFSR)

MMFSR报告内存保护单元故障。

一般地,MPU故障只在MPU配置并使能的情况下才可能会被触发,然而,例如尝试在系统地址的范围执行代码等情况下,也会触发内存访问错误。

  • MMARVALID - 32位寄存器MemManage Fault Address Register (MMFAR)是否有效保存了期望访问的内存地址

  • MLSPERR & MSTKERR - 典型的错误是,MPU用来检测栈溢出时,lazy state     preservationexception entry时的故障

  • MUNSTKERR - 从异常返回时发生的故障

  • DACCVIOL - 数据访问导致的错误

  • IACCVIOL - 指令执行导致的MPUExecute Never (XN) 故障(0xE0000000及更高的地址是无法被执行的)

HardFault Status Register (HFSR)

HFSR寄存器带有导致HardFault的原因信息。

  • DEBUGEVT -     debug子系统未被使能的情况下,发生了debug事件,也即执行断点指令

  • FORCED - 上文提到的可配置故障升级为了HardFault,有可能是因为可配置故障handler没有被使能,或者在handler中处理时又发生了故障

  • VECTTBL - 由于从向量表中读出地址时发生故障。一般不会发生,但如果向量表中有''地址并且非预期中断发生时,还是可能产生这个故障


恢复现场

为了修复故障,我们需要知道故障发生时正在执行什么代码,因此我们需要在进入异常时获取寄存器的状态。

如果你有调试器,那很好,把断点打在异常handler

进入异常前,硬件总是会将一些寄存器的内容push在栈顶。Cortex-M有两个SP指针,msppsp(不是游戏掌机!),在异常入口,EXC_RETURNbit 2的值代表了激活的是哪一个SP指针,如果为1,那么是psp被激活,反之则是msp

以下方代码为例,

int illegal_instruction_execution(void) {

int (*bad_instruction)(void) =(void *)0xE0000000;

returnbad_instruction();

}

当发生故障时,在handler中我们可以查看栈的前八个值,它们是r0, r1, r2, r3, r12, LR, pc, xPSR,我们可以得到:

0x0 <g_pfnVectors>,

0x200003c4 <ucHeap+604>,

0x10000000,

0xe0000000,

0x200001b8 <ucHeap+80>,

0x61 <illegal_instruction_execution+16>,

0xe0000000,

0x80000000

让我们把目光放在LRpc,我们就能知道是在程序的什么地方(illegal_instruction_execution中)以及具体执行了什么命令(执行0xE0000000地址的指令)。

当然,实际在项目当中我们调试过程倒不必这么麻烦,以EB Autosar产品为例(欠的广告费什么时候到账TAT),你能够以一种更加直观的方式获取各种你需要的值。

故障中的故障!

我知道有人此时非常关心一个问题,要是在handler中处理故障时,发生了一个新的故障,怎!么!办!

其实上文也有提到,如果在使能了可配置故障handler的情况下,在其中产生了新的故障时,会触发HardFault

一旦在HardFault之中,ARM Core运行在一个不可配置优先等级—— -1,此时一个新的故障会让处理器进入一个不可恢复的状态,且需要reset,这种状态叫做Lockup

不过,如果你是连接着调试器,Lockup在此时就不会导致reset


自动恢复

可能你也感受到了,很多时候我们并不能精确地指出是何处的代码发生问题,对于MCU来说可能使用reset是一种最简单最无脑的恢复方式。不过对于如今的Autosar程序来说,客户爸爸会有更细化的需求,比如仅仅重置某一个应用程序或者某一个应用分区。

Anyway,作为Autosar底层软件,已经提供了尽可能多的错误信息,如何定制策略,以及如何单独重置Autosar应用,本文不会涉及。


几种典型的错误

eXecute Never Fault

int illegal_instruction_execution(void) {

int (*bad_instruction)(void) =(void *)0xE0000000;

returnbad_instruction();

}

这段代码上面已经提及,如果尝试执行0xE0000000地址,会进入HardFault

读取''地址

uint32_t read_from_bad_address(void) {

return*(volatile uint32_t *)0xbadcafe;

}

如果尝试从一个读不到的地址获取数据时,会触发Bus Fault

协处理器故障

void access_disabled_coprocessor(void) {

// FreeRTOS will automatically enable the FPU co-processor.

// Let's disable it for the purposes of this example

__asmvolatile(

"ldr r0, =0xE000ED88 \n"

"mov r1, #0 \n"

"str r1, [r0]      \n"

"dsb \n"

"vmov r0, s0 \n"

);

}

在我们没有使能协处理器的情况下,使用类似于vmov(浮点指针指令)这样的指令时,会导致coprocessor fault

Imprecise Fault

void bad_addr_double_word_write(void) {

volatileuint64_t *buf = (volatile uint64_t *)0x30000000;

*buf= 0x1122334455667788;

}

执行这段代码,进入异常并查看IMPRECISERR值时,你会发现此时值为1,那么我们所获取到的故障地址并不能确切代表实际导致故障的代码。

那只好在所指地址附近看看有什么代码可能会导致这个问题。

如果是M3M4系列,你可以通过使能Auxiliary Control Register (ACTLR)DISDEFWBUF bit位来禁止所有的写入缓存操作,这样你就可以避免IMPRECISERR1的情况。—— 当然,这个对性能是有影响的。

所以,对于M7架构的S32G来说,无法做到强制所有存储操作都是同步的,那么也就无法精确获取故障地址了。

(所以不要再问我S32G能不能精确得到故障地址了。臣妾做不到哇 --->不过如果发现架构有升级或者Arm文档有更新,欢迎讨论)

参考

How to debug a HardFault on an ARM Cortex-M MCU | Interrupt (memfault.com)

ARM Cortex-M7 Processor Technical Reference Manual r0p2

Arm Cortex-M7 Devices Generic User Guide r1p2


图片来自网络,如有侵权,请联系删除