ARM 之十 ARMCC(Keil) map 文件(映射文件)详解
在看這篇文章之前
map 文件是什么
??map 文件對(duì)應(yīng)的中文名應(yīng)該是映射文件,用來展示(映射)項(xiàng)目構(gòu)建的鏈接階段的細(xì)節(jié)。通常包含程序的全局符號(hào)、交叉引用和內(nèi)存映射等等信息。目前,大多數(shù)編譯套件(主要是其中的鏈接器)都可以生成 Map 文件。常見的 GCC、VC、IAR 都可以輸出 map 文件(PC平臺(tái)的 map 文件與 ARM 平臺(tái)的差別較大)。
??在 ARM 的官方文檔中,并沒有找到有關(guān)于 ARM 內(nèi)核的 map 文件的介紹文檔。不過倒是有個(gè) C51 生成的 map 文件的說明文檔:Listing (MAP) File。但是 C51 的 map 文件和 ARM 核的 map 文件差別比較大,也沒啥參考價(jià)值!
??map 文件就是用來展示鏈接器工作過程的東西。想要了解 map 文件還需要對(duì) ARM ELF 文件有一定的了解(可以參考博文ARM 之一 ELF文件、鏡像(Image)文件、可執(zhí)行文件、對(duì)象文件 詳解)以及需要對(duì)于編譯過程有一定的了解。下圖顯示了鏈接器在軟件開發(fā)過程中的角色。鏈接器接受幾種類型的文件作為輸入,包括對(duì)象文件、命令文件、庫和部分鏈接的文件,創(chuàng)建一個(gè)可執(zhí)行對(duì)象模塊。
map 文件從哪來
??map 文件是由編譯套件中的鏈接器產(chǎn)生的。ARM 的編譯套件中鏈接器為armlink,map 文件中的各信息均由 armlink 的各參數(shù)(–info topic、–map、–symbols等)控制輸出(由 --list=filename 文件名輸出到文件)。關(guān)于 ARM 編譯套件的詳細(xì)信息,參考博文ARM 之七 主流編譯器(armcc、iar、gcc for arm)詳細(xì)介紹 中關(guān)于 ARM 鏈接器的詳細(xì)參數(shù)。下面是 armlink 的和 map 文件有關(guān)的參數(shù)介紹
--list=filename
將診斷輸出重定向到指定名字為 filename 文件,即輸出 filename.map 文件。
語法
--list=filename。其中 filename 是用于保存診斷輸出的文件。 文件名可以包含路徑。
使用
--info=topic[,topic,…]
打印有關(guān)指定主題的信息。通常與上面的 --list=file 一起使用,將輸出內(nèi)容寫入指定的文本文件中。
語法
--info=topic[,topic,…]。其中 topic 是以下關(guān)鍵字以逗號(hào)分隔的組合(也可以每次只用一個(gè)關(guān)鍵字,然后寫多個(gè) --info=關(guān)鍵字):
-
any:對(duì)于使用 .ANY 模塊選擇器的節(jié)區(qū),列出:
- 排序順序
- 放置算法
- The sections that are assigned to each execution region in the order they are assigned by the placement algorithm.
- 有關(guān)每個(gè)域使用的應(yīng)急空間和策略的信息。
在分散加載文件中使用執(zhí)行域?qū)傩?ANY_SIZE 時(shí),此關(guān)鍵字還會(huì)顯示其他信息。
-
architecture:通過列出處理器,FPU和字節(jié)順序來歸納鏡像文件架構(gòu)。
-
common:列出從鏡像文件中刪除的所有公共節(jié)區(qū)。使用這個(gè)選項(xiàng)意味著:--info=common,totals
-
compression:提供有關(guān) 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:列出由鏈接器內(nèi)聯(lián)的所有函數(shù),并且如果使用了 --inline,則列出內(nèi)聯(lián)的總數(shù)。
-
inputs:列出輸入符號(hào)、對(duì)象和庫
-
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:列出鏡像中每個(gè)輸入對(duì)象和庫成員的代碼和數(shù)據(jù)(RO數(shù)據(jù),RW數(shù)據(jù),ZI數(shù)據(jù)和調(diào)試數(shù)據(jù))大小。 使用此選項(xiàng)意味著–info=sizes,totals。
-
stack:列出所有函數(shù)的堆棧使用情況
-
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:列出輸入對(duì)象和庫的代碼和數(shù)據(jù)(RO數(shù)據(jù),RW數(shù)據(jù),ZI數(shù)據(jù)和調(diào)試數(shù)據(jù))大小的總和。
-
unused:列出由于使用 --remove 而從用戶代碼中刪除的所有未使用的部分。 它不會(huì)列出從 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 文件包含有關(guān)鏈接/定位過程的大量信息。在 Keil 中,需要通過 Project -> Options for Target -> Listing 界面如下的配置才可以輸出 map 文件:
其中,各選項(xiàng)的基本功能如下:
- Select Folder for Listings…: 選擇存放清單文件的文件夾
- Page Width: 為清單文件指定每行字符數(shù)
- Page Length: 為清單文件指定每頁的行數(shù)
- Assembler Listing: 為匯編源文件創(chuàng)建列表文件,對(duì)應(yīng)產(chǎn)生 源文件名.lst 的文件
- Cross Reference: 列出有關(guān)符號(hào)的交叉引用信息,包括它們的定義位置以及宏的內(nèi)部和外部的使用位置
- C Compiler Listing: 為 C 源文件創(chuàng)建列表文件,對(duì)應(yīng)產(chǎn)生 源文件名.txt 的文件 和 源文件名.lst 的文件
- C Preprocessor Listing: 指示編譯器生成預(yù)處理文件。 宏調(diào)用將被展開并且注釋將被刪除 對(duì)應(yīng)產(chǎn)生 源文件名.i 的文件
- Linker Listing: 讓鏈接器為目標(biāo)項(xiàng)目創(chuàng)建映射文件(map 文件)。對(duì)應(yīng)的 armlink 參數(shù)為 --list=filename ,如果不選擇則不會(huì)生成文件,對(duì)應(yīng)生成 用戶指定名.map 的文件。生成的 MAP 文件如下圖所示:
通常需要配合以下參數(shù)一起使用:- Memory Map: 包含一個(gè)內(nèi)存映射,其中包含鏡像中每個(gè)加載區(qū),執(zhí)行區(qū)和輸入節(jié)的地址和大小,包括調(diào)試和鏈接器生成的輸入節(jié)。對(duì)應(yīng)的 armlink 參數(shù)為 --map
- Callgraph: 以 HTML 格式創(chuàng)建函數(shù)的靜態(tài)調(diào)用圖文件。 調(diào)用圖給出了鏡像中所有函數(shù)的定義和參考信息。對(duì)應(yīng)的 armlink 參數(shù)為 --callgraph 。該項(xiàng)會(huì)獨(dú)立生成一個(gè) 配置的輸出名.htm 的文件。如下圖所示:
其中顯示了詳細(xì)的調(diào)用關(guān)系。最重要的是,其中還有使用的棧的大小! - Symbols: 列出本地,全局和鏈接器生成的符號(hào)以及符號(hào)值。對(duì)應(yīng)的 armlink 參數(shù)為 --symbols。注意:該參數(shù)不包含映射符號(hào),下文我們會(huì)詳細(xì)介紹!
- Cross Reference: 列出輸入節(jié)之間的所有交叉引用。對(duì)應(yīng)的 armlink 參數(shù)為 --xref
- Size Info: 給出鏡像中每個(gè)輸入對(duì)象和庫成員的代碼和數(shù)據(jù)(RO 數(shù)據(jù),RW 數(shù)據(jù),ZI 數(shù)據(jù)和調(diào)試數(shù)據(jù))的大小的列表。對(duì)應(yīng)的 armlink 參數(shù)為 --info sizes
- Totals Info: 提供輸入對(duì)象和庫的代碼和數(shù)據(jù)(RO 數(shù)據(jù),RW 數(shù)據(jù),ZI 數(shù)據(jù)和調(diào)試數(shù)據(jù))大小的總和。對(duì)應(yīng)的 armlink 參數(shù)為 --info totals
- Unused Section Info: 列出從鏡像文件中刪除的所有未使用的部分。對(duì)應(yīng)的 armlink 參數(shù)為 --info unused
- Veneers Info: 提供鏈接器生成的 Thumb/ARM 膠合代碼的詳細(xì)信息。對(duì)應(yīng)的 armlink 參數(shù)為 --info veneers
map 文件有啥用
map 文件對(duì)于分析問題是非常有用的!
map 文件中的符號(hào)
?? 在 map 文件中,有很多符號(hào)是編譯套件的開發(fā)商預(yù)定義好的,用戶的符號(hào)不能與編譯套件的開發(fā)商預(yù)定義好的符號(hào)沖突。以下內(nèi)容來自于 ARM 的鏈接器手冊(cè)!關(guān)于如何導(dǎo)入這些符號(hào),鏈接器的手冊(cè)有專門的章節(jié)來介紹!
映射符號(hào)
??映射符號(hào)由編譯器和匯編器生成,以識(shí)別文字池邊界處的代碼和數(shù)據(jù)之間的內(nèi)聯(lián)轉(zhuǎn)換,以及 ARM 代碼和 Thumb 代碼之間的內(nèi)聯(lián)轉(zhuǎn)換。例如 ARM/Thumb 交互操作膠合代碼。映射符號(hào)有如下這些:
- $a:一系列 ARM 指令的開始
- $t:一系列 Thumb 指令的開始
- $t.x:一系列 ThumbEE 指令的開始
- $d:一系列數(shù)據(jù)項(xiàng)的開始,如文字池
??armlink 會(huì)生成 $d.realdata 映射符號(hào),以告訴 fromelf 該數(shù)據(jù)是來自非可執(zhí)行節(jié)區(qū)。因此,fromelf -z 輸出的代碼和數(shù)據(jù)大小與 armlink --info sizes 的輸出相同。例如:
Code (inc. data) RO Datax y z在以上的示例中,y 標(biāo)記為 $d,RO Data 標(biāo)記為 $d.realdata。如果啟用了 --list_mapping_symbols,則會(huì)在 map 文件中有體現(xiàn),如下圖:
請(qǐng)注意:
鏈接器定義的符號(hào)
??當(dāng)鏈接器創(chuàng)建鏡像文件時(shí),它會(huì)創(chuàng)建一些 ARM 預(yù)定義的與域或者節(jié)相關(guān)的符號(hào)。這些符號(hào)就代表了鏈接器創(chuàng)建創(chuàng)建鏡像的依據(jù)。下面我們就重點(diǎn)來了解一下這些符號(hào)。
??鏈接器定義了一些 ARM 保留的符號(hào),我們可以在需要時(shí)訪問這些符號(hào)。這些符號(hào)是包含 $$ 字符序列的符號(hào)以及所有其他包含 $$ 字符序列的外部名稱。 您可以導(dǎo)入這些符號(hào)地址,并將它們作為匯編語言程序的可重定位地址使用,或者將它們作為 C 或 C++ 源代碼中的 extern 符號(hào)來引用。
- 如果使用 --strict 編譯器命令行選項(xiàng),則編譯器不接受包含 $ 的符號(hào)名稱。 要重新啟用支持,請(qǐng)?jiān)诰幾g器命令行中包含 --dollar 選項(xiàng)。
- 鏈接器定義的符號(hào)只有在代碼引用它們時(shí)才會(huì)生成。
- 如果存在僅執(zhí)行(XO)節(jié),則鏈接器定義的符號(hào)受以下約束:
- 不能對(duì)沒有 XO 節(jié)的域或者空域定義 XO 連接器定義符號(hào)
- 不能對(duì)僅包含 RO 節(jié)的域定義 XO 連接器定義符號(hào)
- 對(duì)于僅包含 XO 節(jié)的域,不能定義 RO 連接器定義符號(hào)
引入到 C/C++
可以通過 值引用 或 地址引用 這兩種方式將鏈接器定義的符號(hào)導(dǎo)入到的 C 或 C++ 源代碼中來供我們使用:
- 值引用:extern unsigned int symbol_name;
- 地址引用:extern void *symbol_name;
注意,如果將符號(hào)聲明為 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 將連接器定義的符號(hào)引入到 ARM 匯編文件中來供我們使用:
IMPORT |Image$$ZI$$Limit| … zi_limit DCD |Image$$ZI$$Limit|LDR r1, zi_limit域相關(guān)的符號(hào)
??鏈接器為鏡像文件中的每個(gè)域生成不同類型的與域相關(guān)的符號(hào),我們可以根據(jù)需要訪問這些符號(hào)。域相關(guān)的符號(hào)主要有以下兩種:
- Image$$ 或者 Load$$ 開頭的符號(hào),用于各執(zhí)行域
- Load$$LR$$ 開頭的符號(hào),用于各加載域
??如果未使用分散加載文件,則會(huì)以默認(rèn)的 region 名稱來生成域相關(guān)的符號(hào)。鏈接器默認(rèn)的域名稱如下:
-
ER_XO : 用于僅執(zhí)行屬性的執(zhí)行域(如果存在)。
-
ER_RO : 用于只讀執(zhí)行域。
-
ER_RW : 用于可讀寫執(zhí)行域。
-
ER_ZI : 用于零初始化的執(zhí)行域。
可以將這些名稱插入 Image$$ 和 Load$$ 中以獲取所需的地址,例如:Load$$ER_RO$$Base 就是只讀域的基地址。
??使用分散加載時(shí),連接器將使用分散加載文件中的名稱來生成各種域相關(guān)的符號(hào)。分散加載文件可以實(shí)現(xiàn)以下功能:
- 命名鏡像中的所有執(zhí)行域,并提供他們的加載和執(zhí)行地址。
- 定義堆棧和堆。 鏈接器還會(huì)生成特殊的棧和堆符號(hào)。
執(zhí)行域符號(hào) Image$$
??鏈接器為鏡像中存在的每個(gè)執(zhí)行域生成符號(hào) Image$$。下表列出了鏈接器為鏡像中存在的每個(gè)執(zhí)行域生成的符號(hào)。 初始化 C 庫后,所有符號(hào)都指向執(zhí)行地址。
| Image$$region_name$$Base | 域的執(zhí)行地址 |
| Image$$region_name$$Length | 執(zhí)行域長(zhǎng)度(以字節(jié)為單位),不包括 ZI 的長(zhǎng)度。 |
| Image$$region_name$$Limit | 超出執(zhí)行域中非 ZI 部分末尾的字節(jié)的地址 |
| Image$$region_name$$RO$$Base | 域中的輸出節(jié) RO 的執(zhí)行地址 |
| 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. |
執(zhí)行域符號(hào) Load$$
??鏈接器為鏡像中存在的每個(gè)執(zhí)行域生成符號(hào) Load$$。下表列出了鏈接器為鏡像中存在的每個(gè) Load$$ 執(zhí)行域生成的符號(hào)。 初始化 C 庫后,所有符號(hào)都指向加載地址。
| 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 庫之前,此表中的所有符號(hào)均指加載地址。請(qǐng)注意以下事項(xiàng):
- 這些符號(hào)是絕對(duì)的,因?yàn)橄鄬?duì)于節(jié)的符號(hào)只能有執(zhí)行地址。
- 這些符號(hào)考慮了 RW 壓縮。
- 從 RW 壓縮執(zhí)行域引用的鏈接器定義的符號(hào)必須是在應(yīng)用 RW 壓縮之前可解析的符號(hào)。
- 如果鏈接器檢測(cè)到從 RW 壓縮域到依賴于 RW 壓縮的鏈接器定義符號(hào)的重定位,則鏈接器將禁用當(dāng)前域的壓縮。
- Limit 和 Length 值影響寫入文件的任何零初始化數(shù)據(jù)。 使用 ZEROPAD 分散加載關(guān)鍵字時(shí),零初始化數(shù)據(jù)將寫入文件。
加載域符號(hào) Load$$LR$$
??鏈接器為鏡像中存在的每個(gè)加載區(qū)生成符號(hào) Load$$LR$$。一個(gè) Load$$LR$$ 加載域可以包含許多執(zhí)行域,因此沒有單獨(dú)的 $$RO 和 $$RW 部分。下表顯示了鏈接器為鏡像中存在的每個(gè) Load$$LR$$ 加載域生成的符號(hào)。
| 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. |
節(jié)相關(guān)的符號(hào)
??與節(jié)相關(guān)的符號(hào)是鏈接器在創(chuàng)建沒有使用分散加載文件的鏡像時(shí)生成的符號(hào)。鏈接器會(huì)為輸出和輸入節(jié)生成不同類型的與節(jié)相關(guān)的符號(hào):
- 鏡像符號(hào)(Image symbols)(如果不使用分散加載來創(chuàng)建簡(jiǎn)單的鏡像文件)。 簡(jiǎn)單的鏡像文件具有多達(dá)四個(gè)輸出節(jié)(XO,RO,RW 和 ZI),用于生成相應(yīng)的執(zhí)行域。
- 輸入節(jié)符號(hào)(Input section symbols) 鏡像中存在的每個(gè)輸入節(jié)的輸入節(jié)符號(hào)(Input section symbols)
??鏈接器首先按屬性 RO,RW 或 ZI 對(duì)執(zhí)行域內(nèi)的節(jié)進(jìn)行排序,然后按名稱排序。 例如,所有 .text 節(jié)都放在一個(gè)連續(xù)的塊中。 具有相同屬性和名稱的連續(xù)塊部分稱為合并節(jié)。
鏡像符號(hào)
??當(dāng)您不使用分散加載文件來創(chuàng)建簡(jiǎn)單鏡像時(shí),鏡像符號(hào)將由鏈接器生成。我們常用的 Keil 會(huì)默認(rèn)生成分散加載文件的,所以基本沒有不使用分散加載文件的情況。下表顯示了鏡像符號(hào):
| 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 節(jié),那么還包含符號(hào) Image$$XO$$Base 和 Image$$XO$$Limit
??如果使用了分散加載文件,則上面這些鏡像符號(hào)都將稱為未定義的。 如果在代碼中訪問這些符號(hào)中的任何一個(gè),則必須將它們視為弱引用。__user_setup_stackheap() 的標(biāo)準(zhǔn)實(shí)現(xiàn)中就使用 Image$$ZI$$Limit 中的值,因此,如果您使用的是分散加載文件,則必須手動(dòng)設(shè)置堆棧和堆。 方法主要有以下兩種:
- 在分散文件中使用下列方法之一
- 定義名為 ARM_LIB_STACK 和 ARM_LIB_HEAP 的單獨(dú)的棧和單獨(dú)的堆域。
- 定義包含堆棧和堆的組合域,名為 ARM_LIB_STACKHEAP。
- 通過重新實(shí)現(xiàn) __user_setup_stackheap() 來設(shè)置堆和堆棧邊界。(在我們的項(xiàng)目中的 .s 啟動(dòng)文件中,是這種方法)
具體見博文 ARM 之十三 armlink(Keil) 分散加載機(jī)制詳解 及 分散加載文件的編寫
輸入節(jié)符號(hào)
鏈接器為鏡像中存在的每個(gè)輸入節(jié)生成輸入節(jié)符號(hào)。下表顯示了鏈接器定義的輸入節(jié)符號(hào):
| 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. |
??如果在的代碼引用輸入節(jié)符號(hào),則表示希望將鏡像中具有相同名稱的所有輸入節(jié)都連續(xù)放置在鏡像內(nèi)存映射中。如果分散加載文件不連續(xù)地放置輸入節(jié),則鏈接器會(huì)發(fā)出錯(cuò)誤。 這是因?yàn)樵诜沁B續(xù)存儲(chǔ)器上將導(dǎo)致 Base 符號(hào)和 Limit 符號(hào)是不明確的。
map 文件示例
??根據(jù)選擇的參數(shù)不同,map 文件中的內(nèi)容肯定是有變化的!下面以 Keil 中全選以上所說的參數(shù)后生成的 map 文件為例來進(jìn)行說明。map 文件中,有如下圖所示的六個(gè)大部分:
其中,有些符號(hào)需要我們知道其含義:
- .data:這些部分保存有助于程序內(nèi)存映像的已初始化數(shù)據(jù)
- .text:本節(jié)包含程序的文本或可執(zhí)行指令
- .bss:本節(jié)保存有助于程序內(nèi)存映像的未初始化數(shù)據(jù)
- .ARM.exidx*:以.ARM.exidx開頭的節(jié)包含部分展開的索引條目
- i.xxxx: i 是 interface 的意思,i.xxxx 就表示 xxx 接口(一般就是指函數(shù)名)
更信息的請(qǐng)參考博文 ARM 之一 ELF 文件、鏡像(Image)文件、可執(zhí)行文件、對(duì)象文件 詳解
Section Cross References
??該部分顯示了節(jié)區(qū)之間的交叉引用,指的是各個(gè)源文件生成的模塊之間相互引用的關(guān)系,是由 armlink 的參數(shù) --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 中的函數(shù) LL_FLASH_EraseChip 引用了 stm32f4xx_ll_flash.o 中的 函數(shù) LL_FLASH_IsActiveFlag_BSY,其中的 stm32f4xx_ll_flash.o 是由用戶代碼 stm32f4xx_ll_flash.c 生成的模塊
-
用戶接口引用用戶數(shù)據(jù):主要有兩類,例如:lora.o(i.LoRaRcvProcess) refers to lora.o(.data) for Radio 表示 lora.o 中的接口 LoRaRcvProcess 引用了 lora.o 中的已初始化數(shù)據(jù) Radio;lora.o(i.LoRaRcvProcess) refers to lora.o(.bss) for ProcLoRa 表示 lora.o 中的接口 LoRaRcvProcess 引用了 lora.o 中的未初始化默認(rèn)初始化為0數(shù)據(jù) ProcLoRa
-
用戶數(shù)據(jù)引用接口: 這個(gè)要是函數(shù)指針的使用。例如: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
-
代碼引用接口: 主要發(fā)生于 C 庫之間(ARM C 庫是不開源的,僅提供二進(jìn)制文件),例如:``
-
C 庫接口之間的引用: 主要發(fā)生于 C 庫之間(ARM C 庫是不開源的,僅提供二進(jìn)制文件),例如:__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庫引用連接器符號(hào): 例如:exit.o(.text) refers to rtexit.o(.ARM.Collect$$rtexit$$00000000) for __rt_exit
-
符號(hào)之間的引用: 例如: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 內(nèi)核啟動(dòng)過程 / 程序啟動(dòng)流程(基于ARMCC、Keil)。
Removing Unused input sections from the image
??這部分列出了鏈接器移除的我們?cè)创a中實(shí)際未使用的數(shù)據(jù)和函數(shù)。其中包含移除數(shù)據(jù)的大小。例如Removing flash.o(.rrx_text), (6 bytes). 表示移除 flash.o 中的 6 字節(jié)的代碼;Removing virtualuart.o(i.VirtualUartBufClear), (92 bytes). 表示移除 virtualuart.o 中的函數(shù) VirtualUartBufClear,共 92 字節(jié)。
??需要注意的是,被移除的函數(shù)在調(diào)試時(shí)將無法進(jìn)行調(diào)試。如果不注意,在調(diào)試時(shí)很容易造成困擾。如下圖所示:
Image Symbol Table
??鏡像符號(hào)映射表。就是每個(gè)符號(hào)(這里的符號(hào)可以指模塊、變量、函數(shù))實(shí)際的地址等信息。分為以下三部分:
Mapping Symbols
這一部分只有在指定了連接器參數(shù) --list_mapping_symbols 才會(huì)有!下面是一個(gè)對(duì)比:
各列的含義如下:
- Sym: 連接器定義的各種符號(hào)
- Value: 符號(hào)表示的地址
- Execution Region: 符號(hào)所在的執(zhí)行域
Local Symbols
不知道 ARMCC 是怎么分的 Local 和 Global。各列含義如下:
- Symbol Name
- Value
- Ov Type
- Size
- Object(Section)
Global Symbols
不知道 ARMCC 是怎么分的 Local 和 Global。
Memory Map of the image
??該映射包含鏡像文件中每個(gè)加載域,執(zhí)行域和輸入節(jié)(包括連接器生成的輸入節(jié))的地址和大小。由連接器 armlink 通過參數(shù) --map 生成。這部分與 分散加載文件(Scatter File) 有密切的關(guān)系。如果使用 Keil,則在 Keil 的配置界面中有如下配置:
這里的設(shè)置就是對(duì)應(yīng)的分散加載文件中的內(nèi)容。如果我們?cè)阪溄悠鞯呐渲庙撁娌贿x擇 Use Memory Layout from Taget Dialog,則需要如上圖自己指定一個(gè)分散加載文件。其實(shí),如果選擇 Use Memory Layout from Taget Dialog,Keil 會(huì)根據(jù)我們左邊的配置自行生成一個(gè)分散加載文件來給鏈接器使用(這個(gè)文件就在我們的編譯輸出指定的目錄中)。上圖的示例就是沒有使用 Keil 默認(rèn),而是通過自己指定的分散加載文件生成鏡像文件。
??以上的 Keil 配置,最終是通過鏈接器的參數(shù) --scatter=filename 來讓鏈接器使用該文件的。分散加載文件一次性描述了我們的鏡像文件怎么布局。了解鏈接器的應(yīng)該知道,鏈接器還有一些獨(dú)立使用的和鏡像文件生成有關(guān)的參數(shù):--first, --last, --partial, --reloc, --ro_base, --ropi,--rosplit, --rw_base, --rwpi, --split, --startup, --xo_base, and --zi_base.,如果使用了 --scatter=filename,則以上參數(shù)就不可再用了! Keil 就是直接使用的 --scatter=filename。(默認(rèn)下,Keil 根據(jù)配置界面的配置,會(huì)成一個(gè)分散加載文件)。
接下來以一個(gè)示例來看看 map 文件中關(guān)于鏡像內(nèi)存映射的內(nèi)容,如下圖:
-
Image Entry point: 這個(gè)是鏡像的入口點(diǎn)。就是鏡像在被執(zhí)行時(shí),開始的位置(地址)。
-
Load Region LR_IROM1: 表示一個(gè)叫做 LR_IROM1 加載域
- Base:0x0800c000 這個(gè)表示 LR_IROM1 的基地址
- Size:0x0000c6ec 表示 LR_IROM1 的大小為 0x0000c6ec,單位字節(jié)。
- Max:0x00014000 表示 LR_IROM1 的最大大小,單位字節(jié)。這個(gè)是由分散加載文件中指定,如果不顯示指出,默認(rèn)為 0xFFFFFFFF
- ABSOLUTE 表示地址為絕對(duì)地址
- COMPRESSED[0x0000b9a4] 表示壓縮之后的大小為 0x0000b9a4,單位字節(jié)。
-
Execution Region ER_IROM1、Execution Region ER_IROM2、Execution Region ER_IROM3: 這是 3 個(gè)執(zhí)行域,分別叫做 ER_IROM1、ER_IROM2、ER_IROM3。下面以 ER_IROM1 為例,來說說每個(gè)列(字段)的含義:
- Exec base:0x0800c000 這個(gè)表示 ER_IROM1 執(zhí)行時(shí)的基地址
- Load base:0x0800c000 該執(zhí)行域?qū)?yīng)的加載域地址是 0x0800c000
- Size: 0x000001c8 表示該執(zhí)行域的大小為 0x000001c8,單位字節(jié)
- Max: 0x00014000 表示該執(zhí)行域的最大大小為 0x00014000,單位字節(jié)。這個(gè)是由分散加載文件中指定,如果不顯示指出,默認(rèn)為 0xFFFFFFFF
- ABSOLUTE 表示地址為絕對(duì)地址
為什么有 3 個(gè)?因?yàn)槲冶旧硎褂昧俗约簩懙姆稚⒓虞d文件。手動(dòng)指定了三個(gè)執(zhí)行域。我的分散加載文件如下
; ************************************************************* ; *** 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 { ; 應(yīng)用程序信息*.o (SECTION_APP_INFO, +First)}ER_IROM3 + 0 { ; 初始化相關(guān)代碼+其他代碼*(InRoot$$Sections) ; 初始化相關(guān).ANY (+RO) ; 其他所有代碼}RW_IRAM1 0x20000000 0x00008000 { ; 內(nèi)存.ANY (+RW +ZI)} }??在這三個(gè)執(zhí)行域中,有很多類型為 PAD 的行,并且這些行沒有節(jié)名字也沒有所屬的模塊。這些其實(shí)是一些鏈接器自己添加的對(duì)齊。除了對(duì)齊沒有其他作用。關(guān)于對(duì)齊本文之前的章節(jié)有介紹。除了 PAD 之外,剩下的就全是 Code 了。
-
Execution Region RW_IRAM1: 一個(gè)名為 RW_IRAM1 的執(zhí)行域。括號(hào)中的內(nèi)容含義與上面的相同
這個(gè)就對(duì)應(yīng)我們的內(nèi)存部分,存放我們的代碼中用到的各種變量數(shù)據(jù)(常量數(shù)據(jù)在以上的 ER_IROM 中)。同樣,該部分也有些對(duì)齊,除此之外全部是 Data、Zero、 HEAP(堆)、STACK(棧)。還有一點(diǎn)就是,.bss 數(shù)據(jù)沒有加載域地址
最后在說明一下每一列(字段)的具體含義如下::
| 節(jié)的基地址 | 節(jié)的大小 | 類型 | 屬性 | 索引 | 節(jié)的名字 | 對(duì)象(節(jié)所屬的文件模塊) |
Image component sizes
該部分列出了組成鏡像的各部分內(nèi)容的大小等詳細(xì)信息。主要有三部分組成:用戶文件大小信息、庫文件大小信息、匯總信息。
每一行表示一個(gè)模塊,其中各列的具體含義如下:
- Code (inc. data): 這對(duì)應(yīng)兩列數(shù)據(jù),分別表示代碼占用的字節(jié)數(shù)和代碼中內(nèi)聯(lián)數(shù)據(jù)占用的字節(jié)數(shù)。inc. data 是內(nèi)聯(lián)數(shù)據(jù)(inline data)的縮寫。 內(nèi)聯(lián)數(shù)據(jù)包括文字池和短字符串等。例如,上圖中第一行:表示 bh1750fvi.o 中代碼有 700 字節(jié),其中內(nèi)聯(lián)數(shù)據(jù) 40 字節(jié)。
- RO Data: 模塊中 RO 數(shù)據(jù)占用的字節(jié)數(shù)。這是除去 Code(inc. data)列中 inc. data 數(shù)據(jù)外的只讀數(shù)據(jù)的字節(jié)數(shù)。 例如,我們定義的 const 數(shù)組!
- RW Data: 模塊中 RW 數(shù)據(jù)占用的字節(jié)數(shù)。
- ZI Data: 模塊中 ZI 數(shù)據(jù)占用的字節(jié)數(shù)。
- Debug: 模塊中調(diào)試數(shù)據(jù)占用的字節(jié)數(shù)。例如,調(diào)試用的輸入節(jié)以及符號(hào)和字符串表。
- Object Name: 對(duì)象文件的名字。
- 特殊行的含義如下:
- Object Totals: 它所在的行就是對(duì)各列數(shù)據(jù)的匯總。
- (incl. Generated): armlink 在生成鏡像文件時(shí),可能會(huì)產(chǎn)生一些額外數(shù)據(jù)(interworking veneers, and input sections such as region tables)。如果存在這些額外數(shù)據(jù),那么他們就位于該行中顯示。
- Library Totals: 顯示當(dāng)前用戶代碼使用的庫文件中的各成員占用的字節(jié)數(shù)。
- (incl. Padding): armlink 會(huì)插入填充以強(qiáng)制部分對(duì)齊。 如果 Object Totals 行中包含此類數(shù)據(jù),則會(huì)在相關(guān)的(incl. Padding)行中顯示出 armlink 添加的對(duì)齊占用的字節(jié)數(shù)。 同樣,如果 Library Totals 行中包含此類數(shù)據(jù),則會(huì)在其關(guān)聯(lián)的行中顯示。
- Grand Totals: 鏡像文件中所有模塊的每一列數(shù)據(jù)的總大小。
- ELF Image Totals: 如果使用 RW 數(shù)據(jù)壓縮(默認(rèn)值)來優(yōu)化 ROM 大小,則最終鏡像的大小會(huì)發(fā)生變化,這會(huì)反映在 --info 的輸出中。 比較 Grand Totals 和 ELF Image Totals 下的字節(jié)數(shù),以查看壓縮效果。
- ROM Totals: 顯示包含鏡像所需的 ROM 的最小大小。 這其中不包括未存儲(chǔ)在 ROM 中的 ZI 數(shù)據(jù)和調(diào)試信息。
用戶文件大小信息
第一部分就是用戶源碼各模塊的大小信息!如下圖所示:
庫文件大小信息
第二部分用戶源碼中使用的各 C 庫模塊的大小信息以及使用的 C 庫文件名!如下圖所示:
這部分中,除了列出了庫文件的獨(dú)立單元模塊的大小,還列出了我們的源碼中實(shí)際使用的庫文件。這個(gè)會(huì)根據(jù)我們?cè)创a中引用的庫函數(shù)的不同而變化。
匯總信息
主要就是各部分?jǐn)?shù)據(jù)的匯總大小,如下圖所示
其中最下面的三行數(shù)據(jù)是匯總的再匯總,以方便我們的使用
- Total RO Size: 就是我們的可執(zhí)行程序中常量數(shù)據(jù)(代碼和只讀數(shù)據(jù))的大小。
- Total RW Size: 就是我們的可執(zhí)行程序中需要占用的內(nèi)存的大小。
- Total ROM Size: 就是我們的可執(zhí)行程序本身的大小。這個(gè)大小就等于我們的可執(zhí)行文件的大小。
??需要特殊注意的是,armlink 輸出的是 .axf 文件,這個(gè)文件中包含調(diào)試信息,并不是我們需要使用的可執(zhí)行文件,我們使用 fromelf 工具從中提取的文件才是真正的可執(zhí)行文件。這里的匯總大小指定是實(shí)際使用的可執(zhí)行文件的大小。關(guān)于這部分,參考博文ARM 之一 ELF文件、鏡像(Image)文件、可執(zhí)行文件、對(duì)象文件 詳解。
ARM 庫文件
我們可以在 ARM 編譯套件的目錄下找到這兩個(gè)文件,路徑如下圖所示:
下面我們使用 ARM 編譯套件中相應(yīng)的工具來看看具體文件。關(guān)于編譯套件的詳細(xì)使用說明可以參考博文《ARM 之 主流編譯器(armcc、iar、gcc for arm)詳細(xì)介紹》。具體使用的工具就是armar.exe,這是 ARM 的庫文件管理工具。
從中我們可以看到有__main.o等文件,接下來我們可以使用armar -x命令將c_w.l解壓出以上全部文件,然后使用fromelf來查看__main.o的詳細(xì)信息,這里就不一一嘗試了!
參考
總結(jié)
以上是生活随笔為你收集整理的ARM 之十 ARMCC(Keil) map 文件(映射文件)详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FatFs 之一 R0.13c版源码目录
- 下一篇: Git 之一 起源、安装、配置