文章目录
39.1 编译过程
39.1.1 编译过程简介
编译,MDK 软件使用的编译器是 armcc 和 armasm
- 它们根据每个 c/c++ 和汇编源文件编译成对应的以“.o”为后缀名的对象文件 (Object Code,也称目标文件)
- 其内容主要是从源文件编译得到的机器码,包含了代码、数据以及调试使用的信息;
链接,链接器 armlink 把各个.o 文件及库文件链接成一个映像文件“.axf”或“.elf”;
格式转换
- 一般来说 Windows 或 Linux 系统使用链接器直接生成可执行映像文件 elf 后,内核根据该文件的信息加载后,就可以运行程序了
- 在单片机平台上,需要把该文件的内容加载到芯片上,所以还需要对链接器生成的 elf 映像文件利用格式转换器 fromelf 转换成“.bin”或“.hex”文件,交给下载器下载到芯片的 FLASH 或 ROM 中
编译完成
- 提示信息的第一部分说明构建过程调用的编译器。
- 图中的编译器名字是“V5.06(build 20)”,后面附带了该编译器所在的文件夹。
- 在电脑上打开该路径,可看到该编译器包含图 39‑3 中的各个编译工具,如 armar、armasm、armcc、armlink 及 fromelf
- 使用 armasm 编译汇编文件。
- 图中列出了编译 startup 启动文件时的提示,编译后每个汇编源文件都对应有一个独立的.o 文件。
- 使用 armcc 编译 c/c++ 文件。
- 图中列出了工程中所有的 c/c++ 文件的提示,同样地,编译后每个 c/c++ 源文件都对应有一个独立的.o 文件。
- armar 是用于把.o 文件打包成 lib 文件的。
-
使用 armlink 链接对象文件,根据程序的调用把各个.o 文件的内容链接起来,最后生成程序的 axf 映像文件,并附带程序各个域大小的说明,包括 Code、RO-data、RW-data 及 ZI-data的大小。
-
使用 fromelf 生成下载格式文件,它根据 axf 映像文件转化成 hex 文件,并列出编译过程出现的错误 (Error) 和警告 (Warning) 数量。
-
最后一段提示给出了整个构建过程消耗的时间。
在工程的“Output”及“Listing”目录下找到由以上过程生成的各种文件
每个 C 源文件都对应生成了.o、.d 及.crf 后缀的文件,还有一些额外的.dep、.hex、.axf、.htm、.lnp、.sct、.lst 及.map 文件。
39.2 程序的组成、存储与运行
CODE、RO、RW、ZI Data 域及堆栈空间参见STM32的内存管理
程序的加载与执行见SCF文件
39.3 编译工具链
39.3.1 MDK编译文件
MDK编译工程,会生成一些中间文件(.o .axf .map等),最终会生成HEX文件,以便下载到MCU上面执行,我们通常使用STM32工程中,output文件夹下面会生成十多种文件类型。
- .o文件:它是由编译器编译.c/.s文件时所产生的可重定向对象文件。
- 【注:①可重定向是指该文件包涵数据、代码,但是没有指定地址,他的地址可以由后续链接的时候进行指定,②不可重定向是指这种文件所包含的数据/代码都已经指定地址了,不能再改变】
- .axf文件:它是由armlink链接器,将整个工程参与编译的.o文件链接成一个可执行对象文件。它是不可重定向的。
- 【注】各类仿真器,在进行下载调试的时候都是用的,axf文件
- .hex文件:它是由.axf转换而来的一个可执行对象文件。
-
.hex文件和.bin文件的区别是:.bin文件不含地址信息,全部是可执行代码;而hex文件则是包含地址信息的可执行代码。同样的.bin文件也是由.axf文件转换而来的。可理解为带存储地址描述格式的 bin 文件。
-
在使用ISP软件进行程序下载的时候,一般使用的是.hex文件包含的地址信息来实现程序下载。 而我们在进行BootLoader升级的时候,一般使用的是.bin文件,地址有Bootloader程序指定。
-
- .htm文件:它是编译器在编译代码的时候生成的一个列表文件,包含了整个工程的静态调用图,最大用处就是可以查看栈深度(最小深度),方便设置栈的大小。.htm文件可以直接由浏览器打开。
- .map文件.map文件时编译器链接时生成的一个文件,它主要包含了交叉链接信息。通过.map文件,我们可以知道整个工程的函数调用关系、FLASH和RAM 占用情况及其详细的汇总信息。能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可对代码进行优化。
若希望使用 MDK 编译生成 bin 文件的,需要在 MDK中输入指令控制 fromelf 工具;在本章后面讲解 AXF 及 O 文件的时候,需要利用 fromelf 工具查看其文件信息,这都是无法直接通过 MDK 做到的。
- 关于这些工具链的说明,在 MDK 的帮助手册《ARM Development Tools》都有详细讲解
- 点击 MDK 界面的“help->uVision Help”菜单可打开该文件。
39.3.2 设置环境变量
调用这些编译工具,需要用到 Windows 的“命令行提示符工具”,为了让命令行方便地找到这些工具,我们先把工具链的目录添加到系统的环境变量中。
- 查看本机工具链所在的具体目录可根据上一小节讲解的工程编译提输出信息中找到,如本机的路径为“D:workkeil5ARMARMCCbin”。
39.3.3 运行
打开 Windows 的命令行,点击系统的“开始菜单”,在搜索框输入“cmd”
- 在弹出的命令行窗口中输入“fromelf”回车,若窗口打印出 formelf 的帮助说明,那么路径正常
MDK 本质上也是如此调用工具链的,只是它集成为 GUI,相对于命令行对用户更友好
39.3.4 armcc、armasm 及 armlink
39.3.4.1 armcc
armcc 用于把 c/c++ 文件编译成 ARM 指令代码,编译后会输出 ELF 格式的 O 文件
第一部分是 armcc 版本信息,第二部分是命令的用法,第三部分是主要命令选项。
输入命令armcc [options] file1 file2 ⋯filen
- 在 [option] 位置可输入下面的“–arm”、“–cpu list”等选项
- 若选项带文件输入,则把文件名填充在 file1 file2⋯的位置,这些文件一般是 c/c++ 文件。
“–cpu list”可列出编译器支持的所有 cpu,我们在命令行中输入“armcc –cpu list”
打开 MDK 的 Options for Targe->c/c++ 菜单,可看到 MDK 对编译器的控制命令
它调用了-c、-cpu –D –g –O1 等编译选项
- 当我们修改 MDK 的编译配置时,可看到该控制命令也会有相应的变化。
- 然而我们无法在该编译选项框中输入命令,只能通过 MDK 提供的选项修改。
39.3.4.2 查询具体的MDK编译选项的具体信息
如c/c++选项中的“Optimization:Leve 1(-O1)”是什么功能呢?
查看 MDK 的帮助手册,在 armcc 编译器说明章节:
39.3.4.3 armasm
armasm 是汇编器,它把汇编文件编译成 O 文件。
- 与 armcc 类似,MDK 对 armasm 的调用选项可在“Option for Target->Asm”页面进行配置
39.3.4.4 armlink
armlink 是链接器,它把各个 O 文件链接组合在一起生成 ELF 格式的 AXF 文件
- AXF 文件是可执行的,下载器把该文件中的指令代码下载到芯片后,该芯片就能运行程序了;
- 利用 armlink 还可以控制程序存储到指定的 ROM 或 RAM 地址。
- 在 MDK 中可在“Option for Target->Linker”页面配置 armlink 选项
链接器默认是根据芯片类型的存储器分布来生成程序的,该存储器分布被记录在工程里的 sct 后缀的文件中,有特殊需要的话可自行编辑该文件,改变链接器的链接方式
39.3.4.5 armar、fromelf 及用户指令
armar 工具用于把工程打包成库文件,fromelf 可根据 axf 文件生成 hex、bin 文件,hex 和 bin 文件是大多数下载器支持的下载文件格式
在 MDK 中,针对 armar 和 fromelf 工具的选项几乎没有,仅集成了生成 HEX 或 Lib 的选项
例如如果我们想利用 fromelf 生成 bin 文件,可以在 MDK 的“Option for Target->User”页中添加调用 fromelf 的指令
在 User 配置页面中,提供了三种类型的用户指令输入框,在不同组的框输入指令,可控制指令的执行时间
- 分别是编译前 (Before Compile c/c++ file)、构建前 (Before Build/Rebuild) 及构建后(After Build/Rebuild) 执行。
- 这些指令并没有限制必须是 arm 的编译工具链,例如如果您自己编写了 python 脚本,也可以在这里输入用户指令执行该脚本。
图中的生成 bin 文件指令调用了 fromelf 工具
- 由于 fromelf 是根据 axf 文件生成 bin 的,而 axf 文件又是构建 (build) 工程后才生成,所以我们把该指令放到“After Build/Rebuild”一栏
39.3.5 MDK 工程的文件类型
MDK工程中还包含了各种各样的文件
39.3.5.1 uvprojx、uvoptx 及 uvguix 工程文件
工程的“Project”目录下主要是 MDK 工程相关的文件
uvprojx
uvprojx 文件就是我们平时双击打开的工程文件,它记录了整个工程的结构,如芯片类型、工程包含了哪些源文件等内容
uvoptx 文件
uvoptx 文件记录了工程的配置选项,如下载器的类型、变量跟踪配置、断点位置以及当前已打开的文件等等
uvguix 文件
uvguix 文件记录了 MDK 软件的 GUI 布局,如代码编辑区窗口的大小、编译输出提示窗口的位置等等
uvprojx、uvoptx 及 uvguix 都是使用 XML 格式记录的文件,若使用记事本打开可以看到 XML 代码
这些工程参数都是当 MDK 正常退出时才会被写入保存,所以若 MDK 错误退出时 (如使用 Win-dows 的任务管理器强制关闭),工程配置参数的最新更改是不会被记录的,重新打开工程时要再次配置
uvprojx 文件是最重要的,删掉它我们就无法再正常打开工程了
- uvoptx 及 uvguix 文件并不是必须的,可以删除
- 重新使用 MDK 打开 uvprojx工程文件后,会以默认参数重新创建 uvoptx 及 uvguix 文件
39.3.6 源文件
MDK 支持 c、cpp、h、s、inc 类型的源代码文件
- 其中 c、cpp 分别是 c/c++ 语言的源代码,h 是它们的头文件
- s 是汇编文件,inc 是汇编文件的头文件,可使用“$include”语法包含。
编译器根据工程中的源文件最终生成机器码。
Output 目录下生成的文件
点击 MDK 中的编译按钮,它会根据工程的配置及工程中的源文件输出各种对象和列表文件
- 在工程的“Options for Targe->Output->Select Folder for Objects”
- “Options for Targe->Listing->Select Folder for Listings”选项配置它们的输出路径
39.3.8 lib 库文件
某些场合下我们希望提供给第三方一个可用的代码库,但不希望对方看到源码,就可以把工程生成 lib 文件 (Library file) 提供给对方
在 MDK 中可配置“Options for Target->Create Library”选项把工程编译成库文件
得到生成的 *.lib 文件后,可把它像 C 文件一样添加到其它工程中,并在该工程调用 lib 提供的函数接口,除了不能看到 *.lib 文件的源码,在应用方面它跟 C 源文件没有区别
39.3.9 dep、d 依赖文件
*.dep 和 *.d 文件 (Dependency file) 记录的是工程或其它文件的依赖,主要记录了引用的头文件路径
- 其中 *.dep 是整个工程的依赖,它以工程名命名
- 而 *.d 是单个源文件的依赖,它们以对应的源文件名命名。
- 这些记录使用文本格式存储,我们可直接使用记事本打开
39.3.10 crf 交叉引用文件
*.crf 是交叉引用文件 (Cross-Reference file),它主要包含了浏览信息 (browse information),即源代码中的宏定义、变量及函数的定义和声明的位置。
在代码编辑器中点击“Go To Definition Of‘xxxx’”可实现浏览跳转,跳转的时候,MDK 就是通过 *.crf 文件查找出跳转位置的。
*.crf 文件使用了特定的格式表示,直接用文本编辑器打开会看到大部分乱码,不作深入研究。
39.3.11 o、axf 及 elf 文件
*.o、.elf、.axf、.bin 及.hex 文件都存储了编译器根据源代码生成的机器码,根据应用场合的不同,它们又有所区别。
39.3.11.1 ELF 文件说明
*.o、.elf、.axf 以及前面提到的 lib 文件都是属于目标文件,它们都是使用 ELF 格式来存储的,关于 ELF 格式的详细内容请参考配套资料里的《ELF 文件格式》文档了解,它讲解的是 Linux 下的ELF 格式,与 MDK 使用的格式有小区别,但大致相同。
ELF 是 Executable and Linking Format 的缩写,译为可执行链接格式,该格式用于记录目标文件的内容。在 Linux 及 Windows 系统下都有使用该格式的文件 (或类似格式) 用于记录应用程序的内容,告诉操作系统如何链接、加载及执行该应用程序。
目标文件主要有如下三种类型:
-
可重定位的文件 (Relocatable File),包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件。
- 例如 MDK 的 armcc 和 armasm 生成的 *.o 文件就是这一类,另外还有 Linux 的 *.o 文件,Windows 的 *.obj 文件。
-
可执行文件 (Executable File) ,它包含适合于执行的程序,它内部组织的代码数据都有固定的地址 (或相对于基地址的偏移),系统可根据这些地址信息把程序加载到内存执行。
- 这种文件一般由链接器根据可重定位文件链接而成,它主要是组织各个可重定位文件,给它们的代码及数据一一打上地址标号,固定其在程序内部的位置,链接后,程序内部各种代码及数据段不可再重定位 (即不能再参与链接器的链接)。
- 例如 MDK 的 armlink 生成的 *.elf 及 *.axf 文件,(使用 gcc 编译工具可生成 *.elf 文件,用armlink 生成的是 *.axf 文件,.axf 文件在.elf 之外,增加了调试使用的信息,其余区别不大
- 另外还有 Linux 的/bin/bash 文件,Windows 的 *.exe 文件。
- axf和elf都是编译器生成的可执行文件,区别是axf文件是调试文件,可由ads或mdk生成;而gcc编译出来的是elf文件。两者虽然很像,但还是有差别的。
-
共享目标文件 (Shared Object File),它的定义比较难理解,我们直接举例
- MDK 生成的 *.lib文件就属于共享目标文件,它可以继续参与链接,加入到可执行文件之中。
- 另外,Linux的.so,如/lib/ glibc-2.5.so,Windows 的 DLL 都属于这一类。
39.3.11.2 o 文件与 axf 文件的关系
根据上面的分类,我们了解到,.axf 文件是由多个.o 文件链接而成的,而 *.o 文件由相应的源文件编译而成,一个源文件对应一个 *.o 文件。
中间代表的是 armlink 链接器,在它的右侧是输入链接器的 *.o 文件,左侧是它输出的 *axf文件。
由于都使用 ELF 文件格式,.o 与.axf 文件的结构是类似的,它们包含 ELF 文件头、程序头、节区 (section) 以及节区头部表。
- ELF 文件头用来描述整个文件的组织,例如数据的大小端格式,程序头、节区头在文件中的位置等。
- 程序头告诉系统如何加载程序,例如程序主体存储在本文件的哪个位置,程序的大小,程序要加载到内存什么地址等等。
- 节区是 *.o 文件的独立数据区域,它包含提供给链接视图使用的大量信息
- 存储在最后的节区头则包含了本文件节区的信息,如节区名称、大小等等。
链接器把各个 *.o 文件的节区归类、排列,根据目标器件的情况编排地址生成输出,汇总到 *.axf 文件。
指令在 *.o 文件都没有指定地址,仅包含了内容、大小以及调用的链接信息,而经过链接器后,链接器给它们都分配了特定的地址,并且把地址根据调用指向链接起来。
39.3.11.3 ELF 文件头
使用 fromelf 文件可以查看 *.o、.axf 及.lib 文件的 ELF 信息。
- 切换到文件所在的目录,输入“fromelf –text –v bsp_led.o”命令,可控制输出 bsp_led.o的详细信息
- 利用“-c、-z”等选项还可输出反汇编指令文件、代码及数据文件等信息
“elf 信息输出”文件夹
打开“bsp_led_o_elfInfo /bsp_led_o_elfInfo_v.txt 文件
3 ** ELF Header Information
4
5 File Name: bsp_led.o //bsp_led.o 文件
6
7 Machine class: ELFCLASS32 (32-bit)//32 位机
8 Data encoding: ELFDATA2LSB (Little endian)// 小端格式
9 Header version: EV_CURRENT (Current version)
10 Operating System ABI: none
11 ABI Version: 0
12 File Type: ET_REL (Relocatable object) (1) // 可重定位类型
13 Machine: EM_ARM (ARM)
14
15 Entry offset (in SHF_ENTRYSECT section): 0x00000000
16 Flags: None (0x05000000)
17
18 ARM ELF revision: 5 (ABI version 2)
19
20 Built with
21 Component: ARM Compiler 5.06 update 4 (build 422) Tool: armasm [4d35cf]
22 Component: ARM Compiler 5.06 update 4 (build 422) Tool: armlink [4d35d2]
23
24 Header size: 52 bytes (0x34)
25 Program header entry size: 0 bytes (0x0)// 程序头大小
26 Section header entry size: 40 bytes (0x28)
27
28 Program header entries: 0
29 Section header entries: 166
31 Program header offset: 0 (0x00000000)// 程序头在文件中的位置(没有程序头)
32 Section header offset: 1525104 (0x00174570)// 节区头在文件中的位置
33
34 Section header string table index: 163
35
36 ========================================================================
在这个 *.o 文件中,它的ELF 文件头中告诉我们它的程序头 (Program header) 大小为“0 bytes”,且程序头所在的文件位置偏移也为“0”,这说明它是没有程序头的。
39.3.11.4 程序头
打开“axf_elfInfo /_axf_elfInfo_v.txt”文件,查看工程的 *.axf 文件的详细信息
3 ** ELF Header Information
4
5 File Name: YH-RT1052.axf //YH-RT1052.axf 文件
6
7 Machine class: ELFCLASS32 (32-bit) //32 位机
8 Data encoding: ELFDATA2LSB (Little endian) //小端格式
9 Header version: EV_CURRENT (Current version)
10 Operating System ABI: none
11 ABI Version: 0
12 File Type: ET_EXEC (Executable) (2) //可执行文件类型
13 Machine: EM_ARM (ARM)
14
15 Image Entry point: 0x60002401
16 Flags: EF_ARM_HASENTRY (0x05000002)
17
18 ARM ELF revision: 5 (ABI version 2)
19
20 Conforms to Base float procedure-call standard
21
22 Built with
23 Component: ARM Compiler 5.06 update 4 (build 422) Tool: armasm [4d35cf]
24 Component: ARM Compiler 5.06 update 4 (build 422) Tool: armlink [4d35d2]
25
26 Header size: 52 bytes (0x34)
27 Program header entry size: 32 bytes (0x20) //程序头大小
28 Section header entry size: 40 bytes (0x28)
29
30 Program header entries: 3
31 Section header entries: 21
32
33 Program header offset: 3048048 (0x002e8270) //程序头在文件中的位置
34 Section header offset: 3048144 (0x002e82d0) //节区头在文件中的位置
35
36 Section header string table index: 20
37
38 ========================================================================
39
40 ** Program header #0
41
42 Type : PT_LOAD (1) //表示这是可加载的内容
43 File Offset : 52 (0x34) //在文件中的偏移
44 Virtual Addr : 0x60000000 //虚拟地址(此处等于物理地址)
45 Physical Addr : 0x60000000 //物理地址
46 Size in file : 512 bytes (0x200)//程序在文件中占据的大小
47 Size in memory: 512 bytes (0x200)//若程序加载到内存,占据的内存空间
48 Flags : PF_R (0x4)
49 Alignment : 4 //地址对齐
*.axf 文件的 ELF 文件头对程序头的大小说明为非 0 值,且给出了它在文件的偏移地址,在输出信息之中,包含了程序头的详细信息。
- 可看到,程序头的“Physical Addr”描述了本程序要加载到的内存地址“0x08000000”,正好是 STM32 内部 FLASH 的首地址;
- “size in file”描述了本程序占据的空间大小为“3176bytes”,它正是程序烧录到 FLASH 中需要占据的空间。
39.3.12.5 节区头
在 ELF 的原文件中,紧接着程序头的一般是节区的主体信息,在节区主体信息之后是描述节区主体信息的节区头。通过对比 *.o 文件及 *.axf 文件的节区头部信息,可以清楚地看出这两种文件的区别
2 ** Section #1
3
4 Name : .rev16_text //节区名
5 //此节区包含程序定义的信息,其格式和含义都由程序来解释。
6 Type : SHT_PROGBITS (0x00000001)
7 //此节区在进程执行过程中占用内存。 节区包含可执行的机器指令。
8 Flags : SHF_ALLOC + SHF_EXECINSTR (0x00000006)
9 Addr : 0x00000000
10 File Offset : 52 (0x34) //在文件中的偏移
11 Size : 4 bytes (0x4) //大小
12 Link : SHN_UNDEF
13 Info : 0
14 Alignment : 4 //字节对齐
15 Entry Size : 0
节区的名称为.rev16_text,在对应的工程文件中全局搜索可以找到对应的代码。
- 注意:编译时要勾选“Options for Target ->C/C++ -> One ELF Section per Function”中的选项,生成的 *.o 文件内部的代码区域才会与 C 文件中定义的函数名一致,否则它会把多个函数合成一个代码段,名字一般跟 C 文件中的函数名不同。
节区头描述的是该函数被编译后的节区信息,其中包含了节区的类型 (指令类型SHT_PROGBITS)、节区应存储到的地址 (0x00000000)、它主体信息在文件位置中的偏移 (52) 以及节区的大小 (4 bytes)。
*.o 文件是可重定位文件,所以它的地址并没有被分配,是 0x00000000
- 当链接器链接时,根据这个节区头信息,在文件中找到它的主体内容,并根据它的类型,把它加入到主程序中,并分配实际地址,链接后生成的 *.axf 文件
*.axf 文件的节区信息 :
1 ========================================
2 ** Section #1
3
4 Name : RW_m_config_text //节区名
5 //此节区包含程序定义的信息,其格式和含义都由程序来解释。
6 Type : SHT_PROGBITS (0x00000001)
7 //此节区在进程执行过程中占用内存。 节区包含可执行的机器指令
8 Flags : SHF_ALLOC (0x00000002)
9 Addr : 0x60000000 //地址
10 File Offset : 52 (0x34)
11 Size : 512 bytes (0x200) //大小
12 Link : SHN_UNDEF
13 Info : 0
14 Alignment : 4
15 Entry Size : 0
16 ====================================
17 ** Section #2
18
19 Name : RW_m_ivt_text //节区名
20 /*
21 * 包含将出现在程序的内存映像中的为初始
22 * 化数据。 根据定义, 当程序开始执行, 系统
23 * 将把这些数据初始化为 0。
24 */
25 Type : SHT_PROGBITS (0x00000001)
26 //此节区在进程执行过程中占用内存。 节区包含进程执行过程中将可写的数据。
27 Flags : SHF_ALLOC (0x00000002)
28 Addr : 0x60001000 //地址
29 File Offset : 564 (0x234)
30 Size : 1120 bytes (0x460) //大小
31 Link : SHN_UNDEF
32 Info : 0
33 Alignment : 4
34 Entry Size : 0
35 ====================================
在 *.axf 文件中,主要包含了两个节区
- 一个名为 RW_m_config_text,一个名为 RW_m_ivt_text
这些节区头信息中除了具有 *.o 文件中节区头描述的节区类型、文件位置偏移、大小之外,更重要的是它们都有具体的地址描述
- ER_IROM1 的地址为 0x60000000,而 RW_IRAM1 的地址为 0x60001000。
经过链接器后,它生成的 *.axf 文件已经汇总了其它 *.o 文件的所有内容,生成的RW_m_config_text 节区内容可直接写入到 RT1052 外部 FLASH 的具体位置。
39.3.12.6 节区主体及反汇编代码
使用 fromelf 的-c 选项可以查看部分节区的主体信息,对于指令节区,可根据其内容查看相应的反汇编代码,打开“bsp_led_o_elfInfo/_o_elfInfo_c”文件可查看这些信息
1 ** Section #5 'i.LED_GPIO_Config' (SHT_PROGBITS) [SHF_ALLOC+SHF_EXECINSTR]
2 Size : 172 bytes (alignment 4)
3 Address: 0x00000000
4
5 $t
6 i.LED_GPIO_Config
7 LED_GPIO_Config
8 0x00000000: e92d41fc -..A PUSH {r2-r8,lr}
9 0x00000004: 2400 .$ MOVS r4,#0
10 0x00000006: 4d26 &M LDR r5,[pc,#152] ; [0xa0] = 0x401f82d0
11 0x00000008: 4826 &H LDR r0,[pc,#152] ; [0xa4] = 0x401f80e0
12 0x0000000a: 2105 .! MOVS r1,#5
13 0x0000000c: 4623 #F MOV r3,r4
14 0x0000000e: 4622 "F MOV r2,r4
15 0x00000010: e9cd5400 ...T STRD r5,r4,[sp,#0]
16 0x00000014: f7fffffe .... BL IOMUXC_SetPinMux ; 0x0 Section #4
17 0x00000018: 4e21 !N LDR r6,[pc,#132] ; [0xa0] = 0x401f82d0
18 0x0000001a: 2300 .# MOVS r3,#0
19 0x0000001c: 4821 !H LDR r0,[pc,#132] ; [0xa4] = 0x401f80e0
20 0x0000001e: 2105 .! MOVS r1,#5
21 0x00000020: 363c <6 ADDS r6,r6,#0x3c
22 0x00000022: 461a .F MOV r2,r3
23 0x00000024: 303c <0 ADDS r0,r0,#0x3c
24 0x00000026: e9cd6400 ...d STRD r6,r4,[sp,#0]
25 0x0000002a: f7fffffe .... BLIOMUXC_SetPinMux ; 0x0 Section #4
26 0x0000002e: 481d .H LDR r0,[pc,#116] ; [0xa4] = 0x401f80e0
27 0x00000030: 2300 .# MOVS r3,#0
28 0x00000032: f1050804 .... ADD r8,r5,#4
29 /* 一下内容省略 */
由于这是 *.o 文件,它的节区地址还是没有分配的,基地址为 0x00000000
- 接着在LED_GPIO_Config 标号之后,列出了一个表,表中包含了地址偏移、相应地址中的内容以及根据内容反汇编得到的指令。
细看汇编指令,还可看到它包含了跳转到 IOMUXC_SetPinMux 标号的语句,而且这跳转语句原来的内容都是“f7fffffe”,这是因为*.o文件中并没有IOMUXC_SetPinMux标号的具体地址索引,在 *.axf 文件中,这是不一样的
们打开“axf_elfInfo/_axf_elfInfo_c.txt”文件,查看 *.axf 文件中对应 LED_GPIO_Config 的内容
1 LED_GPIO_Config
2 0x60003730: e92d41fc -..A PUSH {r2-r8,lr}
3 0x60003734: 2400 .$ MOVS r4,#0
4 0x60003736: 4d26 &M LDR r5,[pc,#152];[0x600037d0]=0x401f82d0
5 0x60003738: 4826 &H LDR r0,[pc,#152] ; [0x600037d4] = 0x401f80e0
6 0x6000373a: 2105 .! MOVS r1,#5
7 0x6000373c: 4623 #F MOV r3,r4
8 0x6000373e: 4622 F MOV r2,r4
9 0x60003740: e9cd5400 ...T STRD r5,r4,[sp,#0]
10 0x60003744: f7ffffa8.... BL IOMUXC_SetPinMux ; 0x60003698
11 0x60003748: 4e21 !N LDR r6,[pc,#132] ; [0x600037d0] = 0x401f82d0
12 0x6000374a: 2300 .# MOVS r3,#0
13 0x6000374c: 4821!H LDR r0,[pc,#132] ; [0x600037d4] = 0x401f80e0
14 /* 此处省略部分内容 */
15 0x600037a8: 4620 F MOV r0,r4
16 0x600037aa: f7fffedd.... BL GPIO_PinInit ; 0x60003568
17 0x600037ae: 2118 .! MOVS r1,#0x18
18 0x600037b0: 466a jF MOV r2,sp
19 0x600037b2: 4620 F MOV r0,r4
20 0x600037b4: f7fffed8.... BL GPIO_PinInit ; 0x60003568
21 0x600037b8: 2119 .! MOVS r1,#0x19
22 0x600037ba: 466a jF MOV r2,sp
23 0x600037bc: 4620 F MOV r0,r4
24 0x600037be: f7fffed3.... BL GPIO_PinInit ; 0x60003568
25 0x600037c2: 210a .! MOVS r1,#0xa
26 0x600037c4: 466a jF MOV r2,sp
27 0x600037c6: 4620 F MOV r0,r4
28 0x600037c8: f7fffece.... BL GPIO_PinInit ; 0x60003568
29 0x600037cc: e8bd81fc .... POP {r2-r8,pc}
除了基地址以及跳转地址不同之外,LED_GPIO_Config 中的内容跟 *.o 文件中的一样。
- 由于 *.o 是独立的文件,而 *.axf 是整个工程汇总的文件,所以在 *.axf 中包含了所有调用到 *.o 文件节区的内容。
在 *.axf 文件中,跳转到 IOMUXC_SetPinMux 及 GPIO_PinInit 标号的这两个指令后都有注释,分别是“0x60003744”及“; 0x600037aa”,它们是这两个标号所在的具体地址
- 这两个跳转语句的跟 *.o 中的也有区别,内容分别为“f7ffffa8”及“f7fffedd”(.o 中的均为 f7fffffe)。这就是链接器链接的含义,它把不同.o 中的内容链接起来了。
39.3.12.7 分散加载代码
把存储在 FLASH 中的 RW-data 数据拷贝至 SRAM
通过查看“axf_elfInfo/_axf_elfInfo_c.txt”的反汇编信息,了解到程序中具有一段名为“__scatterload”的分散加载代码,它是由 armlink 链接器自动生成的。
1 .text
2 __scatterload
3 __scatterload_rt2
4 0x60002860: 4c06 .L LDR r4,[pc,#24] ; [0x6000287c] = 0x600051a8
5 0x60002862: 4d07 .M LDR r5,[pc,#28] ; [0x60002880] = 0x600051c8
6 0x60002864: e006 .. B 0x60002874 ; __scatterload + 20
7 0x60002866: 68e0 .h LDR r0,[r4,#0xc]
8 0x60002868: f0400301 @... ORR r3,r0,#1
9 0x6000286c: e8940007 .... LDM r4,{r0-r2}
10 0x60002870: 4798 .G BLX r3
11 0x60002872: 3410 .4 ADDS r4,r4,#0x10
12 0x60002874: 42ac .B CMP r4,r5
13 0x60002876: d3f6 .. BCC 0x60002866 ; __scatterload + 6
14 0x60002878: f7fffdc6 .... BL __main_after_scatterload ; 0x60002408
15 $d
16 0x6000287c: 600051a8 .Q. DCD 1610633640
17 0x60002880: 600051c8 .Q. DCD 1610633672
18 /* 一下内容省略 */
分散加载代码包含了拷贝过程 (主要使用 LDM 复制指令)
- LDM 指令的操作数中包含了加载的源地址,这些地址中包含了内部 FLASH 存储的 RW-data 数据,执行这些指令后数据就会
从 FLASH 地址加载到内部 SRAM 的地址。 - 而“__scatterload ”的代码会被“__main”函数调用,__main 在启动文件中的“Reset_Handler”会被调用,因而,在主体程序执行前,已经完成了分散加载过程。
__main 的反汇编代码
1 $t
2 .ARM.Collect$$$$00000000
3 .ARM.Collect$$$$00000003
4 __main
5 _main_stk
6 0x60002400: f8dfd00c .... LDRsp,__lit_00000000;[0x60002410] = 0x20020000
7 .ARM.Collect$$$$00000004
8 _main_scatterload
9 0x60002404: f000fa2c .., BL __scatterload ; 0x60002860
10 .ARM.Collect$$$$00000008
11 .ARM.Collect$$$$0000000A
12 .ARM.Collect$$$$0000000B
13 __main_after_scatterload
14 _main_clock
15 _main_cpp_init
16 _main_init
17 0x60002408: 4800 .H LDR r0,[pc,#0] ; [0x6000240c] = 0x60004cf9
18 0x6000240a: 4700 .G BX r0
19 /* 一下内容省略 */
39.3.12.8 hex 文件及 bin 文件
在 MDK 中使用下载器(DAP/JLINK/ULINK 等) 下载程序或仿真的时候,MDK 调用的就是 *.axf 文件
- 它解释该文件,然后控制下载器把 *.axf 中的代码内容下载到 RT1052 芯片对应的存储空间
- 然后复位后芯片就开始执行代码了。
脱离了 MDK 或 IAR 等工具,下载器就无法直接使用 *.axf 文件下载代码了
39.3.12.8.1 生成 hex 文件
Options for Target->Output->Create Hex File”中勾选该选项
39.3.12.8.2 生成 bin 文件
使用 MDK 生成 bin 文件需要使用 fromelf 命令
- MDK 的“Options For Target->Users”中加入以下命令
- fromelf –bin –output . \ nor_txt_ram\ Objects\ led 灯 bin 文件.\ nor_txt_ram\ObjectsYH-RT1052.axf
该指令是根据本机及工程的配置而写的,在不同的系统环境或不同的工程中,指令内容都不一样。
在 MDK 输入的指令格式是遵守 fromelf 帮助里的指令格式说明的,其格式为:
- “fromelf [options] input_file”
- optinos 是指令选项,一个指令支持输入多个选项,每个选项之间使用空格隔开
- 使用“–bin”选项设置输出 bin 文件
- 使用“–output file”选项设置输出文件的名字为“. \nor_txt_ram\ Objects\ led 灯 bin 文件”,这个名字是一个相对路径格式相对于 YH-RT1052.uvprojx文件。
- “.”表示当前目录,这里的“当前目录”指的是“YH-RT1052.uvprojx”文件所在目录。
- fromelf需要根据工程的*.axf文件输入来转换得到bin文件,所以在命令的输入文件参数中要选择本工程对应的 *.axf 文件
在 MDK 命令输入栏中,我们把 fromelf 指令放置在“After Build/Rebuild”(工程构建完成后执行)
- 这样设置后,工程构建完成生成了最新的 *.axf文件
- MDK 再执行 fromelf 指令,从而得到最新的 bin 文件
点击工程的编译 (build) 按钮,重新编译工程,成功后可看到如下输出:
打开生成的“led 灯 bin 文件”文件夹可以看到生成了三个二进制文件
这里涉及到分散加载文件,该版本(“nor_txt_ram”版本)的分散加载文件设置了三个加载域,所以会生成三个二进制文件。
39.3.12.9 hex 文件格式
hex 是 Intel 公司制定的一种使用 ASCII 文本记录机器码或常量数据的文件格式
- 这种文件常常用来记录将要存储到 ROM 中的数据,绝大多数下载器支持该格式
- 一个 hex 文件由多条记录组成,而每条记录由五个部分组成
1 :020000040800F2
2 :10000000000400204501000829030008BF02000881
3 :10001000250300088D0100089D0400080000000071
4 :100020000000000000000000000000004D03000878
5 :1000300091010008000000002B03000839040008AB
6 :100040005F0100085F0100085F0100085F01000810
各个部分介绍如下:
- “:”:每条记录的开头都使用冒号来表示一条记录的开始;
- ll :以 16 进制数表示这条记录的主体数据区的长度 (即后面 [dd⋯] 的长度);
- aaaa: 表示这条记录中的内容应存放到 FLASH 中的起始地址;
- tt:表示这条记录的类型,它包含中的各种类型;
- dd:表示一个字节的数据,一条记录中可以有多个字节数据,ll 区表示了它有多少个字节的数据;
- cc:表示本条记录的校验和,它是前面所有 16 进制数据 (除冒号外,两个为一组) 的和对256 取模运算的结果的补码
第一条记录解释如下:
(1) 02:表示这条记录数据区的长度为 2 字节;
(2) 0000:表示这条记录要存储到的地址;
(3) 04:表示这是一条扩展线性地址记录;
(4) 0800:由于这是一条扩展线性地址记录,所以这部分表示地址的高 16 位,与前面的“0000”结合在一起,表示要扩展的线性地址为“0x0800 0000”,这正好是 STM32 内部 FLASH 的首地址;
(5) F2:表示校验和,它的值为 (0x02+0x00+0x00+0x04+0x08+0x00)%256 的值再取补码。
再来看第二条记录:
(1) 10:表示这条记录数据区的长度为 2 字节;
(2) 0000:表示这条记录所在的地址,与前面的扩展记录结合,表示这条记录要存储的 FLASH首地址为 (0x0800 0000+0x0000);
(3) 00:表示这是一条数据记录,数据区的是地址;
(4) 000400204501000829030008BF020008:这是要按地址存储的数据;
(5) 81: 校验和
39.3.12.10 hex、bin 及 axf 文件的区别与联系
bin、hex 及 axf 文件都包含了指令代码bin 文件是最直接的代码映像,它记录的内容就是要存储到 FLASH 的二进制数据 (机器码本质上就是二进制数据)
hex 文件是一种使用十六进制符号表示的代码记录,记录了代码应该存储到 FLASH 的哪个地址,下载器可以根据这些信息辅助下载
axf 文件在前文已经解释,它不仅包含代码数据,还包含了工程的各种信息,因此它也是三个文件中最大的
同一个工程生成的 bin、hex 及 axf 文件的大小
我们打开本工程的“流水灯.bin”、“流水灯.hex”及由“流水灯.axf”使用 fromelf 工具输出的反汇编文件“流水灯 _axf_elfInfo_c.txt”文件,清晰地对比它们的差异
bin 文件,推荐使用 sublime 软件打开,它可以把二进制数以 ASCII 码呈现出来,便于阅读
在“流水灯 _axf_elfInfo_c.txt”文件中不仅可以看到代码数据,还有具体的标号、地址以及反汇编得到的代码
在 hex 文件中包含了地址信息以及地址中的内容,而在 bin 文件中仅包含了内容,连存储的地址信息都没有。
bin、hex 及 axf 文件中的数据内容都是相同的,它们存储的都是机器码。
该图是根据 axf 文件的 GPIO_Init 函数的机器码,在 bin 及 hex 中找到的对应位置。
- 经验丰富的人是有可能从 bin 或 hex 文件中恢复出汇编代码的,只是成本较高,但不是不可能。
如果芯片没有做任何加密措施,使用下载器可以直接从芯片读回它存储在 FLASH 中的数据,从而得到 bin 映像文件,根据芯片型号还原出部分代码即可进行修改,甚至不用修改代码,直接根据目标产品的硬件 PCB,抄出一样的板子,再把 bin 映像下载芯片,直接山寨出目标产品
- 在实际的生产中,一定要注意做好加密措施。
- 由于 axf 文件中含有大量的信息,且直接使用 fromelf即可反汇编代码,所以更不要随便泄露 axf 文件。
- lib 文件也能反使用 fromelf 文件反汇编代码,不过它不能还原出 C 代码
- 由于 lib 文件的主要目的是为了保护 C 源代码。
39.3.12.11 htm 静态调用图文件
*.build_log.htm 是工程的构建过程日志,而 *.htm 是链接器生成的静态调用图文件。
静态调用图文件
静态调用图文件中包含了整个工程各种函数之间互相调用的关系图
- 给出了静态占用最深的栈空间数量以及它对应的调用关系链。
“流水灯.htm”文件顶部说明:
- 本工程的静态栈空间最大占用 584 字节 (Maximum Stack Usage:584bytes)
- 这个占用最深的静态调用为“main ⇒ DbgConsole_Printf ⇒ StrFormatPrintf ⇒ ConvertFloatRadixNumToString⇒ __hardfp_pow ⇒ sqrt t”
- 这里给出的空间只是静态的栈使用统计,链接器无法统计动态使用情况
在 RT1052 中可修改分散加载文件改变堆栈的大小
- 如果空间不足,可从该文件中了解到调用深度的信息,然后优化该代码。
- 建议在实际的大型工程应用中 (特别是使用了各种外部库时,如 Lwip/emWin/Fatfs 等),要查看本静态调用图文件,了解程序的栈使用情况,给程序分配合适的栈空间
Listing 目录下的文件
Listing 目录下包含了 *.map 及 *.lst 文件
- lst 文件仅包含了一些汇编符号的链接信息
map 文件说明
map 文件是由链接器生成,主要包含交叉链接信息
- 各种符号之间的引用
- 整个工程的 Code、RO-data、RW-data 以及 ZI-data 的详细及汇总信息
- 内容中主要包含了“节区的跨文件引用”、“删除无用节区”、“符号映像表”、“存储器映像索引”以及“映像组件大小”
Sct分散加载文件的格式与添加方式
当工程按默认配置构建时,MDK 会根据我们选择的芯片型号,获知芯片的内部 FLASH 及内部SRAM 存储器概况,生成一个以工程名命名的后缀为 *.sct 的分散加载文件 (Linker Control File,scatter loading),
- 链接器根据该文件的配置分配各个节区地址,生成分散加载代码,因此我们通过修改该文件可以定制具体节区的存储位置。