ESP-IDF Guru Meditation 错误分析

此博客会列举说明开发者在开发过程中遇到的 Guru Meditation Error 以及对应的分析 & DEBUG 思路。


1 序言

在一一分析不同 Guru Meditation Error 前,您可以先了解遇到 ESP-IDF crash log 时的大体分析思路,请参考 ESP-IDF 根据 crash log 进行代码初步 debug 思路


2 Guru Meditation Error 分析

Guru Meditation Error 往往显示如下:

Guru Meditation Error: Core 0 panic'ed (IllegalInstruction). Exception was unhandled.

错误原因往往会被打印在括号中。本节将对打印在 Guru Meditation Error: Core panic'ed 后面括号中的致错原因进行逐一解释。

注:您也可以参阅 Guru Meditation 错误 以查看所有可能的出错原因。

IllegalInstruction

此 CPU 异常表示当前执行的指令不是有效指令,引起此错误的常见原因包括:

  • FreeRTOS 未调用 vTaskDelete()函数释放当前任务的资源,而是直接返回
  • 无法从 SPI Flash 中加载下一条指令,这通常发生在:
    • 应用程序将 SPI Flash 的引脚重新配置为其它功能(如 GPIO,UART 等等)
    • 某些外部设备意外连接到 SPI Flash 的引脚上,干扰了 ESP32 和 SPI Flash 之间的通信

InstrFetchProhibited

此 CPU 异常表示 CPU 无法加载指令,因为指令的地址不在 IRAM 或者 IROM 中的有效区域中。

通常这意味着代码中调用了并不指向有效代码块的函数指针。这种情况下,可以查看 PC(程序计数器)寄存器的值并做进一步判断:若为 0 或者其它非法值(即只要不是 0x4xxxxxxx 的情况),则证实确实是该原因。

若 PC 指针大规模指向无效区域,那么 coredump 发生的可能原因某个引脚接触不良等原因导致。

可对照下面 ESP32 映射图,确定其 PC 指针位置。

ESP-IDF Guru Meditation 错误分析_第1张图片

LoadProhibited, StoreProhibited

当应用程序尝试读取或写入无效的内存位置时,会发生此类 CPU 异常。此类无效内存地址可以在寄存器转储的EXCVADDR 中找到:

  • 如果该地址为零,通常意味着应用程序正尝试解引用一个 NULL 指针
  • 如果该地址接近于零,则通常意味着应用程序尝试访问某个结构体的成员,但是该结构体的指针为 NULL
  • 如果该地址是其它非法值(不在 0x3fxxxxxx - 0x6xxxxxxx 的范围内),则可能意味着用于访问数据的指针未初始化或者已经损坏

举例:引入空指针导致 StoreProhibited 错误。如下:
ESP-IDF Guru Meditation 错误分析_第2张图片

IntegerDivideByZero

应用程序尝试将整数除以零。

LoadStoreAlignment

应用程序尝试读取/写入的内存位置不符合加载/存储指令对字节对齐大小的要求。

举例:32 位加载指令只能访问 4 字节对齐的内存地址,而 16 位加载指令只能访问 2 字节对齐的内存地址。

LoadStoreError

应用程序尝试从仅支持 32 位加载/存储的内存区域执行 8 位或 16 位加载/存储操作。

举例:解引用一个指向指令内存区域的 char * 指针就会导致这样的错误。

Unhandled debug exception

这后面通常会再跟一条消息:

Debug exception reason: Stack canary watchpoint triggered (task_name)

此错误表示应用程序写入的位置越过了 task_name 任务堆栈的末尾,例如:

21:54:21.306 -> Debug exception reason: Stack canary watchpoint triggered (uart_echo) 

以上看来是 uart_echo task 出现了栈 overflow 的情况,此时可以在增大对应的任务栈。如下,将

xTaskCreate(uart_echo, "uart_echo_task", 1024, NULL, 10, NULL);

修改为

xTaskCreate(uart_echo, "uart_echo_task", 8192, NULL, 10, NULL);

注:并非每次堆栈溢出都会触发此错误。任务有可能会绕过堆栈金丝雀(stack canary)的位置访问堆栈,在这种情况下,监视点就不会被触发。

Interrupt wdt timeout on CPU0 / CPU1

这表示发生了中断看门狗超时。中断看门狗确保 FreeRTOS 任务切换中断不会长时间被阻塞。如果没有其他任务在运行,这样是不正确的(包含可能重要的任务,例如WiFi 任务和空闲任务),将会无法获取任何 CPU 运行时间。因为程序在禁用中断或陷入中断的情况下进入了无限循环,可能会发生阻塞的任务切换中断。

注:除了中断看门狗,ESP-IDF 还有任务看门狗:任务看门狗定时器(TWDT)负责检测任务实例长时间运行而没有任务切换。这是 CPU饥饿的症状,通常是由于优先级较高的任务循环而没有及时切换至优先级的任务而导致的,因此将优先级较低的任务从 CPU 时间中饿死了。(只有开启 CONFIG_ESP_TASK_WDT_PANIC 后才会触发严重错误)。

Cache disabled but cached memory region accessed

这表示在 cache 被禁用期间(例如在使用 spi_flash API 读取/写入/擦除/映射 SPI Flash 的时候),发生了 IRAM-Safe 中断并且中断程序访问了 flash 的资源。通常发生在中断处理程序调用了在 flash 中的程序,引用了 flash 中的常量。

注:当在中断处理程序里使用 double 型变量时,由于 double 型变量操作的实现是软件实现的,所以该实现部分也被存放在了 flash 中(例如强制类型转换操作)。

解决办法为:

  • 给在中断中访问的函数加上 IRAM_ATTR 修饰符
  • 给在中断中访问的常量加 DRAM_ATTR 修饰符
  • 不在中断处理程序中使用 double 类型操作

以上解决办法的详细描述可参见 3.1 IRAM 安全中断处理程序配置。

注:此异常的补充情况如下:
1.该异常只发生在 IRAM-Safe 中断,即使用 ESP_INTR_FLAG_IRAM 注册的中断处理程序。
2.该异常只有在 cache 被禁期间会出现,所以当 cache 未被禁用期间不会出现,换句话说该异常具有随机性。
3.编译器可能会将一些不会被改变的变量放入 .rodata 段中,哪怕程序员没有加 const 修饰符。对于 ESP32 的应用程序来说,.rodata 意味着该部分位于 flash 中。对于字符串变量,这种情况很容易被察觉,但是对于某些常量,就不是那么好察觉了

在某些情况下,ESP-IDF 会暂时禁止通过高速缓存访问外部 SPI Flash 和 SPI RAM,例如在使用 spi_flash API 读取/写入/擦除/映射 SPI Flash 的时候。在这些情况下,任务会被挂起,并且未使用 ESP_INTR_FLAG_IRAM 注册的中断处理程序会被禁用。请确保任何使用此标志注册的中断处理程序所访问的代码和数据分别位于 IRAM 和 DRAM 中。请参阅 SPI Flash API 文档。

出现该问题的调试技巧

  1. CPU 抛出该类型异常时,通常异常位置就是出错位置。举例:

    Guru Meditation Error: Core  1 panic'ed (Cache disabled but cached memory region accessed)
    Core 1 register dump:
    PC      : 0x4011b808  PS      : 0x00060034  A0      : 0x8008c664  A1      : 0x3ffbeca0 
    0x4011b808: __fixunsdfdi at /builds/idf/crosstool-NG/.build/src/gcc-5.2.0/libgcc/config/xtensa/ieee754-df.S:2041
    A2      : 0x0099fd28  A3      : 0x00000000  A4      : 0x3ffbf6a0  A5      : 0x3f7909f1 
    A6      : 0x00000000  A7      : 0x00013ffc  A8      : 0x80086136  A9      : 0x3ffbec80 
    A10     : 0xfffffffc  A11     : 0x40ddd57f  A12     : 0x000007fe  A13     : 0x00000000 
    A14     : 0x65580000  A15     : 0x00000000  SAR     : 0x00000014  EXCCAUSE: 0x00000007 
    EXCVADDR: 0x00000000  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0x00000000 
    Core 1 was running in ISR context:
    EPC1    : 0x40086914  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x4011b808
    0x40086914: spi_flash_enable_interrupts_caches_and_other_cpu at /home/chenzhengwei/esp/esp-idf/components/spi_flash/cache_utils.c:147
    0x4011b808: __fixunsdfdi at /builds/idf/crosstool-NG/.build/src/gcc-5.2.0/libgcc/config/xtensa/ieee754-df.S:2041
    Backtrace: 0x4011b808:0x3ffbeca0 0x4008c661:0x3ffbecd0 0x4008c69d:0x3ffbecf0 0x400857e5:0x3ffbed10 0x40086911:0x3ffd0970 0x40086cc9:0x3ffd0990 0x40117789:0x3ffd09e0 0x4011785e:0x3ffd0a10 0x401179ea:0x3ffd0a30 0x40116c51:0x3ffd0aa0 0x40117109:0x3ffd0b20 0x40116729:0x3ffd0b90 0x40105c36:0x3ffd0bd0 0x401057d7:0x3ffd0c10 0x401058ce:0x3ffd0c40 0x4008dfb5:0x3ffd0c90
    0x4011b808: __fixunsdfdi at /builds/idf/crosstool-NG/.build/src/gcc-5.2.0/libgcc/config/xtensa/ieee754-df.S:2041
    0x4008c661: gpio_isr_loop at /home/chenzhengwei/esp/esp-idf/components/driver/gpio.c:433
    0x4008c69d: gpio_intr_service at /home/chenzhengwei/esp/esp-idf/components/driver/gpio.c:433
    0x400857e5: _xt_lowint1 at /home/chenzhengwei/esp/esp-idf/components/freertos/xtensa_vectors.S:1154
    0x40086911: spi_flash_enable_interrupts_caches_and_other_cpu at /home/chenzhengwei/esp/esp-idf/components/spi_flash/cache_utils.c:147
    

    上述示例中可以发现发生异常的位置为 0x4011b808, 同时通过 backtrace 可以看到 0x4011b808__fixunssfdi 函数的存放位置,且该位置位于片外存储器(见 ESP32 技术参考手册 的 1.3.3 节),此函数用来将浮点数转换为 64 位无符号整数,意味着中断处理程序里使用了 double 型数据的强制类型转换操作。这在 cache 被关闭时操作是不被允许的。

  2. 如果不知道变量是在 .data/.bss 段还是 .rodata 段,那么可以使用 readelf 命令查看 .elf 中的符号详细信息,如下:

    # 将 build/empty-project.elf 中的符号表覆盖写到 empty-project.out 文件中
    readelf -a build/empty-project.elf > empty-project.out
    

    使用 readelf 命令提取 elf 文件中的符号表,如何查看符号表可参考 C语言变量的存储布局,通过符号表,可以准确的看到每一个变量、函数的存储位置,大小等信息。有了这些信息,就可以检查 IRAM-Safe 中断处理程序里使用变量和函数是否在 flash 中。


3 补充

3.1 IRAM 安全中断处理程序配置

如果您需要在 flash 操作期间运行中断处理程序(比如低延迟操作),请在注册中断处理程序时设置 ESP_INTR_FLAG_IRAM

注:确保中断处理程序访问的所有数据和函数(包括其调用的数据和函数)都存储在 IRAM 或 DRAM 中。

  1. 为函数添加 IRAM_ATTR 属性:
    #include "esp_attr.h"
    void IRAM_ATTR gpio_isr_handler(void* arg)
    {
        // ...
    }
    
  2. 为常量添加 DRAM_ATTR 和 DRAM_STR 属性:
    void IRAM_ATTR gpio_isr_handler(void* arg)
    {
       const static DRAM_ATTR uint8_t INDEX_DATA[] = { 45, 33, 12, 0 };
       const static char *MSG = DRAM_STR("I am a string stored in RAM");
    }
    
    

辨别哪些数据应标记为 DRAM_ATTR 可能会比较困难,除非明确标记为 DRAM_ATTR,否则编译器依然可能将某些变量或表达式当做常量(即便没有 const 标记),并将其放入 flash。
如果函数或符号未被正确放入 IRAM/DRAM 中,当中断处理程序在 flash 操作期间从 flash cache 中读取数据,则会产生非法指令异常(这是因为代码未被正确放入 IRAM)或读取垃圾数据(这是因为常数未被正确放入 DRAM),而导致崩溃。

4 参考资料

  • ESP 严重错误
  • ESP32 Guru Meditation 错误解析及解决方案
  • Why do I get the Debug exception reason: Stack canary watchpoint triggered (main)

你可能感兴趣的:(代码调试,(Code,Debug))