痞子衡嵌入式:IVT裏的不一樣entry設置可能會形成i.MXRT1xxx系列啓動App後發生異常跑飛


  你們好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給你們分享的是IVT裏的不一樣entry設置可能會形成i.MXRT1xxx系列啓動App後發生異常跑飛問題的分析解決經驗html

  事情緣起恩智浦官方論壇上的一個疑問帖 《RT1015 dev_cdc_vcom_freertos reset entry failed》,這是客戶QISDA遇到的問題,由痞子衡的同事 - 很是細心負責的Kerry小姐姐將問題整理出來併發了貼,帖子裏作了詳盡的問題描述以及各類測試結果。看完長帖後,痞子衡第一猜測就是跟App棧設置有關,最終也確實是這個緣由。那麼爲何棧設置會出問題呢?且聽痞子衡細聊:git

1、問題描述

  讓咱們先來整理一下帖子裏的問題現象,客戶在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下才能復現。併發

2、緣由探究

  既然暫時看不出緣由,那咱們先作一些準備工做吧。咱們把三個影響因子(App, entry值, IDE)的差別先整理出來:app

2.1 兩個App的不一樣連接分配

  兩個App都來自SDK,是通過官方詳盡測試的,因此咱們不去懷疑App自己的功能異常。它們的差別主要在連接分配上。以IAR爲例,咱們只看flexspi_nor build,在連接文件中默認分配的堆、棧大小均爲1KB:ide

/* 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成員主要用於跳轉執行。flex

  爲何這個entry值既能夠是中斷向量表(Vector Table)起始地址也能夠是復位向量(Reset_Handler)函數地址呢?這取決於BootROM中是怎麼利用這個entry值的,下面函數便是BootROM中最終跳轉函數:ui

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主頁知乎主頁微信公衆號 平臺上。

微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就能夠在手機上第一時間看了哦。

相關文章
相關標籤/搜索