5.1. 串口调试

在嵌入式平台开发中, 串口调试 以其依赖硬件结构简单和稳定可靠的优点,被誉为硬件状态和软件运行结果的 听诊器 ,成为调试软件的重要手段。SDK中串口可以输出的信息有:

Note

  1. 常规调试信息

  2. 系统异常信息

以下章节分别进行说明。

5.1.1. 常规调试信息

SDK发布时会附带一些基本的调试信息,比如输出上电流程的各个模块的基本信息和状态。

5.1.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

    需要修改点:

    1. 宏LIB_DEBUG配置为1.

    2. 宏CONFIG_DEBUG_ENABLE需要被定义.

  2. 某些模块的的调试信息可能还有自己的单独开关

    相关文件路径: 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

    1. log_tag_const_v_xxx: verbose, 模块中比较冗余的信息。

    2. log_tag_const_i_xxx: info, 模块中一些基本的状态信息。

    3. log_tag_const_d_xxx: debug, 模块中用于定位问题的基本信息。

    4. log_tag_const_w_xxx: warning, 模块中用于定位问题的警告信息。

    5. log_tag_const_e_xxx: error, 模块中用于定位问题的错误信息。

    用户可以将需要看调试信息的模块对应的开关置位 TURE

  3. 使能硬件驱动开关

    除了上述的逻辑开关外,还需要确认对应硬件配置,举例文件 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                  //串口波特率配置
    

    用户根据实际硬件平台可灵活配置 TXBAUD 参数。

5.1.1.2. 添加自己的调试信息

用户在开发自己软件流程时, 经常需要添加自己的调试信息,SDK中支持的输出调试信息的接口有

  1. 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}
    
  2. log_xxx族接口

    log_xxx族接口与printf的区别是该接口可以分模块控制,以及输出信息可以带模块名前缀,可以快速定位到对应模块输出的信息。举例文件举例文件 sdk/apps/hid/app_main.c, 使用步骤如下:

    1. 在模块对应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

      修改注意点:

      1. 定义LOG_TAG_CONST宏的命令与模块Debug名相同。

      2. debug.h 文件被include的位置必须位于这些宏定义之后。

    2. 增加模块打印开关控制变量, 文件夹路径 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;
      
    3. 在对应模块添加调试信息,可用接口示例如下:

       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
      
    4. 系统时间戳

      在输出的log中存在系统时间戳,该时间戳代表输出该log时距离系统的上电时间,如果用户不需要输出该时间戳,可以通过修改文件 sdk/apps/xxx/config/lib_system_config.c

      ///打印是否时间打印信息
      const int config_printf_time         = 1;
      

      将config_printf_time变量修改为0,可以关闭log打印中输出时间戳。

5.1.2. 系统异常信息

系统异常指芯片在运行的代码时,由于软件/硬件状态出错,当该错误状态未在硬件/软件程序设计覆盖的容错范围,就会引起的系统处于未知状态的 异常 ,异常的结果是触发 系统复位 或者进入 系统异常中断函数 ,常见的异常原因主要有:

  1. CPU内部触发的异常:
    • 程序跑飞 : CPU运行到程序员非指定地址软件流程,该异常通常由一些没有正确初始化的 函数指针变量 引起。

    • 除0异常 : CPU在除法运算中除数为0,就会触发除0异常。

    • 非对齐内存访问 :CPU运行word内存访问指令时,寻址寄存器不是4对齐的值,或者运行half-word内存访问指令时,寻址寄存器不是2对齐的值,就会触发非对齐内存访问异常。

    • 堆栈溢出 :但CPU的堆栈超出所设定的限制范围时,就会出现堆栈溢出异常,该异常通常与任务用到的堆栈大于配置的堆栈大小引起。

  2. 与内存相关触发的异常:
    • 读写非法内存地址 : 当CPU读写到非法的内存地址,非法内存地址主要包括:空白地址,只读地址、程序员使用MPU保护的内存地址,当CPU读写到没有权限的内存地址时,就会触发异常异常,该异常主要由于buf野指针引起,或者动态申请中在free buf后,还在访问buf的数据。

    • 外设DMA读写非法地址 : 与CPU读写非法内存地址类似,当外设DMA读写到了没有读写权限的非法地址,就会触发外设的读写非法地址异常。

  3. 与外设相关触发的异常:
    • 看门狗超时 : 系统由于长时间没有清看门狗导致的系统异常,该问题常见原因是CPU负载过高,长时间没有进idle任务,或者在某个软件流程死循环,导致系统没有清看门狗。

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变量配置定义如下:

  1. 配置为0:系统异常触发复位

  2. 配置为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. 各系列异常信息列表

异常信息列表V1(适用AC635N, AC636N)

异常信息

异常描述

misalign_err

内存非对齐异常访问

illeg_err

非法指令异常

stack_overflow_err

堆栈溢出异常

cpu_execute_over_limit

程序跑飞异常

fpu_ine_err

浮点不精确异常

fpu_huge_err

浮点上溢出异常

fpu_tiny

浮点下溢出异常

fpu_inf_err

浮点无穷大异常

fpu_inv_err

浮点NaN异常

peripheral_write_data_over_limit

设备(CPU/外设)写地址超出权限范围

watchdog time out

看门狗超时异常

peripheral_access_mmu_error

设备(CPU/外设)写了没有被映射的mmu虚拟空间

cpu_write_data_over_limit

CPU写地址超出权限范围

peripheral_read_external_memory_when_cache_disable

Cache被关闭时设备(CPU/外设)读外存异常

peripheral_write_external_memory_when_cache_disable

Cache被关闭时设备(CPU/外设)写外存异常

peripheral_access_reserved_address

设备(CPU/外设)访问空白地址异常

cpu_instruction_fetch_from_reserved_address

CPU在 空白地址 取指令异常

cpu_read_reserved_address

CPU读 空白地址 异常

cpu_write_reserved_address

CPU写 空白地址 异常

access_sbcRam_is_not_sbc

sbc设备内部RAM被其他设备访问异常

access_IcacheRam_or_DcacheRam

Icache/Dcache Ram不可直接访问异常

prp wr mmu err msg : CPU/USB/…

CPU/USB/其他设备访问了没有被映射的mmu虚拟空间(具体设备请看 PRP设备列表V1)

prp wr limit err msg : CPU/USB/…

CPU/USB/其他设备设备访问了超出权限范围的地址(具体设备请看 PRP设备列表V1)

lsb wr mmu err msg : UART0_RD/…

LSB设备访问了没有被映射的mmu虚拟空间(具体设备请看 PRP设备列表V1)

lsb wr limit err msg : UART0_RD/…

LSB设备设备访问了超出权限范围的地址(具体设备请看 PRP设备列表V1)

异常信息列表V1(适用AC635N, AC636N)

设备名称

设备描述

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设备

异常信息列表V2(适用AC630N, AC631N, AC632N, AC637N, AC638N)

异常信息

异常描述

misalign_err

内存非对齐异常访问

illegal_err

非法指令异常

div0_err

除0异常

stack overflow err

堆栈溢出异常

pc_limit

程序跑飞异常

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 tlb保护空间(具体设备请看 PRP设备列表V2)

xxx mmu excption <at dev: xxx>

xxx设备访问了没有被映射的mmu虚拟空间(具体设备请看 PRP设备列表V2)

csfr_write invalid <at dev: xxx>

xxx设备写到csfr 空白地址 (具体设备请看 PRP设备列表V2)

csfr_read invalid <at dev: xxx>

xxx某个设备读到csfr 空白地址 (具体设备请看 PRP设备列表V2)

mpu_err <at dev: xxx>

某个设备访问到mpu保护区

cpu write hsb sfr reserved memory

CPU写到高速设备sfr 空白地址

cpu read hsb sfr reserved memory

CPU读到高速设备sfr 空白地址

cpu write axi reserved memory

CPU写到axi 空白地址

cpu read axi reserved memory

CPU读到axi 空白地址

watchdog time out

看门狗超时异常

sbc ram access error <at dev: xxx>

sbc设备内部ram被其他设备访问异常 (具体设备请看 PRP设备列表V2)

cpu write lsb sfr reserved memory

CPU写到低速设备sfr 空白地址

cpu read lsb sfr reserved memory

CPU读到低速设备sfr 空白地址

异常信息列表V2(适用AC630N, AC631N, AC632N, AC637N, AC638N)

设备名称

设备描述

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 Tracereti 的信息,分析异常位置,分析示例如下:

分析需要的用到相关文件有:

  • 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

该文件记录芯片运行指令信息,部分内容截取如下:

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列:

sdk.lst文件格式

描述

示例内容

说明

第1列

1e0e6c6:

指令运行地址

第2列

80 ea eb 4a

指令机器码

第3列

call 38358 <wdt_close : 1e17ca0 >

机器码反汇编的汇编代码

第4列

## init.c:205:5

该指令来自的源文件:行数:列数

在目录 sdk/cpu/xxx/tools 准备好上述文件后,就可以对照 异常信息示例 的信息,分析一般的步骤如下:

  1. 先浏览异常信息的debug err msg获取有用信息,初步定位异常原因,如 异常信息示例 示例中描述了 peripheral_access_reserved_addresscpu_write_reserved_address ,可知初步定位到是外设或者CPU访问了非法地址。

  2. 根据 异常信息示例 中的 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文件找到该地址的地方:

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这个跳转地址,根据文件路径信息查看源文件代码:

app_main.c
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}
app_power_manage.c
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}
charge.c
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. 常见异常信息案例分析

  1. 程序跑飞案例分析

    当程序跑飞,指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
    
  2. 除0异常案例分析

    在除法运算中当除数为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
    
  3. 非法指令异常案例分析

    当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
    

    实际上,执行上述代码时,可能会触发程序跑飞异常 程序跑飞异常

  4. 堆栈溢出案例分析

    当用户在任务中调用函数时,所用到的堆栈最大值大于该任务配置的堆栈深度时,就会触发堆栈溢出问题,该问题通常是用户在函数用到的临时变量较大或者嵌套的函数深度较深有关,错误的示例代码如下:

    堆栈溢出异常代码示例
    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
    
  5. 非对齐访问案例分析

    当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
    
  6. 读写MMU非法内存地址

    SDK中是使用malloc函数族接口动态申请的内存,被free后仍在访问,就会触发MMU异常,触发该异常的访问可以是 CPU ,也可以是 外设的DMA ,错误的示例代码如下:

    读写MMU非法内存地址异常代码示例
    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;
    }
    
    读写MMU非法内存地址异常打印信息1
    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时,就会触发异常,异常信息可能就会变成:

    读写MMU非法内存地址异常打印信息2
    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
    
  7. 读写内存空白地址

    当指向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
    
  8. 内存保护异常(内存/变量被未知程序修改)

    当某些存在bug的代码产生野指针在合法范围之内,就不会触发 空白地址 或者 MMU非法地址 异常,但是会改掉其他模块的全局变量,就会给令开发者感到疑惑,某些全局变量被不明原因的修改了,当发生这种情况时,可以使用SDK提供的MPU内存保护接口将意外被修改的变量保护起来,保护后被意外修改时将会触发异常,SDK默认会使用相关MPU接口保护系统内部一些内存范围,如果用户流程写到该保护范围,将会触发异常。

    1. 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
      
    2. 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功能

  9. 看门狗超时异常

    系统由于长时间没有清看门狗导致的系统异常,该问题常见原因是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
    

5.1.2.5. 异常信息相关术语与缩写词

reserved

空白区,也叫保留区,指CPU寻址范围内保留的区域,不可访问(invalid),访问会触发异常。

空白地址

reserved

MMU

Memory Management Unit,虚拟内存管理单元。