查看: 2899|回复: 4

[分享] [痞子衡]深扒IAR启动函数流程及其底层初始化设计

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3303

    主题

    6550

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32066
    最后登录
    2024-4-30
    发表于 2021-11-29 13:17:37 | 显示全部楼层 |阅读模式
    深扒IAR启动函数流程及其底层初始化设计


    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是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 函数。
    15.png
    二、从源代码角度看启动流程
    在上一节通用启动流程的指导下,我们还需要增加一些 MCU 外设相关的初始化便形成了完整的芯片启动流程,现在我们从源代码角度再来看一下具体实现。


    2.1 典型的 Cortex-M 复位函数
    我们知道复位函数 Reset_Handler() 是芯片上电启动执行的第一个函数(有时又叫入口函数),它完成了进入用户 main() 函数之前的全部动作。随便下载一家 Cortex-M 厂商芯片 SDK 包,找到 IAR 版启动文件,其复位函数流程都差不多,这是 Cortex-M 内核架构决定的。


    如下是典型的复位函数代码。复位函数里的操作包括关全局中断、设置中断向量表首地址、设置栈顶、系统初始化、开全局中断、进启动函数。其中系统初始化 SystemInit() 函数是因芯片而异的,各厂商 SDK 里会有具体源代码实现(一般在 system_xxDevice.c 文件里),这里面主要做芯片硬件层面的初始化,比如关看门狗、Cache 初步设置等,保证内核不受硬件模块状态影响,能正常执行指令。

    1.         THUMB

    2.         PUBWEAK Reset_Handler
    3.         SECTION .text:CODE:REORDER:NOROOT(2)
    4. Reset_Handler
    5.         CPSID   I               ; Mask interrupts
    6.         LDR     R0, =0xE000ED08
    7.         LDR     R1, =__vector_table
    8.         STR     R1, [R0]
    9.         LDR     R2, [R1]
    10.         MSR     MSP, R2
    11.         LDR     R0, =SystemInit
    12.         BLX     R0
    13.         CPSIE   I               ; Unmask interrupts
    14.         LDR     R0, =__iar_program_start
    15.         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 文件,最终我们在如下路径找到了启动函数相关的全部源文件。
    1. \IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\thumb\cstartup_M.s
    2. \IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\thumb\cmain.s
    3. \IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\runtime\low_level_init.c
    4. \IAR Systems\Embedded Workbench 9.10.2\arm\src\lib\init\data_init.c
    复制代码
    16.png
    结合启动函数相关源文件里的代码,我们终于搞清了启动函数全部流程,也找到了我们最关心的 __low_level_init() 函数调用位置,它在 .data/.bss/.textrw 段初始化之前被执行,所以它的功能应该跟 SystemInit() 差不多。默认 __low_level_init() 函数是空的,返回值是 1(返回值 0/1 决定后面的 __iar_data_init3() 要不要执行,1 是要执行),如果你想激活这个函数,需要在自己的源文件里重新定义实现,IAR 编译时会优先引用重新定义的版本。
    1. __iar_program_start() ->
    2. __cmain() ->
    3. __low_level_init() ->          // 底层初始化,默认是个空函数
    4. __iar_data_init3() ->          // .data, .bss, .textrw 段初始化
    5. main()
    复制代码
    17.png
    2.3 __low_level_init() 设计注意事项
    在 EWARM_DevelopmentGuide.ENU 手册里搜索 __low_level_init,我们可以找到这个函数的设计初衷,官方说法是为了给应用程序一个早期初始化的机会,本质上就是跟 SystemInit() 一样的作用,但是因为这个 __low_level_init 函数只在 IAR 环境下适用,如果用了它,应该程序代码就不具备跨 IDE 的通用性,因此在各厂商 SDK 包里选择了统一定义的 SystemInit() 来完成早期初始化工作。
    1. IAR 开发手册:\IAR Systems\Embedded Workbench 9.10.2\arm\doc\EWARM_DevelopmentGuide.ENU
    复制代码
    18.png
    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() 之后的代码
    19.png
    三、一个 __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() 后面。
    1. void ramfunc_test1(void) @"CodeQuickAccess"
    2. {
    3.     __NOP();
    4. }

    5. __ramfunc void ramfunc_test2(void)
    6. {
    7.     __NOP();
    8. }

    9. void ramfunc_test3(void) @"CodeQuickAccess"
    10. {
    11.     __NOP();
    12. }

    13. __ramfunc void ramfunc_test4(void)
    14. {
    15.     __NOP();
    16. }

    17. // 重定义此函数,让 IAR 编译器使用这个版本,而不是默认版本
    18. int __low_level_init(void)
    19. {
    20.     extern void __iar_data_init3(void);

    21.     ramfunc_test1();
    22.     ramfunc_test2();
    23.    
    24.     // 这里增加 .data/.bss/.textrw 的初始化调用,
    25.     //  便于区分 ramfunc_test1/2 和 ramfunc_test3/4 位置
    26.     __iar_data_init3();
    27.    
    28.     ramfunc_test3();
    29.     ramfunc_test4();
    30.    
    31.     return 0;
    32. }
    复制代码
    编译链接修改后的测试工程,查看其映射文件,以及在板子上实测,得到如下结果:
    结论1:放入自定义程序段的函数,无论其调用位置在 __iar_data_init3() 之前还是之后,一律被 initialize by copy 忽略,函数直接链接在目标 RAM 区,函数重定向无效;
    结论2:放入默认 .textrw 段的函数,如果其调用位置在 __iar_data_init3() 之后,能够被 initialize by copy 作用,函数重定向生效;
    结论3:放入默认 .textrw 段的函数,如果其调用位置在 __iar_data_init3() 之前,从映射文件里看其能够被 initialize by copy 作用,但在板子上实测,发现执行到该函数时返回会产生总线错误,因此函数重定向也是无效的;
    1. *******************************************************************************
    2. *** PLACEMENT SUMMARY
    3. ***
    4. "P1":  place in [from 0x3000'2000 to 0x30fb'ffff] { ro };
    5. "P2":  place in [from 0x2000'0000 to 0x2003'fbff] { rw };
    6. "P8":  place in [from 0x0 to 0x3'ffff] { section CodeQuickAccess };
    7. initialize by copy { rw, section .textrw, section CodeQuickAccess };

    8.   Section              Kind         Address    Size  Object
    9.   -------              ----         -------    ----  ------
    10. "P8":                                           0x8
    11.     CodeQuickAccess    ro code          0x0     0x8  ramfunc_test.o [6]

    12. "P2-P3|P5|P9", part 1 of 2:                     0xc
    13.   RW                            0x2000'0000     0xc  <Block>
    14.       .textrw          inited   0x2000'0004     0x8  ramfunc_test.o [6]

    15. "P1":                                        0x443a
    16.   .text                ro code  0x3000'63d0    0x1a  ramfunc_test.o [6]

    17. *******************************************************************************
    18. *** MODULE SUMMARY
    19. ***
    20.     Module                              ro code  rw code  ro data  rw data
    21.     ------                              -------  -------  -------  -------
    22.     ramfunc_test.o                           34        8        8

    23. *******************************************************************************
    24. *** ENTRY LIST
    25. ***
    26.     Entry                       Address   Size  Type      Object
    27.     ----                       -------   ----  ----      ------
    28.     ramfunc_test1                   0x1    0x4  Code  Gb  ramfunc_test.o [6]
    29.     ramfunc_test2           0x2000'0005    0x4  Code  Gb  ramfunc_test.o [6]
    30.     ramfunc_test3                   0x5    0x4  Code  Gb  ramfunc_test.o [6]
    31.     ramfunc_test4           0x2000'0009    0x4  Code  Gb  ramfunc_test.o [6]
    复制代码
    至此,IAR启动函数流程及其__low_level_init设计对函数重定向的影响痞子衡便介绍完毕了,掌声在哪里~~~

    签到签到
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 12:54
  • 签到天数: 1482 天

    [LV.10]以坛为家III

    203

    主题

    2万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    92880
    最后登录
    2024-5-5
    发表于 2021-11-29 14:24:36 | 显示全部楼层
    这位大佬最近挺活跃的啊~~
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3303

    主题

    6550

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32066
    最后登录
    2024-4-30
     楼主| 发表于 2021-11-29 14:29:57 | 显示全部楼层
    stm1024 发表于 2021-11-29 14:24
    这位大佬最近挺活跃的啊~~

    分享干货,大佬是认真的
    签到签到
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2023-9-7 17:58
  • 签到天数: 4 天

    [LV.2]偶尔看看I

    0

    主题

    7

    帖子

    0

    新手上路

    Rank: 1

    积分
    32
    最后登录
    2023-9-15
    发表于 2023-8-7 16:39:48 | 显示全部楼层
    大佬,能否给个好友位,让小弟瞻仰瞻仰
    我想找个女朋友
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    5 天前
  • 签到天数: 597 天

    [LV.9]以坛为家II

    51

    主题

    2231

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    7104
    最后登录
    2024-5-6
    发表于 2023-8-8 11:06:13 | 显示全部楼层
    干货  够硬 感谢分享
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

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

    本版积分规则

    关闭

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

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

    GMT+8, 2024-5-6 12:26 , Processed in 0.133562 second(s), 23 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

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