痞子衡嵌入式:深扒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 設計:

一、通用晶片上電啟動流程

  在深入挖掘 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主頁知乎主頁微信公眾號 平台上。

微信搜索”痞子衡嵌入式“或者掃描下面二維碼,就可以在手機上第一時間看了哦。