一、 啟動文件的介紹
在 MDK 的啟動文件 startup_stm32f10x_md_vl 中,該文件分別定義了棧段、 堆段、存放中斷向量表的數據段、還有一個代碼段  大小為 0x400 的棧段定義如圖 1-1:
圖 1-1
大小為 0x200 的堆段如圖 1-2:
圖 1-2
由其定義屬性可知,棧和堆都未初始化,該過程由后面的_user_initial_stackheap 來完成。 存放中斷向量的數據段,如圖 1-3 所示:
圖 1-3 10 個系統異常過程段和在同一地址的外部中斷過程段,下面我們就詳細介紹 上電復位的代碼段,如圖 1-4 所示:
二、Reset_Handler 段分析
1. _systeminit 函數分析 STM32 上電啟動,首先從 0x0000 0000 處初始化 sp 的值,然后從 0x0000 0004  處取得復位中斷處理的地址 0x0800 1F6D,程序跳轉
圖 2-1
但是 Reset_Handler 的地址為 0x0800 1F6C,這是因為 Cortex-M3 使用的是 thumb-2  指令集,其最低位必須為 1;如果為 0,則會出現異常,如圖 2-1 所示。 查看反匯編窗口,如圖 2-2 所示:
圖 2-2
可以知道先取得 SystemInit 函數的地址,該地址是多少,我們可以跳轉到  0x08001F94 看看,如下所示,該地址保存了值為 0x0800045D 的數,如圖 2-3:
圖 2-3
 然后我們跳轉到 0x0800045D,發現該處正是我們需要的 SystemInit 函數入口地 址,如圖 2-4:
圖 2-4
該函數首先保存跳轉前的有關狀態,然后根據使用的芯片,進行相應的初始化操 作,函數最后重新映射了中斷向量的存放地址,如圖 2-5:
 查看反匯編窗口,如圖 2-6:
圖 2-6
根據分析可知,該段代碼是把 0x0800 0000 存放到地址為 0xE000 ED08 處, 查找 Cortex_M3 手冊如圖 2-7 所示,該地址是向量表偏移寄存器,也就是說,這 條語句把中斷向量表重新映射到地址為 0x0800 0000。
2. _main 函數分析 圖 2-7
進行完相應的初始化,函數跳轉到_main 函數,_main 函數的入口地址從 0x08001F98 取得,通過查找,發現_main 函數的地址為 0x08000121,跳轉到  0x0800120,證明此處就是我們要找的_main 函數入口,如圖 2-8
圖 2-8
_main 函數到底進行了哪些操作呢,下面我們就一一逐步分析過去。 ? _scatterload 函數分析
首先是一條跳轉指令,跳轉到_scatterload 函數,該函數的第一條指令是把地 址 0x08000154 賦值給 r0(但是此處 PC+4 取得值應該是0x0800012C,為什么會 是 0x08000154 呢?根據 ARMv7-M Architecture Reference Manual,ADR 的二進 制碼,相差 0x28),第二條指令是分別把 0x08000154、0x08000158 存放的 0x0000 1ECC、0x0000 1EEC 給 r10、r11,如圖 2-9。經過該函數的第三、第四條指令, 我們可以得到r10=0x08002020,r11=0x08002040,r7=0x0800201F,
圖 2-9 查看.map 文件,知道 0x08002020 稱為 Region$$Table$$Base,0x08002040 稱為 Region$$Table$$Limit。 ? _scatterload_null 函數分析
如圖 2-10 所示,程序繼續執行到_scatterload_null 函數,首先比較 r10、r11 是否相等,如果不等則跳轉到 0x0800013E。很明顯不等,程序跳轉,第一條指 令是把 0x08000137f 賦值給 lr,其實就是保存_scatterload_null 的入口地址;第二 條指令是把 r10=0x08002020 為起始,0x08002030 為終止的地址內容分別賦值給 r0-r3,最后我們得到 r0=0x08002040, r1=0x20000000, r2=0x00000034, r3=0x0800 015C,r10=0x08002030;第三條指令是判斷 r3 是否為 1,很明顯不為 1,IT 指令 等效于 if-then 模式,最后幾條指令可以得到 r3=0x0800015D,程序跳轉
? MAP 文件分析 圖 2-10
0x0800 015D 地址是函數_scatterload_copy 的入口,該函數到底 copy 了什么 值呢?在此之前我們先要熟悉一下.map 文件 .map 文件是值包括了映像文件信息圖和其它信息的一個映射文件,該文件包 含了: (1) 從映像文件中刪除的輸入段中未使用段的統計信息,對應參數-remove; (2) 域符號映射和全局、局部符號及生成符號映射統計信息,對應參數 -symbol; (3) 映射文件的信息圖,對應參數-map,該信息中包含映像文件中的每個加載 域、運行域和輸入段的大小和地址,如 2-11 圖、2-12 圖所示:
圖 2-11
圖 2-12
 (4) 映像文件的每個輸入文件或庫的RO、RW、ZI 等統計信息,對應參數-info sizes,示例如圖 2-13:
圖 2-13
 (5) 文件對象類和庫總的 RO、RW、ZI等下大小,對應參數-info totals,示例 如圖 2-14:
圖 2-14
由此,根據.map 文件我們得到 RW=0x34, ZI=0x0644, Code+RO=0x2040, RW+ZI=0x0698 現在我們回到_scatterload_copy 中,看看到底 copy 了什么,其代碼如圖 2-15  所示:
圖 2-15
? _scatterload_copy 函數分析
經過_scatter_null 函數我們得到 r2=0x00000034,這正是需要初始化全局變量 的大小,由此我們猜測 copy 的值是全局變量。 第一條指令是得 r2=0x0000 0024,然后判斷標志位 C 是否為 1,如果是 1,則 執行下面兩條語句: LDM r0! , (r3-r6); STM r1! , (r3-r6); r0=0x0800 2040, r1=0x2000 0000,而 0x20000000 正好是 SRAM 的起始地址,所以 上面兩條語句是把以 0x8002040 為起始的地址復制到以0x2000 0000 為起始的地 址,該循環類似我們的 for(r0=0x0800 2040, r1=0x2000 0000;r2=r2-0x10;r2>0), 直到 r2 為負數 LSLS r2,r2,#29 ITT CS 如果 r2 除以 16 是 8 的整數,則復制該 8 字節 LDM r0!,{r4-r5} STM r1!,{r4-r5}
ITT MI LDR r4,[r0,#0x00] STR r4,[r1,#0x00] 如果 r2 除以 16 是 4 的整數,則復制 4 或者 12 字節
這樣_scatterload_copy 完成了需要初始化全局變量 RW 的裝載過程,最后函 數返回到_scatterload_null。 ? _scatterload_zeroinit 函數分析
然后判斷 r10 是否與 r11 相等,很明顯不等,r3=0x0800 0178,函數跳轉,進入 到_scatterload_zeroinit 函數,此時 r0=0x0800 2074,r1=0x20000034, r2=0x0000 0664 _scatterload_zeroinit 函數代碼如圖 2-16:
圖 2-16 通過_scatterload_copy 我們可以猜測_scatterload_zeroinit 是一個清零過程,但 是對什么需要清零呢?當然是 ZI 段,由 r2=0x00000664 這正是 ZI 的大小,所以 該過程是以 0x20000034 為起始地址,大小為 r2=0x00000664 的清零過程,具體 分析和_scatterload_copy 類似,不再重復。
程序最后返回到_scatterload,接著跳轉到_rt_entry,如圖 2-17
2. _rt_entry 函數分析 圖 2-17
_rt_entry 的第一條指令又是一條跳轉指令,程序再次跳轉到 _user_setup_stackheap,如圖 2-18
圖 2-18
_user_setup_stackheap 函數的第一條指令是保存函數的返回地址,此處為 什么沒有用 PUSH ?因為此時堆棧還沒有初始化好。第二條指令是跳轉到 _user_libspace 進行一些微庫的初始化工作,后面的幾條語句是建立一個大小為 90 字節的臨時棧,然后程序跳轉到_user_inital_stackheap 進行用戶棧的初始化, 這也就是啟動文件 startup_stm32f10x_md_vl 中初始化堆棧段的那些語句,如圖 2-19
圖 2-19
經 過 該 函 數 處 理 得 : r0=0x2000 0098,r1=0x2000 0698,r2=0x2000 0298,r3=0x2000 0298。最后用戶棧頂被設置成 0x2000 0698,完成了堆棧的初始 化工作,程序返回到 rt_entry_main
三、 總結
最終函數終于跳轉到我們的 main 函數執行我們寫的代碼。 總結啟動文件的整個過程,分為如下: (1) 系統初始化,包括對中斷向量表的重新映射; (2) 加載 RW 段; (3) ZI 段清零; (4) 初始化用戶堆棧; (5) 初始化微庫(具體干什么我也不知道,屏蔽此處函數好像也能正常運行); (6) 調用 main 函數。
|