痞子衡嵌入式:IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞
大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

痞子衡嵌入式:IVT里的不同entry设置可能会造成i.MXRT1xxx系列启动App后发生异常跑飞的更多相关文章
- 痞子衡嵌入式:MCUBootUtility v3.3发布,可配合SBL项目使用
		-- 痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v3.0.0)发布过去4个多月了,期间痞子衡其实断断续续做个几个小版本更新,这一次痞子衡为大家带来了稳定版本v3.3.0,顺 ... 
- 痞子衡嵌入式:在SBL项目实战中妙用i.MXRT1xxx里SystemReset不复位的GPR寄存器
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1xxx里SystemReset不复位的GPR寄存器的小妙用. 我们知道稍大规模的项目代码设计一般都是多人协作完成的,在项目 ... 
- 痞子衡嵌入式:快速定位i.MXRT600板级设计ISP[2:0]启动模式引脚上电时序问题的方法
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是一种快速定位i.MXRT600板级设计ISP[2-0]启动模式引脚上电时序问题的方法. 我们知道恩智浦i.MXRT600是主打音频市场的 ... 
- 痞子衡嵌入式:在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺. 恩智浦 MCU SE 团队近期一直在加班加点赶 SBL 项目 ... 
- 痞子衡嵌入式:嵌入式里堆栈原理及其纯C实现
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是嵌入式里堆栈原理及其纯C实现. 今天给大家分享的这篇还是2016年之前痞子衡写的技术文档,花了点时间重新编排了一下格式.栈这种结构在嵌入式 ... 
- 痞子衡嵌入式:从头开始认识i.MXRT启动头FDCB里的lookupTable
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT启动头FDCB里的lookupTable. 一个MCU内部通常有很多外设模块,这些外设模块是各MCU厂商做差异化产品的本质, ... 
- 痞子衡嵌入式:在i.MXRT启动头FDCB里调整Flash工作频率也需同步设Dummy Cycle
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是Flash工作频率与Dummy Cycle的联系. 上一篇文章 <从头开始认识i.MXRT启动头FDCB里的lookupTable ... 
- 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的DTR模式
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的DTR模式. 前两篇文章 <IS25WP系列Dummy Cycle设置> 与 < ... 
- 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的Continuous read模式
		大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的Continuous read模式. 前面关于串行Flash传输时序的文章 <Fast R ... 
随机推荐
- PyCharm 2020.1专业版安装教程及破解方法
			一.安装第一步下载 版本一定得是:2020.1 其他版本下载地址:https://www.jetbrains.com/pycharm/download/other.html 安装不多说了: 二.破解 ... 
- Android 开发学习进程0.13   Androidstudio快捷键   xmlns
			xmlns XML namespace xml命名空间 其中主要是定义xml文件定义位置 前缀有三种,android app tools 后面为唯一标识符URI android 表示为引用自安卓系统 ... 
- Ubuntu 16.04 sudo免密码visudo sudoers设置
			使用sudo visudo调用nano对 /etc/sudoers 进行修改 $ sudo visudo 在想修改的对象最后加上 NOPASSWD: ALL 注意:在后面的设置会覆盖前面的设置 
- 【原创】Linux虚拟化KVM-Qemu分析(一)
			背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ... 
- 使用Java API进行tar.gz文件及文件夹压缩解压缩
			在java(JDK)中我们可以使用ZipOutputStream去创建zip压缩文件,(参考我之前写的文章 使用java API进行zip递归压缩文件夹以及解压 ),也可以使用GZIPOutputSt ... 
- Fiddler+模拟器+APP抓包HTTPS 为什么有时候抓不到?
			抓包的原理是什么? 代理 客户端请求 -> 经过代理 -> 到达服务端 服务端返回 -> 经过代理 -> 到达客户端 任何Https的App都能抓到包么? Android7.0 ... 
- python 03 常用操作符
			1. e记法,科学计数法. AeB A,B为整数,A*10的B次方. 2. 逻辑运算,真为1,假为0,最好不使用这个计算 true(1) false(0) true+true=2 3.类型转 ... 
- C++ Templates 目录
			第1部分 : 基本概念 第1章 函数模板 1.1 初识函数模板 1.1.1 定义模板 1.1.2 使用模板 1.1.3 二阶段翻译 1.2 模板参数推导 1.3 多模板参数 1.3.1 返回类型的模板 ... 
- golang slice 简单排序
			原文链接:https://www.jianshu.com/p/603be4962a62 demo package main import ( "fmt" "sort&qu ... 
- hdu.2042 超级楼梯
			这种递归来写,除了递归我也想不到怎么写了 AC代码: #include<iostream>using namespace std;int x[41];//打表,不打表我不用想就知道过不了, ... 
