芯片:ESP32-D0WDQ6
IDF版本:v4.2.1
开发环境:tools for windows,cmake。
这几天在写一个项目,同时用到uart和ota功能。当uart发送数据时,同时进行ota升级,就会产生panic。
经过长时间的追踪和分析,已经确认了bug原因,并采取了临时补救措施。
下面是错误分析过程:
espesp-idfcomponentsdriveruart.c
这个文件里有uart_rx_intr_handler_default函数,它是布局在IRAM里的代码,这是为了在cache禁用期间能处理uart中断请求。
它有一个分支,执行了uart_hal_is_tx_idle宏(其实这个宏是uart_ll_is_tx_idle的别名),请看下面:
espesp-idfcomponentssocincludehaluart_hal.h
Code:
Select all
#define uart_hal_is_tx_idle(hal) uart_ll_is_tx_idle((hal)->dev)
而uart_ll_is_tx_idle定义在如下文件:
espesp-idfcomponentssocsrcesp32includehaluart_ll.h
Code:
Select all
sta
tic inline bool uart_ll_is_tx_idle(uart_dev_t *hw){ typeof(hw->status) status = hw->status; return ((status.txfifo_cnt == 0) && (status.st_utx_out == 0));}
它是一个inline函数。我们都认为inline关键字,会告诉编译器内联此函数,于是它的代码会被展开到调用方函数体内,
比如uart_rx_intr_handler_default函数在IRAM内,那么uart_ll_is_tx_idle的逻辑也就在IRAM里,事实上这是理想状态而已。
就是这个假设引起了bug!
我观察的现象为:uart.c里有三处代码调用了uart_ll_is_tx_idle内联函数,但是
在编译器配置为-Os的情况下,它自作聪明地把uart_ll_is_tx_idle函数编译成非内联函数!估计3处调用,编译为非内联可以节省空间吧?于是uart_ll_is_tx_idle函数实体就被默认安排在flash地址空间了,不再是安全的IRAM里!这就导致uart中断里执行了flash代码,而同时ota禁用cache的话就发生panic了。
bug复现方式就是把sdkconfig文件里优化配置为CONFIG_COMPILER_OPTIMIZATION_SIZE=y,然后观察uart_ll_is_tx_idle是否内联;在readelf -s导出的标号里,也能看到uart_ll_is_tx_idle,并且是在flash地址段。
我的临时解决方法是:在uart.c文件里加一行声明,强制uart_ll_is_tx_idle内联(它比关键字inline靠谱!)Code:
Select all
static inline bool uart_ll_is_tx_idle(uart_dev_t *hw) __attribute((always_inline));
或者把CONFIG_COMPILER_OPTIMIZATION_SIZE取消,设置为CONFIG_COMPILER_OPTIMIZATION_PERF=y也行。
但是通过改编译优化等级来解决问题并不科学,这说明代码经不起优化,而且指不定引入其它隐秘的bug呢?