ADS中startup.s文件啓動分析

映像文件分析,ADS 中startup.s 文件啓動分析,學嵌入式開發ADS 必看
2010-04-17 10:21
聲明: 我也是轉來的,不是原創,由於別人是網易的日誌,不能直接轉,所以…… 感謝原
創!讓我明白了startup.s 文件中的一些代碼。
1、什麼是arm 的映像文件,
arm 映像文件其實就是可執行文件,包括bin 或hex 兩種格式,可以直接燒到ROM 裏執行。
在axd 調試過程中,我們調試的是axf 文件,其實這也是一種映像文件,它只是在bin 文件中
加了一個文件頭和一些調試信息。
映像文件一般由域組成,域最多由三個輸出段組成(RO,RW,ZI),輸出段又由輸入段組成。所
謂域,指的就是整個bin 映像文件所處在的區域,它又分爲加載域和運行域。對於嵌入式系
統而言,程序映象都是存儲在Flash 存儲器等一些非易失性器件中的,而在運行時,程序中
的RW 段必須重新裝載到可讀寫的RAM 中。簡單來說,程序的加載時域就是指程序燒入Flash
中的狀態,運行時域是指程序執行時的狀態。一般來說flash 裏的整個bin 文件所在的地址
空間就是加載域,當然在程序一般都不會放在flash 裏執行,一般都會搬到sdram 裏運行工作,
它們在被搬到sdram 裏工作所處的地址空間就是運行域。我們輸入的代碼,一般有代碼部分
和數據部分,這就是所謂的輸入段,經過編譯後就變成了bin 文件中ro 段和rw 段,還有所
謂的zi 段,這就是輸出段。在ARM 的集成開發環境中,只讀的代碼段和常量被稱作RO 段
(ReadOnly);可讀寫的全局變量和靜態變量被稱作RW 段(ReadWrite);RW 段中要被初始化爲
零的變量被稱爲ZI 段(ZeroInit)。對於加載域中的輸出段,一般來說RO 段後面緊跟着RW 段,
RW 段後面緊跟着ZI 段。在運行域中這些輸出段並不連續,但RW 和ZI 一定是連着的。ZI
段和RW 段中的數據其實可以是RW 屬性。
2、簡單地址映射
對於比較簡單的情況,可以在ADS 集成開發環境的ARM LINKER 選項output 中指定RO Base
和RW Base,即在simple 模式下,告知連接器RO 和RW 的連接基地址。
這種模式下,ARM Linker 會輸出以下符號,它們指示了在運行域中各個輸出段所處的地址空
間,在使用的時候可以用IMPORT 引入:
| Image$$RO$$Base|: 表示RO 段在運行域中的起始地址
|Image$$RO$$Limit|:表示RO 區末地址後面的地址,即RW 數據源的起始地址
|Image$$RW$$Base|:RW 區在RAM 裏的執行區起始地址,也就是編譯器選項RW_Base 指定
的地址
|Image$$ZI$$Base|:ZI 區在RAM 裏面的起始地址
|Image$$ZI$$Limit|:ZI 區在RAM 裏面的結束地址後面的一個地址
RO Base 對應的就是| Image$$RO$$Base|,RW Base 對應的是|Image$$RW$$Base|,由於ZI 段
是包含在RW 段裏的,所以|Image$$RW$$Limit| 就等於|Image$$ZI$$limit| 。
下面給出一個例子,假設RO Base 設爲0x00000000,後面的RW Base 地址是0x30000000,
然後在Options 選項中有Image entry point ,是一個初始程序的入口地址,設爲0x00000000(程
序的入口地址都是從代碼段(RO)開始的)。現在要做的就是將RWsection 移到以0x30000000
開始的地方,並且創造一個ZI section。
首先比較Image$$RO$$Limit 和Image$$RW$$Base,如果相等,說明execution view 下RW
section 的地址和load view 下RW section 的地址相同,這樣,不需要移動RW section;如果不等,
說明需要移動RW section 到它在execution view 中的地方,把ROM 裏|Image$$RO$$Limt|開
始的RW 初始數據拷貝到RAM 裏面|Image$$RW$$Base|開始的地址,當RAM 這邊的目標地址
到達|Image$$ZI$$Base|後就表示RW 區的結束和ZI 區的開始,接下去就對這片ZI 區進行清零
操作,直到遇到結束地址|Image$$ZI$$Limit|。
ARM 映像文件及其地址映射(二)
示例代碼如下:
IMPORT |Image$$RO$$Limit|
IMPORT |Image$$RW$$Base|
IMPORT |Image$$ZI$$Base|
IMPORT |Image$$ZI$$Limit|
IMPORT main ; 聲明C 程序中的Main()函數
AREA Start,CODE,READONLY ; 聲明代碼段Start
ENTRY ; 標識程序入口
CODE32 ; 聲明32 位ARM 指令
Reset LDR SP,=0x40003F00
; 初始化C 程序的運行環境
LDR R0,=|Image$$RO$$Limit| ;得到RW 數據源的起始地址
LDR R1,=|Image$$RW$$Base| ;RW 區在RAM 裏的執行區起始地址
LDR R3,=|Image$$ZI$$Base| ;ZI 區在RAM 裏面的起始地址
CMP R0,R1 ;檢查RWsection 的地址在load view 和execution view 下是否相等
BEQ LOOP1 ;如果相等就不移動RWsection,直接建立ZI scetion
LOOP0 ;否則就copy RWsection 到execution view 下指定的地址
CMP R1,R3
LDRCC R2,[R0],#4 ;它把從R0 中的地址開始的section copy 到R1 中的地址開
始的section
STRCC R2,[R1],#4
BCC LOOP0
LOOP1
LDR R1,=|Image$$ZI$$Limit| ;ZI section 末地址
MOV R2,#0 ;將ZI section 需要的初始化量裝入R2
LOOP2
CMP R3,R1 ;建立並初始化ZI section
STRCC R2,[R3],#4
BCC LOOP2
B main ; 跳轉到C 程序代碼Main()函數
END
注:LDRCC R2,[R0],#4 ;將地址爲R0 的內存單元數據讀取到R2 中,然後R0=R0+4
CC(小於),EQ(相等)爲條件碼。
當我們把程序編寫好以後,就要進行編譯和鏈接了,在ADS1.2 中選擇MAKE 按鈕,會出現
一個Errors and Warnings 的對話框,在該欄中顯示編譯和鏈接的結果,如果沒有錯誤,在文
件的最後應該能看到Image component sizes,後面緊跟的依次是Code,RO Data ,RW Data ,
ZI Data ,Debug 各個項目的字節數,最後會有他們的一個統計數據,後面的字節數是根據
用戶不同的程序而來的。
Image component sizes
Code RO Data RWData ZI Data Debug
17256 158096 8 184 112580 Object
Totals
1064 299 0 0 796
Library Totals
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Code RO Data RWData ZI Data Debug
18320 158395 8 184 113376
Grand Totals
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Total RO Size(Code+RO Data) 176715(172.57KB)
Total RWSize(RWData+ZI Data) 192 ( 0.19KB)
Total ROM Size(Code+RO Data+RWData) 176723(172.58KB)
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Code :顯示代碼佔用了多少字節。
RO Data 顯示只讀數據佔用了多少字節。
RW Data 顯示讀寫數據佔用了多少字節。
ZI Data 顯示零初始化的數據佔用了多少字節。
Debug 顯示調試數據佔用了多少字節。
Object Totals 顯示鏈接到一起以後生成映像的對象佔用了多少字節。
Library Totals 顯示已提取並作爲單個對象添加到映像中的庫成員佔用了多少字節。
Grand Totals 顯示映像的真實大小。Grand Totals=Library Totals+Object Totals
下面就以上面的數據爲例來介紹幾個變量的計算:
|Image$$RO$$Base|=Image entry point=0x00000000;表示程序代碼存放的起始地址
|Image$$RO$$Limit|=|Image$$RO$$Base|+Total RO Size ( Code+Ro Data )
=0x0+176715+1=0x0002B24C(因爲要滿足4 的倍數,所以+1)
|Image$$RW$$Base|=0x30000000;由RW Base 指定
|Image$$RW$$Limit|=|Image$$RW$$Base|+Total RW Size ( RW Data+ZI Data )
=0x30000000+192=0x300000C0
|Image$$ZI$$Base|=|Image$$RW$$Base|+RWData=0x30000000+8=0x30000008
|Image$$ZI$$Limit|=|Image$$RW$$Limit|
3、複雜地址映射
對於複雜情況,如RO段被分成幾部分並映射到存儲空間的多個地方時,需要創建一個稱爲“分
布裝載描述文件”的文本文件,通知連接器把程序的某一部分連接在存儲器的某個地址空間。
需要指出的是,分佈裝載描述文件中的定義要按照系統重定向後的存儲器分佈情況進行。在
引導程序完成初始化的任務後,應該把主程序轉移到RAM 中去運行,以加快系統的運行速
度。
如下圖,爲了解決複雜memory map 的問題需要用到scatter load 機制。
__main() 和main()之不同:
當所有的系統初始化工作完成之後,就需要把程序流程轉入主應用程序,即呼叫主應用程序。
最簡單的一種情況是:
IMPORT main
B main
直接從啓動代碼跳轉到應用程序的主函數入口,當然主函數名字可以由用戶隨便定義。
在ARM ADS 環境中,還另外提供了一套系統級的呼叫機制。
IMPORT _main
B _main
_main()是編譯系統提供的一個函數,負責完成庫函數的初始化和初始化應用程序執行環境,
最後自動跳轉到main()。所以說,前者(_main)是庫函數,後者就是我們自己編寫的main()主
函數;
因此我們用的B _main 其實是執行庫函數,然後該庫函數再調用我們的main() 函數,因此在單
步調試時會看到先要跑一段程序(其實是庫函數),然後再單步到我們自己的main 函數(這個同
時也說明如果有B _main 則就對應必須有main 函數,否則編譯出錯),如果我們用B main 來進
入我們的主函數的話,那在單步調試時就看到直接進入到我們自己的main 函數了,中間不會看
到其他程序;
那麼用B _main 和用B main 這兩這進入我們的main 函數方式有什麼不同呢?
如果採用前者則會由編譯器加入一段"段拷貝"程序,即我們說的從加載域到執行域轉化程序;
而採用後者就沒有這個了,因此如果要進行"段拷貝"只能自己動手編寫程序來實現了,完成段
拷貝後就可以進入我們的主函數了,當然這個主函數不一定是叫做main(),可以起個其他好聽
的名字,這個有別於使用B __main 方式;不管採用哪種方式進入我們的程序,都要有一段"段拷
貝"程序,跑完了段拷貝後才能可以進入我們主程序了!(順便提一下:startup.s 這個文件並沒有
所謂的"段拷貝"功能,再看也無益!)
對含有啓動程序來說,"執行地址與加載地址相同"不容易實現:如果執行地址與加載地址相同
哪當然不需要做"段拷貝",但是個人理解編譯器還會加入"段拷貝"程序(如果用B __main 的話),
只是因爲條件不滿足而不執行而已;但是對含有啓動程序來說,"執行地址與加載地址相同"就
不容易了.因爲啓動程序是要燒到非易失存儲器裏,用來在上電執行的,而這個程序必定會有
RW 段,如果RW 放在非易失存儲器,如FLASH,那就不好實現RW 功能了,因此要給RW 移動到
能夠實現RW 功能的存儲器,如SRAM 等.因此,對含有啓動程序來說,"執行地址與加載地址相
同"就不容易實現;程序的入口點在C 庫中的__main 處,在該點,庫代碼執行以下操作:
1. 將非零(只讀和讀寫)運行區域從其載入地址複製到運行地址。
2. 清零ZI 區域。
3. 跳轉到__rt_entry。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章