大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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后发生异常跑飞的更多相关文章

  1. 痞子衡嵌入式:MCUBootUtility v3.3发布,可配合SBL项目使用

    -- 痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v3.0.0)发布过去4个多月了,期间痞子衡其实断断续续做个几个小版本更新,这一次痞子衡为大家带来了稳定版本v3.3.0,顺 ...

  2. 痞子衡嵌入式:在SBL项目实战中妙用i.MXRT1xxx里SystemReset不复位的GPR寄存器

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1xxx里SystemReset不复位的GPR寄存器的小妙用. 我们知道稍大规模的项目代码设计一般都是多人协作完成的,在项目 ...

  3. 痞子衡嵌入式:快速定位i.MXRT600板级设计ISP[2:0]启动模式引脚上电时序问题的方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是一种快速定位i.MXRT600板级设计ISP[2-0]启动模式引脚上电时序问题的方法. 我们知道恩智浦i.MXRT600是主打音频市场的 ...

  4. 痞子衡嵌入式:在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在串口波特率识别实例里逐步展示i.MXRT上提升代码执行性能的十八般武艺. 恩智浦 MCU SE 团队近期一直在加班加点赶 SBL 项目 ...

  5. 痞子衡嵌入式:嵌入式里堆栈原理及其纯C实现

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是嵌入式里堆栈原理及其纯C实现. 今天给大家分享的这篇还是2016年之前痞子衡写的技术文档,花了点时间重新编排了一下格式.栈这种结构在嵌入式 ...

  6. 痞子衡嵌入式:从头开始认识i.MXRT启动头FDCB里的lookupTable

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT启动头FDCB里的lookupTable. 一个MCU内部通常有很多外设模块,这些外设模块是各MCU厂商做差异化产品的本质, ...

  7. 痞子衡嵌入式:在i.MXRT启动头FDCB里调整Flash工作频率也需同步设Dummy Cycle

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是Flash工作频率与Dummy Cycle的联系. 上一篇文章 <从头开始认识i.MXRT启动头FDCB里的lookupTable ...

  8. 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的DTR模式

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的DTR模式. 前两篇文章 <IS25WP系列Dummy Cycle设置> 与 < ...

  9. 痞子衡嵌入式:在i.MXRT启动头FDCB里使能串行NOR Flash的Continuous read模式

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是在FDCB里使能串行NOR Flash的Continuous read模式. 前面关于串行Flash传输时序的文章 <Fast R ...

随机推荐

  1. nginx进程模型解析

    nginx进程模型解析 概念   master会发送请求给worker,用于处理用户的请求,模型图如下 nginx进程分类   master进程(只有1个)    接受信号传递给worker   wo ...

  2. C#LeetCode刷题之#1-两数之和(Two Sum)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3762 访问. 给定一个整数数组和一个目标值,找出数组中和为目标值 ...

  3. Homekit_二路继电器

    介绍一款二路继电器,使用Homekit进行控制,有兴趣的可以去以下链接看看: https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-11265006 ...

  4. 水题大战Vol.3 B. DP搬运工2

    水题大战Vol.3 B. DP搬运工2 题目描述 给你\(n,K\),求有多少个\(1\)到\(n\) 的排列,恰好有\(K\)个数\(i\) 满足\(a_{i-1},a_{i+1}\) 都小于\(a ...

  5. storcli 命令(更新Ing)

    help [root@centos7]# storcli -h Storage Command Line Tool Ver 007.0606.0000.0000 Mar , (c)Copyright ...

  6. 封装Vue Element的table表格组件

    上周分享了几篇关于React组件封装方面的博文,这周就来分享几篇关于Vue组件封装方面的博文,也好让大家能更好地了解React和Vue在组件封装方面的区别. 在封装Vue组件时,我依旧会交叉使用函数式 ...

  7. 真正从底层理解 Synchronized 实现原理

    实现synchronized的基础有两个:Java 对象头和 Monitor. 在虚拟机规范中定义了对象在内存中的布局,主要由以下 3 部分组成: 对象头 实例数据 对齐填充 而synchronize ...

  8. Storm与SparkStreaming对比

    Storm与SparkStreaming对比 ◆ Spark Streaming 批处理的性能比Storm高出几十倍.◆ Streaming采用小批量模式,Storm是一条消息一条消息的计算.◆ St ...

  9. CSP-J2019 NOIP普及组初赛真题(选择题部分)

    NOIP初赛考试提纲 时间:10月的第2/3个星期六下午14:30~16:30 分数及形式:满分100分,形式为笔试(今年可能上机) 1.单项选择题,共15题,每题2分,共30分 2.阅读程序题,共3 ...

  10. 2020.5.22 第二篇 Scrum冲刺博客

    Team:银河超级无敌舰队 Project:招新通 项目冲刺集合贴:链接 目录 一.每日站立会议 二.项目燃尽图 三.签入记录 3.1 代码/文档签入记录 3.2 主要代码截图 3.3 程序运行截图 ...