映像的結構由以下各項定義:
• 映像的組成區和輸出節的數量
• 加載映像時這些區和節在內存中的位置
• 執行映像時這些區和節在內存中的位置
描述內存映射時:
• 術語 根區域用於描述加載地址和執行地址相同的區。
• 載入區相當於 ELF 段。
1.1.1 對象和映像的構建塊
可執行文件由映像、區、輸出節和輸入節的層次結構構成:
• 映像由一個或多個區組成。 每個區由一個或多個輸出節組成。
• 每個輸出節包含一個或多個輸入節。
• 輸入節是對象文件中的代碼和數據信息。
圖3-1 顯示了區、輸出節和輸入節之間的關係。
輸入節 輸入節包含代碼、初始化數據,或描述未初始化的或在映像執行前必須設爲 0 的內存片斷。 這些特性通過RO、RW和 ZI 這樣的屬性來表示。armlink 使用這些屬性,將輸入節組織到稱爲輸出節和區的更大的構建塊中。
輸出節 一個輸出節由若干個具有相同 RO、RW 或 ZI 屬性的相鄰輸入節組成。 輸出節的屬性與組成它的輸入節的屬性相同。 在輸出節中,輸入節根據節放置中描述的規則進行排序。
區 一個區由一個、兩個或三個相鄰的輸出節組成。 區中的輸出節根據其屬性排序。 首先是 RO 輸出節,然後是 RW 輸出節,最後是 ZI輸出節。 區通常映射到物理內存設備,如 ROM、RAM 或外圍設備。
1.1.2 映像的加載視圖和執行視圖
映像區在加載時放入系統內存映射。 在執行映像之前,您可能要將它的一些區移到執行地址並創建ZI 輸出節。 例如,必須將已初始化的RW 數據從 ROM中的加載地址複製到 RAM中的執行地址。
映像的內存映射具有以下不同的視圖(如圖3-2中所示)。
加載視圖 根據映像加載到內存時所在的地址(即映像開始執行之前的位置)來描述每個映像的區和節。
執行視圖 根據映像執行時所在的地址來描述每個映像的區和節。
1.1.3 指定映像的內存映射
映像可以由任意個區和輸出節組成。 所有這些區可以有不同的加載地址和執行地址。 要構建映像的內存映射,armlink 必須有以下各項信息:
分組 如何將輸入節分組爲輸出節和區。
佈局 在內存映射中如何安排映像區的位置。
根據映像的內存映射的複雜程度,有兩種方法可將此信息傳遞到 armlink:
使用命令行選項
對於映像只有一個或兩個加載區和最多三個執行區的簡單情況,
可以使用以下選項:
• --ro-base
• --rw-base
• --ropi
• --rwpi
• --first
• --last
• --split
• --rosplit
1.1.4 映像入口點
必須爲程序指定至少一個初始入口點,否則鏈接器會生成警告。 並非每個源文件都必須具有入口點。 單個源文件中不允許有多個入口點。對於 ROM 起始地址爲 0的嵌入式應用程序,可使用 --entry 0x0 (或對於有高位向量的 CPU,可以使用 0xFFFF0000)。
初始入口點必須滿足以下條件:
• 映像入口點必須始終在執行區內執行區必須是非重疊的,而且必須是根執行區(加載地址與執行地址相同)。如果不使用 --entry 選項指定初始入口點,則:
• 如果輸入對象僅包含一個由 ENTRY 指令設置的入口點,則鏈接器將使用該入口點作爲映像的初始入口點
• 在以下情況時,鏈接器生成不包含初始入口點的映像:
— 已使用 ENTRY 指令指定多個入口點
— 未使用 ENTRY 指令指定任何入口點。
1.2 使用命令行選項創建簡單映像
數個RO、RW和 ZI 類型的輸入節可以組成一個簡單的映像。 這些輸入節整合成RO、RW和 ZI 輸出節。 根據載入區和執行區中輸出節的排列方式,有三種基礎類型的簡單映像:
類型 1加載視圖中有一個區,執行視圖中有三個相鄰區。 使用--ro-base選項可創建此類型的映像。有關詳細信息,請參閱類型 1,一個加載區和幾個連續執行區。
類型 2加載視圖中有一個區,執行視圖中有三個不連續的區。 使用--ro-base 和 --rw-base 選項可創建此類型的映像。
有關詳細信息,請參閱類型 2,一個加載區和幾個不連續的執行區。
類型 3加載視圖中有兩個區,執行視圖中有三個不連續的區。 使用--ro-base、--rw-base 和 --split 選項可創建此類型的映像。 您也可以使用--rosplit 選項將缺省載入區分爲兩個 RO 輸出節,一個用於代碼,另一個用於數據。
有關詳細信息,請參閱類型 3,兩個加載區和幾個不連續的執行區。
在所有這三種類型的簡單映像中,最多允許有三個執行區:
• 第一個執行區包含 RO 輸出節
• 第二個執行區包含 RW 輸出節(如果有)
• 第三個執行區包含 ZI 輸出節(如果有)。
這些執行區稱爲RO、RW和 ZI 執行區。
也可以用分散加載描述文件創建簡單映像。
1.2.1 類型 1,一個加載區和幾個連續執行區
此類型的映像由加載視圖中的單個載入區和內存映射中相鄰放置的三個執行區組成。 此方法適用於將程序加載到RAM 中的系統,例如OS 引導加載程序或桌面系統
也可以使用以下命令創建此類映像:armlink --ro-base 0x8000
加載視圖
單個載入區由連續放置的RO 和 RW輸出節組成。 RO和 RW執行區都是根區。加載時不存在 ZI輸出節。 該節是在執行前使用映像文件中的輸出節描述創建的。
執行視圖
包含RO、RW 和 ZI輸出節的三個執行區相鄰排列。 RO和 RW執行區的執行地址與其加載地址相同,因此不需要從加載地址向執行地址移動任何內容。 不過,包含ZI 輸出節的 ZI執行區是在運行時創建的。
使用armlink 選項 --ro-base address 可指定包含 RO輸出的區的加載和執行地址。缺省地址是 0x8000,如圖3-3 中所示。
1.2.2 類型 2,一個加載區和幾個不連續的執行區
此類型的映像由單個加載區和執行視圖中的三個執行區組成。 RW執行區與 RO執行區是不相鄰的。 例如,此方法適用於基於ROM 的嵌入式系統(請參閱圖3-4),其中 RW數據在啓動時從 ROM複製到 RAM。
使用以下命令創建此類映像:armlink --ro-base 0x0 --rw-base 0xA000
加載視圖
在加載視圖中,單個加載區由連續放置的 RO 和 RW輸出節(例如在 ROM中)組成。 此處的RO 區是根區, RW區是非根區。 加載時不存在ZI 輸出節。 該節在運行時創建。
執行視圖
在執行視圖中,第一個執行區包含 RO 輸出節,第二個執行區包含 RW和 ZI輸出節。包含 RO輸出節的區的執行地址與加載地址相同,因此不必移動 RO輸出節。即,它是根區。包含 RW輸出節的區的執行地址與加載地址不同,因此 RW輸出節從(單載入區)加載地址移到其執行地址(第二個執行區中)。 ZI執行區及其輸出節與RW 執行區相鄰放置。
使用 armlink 選項 --ro-base address 可指定 RO 輸出節的加載地址和執行地址,
使用 --rw-baseexec_address 可指定 RW 輸出節的執行地址。 如果未使用
--ro-base 選項指定地址,則 armlink 將使用缺省值 0x8000。 對於嵌入式系統,--ro-base 值通常爲 0x0。 如果未使用 --rw-base 選項指定地址,則缺省情況下
將RW 放在緊靠RO 的上方。(如第3-22 頁的類型1,一個加載區和幾個連續執行區中所述)。RW 和 ZI 輸出節的執行區不能與任何加載區重疊。
1.2.3 類型 3,兩個加載區和幾個不連續的執行區
此類型的映像與類型2 的映像相似,只是單個加載區現在分成了兩個根加載區。(請參閱圖3-5)。
使用以下命令創建此類映像:armlink --split --ro-base 0x8000 --rw-base 0xE000
加載視圖
在加載視圖中,第一個載入區由 RO 輸出節組成,第二個載入區由 RW輸出區組成。 加載時不存在ZI 輸出節。 它是在執行前,使用映像文件中包含的輸出節描述創建的。
執行視圖
在執行視圖中,第一個執行區包含 RO 輸出節,第二個執行區包含 RW和 ZI輸出節。
RO 區的執行地址與其加載地址相同,因此 RO輸出節的內容不必從加載地址移動或複製到執行地址。 RO和 RW都是根區。
RW 區的執行地址也與其加載地址相同,因此 RW輸出節的內容不必從加載地址移到執行地址。 不過,ZI 輸出節在運行時創建,並且緊鄰 RW區放置。
使用以下鏈接器選項指定加載和執行地址:
--split將缺省的單加載區(包含 RO 和 RW 輸出節)分成兩個根加載區
(一個包含RO 輸出節,另一個包含RW 輸出節),以便可以使用
--ro-base 和 --rw-base 分別放置這兩個加載區。
--ro-base address 指示 armlink 將包含 RO 節的區的加載和執行地址設置在 4 字節對齊的 address處(例如,ROM 中第一個位置的地址)。 如果未使用
--ro-base 選項指定地址,則 armlink 將使用缺省值 0x8000。
--rw-base address 指示 armlink 將包含 RW 輸出節的區的執行地址設置在 4 字節對齊的 address處。 如果此選項與 --split 結合使用,則會同時指定 RW區(例如根區)的加載地址和執行地址。
1.3 映像符號
鏈接器定義了一些包含$$ 字符序列的符號。 這些符號和所有其他包含$$ 序列的外部名稱都是 ARM 的保留名稱。
彙編語言程序可以導入這些符號地址並將其用作可重定位的地址,或者從 C 或C++ 源代碼中將其作爲 extern 符號進行引用。 有關詳細信息,
注意:僅當代碼引用鏈接器定義的符號時,纔會生成這些符號
1.3.1 與區相關的符號
Image$$ 執行區符號
下表顯示了鏈接器爲映像中存在的每個執行區生成的符號。 下表中所有符號都在初始化 C庫後引用執行地址。
NO. |
Image$$ 執行區符號 |
說明 |
1 |
Image$$region_name$$Base |
區的執行地址。 |
2 |
Image$$region_name$$Length |
執行區長度(以字節爲單位),不包括 ZI長度。 |
3 |
Image$$region_name$$Limit |
執行區中非 ZI 部分末尾後面的字節的地址。 |
4 |
Image$$region_name$$RO$$Base |
此區中的 RO 輸出節的執行地址。 |
5 |
Image$$region_name$$RO$$Length |
RO 輸出節的長度(以字節爲單位)。 |
6 |
Image$$region_name$$RO$$Limit |
執行區中 RO 輸出節末尾後面的字節的地址。 |
7 |
Image$$region_name$$RW$$Base |
此區中的 RW 輸出節的執行地址。 |
8 |
Image$$region_name$$RW$$Length |
RW輸出節的長度(以字節爲單位)。 |
9 |
Image$$region_name$$RW$$Limit |
執行區中 RW 輸出節末尾後面的字節的地址。 |
10 |
Image$$region_name$$ZI$$Base |
此區中的 ZI輸出節的執行地址。 |
11 |
Image$$region_name$$ZI$$Length |
ZI 輸出節的長度(以字節爲單位)。 |
12 |
Image$$region_name$$ZI$$Limit |
執行區中 ZI 輸出節末尾後面的字節的地址。 |
Load$$ 執行區符號
下表顯示了鏈接器爲映像中存在的每個 Load$$執行區生成的符號。此表中的所有符號都在初始化 C 庫之前引用加載地址。 有以下幾點需要注意:
• 這些符號是絕對的,因爲與節相關的符號只能具有執行地址。
• 這些符號會考慮 RW 壓縮。
• 這些符號不包括 ZI 輸出節,因爲該節在初始化 C 庫之前不存在。
• 經過 RW 壓縮的執行區中的所有重定位都必須在壓縮之前執行,因爲鏈接器無法對壓縮數據解析延遲的重定位。
• 如果鏈接器檢測到從經過 RW 壓縮的區到依賴於 RW 壓縮的鏈接器定義符的重定位,則鏈接器對該區禁用壓縮。
• 寫入到文件的任何零字節都可見。 因此,限制 (Limit) 和長度 (Length) 值必須考慮寫入到文件中的零字節。
NO. |
Load$$ 執行區符號 |
說明 |
1 |
Load$$region_name$$Base |
區的加載地址。 |
2 |
Load$$region_name$$Length |
加載區長度(以字節爲單位)。 |
3 |
Load$$region_name$$Limit |
執行區末尾後面的字節的地址。 |
4 |
Load$$region_name$$RO$$Base |
此執行區中的 RO 輸出節的地址。 |
5 |
Load$$region_name$$RO$$Length |
RO 輸出節的長度(以字節爲單位)。 |
6 |
Load$$region_name$$RO$$Limit |
執行區中 RO 輸出節末尾後面的字節的地址。 |
7 |
Load$$region_name$$RW$$Base |
此執行區中的 RW 輸出節的地址。 |
8 |
Load$$region_name$$RW$$Length |
RW 輸出節的長度(以字節爲單位)。 |
9 |
Load$$region_name$$RW$$Limit |
執行區中 RW 輸出節末尾後面的字節的地址。 |
Load$$LR$$ 加載區符號
下表顯示了鏈接器爲映像中存在的每個 Load$$LR$$加載區生成的符號。 一個Load$$LR$$ 加載區可以包含許多執行區,因此沒有單獨的 $$RO 和$$RW 組件。
NO. |
Load$$LR$$加載區符號 |
說明 |
1 |
Load$$LR$$load_region_name$$Base |
加載區的地址。 |
2 |
Load$$LR$$load_region_name$$Length |
加載區的長度。 |
3 |
Load$$LR$$load_region_name$$Limit |
加載區末尾後面的字節的地址。 |
1.3.2 非分散加載時的區名稱值
如果未使用分散加載,鏈接器將使用以下項的 region_name 值:
• ER_RO,適用於只讀執行區
• ER_RW,適用於讀寫執行區
• ER_ZI,適用於零初始化的執行區。
注意
• 映像的 ZI輸出節不是靜態創建的,而是在運行時自動動態創建的。因此,ZI輸出節沒有加載地址符號。
• 建議優先使用與區相關的符號,而不是與節相關的符號。
1.3.3 與節相關的符號
如果使用命令行選項創建簡單映像,則會生成表4-4 中所示的輸出節符號。 簡單映像有三個輸出節(RO、RW 和 ZI),它們生成三個執行區。 對於映像中存在的每個輸入節,鏈接器將生成第4-8 頁的表4-5 中所示的輸入符號。
鏈接器先按 RO、RW 或 ZI 屬性對執行區內的節進行排序,然後按名稱進行排序。例如,將所有 .text 節放在一個連續塊中。 包含相同屬性和名稱的節的連續塊稱爲合併節。
注意:如果使用分散加載描述文件,則不會定義表4-4中的輸出節符號。如果代碼訪問這些符號,則必須將其視爲弱引用。__user_initial_stackheap()的標準實現使用Image$$ZI$$Limit中的值。因此,如果使用分散加載描述文件,則必須手動放置堆棧和堆。可以在分散描述文件中實現此目的,或是通過重新實現__user_initial_stackheap()設置堆和堆棧邊界來實現。
1.3.4 導入鏈接器定義的符號
可以使用兩種方法,將鏈接器定義的符號導入到 C 或 C++ 源代碼中。 請使用以下方法之一:
externunsigned int symbol_name;或
externchar symbol_name[];
如果將符號聲明爲int,則必須使用取址運算符獲取正確的值,如示例4-2 中所示。
示例4-2 導入鏈接器定義的符號
***********************************************************************
externunsigned int Image$$ZI$$Length;
externchar Image$$ZI$$Base[];
memset(Image$$ZI$$Base,0,(unsignedint)&Image$$Length);
***********************************************************************
1.4 使用分散加載描述文件指定堆棧和堆
ARM C 庫提供了 __user_initial_stackheap() 函數的多個實現,可以根據分散加載描述文件中給出的信息自動選擇正確的函數實現。要選擇兩個區內存模型,請在分散加載描述文件中定義兩個名爲 ARM_LIB_HEAP和
ARM_LIB_STACK的特殊執行區。 兩個區均具有 EMPTY 屬性。 這導致庫選擇使用以下
符號值的非缺省 __user_initial_stackheap() 實現:
•Image$$ARM_LIB_STACK$$Base
•Image$$ARM_LIB_STACK$$ZI$$Limit
•Image$$ARM_LIB_HEAP$$Base
•Image$$ARM_LIB_HEAP$$ZI$$Limit
只能指定一個 ARM_LIB_STACK 或 ARM_LIB_HEAP 區,並且必須分配大小,例如:
ARM_LIB_HEAP0x20100000 EMPTY 0x100000-0x8000 ; Heap starts at 1MB
; and grows upwards
ARM_LIB_STACK0x20200000 EMPTY -0x8000 ;Stack space starts at the end
; of the 2MB of RAM
; And grows downwards for 32KB
注意:如果使用上面的堆棧函數,則還必須在彙編源代碼中包含一個 IMPORT __use_two_region_memory,或在 C/C++源代碼中包含一個 #pragmaimport(__use_two_region_memory),因爲不會自動選擇雙區模型。
可以使用 EMPTY 屬性定義單個名爲ARM_LIB_STACKHEAP的執行區,強制__user_initial_stackheap()使用合併的堆棧/堆區。這導致__user_initial_stackheap()使用符號Image$$ARM_LIB_STACKHEAP$$Base和
Image$$ARM_LIB_STACKHEAP$$ZI$$Limit的值。
1.5 Stack/Heap保留空白區
可以在執行區分散加載描述中使用 EMPTY 屬性,爲堆棧保留一個空白內存塊
該內存塊並不構成加載區的一部分,而是在執行時分配使用的。 由於它是作爲
虛ZI 區創建的,因此鏈接器使用以下符號對其進行訪問:
• Image$$region_name$$ZI$$Base
• Image$$region_name$$ZI$$Limit
• Image$$region_name$$ZI$$Length.
如果指定的長度爲負值,則將該地址作爲區結束地址。 它必須是絕對地址,而不是相對地址。 例如,第5-26 頁的示例5-15 中說明的執行區定義 STACK 0x800000 EMPTY –0x10000 定義了一個名爲 STACK 的區,它的開始地址是0x7F0000,結束地址是 0x800000。
注意:
在運行時,不會將爲 EMPTY執行區創建的虛 ZI區初始化爲零
如果地址採用相對格式 (+n),並且長度爲負值,鏈接器將生成錯誤
EMPTY 屬性僅適用於執行區。如果在加載區定義中使用 EMPTY屬性,鏈接器將生成警告並忽略該屬性。
在此示例中,鏈接器生成以下符號:
Image$$STACK$$ZI$$Base = 0x7f0000
Image$$STACK$$ZI$$Limit =0x800000
Image$$STACK$$ZI$$Length =0x10000
Image$$HEAP$$ZI$$Base = 0x800000
Image$$HEAP$$ZI$$Limit = 0x810000
Image$$HEAP$$ZI$$Length= 0x10000