在看这篇文章之前
需要对 ARM ELF 文件有一定的了解。了解什么是域(Region)、节(Section,也称为节区)、段(Segment)、镜像(Image)、镜像文件(Image File)等概念
需要对编译、连接过程有一定的了解
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, --map, --symbol, --verbose, --xref, --xreffrom 和 --xrefto 的诊断信息输出重定向到文件。
输出诊断信息时将创建指定的文件。 如果已存在同名文件,则会覆盖该文件。 但是,如果未输出诊断,则不会创建文件。 在这种情况下,具有相同名称的任何现有文件的内容保持不变。如果指定了 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.
用法
--info=sizes,totals 的输出始终包含输入对象和库的总计中的填充值。
如果使用 RW 数据压缩(默认值),或者使用 --datacompressor=id 选项指定了压缩器,则 --info=sizes,totals 的输出包括 Grand Totals 下的条目以反映真实镜像大小。
列表中的关键字之间不允许有空格。
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:一系列数据项的开始,如文字池
文字池 是代码段中存放常量数据的区域。因为没有一条指令可以生成一个 4 字节的常量,因此编译器将这些常量放到文字池中,然后生成从文字池加载这些常量的代码。
ARM/Thumb交互(ARM/Thumb interworking)是指对汇编语言和 C/C++ 语言的 ARM 和 Thumb 代码进行连接的方法,它进行两种状态(ARM 和 Thumb)间的切换。
胶合代码(Veneer):在进行 ARM/Thumb 交互时,有时需使用额外的代码,这些代码被称为 胶合代码(Veneer)。
AAPCS 定义了 ARM 和 Thumb 过程调用的标准。
armlink 会生成 $d.realdata 映射符号,以告诉 fromelf 该数据是来自非可执行节区。因此,fromelf -z 输出的代码和数据大小与 armlink --info sizes 的输出相同。例如:
Code (inc. data) RO Data x y z 在以上的示例中,y 标记为 $d,RO Data 标记为 $d.realdata。如果启用了 --list_mapping_symbols,则会在 map 文件中有体现,如下图:
请注意:
以字符 $v 开头的符号是与 VFP 相关的映射符号,在使用 VFP 构建目标时可能会输出。 避免在源代码中使用以 $v 开头的符号。
使用 fromelf --elf --strip=localsymbols 命令修改可执行镜像会从镜像中删除所有映射符号。
其必须由 armlink 的参数 --list_mapping_symbols 和 --no_list_mapping_symbols 分别来控制显示与不显示。在默认情况下为 --no_list_mapping_symbols,即不显示这部分符号。
链接器定义的符号
当链接器创建镜像文件时,它会创建一些 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 symbolextern unsigned int Image$$ZI$$Limit;config.heap_base = (unsigned int) &Image$$ZI$$Limit;//Importing symbols that define a ZI output sectionextern 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 就是只读域的基地址。
使用分散加载时,连接器将使用分散加载文件中的名称来生成各种域相关的符号。分散加载文件可以实现以下功能:
命名镜像中的所有执行域,并提供他们的加载和执行地址。
定义堆栈和堆。 链接器还会生成特殊的栈和堆符号。
镜像的 ZI 输出节不是静态创建的,而是在运行时自动动态创建的。 因此,ZI 输出节没有加载地址符号。
符号 Load$$region_name 仅适用于执行域。Load$$LR$$load_region_name 符号仅适用于加载域。
执行域符号 Image$$
链接器为镜像中存在的每个执行域生成符号 Image$$。下表列出了链接器为镜像中存在的每个执行域生成的符号。 初始化 C 库后,所有符号都指向执行地址。
[tr]SymbolDescription[/tr]Image$$region_name$$Base域的执行地址
Image$$region_name$$Length执行域长度(以字节为单位),不包括 ZI 的长度。
Image$$region_name$$Limit超出执行域中非 ZI 部分末尾的字节的地址
Image$$region_name$$RO$$Base域中的输出节 RO 的执行地址
Image$$region_name$$RO$$LengthLength of the RO output section in bytes.
Image$$region_name$$RO$$LimitAddress of the byte beyond the end of the RO output section in the execution region.
Image$$region_name$$RW$$BaseExecution address of the RW output section in this region.
Image$$region_name$$RW$$LengthLength of the RW output section in bytes.
Image$$region_name$$RW$$LimitAddress of the byte beyond the end of the RW output section in the execution region.
Image$$region_name$$XO$$BaseExecution address of the XO output section in this region.
Image$$region_name$$XO$$LengthLength of the XO output section in bytes.
Image$$region_name$$XO$$LimitAddress of the byte beyond the end of the XO output section in the execution region.
Image$$region_name$$ZI$$BaseExecution address of the ZI output section in this region.
Image$$region_name$$ZI$$LengthLength of the ZI output section in bytes.
Image$$region_name$$ZI$$LimitAddress of the byte beyond the end of the ZI output section in the execution region.
执行域符号 Load$$
链接器为镜像中存在的每个执行域生成符号 Load$$。下表列出了链接器为镜像中存在的每个 Load$$ 执行域生成的符号。 初始化 C 库后,所有符号都指向加载地址。
[tr]SymbolDescription[/tr]Load$$region_name$$BaseLoad address of the region.
Load$$region_name$$LengthRegion length in bytes.
Load$$region_name$$LimitAddress of the byte beyond the end of the execution region.
Load$$region_name$$RO$$BaseAddress of the RO output section in this execution region.
Load$$region_name$$RO$$LengthLength of the RO output section in bytes.
Load$$region_name$$RO$$LimitAddress of the byte beyond the end of the RO output section in the execution region.
Load$$region_name$$RW$$BaseAddress of the RW output section in this execution region.
Load$$region_name$$RW$$LengthLength of the RW output section in bytes.
Load$$region_name$$RW$$LimitAddress of the byte beyond the end of the RW output section in the execution region.
Load$$region_name$$XO$$BaseAddress of the XO output section in this execution region.
Load$$region_name$$XO$$LengthLength of the XO output section in bytes.
Load$$region_name$$XO$$LimitAddress of the byte beyond the end of the XO output section in the execution region.
Load$$region_name$$ZI$$BaseLoad address of the ZI output section in this execution region.
Load$$region_name$$ZI$$LengthLoad 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$$LimitLoad 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$$ 加载域生成的符号。
[tr]SymbolDescription[/tr]Load$$LR$$load_region_name$$BaseAddress of the load region.
Load$$LR$$load_region_name$$LengthLength of the load region.
Load$$LR$$load_region_name$$LimitAddress 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 节都放在一个连续的块中。 具有相同属性和名称的连续块部分称为合并节。
ARM 建议优先使用与域相关的符号,而不是与节相关的符号。
镜像符号
当您不使用分散加载文件来创建简单镜像时,镜像符号将由链接器生成。我们常用的 Keil 会默认生成分散加载文件的,所以基本没有不使用分散加载文件的情况。下表显示了镜像符号:
[tr]SymbolSection typeDescription[/tr]Image$$RO$$BaseOutputAddress of the start of the RO output section.
Image$$RO$$LimitOutputAddress of the first byte beyond the end of the RO output section.
Image$$RW$$BaseOutputAddress of the start of the RW output section.
Image$$RW$$LimitOutputAddress 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$$BaseOutputAddress of the start of the ZI output section.
Image$$ZI$$LimitOutputAddress 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) 分散加载机制详解 及 分散加载文件的编写
输入节符号
链接器为镜像中存在的每个输入节生成输入节符号。下表显示了链接器定义的输入节符号:
[tr]SymbolSection typeDescription[/tr]SectionName$$BaseInputAddress of the start of the consolidated section called SectionName.
SectionName$$LengthInputLength of the consolidated section called SectionName (in bytes)。
SectionName$$LimitInputAddress 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_region ER_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 数据没有加载域地址
最后在说明一下每一列(字段)的具体含义如下::
[tr]Base AddrSizeTypeAttrIdxE Section NameObject[/tr]节的基地址节的大小类型属性索引节的名字对象(节所属的文件模块)
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 的库文件管理工具。
D:ARMARM_Compiler_5.06u4》armar --zt 。/lib/armlib/c_w.l Code RO Data RW Data ZI Data Debug Object Name 48 0 0 0 84 version.o 58 0 0 0 68 __dczerorl.o 90 0 0 0 68 __dczerorl2.o 100 0 0 0 68 __dclz77c.o 0 0 28 0 0 dc.o 102 12 0 0 240 sys_io.o 18 0 0 0 76 sys_tmpnam.o 32 0 4 0 84 sys_clock.o 18 0 0 0 76 sys_time.o 12 0 0 0 68 sys_exit.o 4 8 0 0 0 __rtentry3.o 6 8 0 0 0 __rtentry4.o 8 24 0 0 0 __rtentry5.o 8 24 0 0 0 __rtentry6.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 104 0 0 0 84 __printf.o 6 0 0 0 0 _printf_a.o 0 0 0 0 0 _printf_percent.o 4 0 0 0 0 _printf_percent_end.o 6 0 0 0 0 _printf_lli.o 6 0 0 0 0 _printf_lld.o 6 0 0 0 0 _printf_llu.o 52 0 0 0 80 vsscanf.o 60 0 0 0 84 swscanf.o 52 0 0 0 80 vswscanf.o 52 0 0 0 80 __ARM_vsscanf.o 32 0 0 0 84 scanfn.o 24 0 0 0 84 fscanfn.o 60 0 0 0 84 sscanfn.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 16 0 0 0 80 vfwscanf.o 28 0 0 0 68 _chval.o 884 0 0 0 100 _scanf.o 342 0 0 0 100 _scanf_longlong.o 332 0 0 0 96 _scanf_int.o 224 0 0 0 96 _scanf_str.o 1202 0 0 0 216 scanf_fp.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 8 0 0 0 68 feof.o 8 0 0 0 68 ferror.o 100 0 0 0 120 fflush.o 26 0 0 0 136 fgetc.o 32 0 0 0 80 fgetpos.o 74 0 0 0 84 fgets.o 570 0 0 0 132 filbuf.o 470 0 0 0 88 fl***uf.o 28 0 0 0 136 fputc.o 34 0 0 0 80 fputs.o 58 0 0 0 88 fread.o 248 0 0 0 84 fseek.o 40 0 0 0 80 fsetpos.o 66 0 0 0 76 ftell.o 50 0 0 0 84 fwrite.o 4 0 0 0 68 getc.o 12 0 0 0 68 getchar.o 56 0 0 0 80 gets.o 60 0 0 0 80 perror.o 4 0 0 0 68 putc.o 12 0 0 0 68 putchar.o 312 0 0 0 112 initio.o 236 0 0 0 128 fopen.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 18 0 0 0 80 exit.o 22 0 0 0 80 abort.o 4 0 0 0 68 _Exit.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 20 0 0 0 68 strchr.o 32 0 0 0 80 strcspn.o 36 0 0 0 76 strncat.o 30 0 0 0 76 strpbrk.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 14 0 0 0 80 ctime.o 4 0 0 0 68 gmtime.o 12 0 0 44 68 localtime.o 428 0 0 0 116 mktime.o 864 0 0 0 184 strftime.o 0 12 0 0 0 _monlen.o 0 0 0 0 0 asctime_r.o 0 0 0 0 0 localtime_r.o 104 0 0 0 88 asctime_internal.o 184 0 0 0 80 localtime_internal.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 8 0 0 0 68 __main.o 52 0 0 0 68 __scatter.o 76 0 0 0 68 __scatters.o 26 0 0 0 68 __scatter_copy.o 28 0 0 0 68 __scatter_zi.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 34 0 0 0 76 prim_new.o 2 8 0 0 68 init.o 36 8 0 0 80 init_aeabi.o 44 8 0 0 76 arm_relocate.o 36 8 0 0 80 preinit_aeabi.o 4 0 0 0 68 _memcpy.o 8 0 0 0 84 _urdwr4.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 112 0 0 0 104 fputwc.o 36 0 0 0 80 fputws.o 200 0 0 0 104 fgetwc.o 82 0 0 0 84 fgetws.o 4 0 0 0 68 getwc.o 12 0 0 0 68 getwchar.o 73526 40563 112 1686 76484 TOTALENTRY at offset 1 in section !!!main of __main.o 从中我们可以看到有__main.o等文件,接下来我们可以使用armar -x命令将c_w.l解压出以上全部文件,然后使用fromelf来查看__main.o的详细信息,这里就不一一尝试了!
在看这篇文章之前
需要对 ARM ELF 文件有一定的了解。了解什么是域(Region)、节(Section,也称为节区)、段(Segment)、镜像(Image)、镜像文件(Image File)等概念
需要对编译、连接过程有一定的了解
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, --map, --symbol, --verbose, --xref, --xreffrom 和 --xrefto 的诊断信息输出重定向到文件。
输出诊断信息时将创建指定的文件。 如果已存在同名文件,则会覆盖该文件。 但是,如果未输出诊断,则不会创建文件。 在这种情况下,具有相同名称的任何现有文件的内容保持不变。如果指定了 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.
用法
--info=sizes,totals 的输出始终包含输入对象和库的总计中的填充值。
如果使用 RW 数据压缩(默认值),或者使用 --datacompressor=id 选项指定了压缩器,则 --info=sizes,totals 的输出包括 Grand Totals 下的条目以反映真实镜像大小。
列表中的关键字之间不允许有空格。
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:一系列数据项的开始,如文字池
文字池 是代码段中存放常量数据的区域。因为没有一条指令可以生成一个 4 字节的常量,因此编译器将这些常量放到文字池中,然后生成从文字池加载这些常量的代码。
ARM/Thumb交互(ARM/Thumb interworking)是指对汇编语言和 C/C++ 语言的 ARM 和 Thumb 代码进行连接的方法,它进行两种状态(ARM 和 Thumb)间的切换。
胶合代码(Veneer):在进行 ARM/Thumb 交互时,有时需使用额外的代码,这些代码被称为 胶合代码(Veneer)。
AAPCS 定义了 ARM 和 Thumb 过程调用的标准。
armlink 会生成 $d.realdata 映射符号,以告诉 fromelf 该数据是来自非可执行节区。因此,fromelf -z 输出的代码和数据大小与 armlink --info sizes 的输出相同。例如:
Code (inc. data) RO Data x y z 在以上的示例中,y 标记为 $d,RO Data 标记为 $d.realdata。如果启用了 --list_mapping_symbols,则会在 map 文件中有体现,如下图:
请注意:
以字符 $v 开头的符号是与 VFP 相关的映射符号,在使用 VFP 构建目标时可能会输出。 避免在源代码中使用以 $v 开头的符号。
使用 fromelf --elf --strip=localsymbols 命令修改可执行镜像会从镜像中删除所有映射符号。
其必须由 armlink 的参数 --list_mapping_symbols 和 --no_list_mapping_symbols 分别来控制显示与不显示。在默认情况下为 --no_list_mapping_symbols,即不显示这部分符号。
链接器定义的符号
当链接器创建镜像文件时,它会创建一些 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 symbolextern unsigned int Image$$ZI$$Limit;config.heap_base = (unsigned int) &Image$$ZI$$Limit;//Importing symbols that define a ZI output sectionextern 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 就是只读域的基地址。
使用分散加载时,连接器将使用分散加载文件中的名称来生成各种域相关的符号。分散加载文件可以实现以下功能:
命名镜像中的所有执行域,并提供他们的加载和执行地址。
定义堆栈和堆。 链接器还会生成特殊的栈和堆符号。
镜像的 ZI 输出节不是静态创建的,而是在运行时自动动态创建的。 因此,ZI 输出节没有加载地址符号。
符号 Load$$region_name 仅适用于执行域。Load$$LR$$load_region_name 符号仅适用于加载域。
执行域符号 Image$$
链接器为镜像中存在的每个执行域生成符号 Image$$。下表列出了链接器为镜像中存在的每个执行域生成的符号。 初始化 C 库后,所有符号都指向执行地址。
[tr]SymbolDescription[/tr]Image$$region_name$$Base域的执行地址
Image$$region_name$$Length执行域长度(以字节为单位),不包括 ZI 的长度。
Image$$region_name$$Limit超出执行域中非 ZI 部分末尾的字节的地址
Image$$region_name$$RO$$Base域中的输出节 RO 的执行地址
Image$$region_name$$RO$$LengthLength of the RO output section in bytes.
Image$$region_name$$RO$$LimitAddress of the byte beyond the end of the RO output section in the execution region.
Image$$region_name$$RW$$BaseExecution address of the RW output section in this region.
Image$$region_name$$RW$$LengthLength of the RW output section in bytes.
Image$$region_name$$RW$$LimitAddress of the byte beyond the end of the RW output section in the execution region.
Image$$region_name$$XO$$BaseExecution address of the XO output section in this region.
Image$$region_name$$XO$$LengthLength of the XO output section in bytes.
Image$$region_name$$XO$$LimitAddress of the byte beyond the end of the XO output section in the execution region.
Image$$region_name$$ZI$$BaseExecution address of the ZI output section in this region.
Image$$region_name$$ZI$$LengthLength of the ZI output section in bytes.
Image$$region_name$$ZI$$LimitAddress of the byte beyond the end of the ZI output section in the execution region.
执行域符号 Load$$
链接器为镜像中存在的每个执行域生成符号 Load$$。下表列出了链接器为镜像中存在的每个 Load$$ 执行域生成的符号。 初始化 C 库后,所有符号都指向加载地址。
[tr]SymbolDescription[/tr]Load$$region_name$$BaseLoad address of the region.
Load$$region_name$$LengthRegion length in bytes.
Load$$region_name$$LimitAddress of the byte beyond the end of the execution region.
Load$$region_name$$RO$$BaseAddress of the RO output section in this execution region.
Load$$region_name$$RO$$LengthLength of the RO output section in bytes.
Load$$region_name$$RO$$LimitAddress of the byte beyond the end of the RO output section in the execution region.
Load$$region_name$$RW$$BaseAddress of the RW output section in this execution region.
Load$$region_name$$RW$$LengthLength of the RW output section in bytes.
Load$$region_name$$RW$$LimitAddress of the byte beyond the end of the RW output section in the execution region.
Load$$region_name$$XO$$BaseAddress of the XO output section in this execution region.
Load$$region_name$$XO$$LengthLength of the XO output section in bytes.
Load$$region_name$$XO$$LimitAddress of the byte beyond the end of the XO output section in the execution region.
Load$$region_name$$ZI$$BaseLoad address of the ZI output section in this execution region.
Load$$region_name$$ZI$$LengthLoad 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$$LimitLoad 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$$ 加载域生成的符号。
[tr]SymbolDescription[/tr]Load$$LR$$load_region_name$$BaseAddress of the load region.
Load$$LR$$load_region_name$$LengthLength of the load region.
Load$$LR$$load_region_name$$LimitAddress 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 节都放在一个连续的块中。 具有相同属性和名称的连续块部分称为合并节。
ARM 建议优先使用与域相关的符号,而不是与节相关的符号。
镜像符号
当您不使用分散加载文件来创建简单镜像时,镜像符号将由链接器生成。我们常用的 Keil 会默认生成分散加载文件的,所以基本没有不使用分散加载文件的情况。下表显示了镜像符号:
[tr]SymbolSection typeDescription[/tr]Image$$RO$$BaseOutputAddress of the start of the RO output section.
Image$$RO$$LimitOutputAddress of the first byte beyond the end of the RO output section.
Image$$RW$$BaseOutputAddress of the start of the RW output section.
Image$$RW$$LimitOutputAddress 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$$BaseOutputAddress of the start of the ZI output section.
Image$$ZI$$LimitOutputAddress 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) 分散加载机制详解 及 分散加载文件的编写
输入节符号
链接器为镜像中存在的每个输入节生成输入节符号。下表显示了链接器定义的输入节符号:
[tr]SymbolSection typeDescription[/tr]SectionName$$BaseInputAddress of the start of the consolidated section called SectionName.
SectionName$$LengthInputLength of the consolidated section called SectionName (in bytes)。
SectionName$$LimitInputAddress 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_region ER_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 数据没有加载域地址
最后在说明一下每一列(字段)的具体含义如下::
[tr]Base AddrSizeTypeAttrIdxE Section NameObject[/tr]节的基地址节的大小类型属性索引节的名字对象(节所属的文件模块)
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 的库文件管理工具。
D:ARMARM_Compiler_5.06u4》armar --zt 。/lib/armlib/c_w.l Code RO Data RW Data ZI Data Debug Object Name 48 0 0 0 84 version.o 58 0 0 0 68 __dczerorl.o 90 0 0 0 68 __dczerorl2.o 100 0 0 0 68 __dclz77c.o 0 0 28 0 0 dc.o 102 12 0 0 240 sys_io.o 18 0 0 0 76 sys_tmpnam.o 32 0 4 0 84 sys_clock.o 18 0 0 0 76 sys_time.o 12 0 0 0 68 sys_exit.o 4 8 0 0 0 __rtentry3.o 6 8 0 0 0 __rtentry4.o 8 24 0 0 0 __rtentry5.o 8 24 0 0 0 __rtentry6.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 104 0 0 0 84 __printf.o 6 0 0 0 0 _printf_a.o 0 0 0 0 0 _printf_percent.o 4 0 0 0 0 _printf_percent_end.o 6 0 0 0 0 _printf_lli.o 6 0 0 0 0 _printf_lld.o 6 0 0 0 0 _printf_llu.o 52 0 0 0 80 vsscanf.o 60 0 0 0 84 swscanf.o 52 0 0 0 80 vswscanf.o 52 0 0 0 80 __ARM_vsscanf.o 32 0 0 0 84 scanfn.o 24 0 0 0 84 fscanfn.o 60 0 0 0 84 sscanfn.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 16 0 0 0 80 vfwscanf.o 28 0 0 0 68 _chval.o 884 0 0 0 100 _scanf.o 342 0 0 0 100 _scanf_longlong.o 332 0 0 0 96 _scanf_int.o 224 0 0 0 96 _scanf_str.o 1202 0 0 0 216 scanf_fp.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 8 0 0 0 68 feof.o 8 0 0 0 68 ferror.o 100 0 0 0 120 fflush.o 26 0 0 0 136 fgetc.o 32 0 0 0 80 fgetpos.o 74 0 0 0 84 fgets.o 570 0 0 0 132 filbuf.o 470 0 0 0 88 fl***uf.o 28 0 0 0 136 fputc.o 34 0 0 0 80 fputs.o 58 0 0 0 88 fread.o 248 0 0 0 84 fseek.o 40 0 0 0 80 fsetpos.o 66 0 0 0 76 ftell.o 50 0 0 0 84 fwrite.o 4 0 0 0 68 getc.o 12 0 0 0 68 getchar.o 56 0 0 0 80 gets.o 60 0 0 0 80 perror.o 4 0 0 0 68 putc.o 12 0 0 0 68 putchar.o 312 0 0 0 112 initio.o 236 0 0 0 128 fopen.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 18 0 0 0 80 exit.o 22 0 0 0 80 abort.o 4 0 0 0 68 _Exit.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 20 0 0 0 68 strchr.o 32 0 0 0 80 strcspn.o 36 0 0 0 76 strncat.o 30 0 0 0 76 strpbrk.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 14 0 0 0 80 ctime.o 4 0 0 0 68 gmtime.o 12 0 0 44 68 localtime.o 428 0 0 0 116 mktime.o 864 0 0 0 184 strftime.o 0 12 0 0 0 _monlen.o 0 0 0 0 0 asctime_r.o 0 0 0 0 0 localtime_r.o 104 0 0 0 88 asctime_internal.o 184 0 0 0 80 localtime_internal.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 8 0 0 0 68 __main.o 52 0 0 0 68 __scatter.o 76 0 0 0 68 __scatters.o 26 0 0 0 68 __scatter_copy.o 28 0 0 0 68 __scatter_zi.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 34 0 0 0 76 prim_new.o 2 8 0 0 68 init.o 36 8 0 0 80 init_aeabi.o 44 8 0 0 76 arm_relocate.o 36 8 0 0 80 preinit_aeabi.o 4 0 0 0 68 _memcpy.o 8 0 0 0 84 _urdwr4.o 。。。。。。。。。。。。省略一大部分。。。。。。。。。。。 112 0 0 0 104 fputwc.o 36 0 0 0 80 fputws.o 200 0 0 0 104 fgetwc.o 82 0 0 0 84 fgetws.o 4 0 0 0 68 getwc.o 12 0 0 0 68 getwchar.o 73526 40563 112 1686 76484 TOTALENTRY at offset 1 in section !!!main of __main.o 从中我们可以看到有__main.o等文件,接下来我们可以使用armar -x命令将c_w.l解压出以上全部文件,然后使用fromelf来查看__main.o的详细信息,这里就不一一尝试了!
举报