痞子衡嵌入式:深扒IAR启动函数流程及其__low_level_init设计对函数重定向的影响
大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是IAR启动函数流程及其__low_level_init设计对函数重定向的影响。
上一篇文章 《IAR下RT-Thread工程自定义函数段重定向失效分析》 里我们找出了影响 IAR 链接器处理自定义程序段重定向的原因,主要跟 __low_level_init() 函数有关,这个函数属于 IAR 底层设计,它在 IAR 启动函数 __iar_program_start() 中会被自动调用。
__iar_program_start() 是 IAR 标准启动函数,也属于 IDE 底层设计。在任何一个 Cortex-M 厂商芯片的启动文件里(startup_xxDevice.s)都能看到它的身影,它是复位函数 Reset_Handler() 和 主函数 main() 之间的桥梁,今天我们就仔细说说这个启动函数以及其中 __low_level_init 设计:
- Note 1: 阅读本文前需要对 《IAR链接文件(.icf)》、《IAR映射文件(.map)》 这两种文件有所了解。
- Note 2: 本文使用的 IAR EWARM 软件版本是 v9.10.2。
一、通用芯片上电启动流程
在深入挖掘 IAR 启动函数源代码之前,有必要先整体了解一下通用的芯片上电启动流程,即进入用户 main 函数之前内核必须要做的事情,注意这里并不包含芯片底层外设的初始化(这是因芯片而异的)。
通用启动流程简单来说分为如下四步:第一步是从 ROM 区域中断向量表里获取入口函数开始执行,设置好初始栈指针,有了正确的栈,内核就具备函数跳转执行的能力了。第二步和第三步是全局变量的初始化(将全局变量初值从 ROM 区域拷贝到变量所链接的 RAM 区域),全局变量初始化完成,应用程序就有了正确的初始态,最后一步就是跳转到 main 函数。
二、从源代码角度看启动流程
在上一节通用启动流程的指导下,我们还需要增加一些 MCU 外设相关的初始化便形成了完整的芯片启动流程,现在我们从源代码角度再来看一下具体实现。
2.1 典型的 Cortex-M 复位函数
我们知道复位函数 Reset_Handler() 是芯片上电启动执行的第一个函数(有时又叫入口函数),它完成了进入用户 main() 函数之前的全部动作。随便下载一家 Cortex-M 厂商芯片 SDK 包,找到 IAR 版启动文件,其复位函数流程都差不多,这是 Cortex-M 内核架构决定的。
如下是典型的复位函数代码。复位函数里的操作包括关全局中断、设置中断向量表首地址、设置栈顶、系统初始化、开全局中断、进启动函数。其中系统初始化 SystemInit() 函数是因芯片而异的,各厂商 SDK 里会有具体源代码实现(一般在 system_xxDevice.c 文件里),这里面主要做芯片硬件层面的初始化,比如关看门狗、Cache 初步设置等,保证内核不受硬件模块状态影响,能正常执行指令。
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
2.2 __iar_program_start() 到底干了啥?
上一小节里我们知道复位函数里的最后一个动作就是跳转到启动函数,将内核执行权交给 __iar_program_start(),这个启动函数源代码并不在厂商 SDK 包里,而在 IAR 安装目录下,因为它是 IAR 的通用底层设计。
为了找到 __iar_program_start() 的源代码,我们可以随便编译一个 SDK 例程(痞子衡选择的是 \SDK_2.11.0_MIMXRT1170-EVK\boards\evkmimxrt1170\demo_apps\hello_world\cm7\iar),查看其对应映射文件(.map),发现启动函数来自于 cstartup_M.o,然后我们在 IAR 安装目录下搜索 cstartup_M.c/.s 文件,最终我们在如下路径找到了启动函数相关的全部源文件。
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\thumb\cstartup_M.s
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\thumb\cmain.s
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\runtime\low_level_init.c
\IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\init\data_init.c
结合启动函数相关源文件里的代码,我们终于搞清了启动函数全部流程,也找到了我们最关心的 __low_level_init() 函数调用位置,它在 .data/.bss/.textrw 段初始化之前被执行,所以它的功能应该跟 SystemInit() 差不多。默认 __low_level_init() 函数是空的,返回值是 1(返回值 0/1 决定后面的 __iar_data_init3() 要不要执行,1 是要执行),如果你想激活这个函数,需要在自己的源文件里重新定义实现,IAR 编译时会优先引用重新定义的版本。
__iar_program_start() ->
__cmain() ->
__low_level_init() -> // 底层初始化,默认是个空函数
__iar_data_init3() -> // .data, .bss, .textrw 段初始化
main()
2.3 __low_level_init() 设计注意事项
在 EWARM_DevelopmentGuide.ENU 手册里搜索 __low_level_init,我们可以找到这个函数的设计初衷,官方说法是为了给应用程序一个早期初始化的机会,本质上就是跟 SystemInit() 一样的作用,但是因为这个 __low_level_init 函数只在 IAR 环境下适用,如果用了它,应该程序代码就不具备跨 IDE 的通用性,因此在各厂商 SDK 包里选择了统一定义的 SystemInit() 来完成早期初始化工作。
IAR 开发手册: \IAR Systems\Embedded Workbench 9.10.2\arm\doc\EWARM_DevelopmentGuide.ENU
EWARM_DevelopmentGuide.ENU 手册里还特别提了几点跟 __low_level_init 相关的注意事项,均跟 IAR 链接器所识别的 initialize by copy 链接语法有关,概括来说就是因为 __low_level_init 是在 .data/.bss/.textrw 段初始化之前被执行的,所以其代码本身及其调用的全部代码都不受 initialize by copy 作用,也就是这些代码都不应是 RAMFUNC 型。
- Note: 更准确地说 initialize by copy 作用范围其实是 __iar_data_init3() 之后的代码
三、一个 __low_level_init() 相关的重定向实验
最后我们再做个 __low_level_init() 相关的小实验,在 \SDK_2.11.0_MIMXRT1170-EVK\boards\evkmimxrt1170\demo_apps\hello_world\cm7\iar 例程基础上(flexspi_nor_debug build),创建一个包含如下内容的 ramfunc_test.c 源文件,并将其添加进工程编译。
ramfunc_test1/3() 函数放入自定义程序段 CodeQuickAccess,ramfunc_test2/4() 函数放到默认 .textrw 段,然后重写 __low_level_init() 函数,在 __low_level_init() 函数里分别调用 ramfunc_test1/2/3/4(),其中 ramfunc_test1/2() 函数的调用在 __iar_data_init3() 前面,ramfunc_test1/2() 函数的调用在 __iar_data_init3() 后面。
void ramfunc_test1(void) @"CodeQuickAccess"
{
__NOP();
}
__ramfunc void ramfunc_test2(void)
{
__NOP();
}
void ramfunc_test3(void) @"CodeQuickAccess"
{
__NOP();
}
__ramfunc void ramfunc_test4(void)
{
__NOP();
}
// 重定义此函数,让 IAR 编译器使用这个版本,而不是默认版本
int __low_level_init(void)
{
extern void __iar_data_init3(void);
ramfunc_test1();
ramfunc_test2();
// 这里增加 .data/.bss/.textrw 的初始化调用,
// 便于区分 ramfunc_test1/2 和 ramfunc_test3/4 位置
__iar_data_init3();
ramfunc_test3();
ramfunc_test4();
return 0;
}
编译链接修改后的测试工程,查看其映射文件,以及在板子上实测,得到如下结果:
- 结论1:放入自定义程序段的函数,无论其调用位置在 __iar_data_init3() 之前还是之后,一律被 initialize by copy 忽略,函数直接链接在目标 RAM 区,函数重定向无效;
- 结论2:放入默认 .textrw 段的函数,如果其调用位置在 __iar_data_init3() 之后,能够被 initialize by copy 作用,函数重定向生效;
- 结论3:放入默认 .textrw 段的函数,如果其调用位置在 __iar_data_init3() 之前,从映射文件里看其能够被 initialize by copy 作用,但在板子上实测,发现执行到该函数时返回会产生总线错误,因此函数重定向也是无效的;
*******************************************************************************
*** PLACEMENT SUMMARY
***
"P1": place in [from 0x3000'2000 to 0x30fb'ffff] { ro };
"P2": place in [from 0x2000'0000 to 0x2003'fbff] { rw };
"P8": place in [from 0x0 to 0x3'ffff] { section CodeQuickAccess };
initialize by copy { rw, section .textrw, section CodeQuickAccess };
Section Kind Address Size Object
------- ---- ------- ---- ------
"P8": 0x8
CodeQuickAccess ro code 0x0 0x8 ramfunc_test.o [6]
"P2-P3|P5|P9", part 1 of 2: 0xc
RW 0x2000'0000 0xc <Block>
.textrw inited 0x2000'0004 0x8 ramfunc_test.o [6]
"P1": 0x443a
.text ro code 0x3000'63d0 0x1a ramfunc_test.o [6]
*******************************************************************************
*** MODULE SUMMARY
***
Module ro code rw code ro data rw data
------ ------- ------- ------- -------
ramfunc_test.o 34 8 8
*******************************************************************************
*** ENTRY LIST
***
Entry Address Size Type Object
---- ------- ---- ---- ------
ramfunc_test1 0x1 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test2 0x2000'0005 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test3 0x5 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test4 0x2000'0009 0x4 Code Gb ramfunc_test.o [6]
至此,IAR启动函数流程及其__low_level_init设计对函数重定向的影响痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。
痞子衡嵌入式:深扒IAR启动函数流程及其__low_level_init设计对函数重定向的影响的更多相关文章
- 痞子衡嵌入式:深扒IAR启动函数流程之段初始化函数__iar_data_init3实现
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程里的段初始化函数__iar_data_init3实现. 本篇是 <IAR启动函数流程及其__low_level_ ...
- 痞子衡嵌入式:深扒IAR启动函数流程之段初始化实现中可用的压缩选项
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程里段初始化实现中可用的压缩选项. 接着 <IAR启动函数流程之段初始化函数__iar_data_init3实现& ...
- 痞子衡嵌入式:在IAR开发环境下将关键函数重定向到RAM中执行的三种方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下将关键函数重定向到RAM中执行的三种方法. 嵌入式项目里应用程序代码正常是放在 Flash 中执行的,但有时候也需要将 ...
- 痞子衡嵌入式:在IAR开发环境下RT-Thread工程函数重定向失效分析
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下RT-Thread工程函数重定向失效分析. 痞子衡旧文 <在IAR下将关键函数重定向到RAM中执行的方法> ...
- 痞子衡嵌入式:在IAR开发环境下将整个源文件代码重定向到任意RAM中的方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下将整个源文件代码重定向到任意RAM中的方法. 痞子衡旧文 <在IAR下将关键函数重定向到RAM中执行的方法> ...
- 痞子衡嵌入式:在IAR开发环境下为工程开启CRC完整性校验功能的方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下为工程开启CRC完整性校验功能的方法. CRC校验在嵌入式领域里的应用非常广,比如在通信领域,CRC检验值可以作为数据 ...
- 痞子衡嵌入式:浅析IAR下调试信息输出机制之硬件UART外设
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR下调试信息输出机制之硬件UART外设. 在嵌入式世界里,输出打印信息是一种非常常用的辅助调试手段,借助打印信息,我们可以比较容易地 ...
- 痞子衡嵌入式:浅析IAR下调试信息输出机制之半主机(Semihosting)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR下调试信息输出机制之半主机(Semihosting). 在嵌入式世界里,输出打印信息是一种非常常用的辅助调试手段,借助打印信息,我 ...
- 痞子衡嵌入式:深扒i.MXRTxxx系列ROM中集成的串行NOR Flash启动SW Reset功能及其应用场合
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRTxxx系列ROM中集成的串行NOR Flash启动SW Reset功能及其应用场合. 在串行 NOR Flash 热启动过程 ...
随机推荐
- 10.8 location
创建一个前台站点 server { listen 80; server_name www.nginx.com; locaiton / { root /var/www/html/www; } } 创建一 ...
- gRPC,爆赞
原文链接: gRPC,爆赞 gRPC 这项技术真是太棒了,接口约束严格,性能还高,在 k8s 和很多微服务框架中都有应用. 作为一名程序员,学就对了. 之前用 Python 写过一些 gRPC 服务, ...
- 前段--->js
一,java script的引入方式 1,直接在script里书写你的代码 <script> alert("hbflfb")</script> 2,引入额外 ...
- 题解 AVL 树
link Description 给出一个 \(n\) 个点的 AVL 树,求保留 \(k\) 个点使得字典序最小. \(n\le 5\times 10^5\) Solution 因为我很 sb ,所 ...
- 微信小程序_快速入门01
这段时间,嗯,大四课程已经结束了,工作也已经找到了,但是呢,到公司报道的时间还没到,哈哈,马上就开始人生的第一份工作了,怎么说确实有点期待~ 在这段时间一方面为第一份工作做各种准备,另一方面也没有停止 ...
- PTA实验11-1-7 藏头诗 (15分)
实验11-1-7 藏头诗 (15分) 本题要求编写一个解密藏头诗的程序. 输入格式: 输入为一首中文藏头诗,一共四句,每句一行.注意:一个汉字占两个字节. 输出格式: 取出每句的第一个汉字并连接在一起 ...
- 痞子衡嵌入式:超级下载算法RT-UFL v1.0在IAR EW for Arm下的使用
痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...
- Java:String对象小记
Java:String对象小记 对 Java 中的 String 对象,做一个微不足道的小小小小记 字节和字符的区别 字节 byte: 一个字节包含8个位(bit),因此byte的取值范围为-128~ ...
- 安装多个版本的MySQL
安装多个版本的MySQL 之前在PC机上安装了 MySQL 5.5 后续发现了窗口函数,而窗口函数是 MySQL8 以后才支持的,故在本地又安装了一个 MySQL 8 安装MySQL 5.5 进入my ...
- 进阶区forgotg攻防世界
攻防世界进阶区--forgot 前言,这题中看不中用啊宝友!!! 1.查看保护 第一反应就是蛮简单的,32位. 2.获取信息(先运行程序看看) 装的可以,蛮多的东西. 但是就是中看不中用 3.ida ...