大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是IVT裡的不同entry設定可能會造成i.MXRT1xxx系列啟動App後發生異常跑飛問題的分析解決經驗。
事情緣起恩智浦官方論壇上的一個疑問帖 《RT1015 dev_cdc_vcom_freertos reset entry failed》,這是客戶QISDA遇到的問題,由痞子衡的同事 - 非常細心負責的Kerry小姐姐將問題整理出來併發了貼,帖子裡做了詳盡的問題描述以及各種測試結果。看完長帖後,痞子衡第一猜想就是跟App棧設定有關,最終也確實是這個原因。那麼為什麼棧設定會出問題呢?且聽痞子衡細聊:
一、問題描述
讓我們先來整理一下帖子裡的問題現象,客戶在RT1015-EVK上測試了恩智浦官方SDK裡的兩個例程,一個是簡單的hello_world,另一個是複雜的dev_cdc_vcom_freertos,這兩個例程在不同IDE、IVT中entry值組合下現象不一致:
測試App | IVT中entry | 測試IDE | App執行結果 |
---|---|---|---|
hello_world | 中斷向量表起始地址/ 復位向量函式地址 |
IAR EWARM/ MCUXpresso IDE |
正常 |
dev_cdc_vcom_freertos | 中斷向量表起始地址 | IAR EWARM/ MCUXpresso IDE |
正常 |
dev_cdc_vcom_freertos | 復位向量函式地址 | IAR EWARM | 正常 |
dev_cdc_vcom_freertos | 復位向量函式地址 | MCUXpresso IDE | 異常跑飛 |
根據上表結果,其實我們很難得出一個有效推論,只能說這個異常結果在特定的App, entry值, MCUXpresso IDE下才能復現。
二、原因探究
既然暫時看不出原因,那我們先做一些準備工作吧。我們把三個影響因子(App, entry值, IDE)的差異先整理出來:
2.1 兩個App的不同連結分配
兩個App都來自SDK,是經過官方詳盡測試的,所以我們不去懷疑App本身的功能異常。它們的差異主要在連結分配上。以IAR為例,我們只看flexspi_nor build,在連結檔案中預設分配的堆、棧大小均為1KB:
/* Sizes */
if (isdefinedsymbol(__stack_size__)) {
define symbol __size_cstack__ = __stack_size__;
} else {
define symbol __size_cstack__ = 0x0400;
}
if (isdefinedsymbol(__heap_size__)) {
define symbol __size_heap__ = __heap_size__;
} else {
define symbol __size_heap__ = 0x0400;
}
hello_world例程因為比較簡單,所以用直接用了預設的堆疊大小,而dev_cdc_vcom_freertos例程比較複雜,堆疊做了額外調整,棧增大到了8KB。
此外我們還注意到hello_world例程將其RW, ZI, 堆疊全部放進了32KB的DTCM;而dev_cdc_vcom_freertos例程則將RW, ZI放入了64KB OCRAM,只將堆疊放進了DTCM:
define symbol m_data_start = 0x20000000;
define symbol m_data_end = 0x20007FFF;
define symbol m_data2_start = 0x20200000;
define symbol m_data2_end = 0x2020FFFF;
define region DATA_region = mem:[from m_data_start to m_data_end-__size_cstack__];
define region DATA2_region = mem:[from m_data2_start to m_data2_end];
define region CSTACK_region = mem:[from m_data_end-__size_cstack__+1 to m_data_end];
// 適用hello_world例程
place in DATA_region { block RW };
place in DATA_region { block ZI };
place in DATA_region { last block HEAP };
place in DATA_region { block NCACHE_VAR };
place in CSTACK_region { block CSTACK };
// 適用dev_cdc_vcom_freertos例程
place in DATA2_region { block RW };
place in DATA2_region { block ZI };
place in DATA_region { last block HEAP };
place in DATA_region { block NCACHE_VAR };
place in CSTACK_region { block CSTACK };
2.2 entry值在BootROM中的使用
再說IVT中的entry,痞子衡在i.MXRT1xxx系列啟動那些事系列文章中的 《Bootable image格式與載入》 的3.2節介紹過IVT結構以及其作用,IVT是關鍵啟動頭,指導了BootROM去搬移App以及載入執行,其中entry成員主要用於跳轉執行。
為什麼這個entry值既可以是中斷向量表(Vector Table)起始地址也可以是復位向量(Reset_Handler)函式地址呢?這取決於BootROM中是怎麼利用這個entry值的,下面函式即是BootROM中最終跳轉函式:
void jump_to_entry(uint32_t entry)
{
typedef void (*application_callback_t)(void);
static application_callback_t s_app_callback;
pu_irom_mpu_disable();
__DMB();
__DSB();
__ISB();
// The entry point is the absolute address of the call back function
if ((uint32_t)entry & 1)
{
s_app_callback = (application_callback_t)entry;
}
// The entry point is the base address of vector table
else
{
static uint32_t s_stack_pointer;
// Ensure Core read vector table for destination instead of register
volatile uint32_t *vector_table = (volatile uint32_t *)entry;
s_stack_pointer = vector_table[0];
s_app_callback = (application_callback_t)vector_table[1];
// Update Stack pointer
__set_MSP(s_stack_pointer);
__set_PSP(s_stack_pointer);
}
__DSB();
__ISB();
// Jump to user application in the end
s_app_callback();
// Should never reach here
__NOP();
__NOP();
}
從上面的跳轉函式jump_to_entry()實現可以看出,entry值如果是復位函式地址(即奇地址),那麼BootROM直接跳轉到復位函式執行;如果entry值是中斷向量表首地址(即偶地址),BootROM會先將當前SP重設到App指定的棧頂,然後再跳轉到復位函式。
好的,現在我們知道了IVT中不同的entry值差異在哪了。
2.3 不同IDE下startup流程
因為涉及到兩個不同IDE,即IAR和MCUXpresso IDE,所以我們分別看一下這兩個IDE下的startup實現。我們知道main函式之後的程式碼基本是IDE無關的,而startup卻是因編譯器而異。
痞子衡以i.MXRT1010的SDK2.8.2包裡的例程為例,先用IAR開啟其中的dev_cdc_vcom_freertos例程,找到工程下的startup_MIMXRT1011.s檔案,看它的Reset_Handler實現:
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler
DCD NMI_Handler ;NMI Handler
DCD HardFault_Handler ;Hard Fault Handler
; ...
__Vectors_End
THUMB
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER:NOROOT(2)
Reset_Handler
CPSID I ; Mask interrupts
LDR R0, =0xE000ED08
LDR R1, =__vector_table
STR R1, [R0]
LDR R2, [R1]
MSR MSP, R2
LDR R0, =SystemInit
BLX R0
CPSIE I ; Unmask interrupts
LDR R0, =__iar_program_start
BX R0
IAR版本Reset_Handler主要分四步: 重設VTOR、重設SP、執行SystemInit(關看門狗,關Systick,處理Cache)、執行IAR庫函式__iar_program_start(data/bss/ramfunc段初始化,跳轉到main)。
再用MCUXpresso IDE開啟同樣的dev_cdc_vcom_freertos例程,找到工程下的startup_mimxrt1011.c檔案,看它的ResetISR實現:
extern void _vStackTop(void);
__attribute__ ((used, section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
// Core Level - CM7
&_vStackTop, // The initial stack pointer
ResetISR, // The reset handler
NMI_Handler, // The NMI handler
HardFault_Handler, // The hard fault handler
// ...
}; /* End of g_pfnVectors */
__attribute__ ((section(".after_vectors.reset")))
void ResetISR(void) {
__asm volatile ("cpsid i");
SystemInit();
// Copy the data sections from flash to SRAM.
unsigned int LoadAddr, ExeAddr, SectionLen;
unsigned int *SectionTableAddr;
// Load base address of Global Section Table
SectionTableAddr = &__data_section_table;
// Copy the data sections from flash to SRAM.
while (SectionTableAddr < &__data_section_table_end) {
LoadAddr = *SectionTableAddr++;
ExeAddr = *SectionTableAddr++;
SectionLen = *SectionTableAddr++;
data_init(LoadAddr, ExeAddr, SectionLen);
}
// At this point, SectionTableAddr = &__bss_section_table;
// Zero fill the bss segment
while (SectionTableAddr < &__bss_section_table_end) {
ExeAddr = *SectionTableAddr++;
SectionLen = *SectionTableAddr++;
bss_init(ExeAddr, SectionLen);
}
__asm volatile ("cpsie i");
// Call the Redlib library, which in turn calls main()
__main();
while (1);
}
MCUXpresso IDE版本ResetISR主要分三步: 執行SystemInit(重設VTOR,關看門狗,關Systick,處理Cache)、data/bss/ramfunc段初始化、跳轉到main。
經過上面對比,看出差異沒有?MCUXpresso IDE相比IAR的startup少了一步重設SP的動作。
2.4 導致異常跑飛的棧錯誤
有了前面三節的分析基礎,我們基本可以得出dev_cdc_vcom_freertos例程異常跑飛的原因是發生了棧錯誤。為什麼會發生棧錯誤?這是由於MCUXpresso下的startup中沒有重設SP操作,所以當IVT中的entry是復位向量時,BootROM跳轉到App後依舊延用BootROM中的棧,根據晶片參考手冊System Boot章節裡的資訊,BootROM的棧放在了OCRAM空間(0x20200000 - 0x202057FF),但是dev_cdc_vcom_freertos例程又把RW, ZI段也放進了OCRAM中,因此隨著App的執行對棧的利用(函式呼叫、區域性變數定義)有可能與App中的RW, ZI段資料(全域性變數)互相破壞,程式發生未知跑飛也在意料之中。
解決問題的方法是什麼?當然是在MCUXpresso IDE的startup流程中加入重設SP操作,保持與IAR startup流程一致。
__attribute__ ((section(".after_vectors.reset")))
void ResetISR(void) {
__asm volatile ("cpsid i");
/* 新增SP重設程式碼 */
__asm volatile ("MSR msp, %0" : : "r" (&_vStackTop) : );
__asm volatile ("MSR psp, %0" : : "r" (&_vStackTop) : );
SystemInit();
// ...
}
至此,IVT裡的不同entry設定可能會造成i.MXRT1xxx系列啟動App後發生異常跑飛問題的分析解決經驗痞子衡便介紹完畢了,掌聲在哪裡~~~
歡迎訂閱
文章會同時釋出到我的 部落格園主頁、CSDN主頁、知乎主頁、微信公眾號 平臺上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。