5.1. 串口调试
在嵌入式平台开发中, 串口调试 以其依赖硬件结构简单和稳定可靠的优点,被誉为硬件状态和软件运行结果的 听诊器
,成为调试软件的重要手段。SDK中串口可以输出的信息有:
Note
常规调试信息
系统异常信息
以下章节分别进行说明。
5.1.1. 常规调试信息
SDK发布时会附带一些基本的调试信息,比如输出上电流程的各个模块的基本信息和状态。
5.1.1.1. 调试信息使能配置
打开lib库总开关
修改文件位置:
apps/xxx/include/app_config.h
1#ifndef APP_CONFIG_H 2#define APP_CONFIG_H 3 4/* 5* 系统打印总开关 6*/ 7 8 9#ifdef CONFIG_RELEASE_ENABLE 10#define LIB_DEBUG 1 11#else 12#define LIB_DEBUG 1 13#endif 14#define CONFIG_DEBUG_LIB(x) (x & LIB_DEBUG) 15#define CONFIG_DEBUG_ENABLE
Note
需要修改点:
宏LIB_DEBUG配置为1.
宏CONFIG_DEBUG_ENABLE需要被定义.
某些模块的的调试信息可能还有自己的单独开关
相关文件路径:
sdk/apps/xxx/config/lib_xxx_config.c
, 不同模块存放于不同问题中。举例文件sdk/apps/hid/config/lib_system_config.c
, 该文件存放与系统相关的打印开关, 比如sys_timer模块:1const char log_tag_const_v_SYS_TMR AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE); 2const char log_tag_const_i_SYS_TMR AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE); 3const char log_tag_const_d_SYS_TMR AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE); 4const char log_tag_const_w_SYS_TMR AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE); 5const char log_tag_const_e_SYS_TMR AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
每个模块有4个调试级别的开关:
Note
log_tag_const_v_xxx: verbose, 模块中比较冗余的信息。
log_tag_const_i_xxx: info, 模块中一些基本的状态信息。
log_tag_const_d_xxx: debug, 模块中用于定位问题的基本信息。
log_tag_const_w_xxx: warning, 模块中用于定位问题的警告信息。
log_tag_const_e_xxx: error, 模块中用于定位问题的错误信息。
用户可以将需要看调试信息的模块对应的开关置位 TURE 。
使能硬件驱动开关
除了上述的逻辑开关外,还需要确认对应硬件配置,举例文件
apps/hid/board/br25/board_ac636n_demo_cfg.h
,相关配置如下:1//*********************************************************************************// 2// UART配置 // 3//*********************************************************************************// 4#define TCFG_UART0_ENABLE ENABLE_THIS_MOUDLE //串口打印模块使能 5#define TCFG_UART0_RX_PORT NO_CONFIG_PORT //串口接收脚配置(用于打印可以选择NO_CONFIG_PORT) 6#define TCFG_UART0_TX_PORT IO_PORTC_04 //串口发送脚配置 7#define TCFG_UART0_BAUDRATE 1000000 //串口波特率配置
用户根据实际硬件平台可灵活配置 TX 和 BAUD 参数。
5.1.1.2. 添加自己的调试信息
用户在开发自己软件流程时, 经常需要添加自己的调试信息,SDK中支持的输出调试信息的接口有
printf
printf 是计算机行业内通用的终端打印信息的标准接口, 在开发环境中添加如下头文件后即可使用
1#include "generic/printf.h"
相应的,如果有需要打印一块内存的十六进制数据,可使用put_buf接口,使用示例如下;
1#include "generic/printf.h" 2 3void printf_foo(void) 4{ 5 int tmp[50]; 6 printf("Test Demo: %s:%d\n", __func__, __LINE__); 7 8 memset(tmp, 0x5A, sizeof(tmp)); 9 put_buf(tmp, sizeof(tmp)); 10 11 return; 12}
log_xxx族接口
log_xxx族接口与printf的区别是该接口可以分模块控制,以及输出信息可以带模块名前缀,可以快速定位到对应模块输出的信息。举例文件举例文件
sdk/apps/hid/app_main.c
, 使用步骤如下:在模块对应C文件开头增加如下修改
1#define LOG_TAG_CONST APP 2#define LOG_TAG "[APP]" 3#define LOG_ERROR_ENABLE 4#define LOG_DEBUG_ENABLE 5#define LOG_INFO_ENABLE 6/* #define LOG_DUMP_ENABLE */ 7#define LOG_CLI_ENABLE 8#include "debug.h"
Note
修改注意点:
定义LOG_TAG_CONST宏的命令与模块Debug名相同。
debug.h
文件被include的位置必须位于这些宏定义之后。
增加模块打印开关控制变量, 文件夹路径
apps/xxx/config/
用户应根据自己模块的属性放到合适的文件, 举例将app模块的控制开关放在 :file:`apps/hid/config/log_config.c`文件, 增加变量声明如下:1const char log_tag_const_v_APP AT(.LOG_TAG_CONST) = 0; 2const char log_tag_const_i_APP AT(.LOG_TAG_CONST) = 1; 3const char log_tag_const_d_APP AT(.LOG_TAG_CONST) = 1; 4const char log_tag_const_w_APP AT(.LOG_TAG_CONST) = 1; 5const char log_tag_const_e_APP AT(.LOG_TAG_CONST) = 1;
在对应模块添加调试信息,可用接口示例如下:
1void log_foo(void) 2 { 3 int tmp[10]; 4 5 log_info("I am info Message.\n"); 6 log_debug("I am debug Message.\n"); 7 log_error("I am error Message.\n"); 8 9 memset(tmp, 0x5A, sizeof(tmp)); 10 log_info_hexdump(tmp, sizeof(tmp)); //输出十六进制数据 11 log_error_hexdump(tmp, sizeof(tmp)); //输出十六进制数据 12 13 return; 14 }
上述示例输出信息如下:
[00:00:00.246][Info]: [APP]I am info Message. [00:00:00.246][Debug]: [APP]I am debug Message. [00:00:00.247](error): <Error>: [APP]I am error Message. 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A
系统时间戳
在输出的log中存在系统时间戳,该时间戳代表输出该log时距离系统的上电时间,如果用户不需要输出该时间戳,可以通过修改文件
sdk/apps/xxx/config/lib_system_config.c
///打印是否时间打印信息 const int config_printf_time = 1;
将config_printf_time变量修改为0,可以关闭log打印中输出时间戳。
5.1.2. 系统异常信息
系统异常指芯片在运行的代码时,由于软件/硬件状态出错,当该错误状态未在硬件/软件程序设计覆盖的容错范围,就会引起的系统处于未知状态的 异常 ,异常的结果是触发 系统复位
或者进入 系统异常中断函数
,常见的异常原因主要有:
- CPU内部触发的异常:
程序跑飞 : CPU运行到程序员非指定地址软件流程,该异常通常由一些没有正确初始化的 函数指针变量 引起。
除0异常 : CPU在除法运算中除数为0,就会触发除0异常。
非对齐内存访问 :CPU运行word内存访问指令时,寻址寄存器不是4对齐的值,或者运行half-word内存访问指令时,寻址寄存器不是2对齐的值,就会触发非对齐内存访问异常。
堆栈溢出 :但CPU的堆栈超出所设定的限制范围时,就会出现堆栈溢出异常,该异常通常与任务用到的堆栈大于配置的堆栈大小引起。
- 与内存相关触发的异常:
读写非法内存地址 : 当CPU读写到非法的内存地址,非法内存地址主要包括:空白地址,只读地址、程序员使用MPU保护的内存地址,当CPU读写到没有权限的内存地址时,就会触发异常异常,该异常主要由于buf野指针引起,或者动态申请中在free buf后,还在访问buf的数据。
外设DMA读写非法地址 : 与CPU读写非法内存地址类似,当外设DMA读写到了没有读写权限的非法地址,就会触发外设的读写非法地址异常。
- 与外设相关触发的异常:
看门狗超时 : 系统由于长时间没有清看门狗导致的系统异常,该问题常见原因是CPU负载过高,长时间没有进idle任务,或者在某个软件流程死循环,导致系统没有清看门狗。
Important
各系列详细异常原因可根据芯片型号查询:
5.1.2.1. 异常信息输出使能配置
用户在软件调试过程中,当出现一些错误级别的的打印,或者打印到某个地方之后就出现了 系统复位 ,复位情况 打印举例如下:
[00:00:00.238][Info]: [BOARD]board_init
[00:00:00.239][Info]: [TEST-UPDATE]testbox msg handle reg:1e125b8
[00:00:00.240]app_update_cfg:0,0,1
[00:00:00.242][Info]: [APP-UPDATE]<--------update_result_deal=0x5a00 0--------->
[00:00:00.243]>>>>>>>>>>>>>>>>>app_main...
[00:00:00.100]--P3 Reset Source : 0x40
[00:00:00.100]SOFT RESET
[00:00:00.102]=================Version===============
[00:00:00.102]BTCTRLER-@20221123-$c284473
[00:00:00.103]UPDATE-@20221123-$330d72d
[00:00:00.104]=======================================
[00:00:00.106][Info]: [SDFILE]VM size: 0x40000 @ 0x3e000
[00:00:00.108][Info]: [SDFILE]disk capacity 2048 KB
[00:00:00.109]last file_addr:35780 8520
[00:00:00.110]end_addr:3e000
从打印可以看出,打印到
[00:00:00.243]>>>>>>>>>>>>>>>>>app_main...
这一行后就出现了系统复位,复位源显示是 SOFT RESET
, 遇到这种情况说明系统在运行代码过程中遇到了一些 异常 ,SDK在发布时,默认会设置为所有异常都会触发 系统复位 ,从这个信息还不能确认系统遇到什么异常,这时需要把系统的异常复位模式配置为异常中断模式,相关配置位于文件 apps/xxx/config/lib_system_config.c
1///异常中断,asser打印开启
2#ifdef CONFIG_RELEASE_ENABLE
3const int config_asser = 1;
4#else
5const int config_asser = 1;
6#endif
Note
config_asser变量配置定义如下:
配置为0:系统异常触发复位
配置为1:系统异常输出异常信息
在将config_asser变量修改为1后,上述的异常情况的打印会变成如下输出信息:
[00:00:00.237][Info]: [BOARD]board_init
[00:00:00.238][Info]: [TEST-UPDATE]testbox msg handle reg:1e12b8a
[00:00:00.239]app_update_cfg:0,0,1
[00:00:00.241][Info]: [APP-UPDATE]<--------update_result_deal=0x5a00 eafa--------->
[00:00:00.242]>>>>>>>>>>>>>>>>>app_main...xFFFFFFFF
usp : 0x00200B28
ssp : 0x00011200
sp : 0x000111A0
Stack : 0x00011200
rets : 0x01E0F26C
reti : 0x01E125BE
retx : 0x00000000
rete : 0x00000000
psr : 0x00000006
icfg : 0x07010280
CPU trace: 0x00014220 -->0x01E0C2F8 -->0x01E0F252 -->0x01E125BE
--i---
BF EA 5D CD 40 E0 DC 2B 40 18 BF EA 58 CD BF EA
65 F6 F7 9F 01 16 60 20 D8 EA CD 2B 50 20 F7 9A
F7 9D 7A 04 14 16 09 16 B8 E1 10 06 A4 F9 20 00
B0 E1 10 04 00 5D AA E0 10 00 16 3F C6 21 0A E9
--r---
E2 01 BF EA 28 E8 40 28 40 EA 5A 00 40 E0 04 02
D8 EE C0 00 80 5E 86 14 45 E0 7C 01 80 EA A9 19
80 EA 28 2E 04 16 00 E1 32 82 41 16 BF EA F0 E6
D8 ED C8 05 00 E9 06 40 C7 21 70 17 46 20 80 F9
0---DEBUG_MSG 0x04100000
DSPCON 0x7FF23100
EMU_CON 0x0000000F
EMU_MSG 0x00000000
PRP_MMU_MSG 0x00000000
LSB_MMU_MSG_CH 0x00000001
PRP_WR_LIMIT_MSG 0x00000000
LSB_WR_LIMIT_CH 0x00000000
PRP_SRM_INV_MSG 0x00000800
LSB_SRM_INV_CH 0x00000000 0x01E38840 0x0001596B 0x00000F00 0x00000000 0x00000000
ZZcurr_task:app_core
sem_rets: 0x01E0DC5A
mutex_rets: 0x00000000
debug err msg : peripheral_access_reserved_address
debug err msg : cpu_write_reserved_address
0=0x00000000
1=0x00000000
2=0x00000000
3=0x00000000
4=0x00005A00
5=0x0000017C
6=0x00000000
7=0x00000000
8=0x01E1ABF0
9=0x01E1EC00
A=0x00012A9C
B=0x0070000F
C=0x00013050
D=0x10F3D121
E=0xDB55F7E2
F=0xCBA1E4FE
当系统异常被触发之后,会进入系统异常函数,在异常异常函数会输出系统异常信息,用户可以根据上述信息进行分析,可在 各系列异常信息列表 查询相关异常原因。
5.1.2.2. 各系列异常信息列表
异常信息 |
异常描述 |
内存非对齐异常访问 |
|
非法指令异常 |
|
堆栈溢出异常 |
|
程序跑飞异常 |
|
fpu_ine_err |
浮点不精确异常 |
fpu_huge_err |
浮点上溢出异常 |
fpu_tiny |
浮点下溢出异常 |
fpu_inf_err |
浮点无穷大异常 |
fpu_inv_err |
浮点NaN异常 |
设备(CPU/外设)写地址超出权限范围 |
|
看门狗超时异常 |
|
设备(CPU/外设)写了没有被映射的mmu虚拟空间 |
|
CPU写地址超出权限范围 |
|
peripheral_read_external_memory_when_cache_disable |
Cache被关闭时设备(CPU/外设)读外存异常 |
peripheral_write_external_memory_when_cache_disable |
Cache被关闭时设备(CPU/外设)写外存异常 |
设备(CPU/外设)访问空白地址异常 |
|
CPU在 空白地址 取指令异常 |
|
CPU读 空白地址 异常 |
|
CPU写 空白地址 异常 |
|
access_sbcRam_is_not_sbc |
sbc设备内部RAM被其他设备访问异常 |
access_IcacheRam_or_DcacheRam |
Icache/Dcache Ram不可直接访问异常 |
CPU/USB/其他设备访问了没有被映射的mmu虚拟空间(具体设备请看 PRP设备列表V1) |
|
CPU/USB/其他设备设备访问了超出权限范围的地址(具体设备请看 PRP设备列表V1) |
|
LSB设备访问了没有被映射的mmu虚拟空间(具体设备请看 PRP设备列表V1) |
|
LSB设备设备访问了超出权限范围的地址(具体设备请看 PRP设备列表V1) |
设备名称 |
设备描述 |
USB |
USB设备 |
DCP_WR |
dma_copy写设备 |
DCP_RD |
dma_copy读设备 |
LSB_LG0 |
/ |
LSB_LG1 |
/ |
FM |
FM设备 |
BT |
BT设备 |
FFT |
FFT设备 |
EQ |
EQ设备 |
FIR |
FIR(SRC)设备 |
CPU_WR |
CPU写设备 |
CPU_RD |
CPU读设备 |
CPU_IF |
CPU取指令设备 |
ALNKn |
Audio Link设备 |
AUDIO |
Audio设备 |
SPDIF_I |
SPDIF_I设备 |
SPDIF_D |
SPDIF_D设备 |
SBC |
SBC设备 |
SD0_C |
SD设备 |
SD0_D |
SD设备 |
SPI0/1/2 |
SPI0/1/2设备 |
UART0/1/2_WR |
UART 0/1/2写设备 |
UART0/1/2_RD |
UART 0/1/2读设备 |
CTMU |
CTMU触摸设备 |
PLNK |
PDM LINK设备 |
PAP |
PAP设备 |
异常信息 |
异常描述 |
内存非对齐异常访问 |
|
非法指令异常 |
|
除0异常 |
|
堆栈溢出异常 |
|
程序跑飞异常 |
|
fpu_ine_err |
浮点不精确异常 |
fpu_huge_err |
浮点上溢出异常 |
fpu_tiny |
浮点下溢出异常 |
fpu_inf_err |
浮点无穷大异常 |
fpu_inv_err |
浮点NaN异常 |
icache excption |
icache异常 |
dcache excption |
dcache异常 |
xxx access hmem excption <at dev: xxx> |
|
xxx mmu excption <at dev: xxx> |
xxx设备访问了没有被映射的mmu虚拟空间(具体设备请看 PRP设备列表V2) |
csfr_write invalid <at dev: xxx> |
|
csfr_read invalid <at dev: xxx> |
|
mpu_err <at dev: xxx> |
某个设备访问到mpu保护区 |
CPU写到高速设备sfr 空白地址 |
|
CPU读到高速设备sfr 空白地址 |
|
CPU写到axi 空白地址 |
|
CPU读到axi 空白地址 |
|
看门狗超时异常 |
|
sbc ram access error <at dev: xxx> |
sbc设备内部ram被其他设备访问异常 (具体设备请看 PRP设备列表V2) |
CPU写到低速设备sfr 空白地址 |
|
CPU读到低速设备sfr 空白地址 |
设备名称 |
设备描述 |
sdtap access/DBG_SDTAP |
SDTAP设备 |
lg0 access |
/ |
axi read |
AXI总线读设备 |
axi write |
AXI总线写设备 |
bt access/DBG_BT |
蓝牙设备 |
fft access/DBG_FFT |
FFT设备 |
eq access/DBG_EQ |
EQ设备 |
fir access/DBG_FIR |
FIR(SRC)设备 |
cpu0 write/DBG_CPU0_WR |
CPU0写设备 |
cpu0 read/DBG_CPU0_RD |
CPU0读设备 |
cpu0 instruction fetch/DBG_CPU0_IF |
CPU0取指令设备 |
DBG_USB |
USB设备 |
DBG_FM |
FM设备 |
DBG_CTM |
CTM设备 |
DBG_UART0/1/2R |
UART0/1/2读设备 |
DBG_UART0/1/2W |
UART0/1/2写设备 |
DBG_SPI0/1/2 |
SPI0/1/2设备 |
DBG_SD0/1 |
SD0/1设备 |
DBG_AES |
AES设备 |
DBG_AAC |
AAC设备 |
DBG_SBC |
SBC设备 |
DBG_PLNK |
PDM LNK设备 |
DBG_PAP |
PAP设备 |
DBG_ISP |
ISP设备 |
DBG_SPIIF_D |
SPIIF_D设备 |
DBG_SPIIF_I |
SPIIF_I设备 |
DBG_AUDIO |
AUDIO设备 |
DBG_ALIN0/1 |
Audio LINK0/1设备 |
DBG_AXI_M0-MF |
AXI总线设备0~F |
5.1.2.3. 异常位置定位方法
在 异常消息示例 中,利用 异常信息列表 查询到异常原因后,可以使用异常信息中使用 PC Trace
和 reti
的信息,分析异常位置,分析示例如下:
分析需要的用到相关文件有:
ELF文件(sdk.elf):编译器自动生成,该文件是一个 二进制 文件。
反汇编文件(sdk.lst):由sdk.elf文件经过工具转换生成,检查后处理脚本:
sdk/cpu/xxx/tools/download.bat
/sdk/cpu/xxx/tools/download.c
,是否使用如下命令行命令
%OBJDUMP% -D -address-mask=0x1ffffff -print-dbg %ELFFILE% > sdk.lst
该文件记录芯片运行指令信息,部分内容截取如下:
main:
1e0e6c4: 77 04 [--sp] = {rets, r7-r4} ## init.c:204:0
1e0e6c6: 80 ea eb 4a call 38358 <wdt_close : 1e17ca0 > ## init.c:205:5
1e0e6ca: 98 ea 4b 4b call 3184278 <os_init : 117d64 > ## init.c:207:5
1e0e6ce: c6 ff 50 30 01 00 r6 = 77904 <gatt_client_conn_handle : 13050 > ## init.c:207:5
1e0e6d4: 50 ee 6c 0d r0 = b[r6+220] (u) ## init.c:207:5
1e0e6d8: c4 ff 30 ac e1 01 r4 = 31566896 <hid_information+0x10 : 1e1ac30 > ## init.c:207:5
内容主要有4列:
描述 |
示例内容 |
说明 |
第1列 |
1e0e6c6: |
指令运行地址 |
第2列 |
80 ea eb 4a |
指令机器码 |
第3列 |
call 38358 <wdt_close : 1e17ca0 > |
机器码反汇编的汇编代码 |
第4列 |
## init.c:205:5 |
该指令来自的源文件:行数:列数 |
在目录 sdk/cpu/xxx/tools
准备好上述文件后,就可以对照 异常信息示例 的信息,分析一般的步骤如下:
先浏览异常信息的debug err msg获取有用信息,初步定位异常原因,如 异常信息示例 示例中描述了
peripheral_access_reserved_address
和cpu_write_reserved_address
,可知初步定位到是外设或者CPU访问了非法地址。根据 异常信息示例 中的
CPU trace
以及reti
,该值表示在意义为:
Note
CPU trace:系统进系统前CPU PC的跳转轨迹,跳转指遇到goto,call,rets,reti等非顺序执行的指令。
reti:指系统进异常中断函数前执行最后一个指令的位置,注:reti所在位置的指令未被执行。
举例分析 异常信息示例 中的CPU轨迹:
CPU trace: 0x00014220 -->0x01E0C2F8 -->0x01E0F252 -->0x01E125BE
reti : 0x01E125BE
CPU最后执行的位置是0x01E125BE,在sdk.lst文件找到该地址的地方,内容如下:
wdt_clear:
1e125ba: 50 20 r0 = 64 ## wdt.c:56:0
1e125bc: f7 9a goto -12 <wdt_or_con : 1e125b2 > ## wdt.c:57:5
clr_wdt:
1e125be: f7 9d goto -6 <wdt_clear : 1e125ba > ## wdt.c:61:0
可知0x01E125BE地址为clr_wdt函数的入口地址,该指令未执行,说明该指令为CPU进异常前被打断的位置,可以看到CPU Trace的最后一次跳转也是该位置,说明软件流程在调用clr_wdt这个函数前就发生了异常,再查看地址0x01E125BE的上一级为0x01E0F252,在sdk.lst文件找到该地址的地方:
24271 1e0f248: c0 ff 10 1d e2 01 r0 = 31595792 <hogp_profile_data+0x40B : 1e21d10 > ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\app_main.c:143:5
24272 1e0f24e: bf ea 28 e8 call -12208 <puts : 1e0c2a2 > ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\app_main.c:143:5
24273 1e0f252: 40 28 r0 = 8 ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\app_main.c:131:12
24274 1e0f254: 40 ea 5a 00 [r0+0] = 0x5A ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\app_main.c:131:12
24275 1e0f258: 40 e0 04 02 r0 = 516 <BTCTLER_CL_BSS_SIZE+0x8 : 204 > ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\cpu\br25\charge.c:159:20
24276 1e0f25c: d8 ee c0 00 r0 = b[r12+r0] (u) ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\cpu\br25\charge.c:159:20
24277 1e0f260: 80 5e if (r0 != 0) goto 60 <app_task_init+0x7EC : 1e0f29e > ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\cpu\br25\charge.c:159:20
24278 1e0f262: 86 14 r7_r6 = 0
24279 1e0f264: 45 e0 7c 01 r5 = 380 <BTCTLER_LE_CONTROLLER_DATA_SIZE+0xAA : 17c >
24280 1e0f268: 80 ea a9 19 call 13138 <clr_wdt : 1e125be > ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\modules\power\app_power_manage.c:337:9
24281 1e0f26c: 80 ea 28 2e call 23632 <get_vbat_level : 1e14ec0 > ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\modules\power\app_power_manage.c:338:15
24282
24283 ......
24284
242851e0f29a: 80 fc e5 15 if (r0 <= 10) goto -54 <app_task_init+0x7B6 : 1e0f268 >
242861e0f29e: 80 ea ad 47 call 36698 <vbat_check_init : 1e181fc >
242871e0f2a2: 40 20 r0 = 0 ## E:\001_SVN_DIR\004_BD29\sdk_release\AC630N_bt_data_transfer_sdk_release_v2.2.1\sdk\apps\hid\app_main.c:172:5
这个0x01E0F252地址是调用了puts函数返回后得到的地址,从这个地址0x01E0F252开始顺序执行到0x1e0f268地址然后调用了clr_wdt(位于0x1e125be)函数,因此PC Trace会存在0x01E125BE这个跳转地址,根据文件路径信息查看源文件代码:
127void exception_trigger(void)
128{
129 int *ptr = NULL;
130
131 ptr[2] = 0x5A;
132}
133
134void app_main()
135{
136 struct intent it;
137
138 if (!UPDATE_SUPPORT_DEV_IS_NULL()) {
139 int update = 0;
140 update = update_result_deal();
141 }
142
143 printf(">>>>>>>>>>>>>>>>>app_main...\n");
144
145 exception_trigger();
146
147 if (get_charge_online_flag()) {
148#if(TCFG_SYS_LVD_EN == 1)
149 vbat_check_init();
150#endif
151 } else {
152 check_power_on_voltage();
153 }
154
155 .....
156
157}
328void check_power_on_voltage(void)
329{
330#if(TCFG_SYS_LVD_EN == 1)
331
332 u16 val = 0;
333 u8 normal_power_cnt = 0;
334 u8 low_power_cnt = 0;
335
336 while (1) {
337 clr_wdt();
338 val = get_vbat_level();
339 ......
340 }
341}
157u8 get_charge_online_flag(void)
158{
159 return __this->charge_online_flag;
160}
由汇编指令与原代码之间分析对应关系:
app_main.c 的143行的
printf
函数调用对应 sdk.lst 的24272行puts
,由于printf调用只打印了字符串,因此编译器将其优化为puts函数。app_main.c 的145行的
exception_trigger
函数调用对应 sdk.lst 的24273-24274行的指令 ,说明exception_trigger
函数调用被 内联(inline) 了。app_main.c 的147行的
get_charge_online_flag
函数调用对应 sdk.lst 的24275-24276行的指令 ,说明get_charge_online_flag
函数调用被 内联(inline) 了,get_charge_online_flag
函数原型对应 charge.c 文件的159行。app_main.c 的149行的
vbat_check_init
函数调用对应 sdk.lst 的24286行的指令。app_main.c 的152行的
check_power_on_voltage
函数调用对应 sdk.lst 的24278-24281行的指令 ,说明check_power_on_voltage
函数调用被 内联(inline) 了,check_power_on_voltage
函数原型对应 app_power_manage.c 文件的332-338行。
由CPU Trace的 0x01E0F252 -->0x01E125BE
,CPU从 sdk.lst 的24273行开始顺序执行,经过了24273-24279行指令,然后在24280行指令调用了call指令,发生了函数跳转,但还没跳转到0x1e125be的位置异常就相应了异常中断,可以对 sdk.lst 的24273-24279行的指令详细分析,由 异常信息示例 中描述的 cpu_write_reserved_address
,说明该异常原因是由CPU写了一个非法地址的内存引起的,可以特别留意 sdk.lst 的24273-24279行的哪些指令是会写内存,可以看出24274行指令是写内存操作,内存地址是 r0
所指向的地址,而 r0
是来自24273行的 r0
赋值语句,经过24273行指令后r0的值变成8,因此24274行写的内存地址是0x8这个位置,由异常信息可知,0x8的地址属于 空白区 ,读写区域地址会触发写内存系统异常,由 sdk.lst 的24273-24274行的源文件信息,找到对应的源文件位置 app_main.c 的131行,该源代码使用了一个NULL作为指针写了内存,属于野指针的 非法操作 ,是引起异常的根本原因。
Attention
上述定位方法适用于CPU引起的异常,对于外设DMA引起的异步异常,CPU的现场信息只起到参考作用,可能并不是问题触发的现场,这时应该跟踪配置给外设DMA的内存流程。
5.1.2.4. 常见异常信息案例分析
程序跑飞案例分析
当程序跑飞,指CPU的PC跑到一个错误的地址,问题原因通常表现为代码中函数存在隐式调用,且函数指针 没有被初始化 或者初始化为 非法值 ,错误的示例代码如下:
1typedef void (*FUN)(void); 2int msg[4]; 3 4void pc_limit_foo(void) 5{ 6 7 FUN err_fun = (FUN *)msg; //错误赋值 8 err_fun(); //隐式调用错误函数,将触发异常 9 10 return; 11}
执行上述代码会出现异常信息:
debug err msg : cpu_execute_over_limit 或者 cpu_instruction_fetch_from_reserved_address 或者 [0-CPU] emu err msg : pc_limit
除0异常案例分析
在除法运算中当除数为0时,就会触发除0异常,错误的示例代码如下:
1void div0_foo(void) 2{ 3 int *ptr = (int *)zalloc(20); //申请之后ptr buf数据为0 4 5 int a = 1234; 6 int b = ptr[0]; //该数据为0 7 int c = a / b; 8 9 printf("c = 0x%x", c); //该行不会打印,因为执行上一行已经触发了异常 10 11 return; 12}
执行上述代码会出现异常信息:
emu err msg : div0_err 或者 [0-CPU] emu err msg : div0_err
非法指令异常案例分析
当CPU遇到不在指令编码范围的指令时就会触发非法指令异常,该异常通常与程序跑飞有关,错误的示例代码如下:
1const int err_code[]= {0x01e101e1, 0x01e101e1, 0x01e101e1, 0x01e101e1, 0x01e101e1}; //错误的指令 2 3typedef void (*FUN)(void); 4 5void illegal_err_foo(void) 6{ 7 FUN *fun = (FUN *)err_code; //错误的赋值 8 9 fun(); //触发异常 10}
emu err msg : illeg_err 或者 [0-CPU] emu err msg : illegal_err
实际上,执行上述代码时,可能会触发程序跑飞异常 程序跑飞异常 。
堆栈溢出案例分析
当用户在任务中调用函数时,所用到的堆栈最大值大于该任务配置的堆栈深度时,就会触发堆栈溢出问题,该问题通常是用户在函数用到的临时变量较大或者嵌套的函数深度较深有关,错误的示例代码如下:
static void stack_ov_err_foo(int cnt) { int buf[100]; //临时变量占用堆栈 printf("cnt: %d, buf @ %x", cnt, (u32)&buf); if (cnt--) { stack_ov_err_foo(cnt); //嵌套调用 } } void test_foo(void) { stack_ov_err_foo(1000); //嵌套1000层,触发堆栈溢出异常 }
emu err msg : stack_overflow_err 或者 [0-CPU] emu err msg : stack overflow err
非对齐访问案例分析
当CPU读word或者half-word内存数据时,寻址指针需要满足如下约束:
Word(4byte)访问:地址4对齐
Half-Word(2byte)访问:地址2对齐
如果上述条件不满足,将会触发非对齐异常,错误示例代码如下:
static void misalign_err_foo(void) { u32 *ptr = (u32 *)(malloc(8) + 1); //malloc得到的地址是4对齐, +1后ptr的值不是4对齐 ptr[1] = 1234; //ptr非4对齐地址,使用32bit访问会触发非对齐异常 }
emu err msg : misalign_err 或者 [0-CPU] emu err msg : misalign_err
读写MMU非法内存地址
SDK中是使用malloc函数族接口动态申请的内存,被free后仍在访问,就会触发MMU异常,触发该异常的访问可以是 CPU ,也可以是 外设的DMA ,错误的示例代码如下:
static void mmu_err_foo(void) { u32 *ptr = malloc(512); //malloc申请内存 //memset(ptr, 0x5A, 256); //访问没问题 ptr[66] = 0x5A; free(ptr); //释放内存 ptr[67] = 0xA5; printf("ptr = 0x%x", (u32)ptr); return; }
debug err msg : cpu_access_mmu_error debug err msg : peripheral_access_mmu_error prp wr mmu err msg : CPU_WR 或者 [0-CPU] emu err msg : sys excption [1-HCORE] mmu err msg : cpu0 write mmu excption
上述举的例子是CPU触发的MMU异常,如果将ptr只给赋值给某个外设(比如SPI/UART)的DMA地址寄存器,当ptr被free后,外设仍在读写ptr所在buf时,就会触发异常,异常信息可能就会变成:
debug err msg : peripheral_access_mmu_error prp wr mmu err msg : UART0_WR 或者 [0-CPU] emu err msg : sys excption [1-HCORE] mmu err msg : lg0 write mmu excption at dev: DBG_SPI1
读写内存空白地址
当指向buf地址的野指针访问时,由于野指针就像随机数一样,有概率会指向 空白地址 ,访问该buf时就会触发读写内存空白地址异常,错误的示例代码如下:
static void reserved_err_foo(void) { u32 *ptr = (u32 *)rand32(); //模拟野指针产生 memset(ptr, 0x5A, 256); //野指针访问,有概率会写到空白地址 return; }
debug err msg : cpu_write_reserved_address 或者 [0-CPU] emu err msg : sys excption [1-HCORE] hmem err msg : cpu0 write hmem excption
内存保护异常(内存/变量被未知程序修改)
当某些存在bug的代码产生野指针在合法范围之内,就不会触发 空白地址 或者 MMU非法地址 异常,但是会改掉其他模块的全局变量,就会给令开发者感到疑惑,某些全局变量被不明原因的修改了,当发生这种情况时,可以使用SDK提供的MPU内存保护接口将意外被修改的变量保护起来,保护后被意外修改时将会触发异常,SDK默认会使用相关MPU接口保护系统内部一些内存范围,如果用户流程写到该保护范围,将会触发异常。
AC632N, AC637N, AC638N MPU使用示例接口
1/* ---------------------------------------------------------------------------- */ 2/** 3 * @brief Memory权限保护设置 4 * 5 * @param idx: 保护框索引, 范围: 0 ~ 3, 目前系统默认使用0和3, 用户可用1和2 6 * @param begin: Memory开始地址 7 * @param end: Memory结束地址 8 * @param inv: 0: 保护框内, 1: 保护框外 9 * @param format: "Cxwr0rw1rw2rw3rw", CPU:外设0:外设1:外设2:外设3, 10 * @param ...: 外设ID号索引, 如: DBG_EQ, 见debug.h 11 */ 12/* ---------------------------------------------------------------------------- */ 13void mpu_set(int idx, u32 begin, u32 end, u32 inv, const char *format, ...); 14 15 16int test_buf[2]; 17void mpu_protect(void) 18{ 19 u32 begin = (u32)test_buf; 20 u32 end = begin + sizeof(test_buf) - 1; 21 mpu_set(2, begin, end, 0, "Cr"); //只给CPU读,写会触发异常 22 //mpu_set(2, begin, end, 0, "0r", get_dev_id("DBG_USB")); //CPU不能读写,USB设备可读 23 //mpu_set(2, begin, end, 0, "Crw0rw", get_dev_id("DBG_USB")); //CPU能读写,USB设备可读写 24 //CPU能读写, USB设备可读写, BT设备可读, EQ设备可写, FFT设备可读,最多支持配置4个设备 25 //mpu_set(2, begin, end, 0, "Crw0rw1r2w3r", get_dev_id("DBG_USB"), get_dev_id("DBG_BT"), get_dev_id("DBG_EQ"), get_dev_id("DBG_FFT")); 26 27 test_buf[1] = 0x5A; //写buf触发异常 28 29 printf("test_buf @ 0x%x, 0x%x", (u32)test_buf, test_buf[0]); 30 31 return; 32}
上述代码运行后会在写bug位置触发异常,开发者可以利用CPU Trace和reti位置在sdk.lst用找到对应被修改的位置。
CPU trace: 0x01E02158 -->0x01E05C82 -->0x01E165F6 -->0x01E133F0 reti : 0x01E133F0 [0-CPU] emu err msg : sys excption [1-HCORE] emu err msg : mpu_err mpu limit : at frame 0x00000002 at dev id: 0x000000F0 DBG_CPU0_WR
AC635N, AC636N MPU使用示例接口
1//CPU写(store)超出设定范围; mode = 1:框内; mode = 0:框外 2void dsp_store_rang_limit_set(void *low_addr, void *high_addr, u8 mode); 3 4//外设写(store)超出设定范围; mode = 1:框内; mode = 0:框外 5void prp_store_rang_limit_set(void *low_addr, void *high_addr, u8 mode); 6 7 8int test_buf[2]; 9void mpu_protect(void) 10{ 11 u32 begin = (u32)test_buf; 12 u32 end = begin + sizeof(test_buf) - 1; 13 14 dsp_store_rang_limit_set((void *)begin, (void *)end, 1); //cpu不能写 15 prp_store_rang_limit_set((void *)begin, (void *)end, 1); //所有外设不能写 16 17 test_buf[1] = 0x5A; //写buf触发异常 18 19 printf("test_buf @ 0x%x, 0x%x", (u32)test_buf, test_buf[0]); 20 21 return; 22}
上述代码运行后会在写bug位置触发异常,开发者可以利用CPU Trace和reti位置在sdk.lst用找到对应被修改的位置。
reti : 0x01E0C05C CPU trace: 0x01E1301E -->0x01E1301A -->0x01E0F242 -->0x01E0C05C debug err msg : cpu_write_data_over_limit
Caution
AC630N,AC631N不具备MPU功能
- 看门狗超时异常
系统由于长时间没有清看门狗导致的系统异常,该问题常见原因是CPU负载过高,长时间没有进idle任务(清看门狗),或者在某个软件流程死循环,导致系统没有清看门狗,错误的示例代码如下:
void watchdog_err_foo(void) { while(1){ //TODO: NO block Code,非阻塞死循环,看门狗超时会触发异常 } return; }
debug err msg : debug err msg : watchdog 或者 [0-CPU] emu err msg : sys excption [1-HCORE] emu err msg : hsb emu err [2-HSB] emu err msg : watchdog time out