ADS映像文件及地址映射分析

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中指定ROBase
和RWBase,即在simple 模式下,告知連接器RO和 RW的連接基地址。
這種模式下,ARMLinker會輸出以下符號,它們指示了在運行域中各個輸出段所處的地址空
間,在使用的時候可以用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 裏面的結束地址後面的一個地址
ROBase 對應的就是| Image$$RO$$Base|,RWBase 對應的是|Image$$RW$$Base|,由於 ZI段
是包含在RW段裏的,所以|Image$$RW$$Limit| 就等於|Image$$ZI$$limit| 。
下面給出一個例子,假設RO Base 設爲 0x00000000,後面的 RWBase 地址是0x30000000,
然後在 Options選項中有 Imageentry point ,是一個初始程序的入口地址,設爲 0x00000000(程
序的入口地址都是從代碼段(RO)開始的)。現在要做的就是將 RWsection移到以 0x30000000
開始的地方,並且創造一個 ZIsection。
首先比較 Image$$RO$$Limit 和 Image$$RW$$Base,如果相等,說明 execution view 下 RW
section 的地址和 load view 下 RWsection 的地址相同,這樣,不需要移動 RWsection;如果不等,
說明需要移動 RWsection 到它在 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|
          IMPORTmain; 聲明C 程序中的Main()函數
AREAStart,CODE,READONLY; 聲明代碼段 Start
ENTRY; 標識程序入口
CODE32 ; 聲明 32位 ARM指令
ResetLDR 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的地址在 loadview和 executionview下是否相等
          BEQ LOOP1 ;如果相等就不移動RWsection,直接建立 ZIscetion
LOOP0 ;否則就copy RWsection 到executionview 下指定的地址
          CMP R1,R3
          LDRCC R2,[R0],#4 ;它把從 R0 中的地址開始的 section copy 到 R1 中的地址開始的section
          STRCC R2,[R1],#4
          BCC LOOP0
LOOP1
          LDR R1,=|Image$$ZI$$Limit| ;ZIsection末地址

          MOV R2,#0 ;將 ZIsection 需要的初始化量裝入 R2

LOOP2

          CMP R3,R1 ;建立並初始化ZIsection
          STRCC R2,[R3],#4
          BCC LOOP2

          Bmain; 跳轉到 C 程序代碼Main()函數
END
注:LDRCCR2,[R0],#4 ;將地址爲 R0的內存單元數據讀取到 R2中,然後 R0=R0+4
CC(小於),EQ(相等)爲條件碼。
當我們把程序編寫好以後,就要進行編譯和鏈接了,在 ADS1.2中選擇 MAKE 按鈕,會出現
一個 Errors andWarnings 的對話框,在該欄中顯示編譯和鏈接的結果,如果沒有錯誤,在文
件的最後應該能看到 Image component sizes,後面緊跟的依次是 Code,ROData ,RWData ,
ZI Data ,Debug 各個項目的字節數,最後會有他們的一個統計數據,後面的字節數是根據
用戶不同的程序而來的。
Image componentsizes
Code ROData RWData ZIData Debug
17256 158096 8 184 112580 Object
Totals
1064 299 0 0 796
LibraryTotals
===============================================
Code ROData RWData ZIData Debug
18320 158395 8 184 113376
GrandTotals
===============================================
TotalROSize(Code+ROData) 176715(172.57KB)
TotalRWSize(RWData+ZIData) 192( 0.19KB)
TotalROMSize(Code+ROData+RWData) 176723(172.58KB)
===============================================
Code :顯示代碼佔用了多少字節。
ROData 顯示只讀數據佔用了多少字節。
RWData 顯示讀寫數據佔用了多少字節。
ZIData 顯示零初始化的數據佔用了多少字節。
Debug 顯示調試數據佔用了多少字節。
ObjectTotals 顯示鏈接到一起以後生成映像的對象佔用了多少字節。
LibraryTotals 顯示已提取並作爲單個對象添加到映像中的庫成員佔用了多少字節。
GrandTotals 顯示映像的真實大小。 GrandTotals=LibraryTotals+ObjectTotals
下面就以上面的數據爲例來介紹幾個變量的計算:
|Image$$RO$$Base|=Imageentrypoint=0x00000000;表示程序代碼存放的起始地址
|Image$$RO$$Limit|=|Image$$RO$$Base|+Total RO Size ( Code+Ro Data )
=0x0+176715+1=0x0002B24C(因爲要滿足 4的倍數,所以+1)
|Image$$RW$$Base|=0x30000000;由RWBase指定


|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 的問題需要用到 scatterload 機制。
__main() 和 main()之不同:
當所有的系統初始化工作完成之後,就需要把程序流程轉入主應用程序,即呼叫主應用程序。
最簡單的一種情況是:
IMPORTmain
Bmain
直接從啓動代碼跳轉到應用程序的主函數入口,當然主函數名字可以由用戶隨便定義。
在ARMADS 環境中,還另外提供了一套系統級的呼叫機制。
IMPORT_main
B_main
_main()是編譯系統提供的一個函數,負責完成庫函數的初始化和初始化應用程序執行環境,
最後自動跳轉到 main()。所以說,前者(_main)是庫函數,後者就是我們自己編寫的 main()主
函數;
因此我們用的B_main 其實是執行庫函數,然後該庫函數再調用我們的main() 函數,因此在單
步調試時會看到先要跑一段程序(其實是庫函數),然後再單步到我們自己的 main 函數(這個同
時也說明如果有B_main 則就對應必須有main函數,否則編譯出錯),如果我們用 Bmain來進
入我們的主函數的話,那在單步調試時就看到直接進入到我們自己的 main 函數了,中間不會看
到其他程序;
那麼用B_main和用Bmain 這兩這進入我們的 main 函數方式有什麼不同呢?
如果採用前者則會由編譯器加入一段"段拷貝"程序,即我們說的從加載域到執行域轉化程序;
而採用後者就沒有這個了,因此如果要進行 "段拷貝"只能自己動手編寫程序來實現了,完成段
拷貝後就可以進入我們的主函數了,當然這個主函數不一定是叫做main(),可以起個其他好聽
的名字,這個有別於使用 B__main 方式;不管採用哪種方式進入我們的程序,都要有一段"段拷
貝"程序,跑完了段拷貝後才能可以進入我們主程序了!(順便提一下:startup.s這個文件並沒有
所謂的"段拷貝"功能,再看也無益!)(再順便提一下:上面例子是段拷貝程序)
對含有啓動程序來說,"執行地址與加載地址相同"不容易實現:如果執行地址與加載地址相同
哪當然不需要做"段拷貝",但是個人理解編譯器還會加入"段拷貝"程序(如果用B__main的話),
只是因爲條件不滿足而不執行而已;但是對含有啓動程序來說,"執行地址與加載地址相同"就
不容易了.因爲啓動程序是要燒到非易失存儲器裏,用來在上電執行的,而這個程序必定會有


RW段,如果RW放在非易失存儲器,如FLASH,那就不好實現RW功能了,因此要給RW移動到
能夠實現 RW 功能的存儲器,如 SRAM 等.因此,對含有啓動程序來說,"執行地址與加載地址相
同"就不容易實現;程序的入口點在C 庫中的__main 處,在該點,庫代碼執行以下操作:
1. 將非零(只讀和讀寫)運行區域從其載入地址複製到運行地址。
2. 清零 ZI 區域。

3. 跳轉到__rt_entry。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章