STM32 的 SystemInit() 和 __main
Author by [YuCloud](https://www.cnblogs.com/yucloud/)
上篇文章 STM32啟動代碼分析及其匯編學習-ARM 分析了 .S 啟動文件 ,這次來研究一下 .S 啟動文件之后執行到 main() 的流程
STM32 總體啟動順序
.s啟動文件
-> 中斷處理函數外部定義
-> SystemInit()
-> SetSysClock
-> __main
-> main()
其中,段的鏈接順序:
- 截圖里的中斷處理函數外部定義位於 stm32f4xx_it.c(當然這個什么名字都可以,只要在 Keil 項目里且函數名為
啟動文件里定義的中斷處理符號名
) SystemInit()
、SystemCoreClockUpdate
、SetSysClock
三個函數均位於 system_stm32f4xx.c
函數 SystemCoreClockUpdate
是用來在時鍾配置改變后刷新時鍾的.
(注意:這只是段連接順序,不是函數調用,之前犯了錯誤,調試后發現應該按照實際匯編跳轉執行順序,這里向各位道歉.但段必須在函數之前才能使用,這也是C語言函數為什么需要先聲明后使用
各函數作用 see: https://www.keil.com/pack/doc/CMSIS/Core/html/group__system__init__gr.html
SystemInit()
用 VSCode+Keil 編程環境,在項目里全局搜索 SystemInit
找到4個文件有用的
- 這是個 map 文件,里面是鏈接編譯后對應的索引/映射
- 這是 list 文件,也就是鏈接后的匯編信息列表
- 這是頭文件的聲明
- 上面頭文件對應的C源文件,那么函數定義就在這里了
其中還調用了 SetSysClock
SystemInit 很簡單,查看一下 STM32 手冊里的寄存器即可
SetSysClock
顧名思義
__main
回顧一下
這里的
startup_stm32f401xx.o(.text) refers to entry.o(.ARM.Collect$$$$00000000) for __main
是個 .text 段,應該是 Keil 通過ARM匯編成 entry.o 鏈接到二進制文件里
總之這個 __main
會調用 system_stm32f4xx.c 里的 SystemCoreClockUpdate
函數,然后再調用我們c語言里的 main()。
但由於網上根本搜不到中文資料,因此我決定逆向
把Object目錄里需要的文件復制出來,用 IDA 打開axf文件,格式選ARM小端(STM32是小端的)
確認后會提示ARM有兩種指令集,並告訴你如何用 IDA 操作 (ALT+G切換模式)
找到 Reset_Handler
,這里二進制丟失了匯編的Label信息,所以反匯編后,名字有所不同
Enter 進入查看函數定義,由於上面有SystemInit()的源碼,所以用不着反編譯,我們來看 __main
也就是這里的 _main_stk 函數的定義
__main本體
雖然是 __main ,反編譯名為 _main_stk()。
因為二進制並沒有包含匯編所有東西,所以反匯編的時候,有些名字只能通過智能推斷。
LDR.W SP, =BuildAttributes$$THM_ISAv4$E$P$D$K$B$S$7EM$VFPi3$EXTD16$VFPS$VFMA$PE$A_L22UL41UL21$X_L11$S22US41US21$IEEE1$IW$USESV6$_STKCKD$USESV7$_SHL$OTIME$ROPI$EBA8$MICROLIB$REQ8$PRES8$EABIv2
人為斷句一下,方便閱讀
BuildAttributes$
$THM_ISAv4
$E$P$D
$K$B$S
$7EM
$VFPi3
$EXTD16
$VFPS
$VFMA
$PE
$A_L22UL41UL21
$X_L11
$S22US41US21
$IEEE1
$IW
$USESV6
$_STKCKD
$USESV7
$_SHL
$OTIME
$ROPI
$EBA8
$MICROLIB
$REQ8
$PRES8
$EABIv2
看起來是一些編譯宏的拼接,Enter 一下,發現是這些:
F5 反編譯成c源碼(當然反編譯只是根據反匯編結果再反編譯成c,匯編里可沒有保留c的所有東西(變量名、指針都是沒有的)
這里介紹一下:IDA 的標簽頁名: IDA View 就是反匯編結果(按空格可切換流程圖和文本模式),Pseudocode 就是反編譯結果(c源碼模式)
main_init(v0)
繼續看
這里 IDA 給我們注釋了,是把 __scatterload_rt2 程序的返回值寫入 R0寄存器,
__scatterload_rt2
用 Enter 進入不了 __scatterload_rt2 程序,IDA直接顯示匯編給我們,也就是說這是一段匯編
這里還調用了兩個程序 Region$$Table$$Base
和 Region$$Table$$Limit
The DCD
directive allocates one or more words of memory, aligned on four-byte boundaries, and defines the initial runtime contents of the memory.
DCD 指令:為一或多個 Word 分配內存,四字節對齊,並定義初始運行時的內存內容(也就是向內存填充 4字節32位 的內容)
又根據網上的資料和上文,
main is your main procedure form main.c file, once __main is an internal procedure created by Keil toolchain which is calling at the end your main, but before it is initializing all variables (copying variables from FLASH to proper positions in RAM). In gcc it is seen explicitly, in Keil you can see it within debug process.
__main 是由 Keil 工具鏈創建的內部過程,它初始化所有變量(將變量從 FLASH 復制到 RAM 中的適當位置),並在最后調用您的 main,
在 gcc 中它是明確可見的,在 Keil 中你可以在調試過程中看到它
Keil 也是用了gcc,因此我們參考 GCC 的文檔-Initialization:
If no init section is available, when GCC compiles any function called
main
(or more accurately, any function designated as a program entry point by the language front end callingexpand_main_function
), it inserts a procedure call to__main
as the first executable code after the function prologue. The__main
function is defined in libgcc2.c and runs the global constructors.
The compiler in Arm® Compiler 6 is based on Clang and LLVM technology. As such, it provides a high degree of compatibility with GCC.
當然 Keil 也可以使用 GCC,見 Home » Creating Applications » Tips and Tricks » GNU C Compiler Support
也就是說,__main 是庫自帶的東西,在編譯時會由編譯器鏈接到二進制程序里
猜測:Keil 把用到的變量編譯在了 entry.o,然后把其二進制直接用DCD寫入內存(因此我們看不到其原始信息)。
官方文檔 https://www.keil.com/support/man/docs/armclang_intro/armclang_intro_pge1362066004603.htm
這個 Region$$Table 正是包含了本節提到的兩個看不懂的程序
根據網上的資料,得出 Region$$Table$$Base
是Region$$Table的起始地址, Region$$Table$$Limit
是Region$$Table的末尾地址。這兩個共同組合了 Region$$Table 本體。
關於 Region$$Table 本體的說明
Region$$Table
section- [英] containing the addresses of the code and data to be copied or decompressed.
- [中] 包含了要被復制或解壓縮的代碼和數據的地址
一些概念:
Code (代碼段)
ZI (Zero-Inintialize Data段)
RO (ReadOnly Data段)
RW (ReacWrite Data段)
占用計算:
FLASH 儲存:Code + RO + RW
RAM 內存: RW + ZI
在項目的 Object 目錄下的 sct 文件可見:
其中 InRoot$$Sections 包含了 Region$$Table
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00018000 { ; RW data
.ANY (+RW +ZI)
}
}
總之這部分超出了我的能力,本文純作拋磚引玉,這里提供一些關鍵資料:
- FSL之深入理解ARM:Cortex核MCU Region Table存儲格式是什么? - 阿莫電子論壇
- RealView Compilation Tools Linker and Utilities Guide - ARM Developer
只能說和 RW Data 壓縮息息相關,DCD那里應該就是ARM的壓縮指令。
實在着急着秋招,沒心思研究這些指令的含義了...
_main_init
反匯編:
反編譯:
反編譯名字有些不一樣,容易混淆,但根據調用流程來看,
這里的 a2,a3是R1 R2寄存器用於存放和傳遞主函數參數,a1 是 R0寄存器用於函數調用
main()
也就是我們 C語言世界的 main 函數,你自己寫的包含 main() 的 c源碼文件
當然我寫完了,才發現網上也有類似的文章
https://blog.csdn.net/hgsdfghdfsd/article/details/103812484
但是我的和它不太一樣,區別在於版本
In RVCT v2.0 and earlier, only the
__main
section and the region tables had to be placed in a root region.In RVCT v2.1 and above, RW data compression requires that additional sections (such as
__dc*.o
sections) be placed in a root region.see: https://developer.arm.com/documentation/dui0206/h/Bhccgbbe
可能因為我是從 STM32 官方手動下載的STM32F4xx DSP and Standard Peripherals Library
1.4.0 庫,而不是 Keil 自帶最新的,問題不大,都是對的