查看: 4318|回复: 0

[分享] IVT中设入口为复位函数为何有时会引起程序执行异常?

[复制链接]
  • TA的每日心情
    开心
    2025-7-11 08:53
  • 签到天数: 301 天

    连续签到: 2 天

    [LV.8]以坛为家I

    3868

    主题

    7472

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    39227
    最后登录
    2025-7-18
    发表于 2020-9-14 12:44:19 | 显示全部楼层 |阅读模式
    IVT中设入口为复位函数为何有时会引起程序执行异常?


    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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值组合下现象不一致:
    1.png
    根据上表结果,其实我们很难得出一个有效推论,只能说这个异常结果在特定的App, entry值, MCUXpresso IDE下才能复现。


    二、原因探究
    既然暂时看不出原因,那我们先做一些准备工作吧。我们把三个影响因子(App, entry值, IDE)的差异先整理出来:


    2.1 两个App的不同链接分配
    两个App都来自SDK,是经过官方详尽测试的,所以我们不去怀疑App本身的功能异常。它们的差异主要在链接分配上。以IAR为例,我们只看flexspi_nor build,在链接文件中默认分配的堆、栈大小均为1KB:
    1. /* Sizes */
    2. if (isdefinedsymbol(__stack_size__)) {
    3.   define symbol __size_cstack__        = __stack_size__;
    4. } else {
    5.   define symbol __size_cstack__        = 0x0400;
    6. }

    7. if (isdefinedsymbol(__heap_size__)) {
    8.   define symbol __size_heap__          = __heap_size__;
    9. } else {
    10.   define symbol __size_heap__          = 0x0400;
    11. }
    复制代码
    hello_world例程因为比较简单,所以用直接用了默认的堆栈大小,而dev_cdc_vcom_freertos例程比较复杂,堆栈做了额外调整,栈增大到了8KB。
    2.png
    此外我们还注意到hello_world例程将其RW, ZI, 堆栈全部放进了32KB的DTCM;而dev_cdc_vcom_freertos例程则将RW, ZI放入了64KB OCRAM,只将堆栈放进了DTCM:
    1. define symbol m_data_start             = 0x20000000;
    2. define symbol m_data_end               = 0x20007FFF;

    3. define symbol m_data2_start            = 0x20200000;
    4. define symbol m_data2_end              = 0x2020FFFF;

    5. define region DATA_region = mem:[from m_data_start to m_data_end-__size_cstack__];
    6. define region DATA2_region = mem:[from m_data2_start to m_data2_end];
    7. define region CSTACK_region = mem:[from m_data_end-__size_cstack__+1 to m_data_end];

    8. // 适用hello_world例程
    9. place in DATA_region                        { block RW };
    10. place in DATA_region                        { block ZI };
    11. place in DATA_region                        { last block HEAP };
    12. place in DATA_region                        { block NCACHE_VAR };
    13. place in CSTACK_region                      { block CSTACK };

    14. // 适用dev_cdc_vcom_freertos例程
    15. place in DATA2_region                       { block RW };
    16. place in DATA2_region                       { block ZI };
    17. place in DATA_region                        { last block HEAP };
    18. place in DATA_region                        { block NCACHE_VAR };
    19. 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中最终跳转函数:
    1. void jump_to_entry(uint32_t entry)
    2. {
    3.     typedef void (*application_callback_t)(void);
    4.     static application_callback_t s_app_callback;

    5.     pu_irom_mpu_disable();

    6.     __DMB();
    7.     __DSB();
    8.     __ISB();

    9.     // The entry point is the absolute address of the call back function
    10.     if ((uint32_t)entry & 1)
    11.     {
    12.         s_app_callback = (application_callback_t)entry;
    13.     }
    14.     // The entry point is the base address of vector table
    15.     else
    16.     {
    17.         static uint32_t s_stack_pointer;
    18.         // Ensure Core read vector table for destination instead of register
    19.         volatile uint32_t *vector_table = (volatile uint32_t *)entry;

    20.         s_stack_pointer = vector_table[0];
    21.         s_app_callback = (application_callback_t)vector_table[1];

    22.         // Update Stack pointer
    23.         __set_MSP(s_stack_pointer);
    24.         __set_PSP(s_stack_pointer);
    25.     }

    26.     __DSB();
    27.     __ISB();

    28.     // Jump to user application in the end
    29.     s_app_callback();

    30.     // Should never reach here
    31.     __NOP();
    32.     __NOP();
    33. }
    复制代码
    从上面的跳转函数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实现:
    1. __vector_table
    2.         DCD     sfe(CSTACK)
    3.         DCD     Reset_Handler

    4.         DCD     NMI_Handler                                   ;NMI Handler
    5.         DCD     HardFault_Handler                             ;Hard Fault Handler
    6.         ; ...
    7. __Vectors_End

    8.         THUMB

    9.         PUBWEAK Reset_Handler
    10.         SECTION .text:CODE:REORDER:NOROOT(2)
    11. Reset_Handler
    12.         CPSID   I               ; Mask interrupts
    13.         LDR     R0, =0xE000ED08
    14.         LDR     R1, =__vector_table
    15.         STR     R1, [R0]
    16.         LDR     R2, [R1]
    17.         MSR     MSP, R2
    18.         LDR     R0, =SystemInit
    19.         BLX     R0
    20.         CPSIE   I               ; Unmask interrupts
    21.         LDR     R0, =__iar_program_start
    22.         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实现:
    1. extern void _vStackTop(void);

    2. __attribute__ ((used, section(".isr_vector")))
    3. void (* const g_pfnVectors[])(void) = {
    4.     // Core Level - CM7
    5.     &_vStackTop,                       // The initial stack pointer
    6.     ResetISR,                          // The reset handler
    7.     NMI_Handler,                       // The NMI handler
    8.     HardFault_Handler,                 // The hard fault handler
    9.     // ...
    10. }; /* End of g_pfnVectors */

    11. __attribute__ ((section(".after_vectors.reset")))
    12. void ResetISR(void) {
    13.     __asm volatile ("cpsid i");

    14.     SystemInit();

    15.     // Copy the data sections from flash to SRAM.
    16.     unsigned int LoadAddr, ExeAddr, SectionLen;
    17.     unsigned int *SectionTableAddr;

    18.     // Load base address of Global Section Table
    19.     SectionTableAddr = &__data_section_table;

    20.     // Copy the data sections from flash to SRAM.
    21.     while (SectionTableAddr < &__data_section_table_end) {
    22.         LoadAddr = *SectionTableAddr++;
    23.         ExeAddr = *SectionTableAddr++;
    24.         SectionLen = *SectionTableAddr++;
    25.         data_init(LoadAddr, ExeAddr, SectionLen);
    26.     }

    27.     // At this point, SectionTableAddr = &__bss_section_table;
    28.     // Zero fill the bss segment
    29.     while (SectionTableAddr < &__bss_section_table_end) {
    30.         ExeAddr = *SectionTableAddr++;
    31.         SectionLen = *SectionTableAddr++;
    32.         bss_init(ExeAddr, SectionLen);
    33.     }

    34.     __asm volatile ("cpsie i");

    35.     // Call the Redlib library, which in turn calls main()
    36.     __main();

    37.     while (1);
    38. }
    复制代码
    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段数据(全局变量)互相破坏,程序发生未知跑飞也在意料之中。
    3.png
    解决问题的方法是什么?当然是在MCUXpresso IDE的startup流程中加入重设SP操作,保持与IAR startup流程一致。
    1. __attribute__ ((section(".after_vectors.reset")))
    2. void ResetISR(void) {
    3.     __asm volatile ("cpsid i");

    4.     /* 新增SP重设代码 */
    5.     __asm volatile ("MSR msp, %0" : : "r" (&_vStackTop) : );
    6.     __asm volatile ("MSR psp, %0" : : "r" (&_vStackTop) : );

    7.     SystemInit();

    8.     // ...
    9. }
    复制代码
    至此,IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞问题的分析解决经验痞子衡便介绍完毕了,掌声在哪里~~~


    文章出处:痞子衡嵌入式

    qiandao qiandao
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-7-19 14:26 , Processed in 0.079390 second(s), 20 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表