ARM 之十 ARMCC(Keil) map 文件(映射文件)详解
在看這篇文章之前
map 文件是什么
??map 文件對應的中文名應該是映射文件,用來展示(映射)項目構建的鏈接階段的細節。通常包含程序的全局符號、交叉引用和內存映射等等信息。目前,大多數編譯套件(主要是其中的鏈接器)都可以生成 Map 文件。常見的 GCC、VC、IAR 都可以輸出 map 文件(PC平臺的 map 文件與 ARM 平臺的差別較大)。
??在 ARM 的官方文檔中,并沒有找到有關于 ARM 內核的 map 文件的介紹文檔。不過倒是有個 C51 生成的 map 文件的說明文檔:Listing (MAP) File。但是 C51 的 map 文件和 ARM 核的 map 文件差別比較大,也沒啥參考價值!
??map 文件就是用來展示鏈接器工作過程的東西。想要了解 map 文件還需要對 ARM ELF 文件有一定的了解(可以參考博文ARM 之一 ELF文件、鏡像(Image)文件、可執行文件、對象文件 詳解)以及需要對于編譯過程有一定的了解。下圖顯示了鏈接器在軟件開發過程中的角色。鏈接器接受幾種類型的文件作為輸入,包括對象文件、命令文件、庫和部分鏈接的文件,創建一個可執行對象模塊。
 
map 文件從哪來
??map 文件是由編譯套件中的鏈接器產生的。ARM 的編譯套件中鏈接器為armlink,map 文件中的各信息均由 armlink 的各參數(–info topic、–map、–symbols等)控制輸出(由 --list=filename 文件名輸出到文件)。關于 ARM 編譯套件的詳細信息,參考博文ARM 之七 主流編譯器(armcc、iar、gcc for arm)詳細介紹 中關于 ARM 鏈接器的詳細參數。下面是 armlink 的和 map 文件有關的參數介紹
--list=filename
將診斷輸出重定向到指定名字為 filename 文件,即輸出 filename.map 文件。
語法
--list=filename。其中 filename 是用于保存診斷輸出的文件。 文件名可以包含路徑。
使用
--info=topic[,topic,…]
打印有關指定主題的信息。通常與上面的 --list=file 一起使用,將輸出內容寫入指定的文本文件中。
語法
--info=topic[,topic,…]。其中 topic 是以下關鍵字以逗號分隔的組合(也可以每次只用一個關鍵字,然后寫多個 --info=關鍵字):
-  any:對于使用 .ANY 模塊選擇器的節區,列出: - 排序順序
- 放置算法
- The sections that are assigned to each execution region in the order they are assigned by the placement algorithm.
- 有關每個域使用的應急空間和策略的信息。
 在分散加載文件中使用執行域屬性 ANY_SIZE 時,此關鍵字還會顯示其他信息。 
-  architecture:通過列出處理器,FPU和字節順序來歸納鏡像文件架構。 
-  common:列出從鏡像文件中刪除的所有公共節區。使用這個選項意味著:--info=common,totals 
-  compression:提供有關 RW 壓縮過程的更多信息。 
-  debug:Lists all rejected input debug sections that are eliminated from the image as a result of using --remove. Using this option implies --info=debug,totals. 
-  exceptions:Gives information on exception table generation and optimization. 
-  inline:列出由鏈接器內聯的所有函數,并且如果使用了 --inline,則列出內聯的總數。 
-  inputs:列出輸入符號、對象和庫 
-  libraries:Lists the full path name of every library automatically selected for the link stage.You can use this option with --info_lib_prefix to display information about a specific library. 
-  merge:Lists the const strings that are merged by the linker. Each item lists the merged result, the strings being merged, and the associated object files. 
-  sizes:列出鏡像中每個輸入對象和庫成員的代碼和數據(RO數據,RW數據,ZI數據和調試數據)大小。 使用此選項意味著–info=sizes,totals。 
-  stack:列出所有函數的堆棧使用情況 
-  summarysizes:Summarizes the code and data sizes of the image. 
-  summarystack:Summarizes the stack usage of all global symbols. 
-  tailreorder:Lists all the tail calling sections that are moved above their targets, as a result of using --tailreorder. 
-  totals:列出輸入對象和庫的代碼和數據(RO數據,RW數據,ZI數據和調試數據)大小的總和。 
-  unused:列出由于使用 --remove 而從用戶代碼中刪除的所有未使用的部分。 它不會列出從 ARM C 庫加載的任何未使用的部分。 
-  unusedsymbols:Lists all symbols that have been removed by unused section elimination. 
-  veneers:列出鏈接器生成的膠合代碼。 
-  veneercallers:Lists the linker-generated veneers with additional information about the callers to each veneer. Use with --verbose to list each call individually. 
-  veneerpools:Displays information on how the linker has placed veneer pools. 
-  visibility:Lists the symbol visibility information. You can use this option with either --info=inputs or --verbose to enhance the output. 
-  weakrefs:Lists all symbols that are the target of weak references, and whether or not they were defined. 
用法
Keil 配置
??鏈接器列表文件或 Map 文件包含有關鏈接/定位過程的大量信息。在 Keil 中,需要通過 Project -> Options for Target -> Listing 界面如下的配置才可以輸出 map 文件:
 
 其中,各選項的基本功能如下:
- Select Folder for Listings…: 選擇存放清單文件的文件夾
- Page Width: 為清單文件指定每行字符數
- Page Length: 為清單文件指定每頁的行數
- Assembler Listing: 為匯編源文件創建列表文件,對應產生 源文件名.lst 的文件 - Cross Reference: 列出有關符號的交叉引用信息,包括它們的定義位置以及宏的內部和外部的使用位置
 
- C Compiler Listing: 為 C 源文件創建列表文件,對應產生 源文件名.txt 的文件 和 源文件名.lst 的文件
- C Preprocessor Listing: 指示編譯器生成預處理文件。 宏調用將被展開并且注釋將被刪除 對應產生 源文件名.i 的文件
- Linker Listing: 讓鏈接器為目標項目創建映射文件(map 文件)。對應的 armlink 參數為 --list=filename ,如果不選擇則不會生成文件,對應生成 用戶指定名.map 的文件。生成的 MAP 文件如下圖所示:
 
 通常需要配合以下參數一起使用:- Memory Map: 包含一個內存映射,其中包含鏡像中每個加載區,執行區和輸入節的地址和大小,包括調試和鏈接器生成的輸入節。對應的 armlink 參數為 --map
- Callgraph: 以 HTML 格式創建函數的靜態調用圖文件。 調用圖給出了鏡像中所有函數的定義和參考信息。對應的 armlink 參數為 --callgraph 。該項會獨立生成一個 配置的輸出名.htm 的文件。如下圖所示:
 
 其中顯示了詳細的調用關系。最重要的是,其中還有使用的棧的大小!
- Symbols: 列出本地,全局和鏈接器生成的符號以及符號值。對應的 armlink 參數為 --symbols。注意:該參數不包含映射符號,下文我們會詳細介紹!
- Cross Reference: 列出輸入節之間的所有交叉引用。對應的 armlink 參數為 --xref
- Size Info: 給出鏡像中每個輸入對象和庫成員的代碼和數據(RO 數據,RW 數據,ZI 數據和調試數據)的大小的列表。對應的 armlink 參數為 --info sizes
- Totals Info: 提供輸入對象和庫的代碼和數據(RO 數據,RW 數據,ZI 數據和調試數據)大小的總和。對應的 armlink 參數為 --info totals
- Unused Section Info: 列出從鏡像文件中刪除的所有未使用的部分。對應的 armlink 參數為 --info unused
- Veneers Info: 提供鏈接器生成的 Thumb/ARM 膠合代碼的詳細信息。對應的 armlink 參數為 --info veneers
 
map 文件有啥用
map 文件對于分析問題是非常有用的!
map 文件中的符號
?? 在 map 文件中,有很多符號是編譯套件的開發商預定義好的,用戶的符號不能與編譯套件的開發商預定義好的符號沖突。以下內容來自于 ARM 的鏈接器手冊!關于如何導入這些符號,鏈接器的手冊有專門的章節來介紹!
映射符號
??映射符號由編譯器和匯編器生成,以識別文字池邊界處的代碼和數據之間的內聯轉換,以及 ARM 代碼和 Thumb 代碼之間的內聯轉換。例如 ARM/Thumb 交互操作膠合代碼。映射符號有如下這些:
- $a:一系列 ARM 指令的開始
- $t:一系列 Thumb 指令的開始
- $t.x:一系列 ThumbEE 指令的開始
- $d:一系列數據項的開始,如文字池
??armlink 會生成 $d.realdata 映射符號,以告訴 fromelf 該數據是來自非可執行節區。因此,fromelf -z 輸出的代碼和數據大小與 armlink --info sizes 的輸出相同。例如:
Code (inc. data) RO Datax y z在以上的示例中,y 標記為 $d,RO Data 標記為 $d.realdata。如果啟用了 --list_mapping_symbols,則會在 map 文件中有體現,如下圖:
 
 請注意:
鏈接器定義的符號
??當鏈接器創建鏡像文件時,它會創建一些 ARM 預定義的與域或者節相關的符號。這些符號就代表了鏈接器創建創建鏡像的依據。下面我們就重點來了解一下這些符號。
??鏈接器定義了一些 ARM 保留的符號,我們可以在需要時訪問這些符號。這些符號是包含 $$ 字符序列的符號以及所有其他包含 $$ 字符序列的外部名稱。 您可以導入這些符號地址,并將它們作為匯編語言程序的可重定位地址使用,或者將它們作為 C 或 C++ 源代碼中的 extern 符號來引用。
- 如果使用 --strict 編譯器命令行選項,則編譯器不接受包含 $ 的符號名稱。 要重新啟用支持,請在編譯器命令行中包含 --dollar 選項。
- 鏈接器定義的符號只有在代碼引用它們時才會生成。
- 如果存在僅執行(XO)節,則鏈接器定義的符號受以下約束: - 不能對沒有 XO 節的域或者空域定義 XO 連接器定義符號
- 不能對僅包含 RO 節的域定義 XO 連接器定義符號
- 對于僅包含 XO 節的域,不能定義 RO 連接器定義符號
 
引入到 C/C++
可以通過 值引用 或 地址引用 這兩種方式將鏈接器定義的符號導入到的 C 或 C++ 源代碼中來供我們使用:
- 值引用:extern unsigned int symbol_name;
- 地址引用:extern void *symbol_name;
注意,如果將符號聲明為 int 類型的值引用,則必須使用尋址操作符(&)來獲得正確的值,如下例所示:
// Importing a linker-defined symbol extern unsigned int Image$$ZI$$Limit; config.heap_base = (unsigned int) &Image$$ZI$$Limit;//Importing symbols that define a ZI output section extern unsigned int Image$$ZI$$Length; extern char Image$$ZI$$Base[]; memset(Image$$ZI$$Base, 0, (unsigned int)&Image$$Length);引入到 匯編
可以使用指令 IMPORT 將連接器定義的符號引入到 ARM 匯編文件中來供我們使用:
IMPORT |Image$$ZI$$Limit| … zi_limit DCD |Image$$ZI$$Limit|LDR r1, zi_limit域相關的符號
??鏈接器為鏡像文件中的每個域生成不同類型的與域相關的符號,我們可以根據需要訪問這些符號。域相關的符號主要有以下兩種:
- Image$$ 或者 Load$$ 開頭的符號,用于各執行域
- Load$$LR$$ 開頭的符號,用于各加載域
??如果未使用分散加載文件,則會以默認的 region 名稱來生成域相關的符號。鏈接器默認的域名稱如下:
-  ER_XO : 用于僅執行屬性的執行域(如果存在)。 
-  ER_RO : 用于只讀執行域。 
-  ER_RW : 用于可讀寫執行域。 
-  ER_ZI : 用于零初始化的執行域。 可以將這些名稱插入 Image$$ 和 Load$$ 中以獲取所需的地址,例如:Load$$ER_RO$$Base 就是只讀域的基地址。 
??使用分散加載時,連接器將使用分散加載文件中的名稱來生成各種域相關的符號。分散加載文件可以實現以下功能:
- 命名鏡像中的所有執行域,并提供他們的加載和執行地址。
- 定義堆棧和堆。 鏈接器還會生成特殊的棧和堆符號。
執行域符號 Image$$
??鏈接器為鏡像中存在的每個執行域生成符號 Image$$。下表列出了鏈接器為鏡像中存在的每個執行域生成的符號。 初始化 C 庫后,所有符號都指向執行地址。
| Image$$region_name$$Base | 域的執行地址 | 
| Image$$region_name$$Length | 執行域長度(以字節為單位),不包括 ZI 的長度。 | 
| Image$$region_name$$Limit | 超出執行域中非 ZI 部分末尾的字節的地址 | 
| Image$$region_name$$RO$$Base | 域中的輸出節 RO 的執行地址 | 
| Image$$region_name$$RO$$Length | Length of the RO output section in bytes. | 
| Image$$region_name$$RO$$Limit | Address of the byte beyond the end of the RO output section in the execution region. | 
| Image$$region_name$$RW$$Base | Execution address of the RW output section in this region. | 
| Image$$region_name$$RW$$Length | Length of the RW output section in bytes. | 
| Image$$region_name$$RW$$Limit | Address of the byte beyond the end of the RW output section in the execution region. | 
| Image$$region_name$$XO$$Base | Execution address of the XO output section in this region. | 
| Image$$region_name$$XO$$Length | Length of the XO output section in bytes. | 
| Image$$region_name$$XO$$Limit | Address of the byte beyond the end of the XO output section in the execution region. | 
| Image$$region_name$$ZI$$Base | Execution address of the ZI output section in this region. | 
| Image$$region_name$$ZI$$Length | Length of the ZI output section in bytes. | 
| Image$$region_name$$ZI$$Limit | Address of the byte beyond the end of the ZI output section in the execution region. | 
執行域符號 Load$$
??鏈接器為鏡像中存在的每個執行域生成符號 Load$$。下表列出了鏈接器為鏡像中存在的每個 Load$$ 執行域生成的符號。 初始化 C 庫后,所有符號都指向加載地址。
| Load$$region_name$$Base | Load address of the region. | 
| Load$$region_name$$Length | Region length in bytes. | 
| Load$$region_name$$Limit | Address of the byte beyond the end of the execution region. | 
| Load$$region_name$$RO$$Base | Address of the RO output section in this execution region. | 
| Load$$region_name$$RO$$Length | Length of the RO output section in bytes. | 
| Load$$region_name$$RO$$Limit | Address of the byte beyond the end of the RO output section in the execution region. | 
| Load$$region_name$$RW$$Base | Address of the RW output section in this execution region. | 
| Load$$region_name$$RW$$Length | Length of the RW output section in bytes. | 
| Load$$region_name$$RW$$Limit | Address of the byte beyond the end of the RW output section in the execution region. | 
| Load$$region_name$$XO$$Base | Address of the XO output section in this execution region. | 
| Load$$region_name$$XO$$Length | Length of the XO output section in bytes. | 
| Load$$region_name$$XO$$Limit | Address of the byte beyond the end of the XO output section in the execution region. | 
| Load$$region_name$$ZI$$Base | Load address of the ZI output section in this execution region. | 
| Load$$region_name$$ZI$$Length | Load length of the ZI output section in bytes. The Load Length of ZI is zero unless region_name has the ZEROPAD scatter-loading keyword set. If ZEROPAD is set then: Load Length = Image$$region_name$$ZI$$Length | 
| Load$$region_name$$ZI$$Limit | Load address of the byte beyond the end of the ZI output section in the execution region. | 
初始化 C 庫之前,此表中的所有符號均指加載地址。請注意以下事項:
- 這些符號是絕對的,因為相對于節的符號只能有執行地址。
- 這些符號考慮了 RW 壓縮。
- 從 RW 壓縮執行域引用的鏈接器定義的符號必須是在應用 RW 壓縮之前可解析的符號。
- 如果鏈接器檢測到從 RW 壓縮域到依賴于 RW 壓縮的鏈接器定義符號的重定位,則鏈接器將禁用當前域的壓縮。
- Limit 和 Length 值影響寫入文件的任何零初始化數據。 使用 ZEROPAD 分散加載關鍵字時,零初始化數據將寫入文件。
加載域符號 Load$$LR$$
??鏈接器為鏡像中存在的每個加載區生成符號 Load$$LR$$。一個 Load$$LR$$ 加載域可以包含許多執行域,因此沒有單獨的 $$RO 和 $$RW 部分。下表顯示了鏈接器為鏡像中存在的每個 Load$$LR$$ 加載域生成的符號。
| Load$$LR$$load_region_name$$Base | Address of the load region. | 
| Load$$LR$$load_region_name$$Length | Length of the load region. | 
| Load$$LR$$load_region_name$$Limit | Address of the byte beyond the end of the load region. | 
節相關的符號
??與節相關的符號是鏈接器在創建沒有使用分散加載文件的鏡像時生成的符號。鏈接器會為輸出和輸入節生成不同類型的與節相關的符號:
- 鏡像符號(Image symbols)(如果不使用分散加載來創建簡單的鏡像文件)。 簡單的鏡像文件具有多達四個輸出節(XO,RO,RW 和 ZI),用于生成相應的執行域。
- 輸入節符號(Input section symbols) 鏡像中存在的每個輸入節的輸入節符號(Input section symbols)
??鏈接器首先按屬性 RO,RW 或 ZI 對執行域內的節進行排序,然后按名稱排序。 例如,所有 .text 節都放在一個連續的塊中。 具有相同屬性和名稱的連續塊部分稱為合并節。
鏡像符號
??當您不使用分散加載文件來創建簡單鏡像時,鏡像符號將由鏈接器生成。我們常用的 Keil 會默認生成分散加載文件的,所以基本沒有不使用分散加載文件的情況。下表顯示了鏡像符號:
| Image$$RO$$Base | Output | Address of the start of the RO output section. | 
| Image$$RO$$Limit | Output | Address of the first byte beyond the end of the RO output section. | 
| Image$$RW$$Base | Output | Address of the start of the RW output section. | 
| Image$$RW$$Limit | Output | Address of the byte beyond the end of the ZI output section. (The choice of the end of the ZI region rather than the end of the RW region is to maintain compatibility with legacy code.) | 
| Image$$ZI$$Base | Output | Address of the start of the ZI output section. | 
| Image$$ZI$$Limit | Output | Address of the byte beyond the end of the ZI output section. | 
如果存在 XO 節,那么還包含符號 Image$$XO$$Base 和 Image$$XO$$Limit
??如果使用了分散加載文件,則上面這些鏡像符號都將稱為未定義的。 如果在代碼中訪問這些符號中的任何一個,則必須將它們視為弱引用。__user_setup_stackheap() 的標準實現中就使用 Image$$ZI$$Limit 中的值,因此,如果您使用的是分散加載文件,則必須手動設置堆棧和堆。 方法主要有以下兩種:
- 在分散文件中使用下列方法之一 - 定義名為 ARM_LIB_STACK 和 ARM_LIB_HEAP 的單獨的棧和單獨的堆域。
- 定義包含堆棧和堆的組合域,名為 ARM_LIB_STACKHEAP。
 
- 通過重新實現 __user_setup_stackheap() 來設置堆和堆棧邊界。(在我們的項目中的 .s 啟動文件中,是這種方法)
具體見博文 ARM 之十三 armlink(Keil) 分散加載機制詳解 及 分散加載文件的編寫
輸入節符號
鏈接器為鏡像中存在的每個輸入節生成輸入節符號。下表顯示了鏈接器定義的輸入節符號:
| SectionName$$Base | Input | Address of the start of the consolidated section called SectionName. | 
| SectionName$$Length | Input | Length of the consolidated section called SectionName (in bytes). | 
| SectionName$$Limit | Input | Address of the byte beyond the end of the consolidated section called SectionName. | 
??如果在的代碼引用輸入節符號,則表示希望將鏡像中具有相同名稱的所有輸入節都連續放置在鏡像內存映射中。如果分散加載文件不連續地放置輸入節,則鏈接器會發出錯誤。 這是因為在非連續存儲器上將導致 Base 符號和 Limit 符號是不明確的。
map 文件示例
??根據選擇的參數不同,map 文件中的內容肯定是有變化的!下面以 Keil 中全選以上所說的參數后生成的 map 文件為例來進行說明。map 文件中,有如下圖所示的六個大部分:
 
 其中,有些符號需要我們知道其含義:
- .data:這些部分保存有助于程序內存映像的已初始化數據
- .text:本節包含程序的文本或可執行指令
- .bss:本節保存有助于程序內存映像的未初始化數據
- .ARM.exidx*:以.ARM.exidx開頭的節包含部分展開的索引條目
- i.xxxx: i 是 interface 的意思,i.xxxx 就表示 xxx 接口(一般就是指函數名)
更信息的請參考博文 ARM 之一 ELF 文件、鏡像(Image)文件、可執行文件、對象文件 詳解
Section Cross References
??該部分顯示了節區之間的交叉引用,指的是各個源文件生成的模塊之間相互引用的關系,是由 armlink 的參數 --xref 生成的。主要分為以下幾種情況:
-  用戶代碼間接口的相互引用: 例如:stm32f4xx_ll_flash.o(i.LL_FLASH_EraseChip) refers to stm32f4xx_ll_flash.o(i.LL_FLASH_IsActiveFlag_BSY) for LL_FLASH_IsActiveFlag_BSY 表示 stm32f4xx_ll_flash.o 中的函數 LL_FLASH_EraseChip 引用了 stm32f4xx_ll_flash.o 中的 函數 LL_FLASH_IsActiveFlag_BSY,其中的 stm32f4xx_ll_flash.o 是由用戶代碼 stm32f4xx_ll_flash.c 生成的模塊 
-  用戶接口引用用戶數據:主要有兩類,例如:lora.o(i.LoRaRcvProcess) refers to lora.o(.data) for Radio 表示 lora.o 中的接口 LoRaRcvProcess 引用了 lora.o 中的已初始化數據 Radio;lora.o(i.LoRaRcvProcess) refers to lora.o(.bss) for ProcLoRa 表示 lora.o 中的接口 LoRaRcvProcess 引用了 lora.o 中的未初始化默認初始化為0數據 ProcLoRa 
-  用戶數據引用接口: 這個要是函數指針的使用。例如:pnwz.o(.data) refers to pnwzuif.o(i.PNWZ_USER_RespGetLife) for PNWZ_USER_RespGetLife 
-  用戶接口引用 C 庫接口: 例如:pnwzuif.o(i.PNWZ_USER_ReqGetVerHW) refers to strlen.o(.text) for strlen 
-  代碼引用接口: 主要發生于 C 庫之間(ARM C 庫是不開源的,僅提供二進制文件),例如:`` 
-  C 庫接口之間的引用: 主要發生于 C 庫之間(ARM C 庫是不開源的,僅提供二進制文件),例如:__2sprintf.o(.text) refers to _sputc.o(.text) for _sputc 及 __printf_flags_ss_wp.o(.text) refers to __printf_wp.o(i._is_digit) for _is_digit 
-  C庫引用連接器符號: 例如:exit.o(.text) refers to rtexit.o(.ARM.Collect$$rtexit$$00000000) for __rt_exit 
-  符號之間的引用: 例如:retnan.o(x$fpl$retnan) refers to trapv.o(x$fpl$trapveneer) for __fpl_cmpreturn 
??在 ARM 編譯套件中,所有的 C 庫由工具armar來管理,位于 ARM 編譯器目錄 lib 下。使用 armar 即可從指定的庫文件中解壓出 __main.o 等模塊。至于如何操作,參見博文ARM 之九 Cortex-M/R 內核啟動過程 / 程序啟動流程(基于ARMCC、Keil)。
Removing Unused input sections from the image
??這部分列出了鏈接器移除的我們源碼中實際未使用的數據和函數。其中包含移除數據的大小。例如Removing flash.o(.rrx_text), (6 bytes). 表示移除 flash.o 中的 6 字節的代碼;Removing virtualuart.o(i.VirtualUartBufClear), (92 bytes). 表示移除 virtualuart.o 中的函數 VirtualUartBufClear,共 92 字節。
 ??需要注意的是,被移除的函數在調試時將無法進行調試。如果不注意,在調試時很容易造成困擾。如下圖所示:
 
Image Symbol Table
??鏡像符號映射表。就是每個符號(這里的符號可以指模塊、變量、函數)實際的地址等信息。分為以下三部分:
Mapping Symbols
這一部分只有在指定了連接器參數 --list_mapping_symbols 才會有!下面是一個對比:
 
 各列的含義如下:
- Sym: 連接器定義的各種符號
- Value: 符號表示的地址
- Execution Region: 符號所在的執行域
Local Symbols
不知道 ARMCC 是怎么分的 Local 和 Global。各列含義如下:
- Symbol Name
- Value
- Ov Type
- Size
- Object(Section)
Global Symbols
不知道 ARMCC 是怎么分的 Local 和 Global。
Memory Map of the image
??該映射包含鏡像文件中每個加載域,執行域和輸入節(包括連接器生成的輸入節)的地址和大小。由連接器 armlink 通過參數 --map 生成。這部分與 分散加載文件(Scatter File) 有密切的關系。如果使用 Keil,則在 Keil 的配置界面中有如下配置:
 
 這里的設置就是對應的分散加載文件中的內容。如果我們在鏈接器的配置頁面不選擇 Use Memory Layout from Taget Dialog,則需要如上圖自己指定一個分散加載文件。其實,如果選擇 Use Memory Layout from Taget Dialog,Keil 會根據我們左邊的配置自行生成一個分散加載文件來給鏈接器使用(這個文件就在我們的編譯輸出指定的目錄中)。上圖的示例就是沒有使用 Keil 默認,而是通過自己指定的分散加載文件生成鏡像文件。
??以上的 Keil 配置,最終是通過鏈接器的參數 --scatter=filename 來讓鏈接器使用該文件的。分散加載文件一次性描述了我們的鏡像文件怎么布局。了解鏈接器的應該知道,鏈接器還有一些獨立使用的和鏡像文件生成有關的參數:--first, --last, --partial, --reloc, --ro_base, --ropi,--rosplit, --rw_base, --rwpi, --split, --startup, --xo_base, and --zi_base.,如果使用了 --scatter=filename,則以上參數就不可再用了! Keil 就是直接使用的 --scatter=filename。(默認下,Keil 根據配置界面的配置,會成一個分散加載文件)。
接下來以一個示例來看看 map 文件中關于鏡像內存映射的內容,如下圖:
 
-  Image Entry point: 這個是鏡像的入口點。就是鏡像在被執行時,開始的位置(地址)。 
-  Load Region LR_IROM1: 表示一個叫做 LR_IROM1 加載域 - Base:0x0800c000 這個表示 LR_IROM1 的基地址
- Size:0x0000c6ec 表示 LR_IROM1 的大小為 0x0000c6ec,單位字節。
- Max:0x00014000 表示 LR_IROM1 的最大大小,單位字節。這個是由分散加載文件中指定,如果不顯示指出,默認為 0xFFFFFFFF
- ABSOLUTE 表示地址為絕對地址
- COMPRESSED[0x0000b9a4] 表示壓縮之后的大小為 0x0000b9a4,單位字節。
 
-  Execution Region ER_IROM1、Execution Region ER_IROM2、Execution Region ER_IROM3: 這是 3 個執行域,分別叫做 ER_IROM1、ER_IROM2、ER_IROM3。下面以 ER_IROM1 為例,來說說每個列(字段)的含義: - Exec base:0x0800c000 這個表示 ER_IROM1 執行時的基地址
- Load base:0x0800c000 該執行域對應的加載域地址是 0x0800c000
- Size: 0x000001c8 表示該執行域的大小為 0x000001c8,單位字節
- Max: 0x00014000 表示該執行域的最大大小為 0x00014000,單位字節。這個是由分散加載文件中指定,如果不顯示指出,默認為 0xFFFFFFFF
- ABSOLUTE 表示地址為絕對地址
 為什么有 3 個?因為我本身使用了自己寫的分散加載文件。手動指定了三個執行域。我的分散加載文件如下 ; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; *************************************************************LR_IROM1 0x0800C000 0x00014000 { ; load region size_regionER_IROM1 0x0800C000 0x00014000 { ; load address = execution address*.o (RESET, +First) ; 中斷向量表}ER_IROM2 + 0 { ; 應用程序信息*.o (SECTION_APP_INFO, +First)}ER_IROM3 + 0 { ; 初始化相關代碼+其他代碼*(InRoot$$Sections) ; 初始化相關.ANY (+RO) ; 其他所有代碼}RW_IRAM1 0x20000000 0x00008000 { ; 內存.ANY (+RW +ZI)} }??在這三個執行域中,有很多類型為 PAD 的行,并且這些行沒有節名字也沒有所屬的模塊。這些其實是一些鏈接器自己添加的對齊。除了對齊沒有其他作用。關于對齊本文之前的章節有介紹。除了 PAD 之外,剩下的就全是 Code 了。 
-  Execution Region RW_IRAM1: 一個名為 RW_IRAM1 的執行域。括號中的內容含義與上面的相同 
 
 這個就對應我們的內存部分,存放我們的代碼中用到的各種變量數據(常量數據在以上的 ER_IROM 中)。同樣,該部分也有些對齊,除此之外全部是 Data、Zero、 HEAP(堆)、STACK(棧)。還有一點就是,.bss 數據沒有加載域地址
最后在說明一下每一列(字段)的具體含義如下::
| 節的基地址 | 節的大小 | 類型 | 屬性 | 索引 | 節的名字 | 對象(節所屬的文件模塊) | 
Image component sizes
該部分列出了組成鏡像的各部分內容的大小等詳細信息。主要有三部分組成:用戶文件大小信息、庫文件大小信息、匯總信息。
 
 每一行表示一個模塊,其中各列的具體含義如下:
- Code (inc. data): 這對應兩列數據,分別表示代碼占用的字節數和代碼中內聯數據占用的字節數。inc. data 是內聯數據(inline data)的縮寫。 內聯數據包括文字池和短字符串等。例如,上圖中第一行:表示 bh1750fvi.o 中代碼有 700 字節,其中內聯數據 40 字節。
- RO Data: 模塊中 RO 數據占用的字節數。這是除去 Code(inc. data)列中 inc. data 數據外的只讀數據的字節數。 例如,我們定義的 const 數組!
- RW Data: 模塊中 RW 數據占用的字節數。
- ZI Data: 模塊中 ZI 數據占用的字節數。
- Debug: 模塊中調試數據占用的字節數。例如,調試用的輸入節以及符號和字符串表。
- Object Name: 對象文件的名字。
- 特殊行的含義如下: - Object Totals: 它所在的行就是對各列數據的匯總。
- (incl. Generated): armlink 在生成鏡像文件時,可能會產生一些額外數據(interworking veneers, and input sections such as region tables)。如果存在這些額外數據,那么他們就位于該行中顯示。
- Library Totals: 顯示當前用戶代碼使用的庫文件中的各成員占用的字節數。
- (incl. Padding): armlink 會插入填充以強制部分對齊。 如果 Object Totals 行中包含此類數據,則會在相關的(incl. Padding)行中顯示出 armlink 添加的對齊占用的字節數。 同樣,如果 Library Totals 行中包含此類數據,則會在其關聯的行中顯示。
- Grand Totals: 鏡像文件中所有模塊的每一列數據的總大小。
- ELF Image Totals: 如果使用 RW 數據壓縮(默認值)來優化 ROM 大小,則最終鏡像的大小會發生變化,這會反映在 --info 的輸出中。 比較 Grand Totals 和 ELF Image Totals 下的字節數,以查看壓縮效果。
- ROM Totals: 顯示包含鏡像所需的 ROM 的最小大小。 這其中不包括未存儲在 ROM 中的 ZI 數據和調試信息。
 
用戶文件大小信息
第一部分就是用戶源碼各模塊的大小信息!如下圖所示:
 
庫文件大小信息
第二部分用戶源碼中使用的各 C 庫模塊的大小信息以及使用的 C 庫文件名!如下圖所示:
 
 這部分中,除了列出了庫文件的獨立單元模塊的大小,還列出了我們的源碼中實際使用的庫文件。這個會根據我們源碼中引用的庫函數的不同而變化。
匯總信息
主要就是各部分數據的匯總大小,如下圖所示
 
 其中最下面的三行數據是匯總的再匯總,以方便我們的使用
- Total RO Size: 就是我們的可執行程序中常量數據(代碼和只讀數據)的大小。
- Total RW Size: 就是我們的可執行程序中需要占用的內存的大小。
- Total ROM Size: 就是我們的可執行程序本身的大小。這個大小就等于我們的可執行文件的大小。
??需要特殊注意的是,armlink 輸出的是 .axf 文件,這個文件中包含調試信息,并不是我們需要使用的可執行文件,我們使用 fromelf 工具從中提取的文件才是真正的可執行文件。這里的匯總大小指定是實際使用的可執行文件的大小。關于這部分,參考博文ARM 之一 ELF文件、鏡像(Image)文件、可執行文件、對象文件 詳解。
ARM 庫文件
我們可以在 ARM 編譯套件的目錄下找到這兩個文件,路徑如下圖所示:
 
 下面我們使用 ARM 編譯套件中相應的工具來看看具體文件。關于編譯套件的詳細使用說明可以參考博文《ARM 之 主流編譯器(armcc、iar、gcc for arm)詳細介紹》。具體使用的工具就是armar.exe,這是 ARM 的庫文件管理工具。
從中我們可以看到有__main.o等文件,接下來我們可以使用armar -x命令將c_w.l解壓出以上全部文件,然后使用fromelf來查看__main.o的詳細信息,這里就不一一嘗試了!
參考
總結
以上是生活随笔為你收集整理的ARM 之十 ARMCC(Keil) map 文件(映射文件)详解的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: FatFs 之一 R0.13c版源码目录
- 下一篇: 版本控制系统 之一 概念、分类、常见版本
