注意:因为没有找到 CSDN 的 Markdown 编辑器显示两对连续的 $$ 的方法,因此本文中的两个连续的 $ $ 之间有个空格。实际应该把空格去掉。例如 $$ 内容 $$
(不以代码形式时)显示异常,只能改为 $ $ 内容 $ $来显示。
map 文件对应的中文名应该是映射文件,用来展示(映射)项目构建的链接阶段的细节。通常包含程序的全局符号、交叉引用和内存映射等等信息。目前,大多数编译套件(主要是其中的链接器)都可以生成 Map 文件。常见的 gcc、VC、IAR 都可以输出 map 文件。
在 ARM 的官方文档中,并没有找到有关于 ARM 内核的 map 文件的介绍文档。不过倒是有个 C51 生成的 map 文件的说明文档:Listing (MAP) File。但是 C51 的 map 文件和 ARM 核的 map 文件差别比较大,也没啥参考价值!
map 文件就是用来展示链接器工作过程的东西。想要了解 map 文件还需要对 ARM ELF 文件有一定的了解(可以参考博文ARM 之一 ELF文件、镜像(Image)文件、可执行文件、对象文件 详解)以及需要对于编译过程有一定的了解。下图显示了链接器在软件开发过程中的角色。链接器接受几种类型的文件作为输入,包括对象文件、命令文件、库和部分链接的文件。链接器创建一个可执行对象模块。
map 文件是由编译套件中的链接器产生的。ARM 的编译套件中链接器为armlink
,map 文件中的各信息均由armlink
的各参数(–info topic、–map、–symbols等)控制输出(由 --list=filename 文件名输出到文件)。关于 ARM 编译套件的详细信息,参考博文ARM 之七 主流编译器(armcc、iar、gcc for arm)详细介绍 中关于 ARM 链接器的详细参数。下面是 armlink
的和 map 文件有关的参数介绍
将诊断输出重定向到文件。
--list=filename
。其中 filename 是用于保存诊断输出的文件。 文件名可以包含路径。
将参数 --info, - map, - symbol, - verbose, - xref, - xreffrom 和 --xrefto的诊断信息输出重定向到文件。
输出诊断信息时将创建指定的文件。 如果已存在同名文件,则会覆盖该文件。 但是,如果未输出诊断,则不会创建文件。 在这种情况下,具有相同名称的任何现有文件的内容保持不变。如果指定了 filename 而没有路径,则会在输出目录中创建它,即输出镜像的写入目录。
打印有关指定主题的信息。您可以使用 --list=file
参数将输出内容写入文本文件。
--info=topic[,topic,…]
。其中 topic 是以下主题关键字的逗号分隔列表:
any:对于使用 .ANY 模块选择器的节区,列出:
在分散加载文件中使用执行区域属性 ANY_SIZE 时,此关键字还会显示其他信息。
architecture:通过列出处理器,FPU和字节顺序来归纳镜像架构。
common:Lists all common sections that are eliminated from the image. Using this option implies --info=common,totals.
compression:Gives extra information about the RW compression process.
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:Lists all functions that are inlined by the linker, and the total number of inlines if --inline is used.
inputs:Lists the input symbols, objects and libraries.
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:Lists the stack usage of all functions.
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.
--info=sizes,totals
的输出始终包含输入对象和库的总计中的填充值。如果使用 RW 数据压缩(默认值),或者使用 --datacompressor=id
选项指定了压缩器,则 --info=sizes,totals
的输出包括 Grand Totals 下的条目以反映真实镜像大小。列表中的主题关键字之间不允许有空格。
如果使用 Keil,则可以直接在 Keil 的设置界面来选择,如下图:
其中,各选项的基本功能如下:
armlink
参数为--list=filename
,如果不选择则不会生成文件。
armlink
参数为--map
armlink
参数为--callgraph
。该项会独立生成一个配置的输出名.htm
的文件。如下图所示:armlink
参数为--symbols
armlink
参数为 --xref
armlink
参数为 --info sizes
armlink
参数为 --info totals
armlink
参数为 --info unused
armlink
参数为 --info veneers
以上的选项其实就是选择链接器 armlink
的各参数!下文中均以使用的具体参数来介绍。
map 文件对于分析问题是非常有用的!
在 map 文件中,有很多符号是编译套件的开发商预定义好的,用户的符号不能与编译套件的开发商预定义好的符号冲突。以下内容来自于 ARM 的链接器手册!关于如何导入这些符号,链接器的手册有专门的章节来介绍!
映射符号由编译器和汇编器生成,以识别文字池边界处的代码和数据之间的内联转换,以及 ARM 代码和 Thumb 代码之间的内联转换。例如 ARM/Thumb 交互操作胶合代码。其必须由 armlink
的参数 --list_mapping_symbols
和 --no_list_mapping_symbols
分别来控制显示与不显示。在默认情况下为 --no_list_mapping_symbols
,即不显示这部分符号。映射符号有如下这些:
需要注意与参数 --symbols
和 --no_symbols
控制的符号的区别!这里的符号由 armlink
生成 $d.realdata
映射符号,以便与 fromelf
通信该数据来自非可执行节区。 因此,fromelf -z
输出的代码和数据大小与armlink --info sizes
的输出相同,例如:
Code (inc. data) RO Data
x y z
在以上的示例中,y 标记为 $d,RO Data 标记为 $d.realdata。如果启用了该参数,则会在 map 文件中有体现,如下图:
以字符 $v 开头的符号是与 VFP 相关的映射符号,在使用 VFP 构建目标时可能会输出。 避免在源代码中使用以 $v 开头的符号。请注意,使用fromelf --elf --strip=localsymbols
命令修改可执行镜像会从镜像中删除所有映射符号。
链接器定义了 ARM 保留的一些符号。包含字符序列 $ $ 的符号,以及包含序列 $ $ 的所有其他外部名称,是 ARM 保留的名称。您可以导入这些符号的地址,并将它们用作汇编语言程序的可重定位地址,或者将它们作为 C 或 C++ 源代码中的外部符号引用。
--strict
编译器命令行选项,则编译器不接受包含 $ 的符号名称。 要重新启用支持,请在编译器命令行中包含 --dollar
选项。链接器生成各种类型的 Region 相关符号,您可以根据需要访问这些符号。如果您使用的是分散加载文件,则会为分散加载文件中的每个区域生成这些符号。如果未使用分散加载文件,则会以默认的 region 名称来生成符号。 也就是说,region 名称是固定的,并且提供相同类型的符号。
链接器为镜像中存在的每个执行区域生成 Image$ $ 符号。下表显示了链接器为镜像中存在的每个执行区域生成的符号。 初始化 C 库后,所有符号都指向执行地址。
Symbol | Description |
---|---|
Image$ $region_name$ $Base | Execution address of the region. |
Image$ $region_name$ $Length | Execution region length in bytes excluding ZI length. |
Image$ $region_name$ $Limit | Address of the byte beyond the end of the non-ZI part of the execution region. |
Image$ $region_name$ $RO$ $Base | Execution address of the RO output section in this region. |
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$ $region_name 符号仅适用于执行区域。Load$ $LR$ $load_region_name 符号仅适用于加载区域。下表显示了链接器为镜像中存在的每个 Load$ $ 执行区域生成的符号。 初始化 C 库后,所有符号都指向加载地址。
Symbol | Description |
---|---|
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 库之前引用加载地址。请注意以下事项:
链接器为镜像中存在的每个加载区生成 Load$ $LR$ $ 符号。一个 Load$ $LR$ $ 加载区域可以包含许多执行区域,因此没有单独的 $ $RO 和 $ $RW 部分。Load$ $region_name 符号仅适用于执行区域。Load$ $LR$ $load_region_name 符号仅适用于加载区域。下表显示了链接器为镜像中存在的每个 Load$ $LR$ $ 加载区域生成的符号。
Symbol | Description |
---|---|
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. |
链接时未使用分散加载时,链接器使用默认区域名称值。 如果您不使用分散加载,则链接器使用以下区域名称值:
您可以将这些名称插入以下符号以获取所需的地址:
例如 Load$ $ER_RO$ $Base.
使用分散加载时,分散文件中的名称将用于链接器定义的符号中。分散加载文件可以实现以下功能:
与 Section 相关的符号是链接器在创建没有分散加载的镜像时生成的符号。链接器为输出和输入 Section 生成不同类型的 Section 相关的符号。
链接器首先按属性 RO,RW 或 ZI 对执行区域内的节进行排序,然后按名称排序。 因此,例如,所有 .text 节都放在一个连续的块中。 具有相同属性和名称的连续块部分称为合并节。
当您不使用分散加载文件来创建简单镜像时,链接器会生成镜像符号。下表显示了镜像符号:
Symbol | Section type | Description |
---|---|---|
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. |
如果您使用分散加载文件,则镜像符号未定义。 如果您的代码访问这些符号中的任何一个,则必须将它们视为弱引用。__user_setup_stackheap()
的标准实现使用 Image$ $ZI$ $Limit 中的值。 因此,如果您使用的是分散加载文件,则必须手动放置堆栈和堆。 你也可以这样做:
输入节符号由链接器为镜像中存在的每个输入节生成。下表显示了输入节符号:
Symbol | Section type | Description |
---|---|---|
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. |
如果您的代码引用输入节符号,则假定您希望镜像中具有相同名称的所有输入节都连续放置在镜像内存映射中。如果分散加载文件不连续地放置输入节,则链接器会发出错误。 这是因为在非连续存储器上使用基本符号和限制符号是不明确的。
根据选择的参数不同,map 文件中的内容肯定是有变化的!下面以 Keil 中全选以上所说的参数后生成的 map 文件为例来进行说明。map 文件中,有如下图所示的五个大部分:
其中,每一部分都是对应上面的配置中的一个或者多个选项,也就是对应链接器armlink
的一个或者多个参数
该部分显示了节区之间的交叉引用,指的是各个源文件生成的模块之间相互引用的关系。交叉引用中可以分为三大部分。
这部分主要就是用户自己实现的代码之间的引用关系。例如gprs.o(i.GPRSGetBytes) refers to uart.o(i.UartGetData) for UartGetData
表示 gprs.o
中的函数GPRSGetBytes
引用了 uart.o
中的函数 UartGetData
。其中的 gprs.o
是由用户代码 gprs.c
生成的模块; uart.o
使用用户源码 uart.c
生成的模块。
这部分主要就是用户自己实现的代码中调用 C 库函数时的引用关系。C 库的代码一般都是编译套件以二进制文件的形式提供的,用户看不到源码。例如 upcomm.o(i.UpCommFrameFilter) refers to memcmp.o(.text) for memcmp
表示 upcomm.o
中的函数UpCommFrameFilter
引用了 memcmp.o
中的函数 memcmp
。其中,memcmp
为 C 库函数。再例如 startup_stm32f411xe.o(.text) refers to __main.o(!!!main) for __main
中的 __main
为程序的启动入口,其也位于 C 库中。
在 ARM 编译套件中,所有的 C 库由工具armar
来管理,位于 ARM 编译器目录 lib
下。使用 armar
即可从指定的库文件中解压出 __main.o
等模块。至于如何操作,参见博文ARM 之九 Cortex-M/R 内核启动过程 / 程序启动流程(基于ARMCC、Keil)。
C 库函数之间也有互相的引用关系。用户使用的 C 库函数之间的引用关系也会显示在该部分。例如 dflt_clz.o(x$fpl$dflt) refers (Special) to usenofp.o(x$fpl$usenofp) for __I$use$fp
,其中所有的函数都是 C 库中的。同样可以使用armar
解压库文件来导出符号表查看各接口。
这部分列出了链接器移除的我们源码中实际未使用的数据和函数。其中包含移除数据的大小。例如Removing flash.o(.rrx_text), (6 bytes).
表示移除flash.o
中的 6 字节的数据;Removing virtualuart.o(i.VirtualUartBufClear), (92 bytes).
表示移除 virtualuart.o
中的函数 VirtualUartBufClear
,共 92 字节。
需要注意的是,被移除的函数在调试时将无法进行调试。如果不注意,在调试时很容易造成困扰。如下图所示:
符号映射表。就是每个符号(这里的符号可以指模块、变量、函数)实际的地址等信息。
不知道 ARMCC 是怎么分的 Local 和 Global。
不知道 ARMCC 是怎么分的 Local 和 Global。
这部分给出了镜像文件的布局。这部分与分散加载文件(Scatter File) 有密切的关系。如果使用 Keil,在 Keil 的配置界面中有如下配置:
这里的设置就是对应的 map 文件中的内容。如果我们在链接器的配置页面不选择 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 文件中的内容,如下图:
0x08010218 0x00000008 Code RO 4985 * !!!main c_w.l(__main.o)
这一行。Base Addr | Size | Type | Attr | Idx | E Section Name | Object |
---|---|---|---|---|---|---|
节的基地址 | 节的大小 | 类型 | 属性 | 索引 | 节的名字 | 对象(节所属的文件模块) |
; *************************************************************
; *** Scatter-Loading Description File generated by ZCShou ***
; *************************************************************
LR_IROM1 0x08010000 0x00030000 { ; load region size_region
ER_IROM1 0x08010000 0x00030000 { ; 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 0x00020000 { ; 内存
.ANY (+RW +ZI)
}
}
在这三个执行域中,有很多类型为 PAD 的行,并且这些行没有节名字也没有所属的模块。这些其实是一些链接器自己添加的对齐。除了对齐没有其他作用。关于对齐本文之前的章节有介绍。除了 PAD 之外,剩下的就全是 Code 了。该部分列出了组成镜像的各部分内容的大小等详细信息。
armlink
在生成镜像文件时,可能会产生一些额外数据(interworking veneers, and input sections such as region tables)。如果存在这些额外数据,那么他们就位于改行中显示。armlink
会插入填充以强制部分对齐。 如果 Object Totals 行包含此类数据,则会在相关的(incl. Padding)行中显示。 同样,如果 Library Totals 行包含此类数据,则会在其关联的行中显示。第二部分用户源码中使用的各 C 库模块的大小信息以及使用的 C 库文件名!如下图所示:
这部分中,除了列出了库文件的独立单元模块的大小,还列出了我们的源码中实际使用的库文件。这个会根据我们源码中引用的库函数的不同而变化。
主要就是各部分数据的汇总大小,如下图所示
其中最下面的三行数据是汇总的再汇总,以方便我们的使用
需要特殊注意的是,armlink
输出的是 .axf 文件,这个文件中包含调试信息,并不是我们需要使用的可执行文件,我们使用 fromelf
工具从中提取的文件才是真正的可执行文件。这里的汇总大小指定是实际使用的可执行文件的大小。关于这部分,参考博文ARM 之一 ELF文件、镜像(Image)文件、可执行文件、对象文件 详解。