MDK的编译过程及文件类型全解——(一)

前言:

为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。

在这里插入图片描述


本文转载自:第48章 MDK的编译过程及文件类型全解—零死角玩转STM32-F429系列


1. 简介

本章参考资料:MDK 的帮助手册《ARM Development Tools》,点击 MDK 界面的 “help->uVision Help” 菜单可打开该文件。关于 ELF 文件格式,参考配套资料里的《ELF文件格式》文件。

在本章中讲解了非常多的文件类型,学习时请跟着教程的节奏,打开实际工程中的文件来了解。

相信您已经非常熟练地使用 MDK 创建应用程序了,平时使用 MDK 编写源代码,然后编译生成机器码,再把机器码下载到 STM32 芯片上运行,但是这个编译、下载的过程 MDK 究竟做了什么工作?它编译后生成的各种文件又有什么作用?本章节将对这些过程进行讲解,了解编译及下载过程有助于理解芯片的工作原理,这些知识对制作 IAP(bootloader) 以及读写控制器内部 FLASH 的应用时非常重要。

2. 编译过程

2.1 编译过程简介

首先我们简单了解下 MDK 的编译过程,它与其它编译器的工作过程是类似的,该过程见下图。

在这里插入图片描述
编译过程生成流程:

(1) 编译:MDK 软件使用的编译器是 armcc 和 armasm,它们根据每个 c/c++ 和汇编源文件编译成对应的以 “.o” 为后缀名的对象文件 (Object Code,也称目标文件),其内容主要是从源文件编译得到的机器码,包含了代码、数据以及调试使用的信息;

(2) 链接:链接器 armlink 把各个 .o 文件及库文件链接成一个映像文件 “.axf” 或 “.elf”;

(3) 格式转换:一般来说 Windows 或 Linux 系统使用链接器直接生成可执行映像文件 elf 后,内核根据该文件的信息加载后,就可以运行程序了,但在单片机平台上,需要把该文件的内容加载到芯片上,所以还需要对链接器生成的 elf 映像文件利用格式转换器 fromelf 转换成 “.bin” 或 “.hex” 文件,交给下载器下载到芯片的FLASH 或 ROM 中。

2.2 具体工程中的编译过程

下面我们打开"多彩流水灯"的工程,以它为例进行讲解,其它工程的编译过程也是一样的,只是文件有差异。打开工程后,点击 MDK 的 “rebuild” 按钮,它会重新构建整个工程,构建的过程会在 MDK 下方的 “Build Output” 窗口输出提示信息,见下图。

在这里插入图片描述
构建工程的提示输出主要分 6 个部分,说明如下:

(1) 提示信息的第一部分说明构建过程调用的编译器。图中的编译器名字是 “V5.06(build 20)”,后面附带了该编译器所在的文件夹。在电脑上打开该路径,可看到该编译器包含上图中的各个编译工具,如 armar、armasm、armcc、armlink及 fromelf。armar 是用于把 .o 文件打包成 lib 文件的。

在这里插入图片描述(2)使用 armasm 编译汇编文件。图中列出了编译 startup 启动文件时的提示,编译后每个汇编源文件都对应有一个独立的 .o 文件。

(3)使用 armcc 编译 c/c++ 文件。图中列出了工程中所有的 c/c++ 文件的提示,同样地,编译后每个 c/c++ 源文件都对应有一个独立的 .o 文件。

(4) 使用 armlink 链接对象文件,根据程序的调用把各个 .o 文件的内容链接起来,最后生成程序的 axf 映像文件,并附带程序各个域大小的说明,包括 Code、RO-data、RW-data 及 ZI-data 的大小。

(5)使用 fromelf 生成下载格式文件,它根据 axf 映像文件转化成 hex 文件,并列出编译过程出现的错误 (Error) 和警告 (Warning) 数量。

(6) 最后一段提示给出了整个构建过程消耗的时间。

构建完成后,可在工程的 “Output” 及 “Listing” 目录下找到由以上过程生成的各种文件,见下图。

在这里插入图片描述

可以看到,每个 C 源文件都对应生成了 .o、.d 及 .crf 后缀的文件,还有一些额外的 .dep、.hex、.axf、.htm、.lnp、.sct、.lst 及 .map 文件。

3. 程序的组成、存储与运行

3.1 CODE、RO、RW、ZI Data域及堆栈空间

在工程的编译提示输出信息中有一个语句 “Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”,它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据(包括代码)被归到一个域,程序在存储或运行的时候,不同的域会呈现不同的状态,这些域的意义如下:

(1) Code:即代码域,它指的是编译器生成的机器指令,这些内容被存储到 ROM 区。

(2)RO-data:Read Only data,即只读数据域,它指程序中用到的只读数据,这些数据被存储在 ROM 区,因而程序不能修改其内容。例如 C 语言中 const 关键字定义的变量就是典型的 RO-data。

(3)RW-data:Read Write data,即可读写数据域,它指初始化为"非 0 值"的可读写数据,程序刚运行时,这些数据具有非 0 的初始值,且运行的时候它们会常驻在 RAM 区,因而应用程序可以修改其内容。例如 C 语言中使用定义的全局变量,且定义时赋予"非 0 值"给该变量进行初始化。

(4)ZI-data:Zero Initialie data,即 0 初始化数据,它指初始化为 " 0 值" 的可读写数据域,它与 RW-data 的区别是程序刚运行时这些数据初始值全都为 0,而后续运行过程与 RW-data 的性质一样,它们也常驻在 RAM 区,因而应用程序可以更改其内容。例如 C 语言中使用定义的全局变量,且定义时赋予 “0 值” 给该变量进行初始化(若定义该变量时没有赋予初始值,编译器会把它当 ZI-data 来对待,初始化为 0);

(5)ZI-data 的栈空间 (Stack) 及堆空间 (Heap):在 C 语言中,函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间。而使用 malloc 动态分配的变量属于堆空间。在程序中的栈空间和堆空间都是属于 ZI-data 区域的,这些空间都会被初始值化为 0 值。编译器给出的 ZI-data 占用的空间值中包含了堆栈的大小(经实际测试,若程序中完全没有使用 malloc 动态申请堆空间,编译器会优化,不把堆空间计算在内)。

综上所述,以程序的组成构件为例,它们所属的区域类别见下表:

在这里插入图片描述

3.2 程序的存储与运行

RW-data 和 ZI-data 它们仅仅是初始值不一样而已,为什么编译器非要把它们区分开?这就涉及到程序的存储状态了,应用程序具有静止状态和运行状态。静止状态的程序被存储在非易失存储器中,如 STM32 的内部 FLASH,因而系统掉电后也能正常保存。但是当程序在运行状态的时候,程序常常需要修改一些暂存数据,由于运行速度的要求,这些数据往往存放在内存中 (RAM),掉电后这些数据会丢失。因此,程序在静止与运行的时候它在存储器中的表现是不一样的,见下图 。

在这里插入图片描述图中的左侧是应用程序的存储状态,右侧是运行状态,而上方是 RAM 存储器区域,下方是R OM 存储器区域。

程序在存储状态时,RO 节 (RO section) 及 RW 节都被保存在 ROM 区。当程序开始运行时,内核直接从 ROM 中读取代码,并且在执行主体代码前,会先执行一段加载代码,它把 RW 节数据从 ROM 复制到 RAM,并且在 RAM 加入 ZI 节,ZI 节的数据都被初始化为 0。加载完后 RAM 区准备完毕,正式开始执行主体程序。

编译生成的 RW-data 的数据属于图中的 RW 节,ZI-data 的数据属于图中的 ZI 节。是否需要掉电保存,这就是把 RW-data 与 ZI-data 区别开来的原因,因为在RAM 创建数据的时候,默认值为 0,但如果有的数据要求初值非 0,那就需要使用 ROM 记录该初始值,运行时再复制到 RAM。

STM32 的 RO 区域不需要加载到 SRAM,内核直接从 FLASH 读取指令运行。计算机系统的应用程序运行过程很类似,不过计算机系统的程序在存储状态时位于硬盘,执行的时候甚至会把上述的 RO 区域(代码、只读数据)加载到内存,加快运行速度,还有虚拟内存管理单元 (MMU) 辅助加载数据,使得可以运行比物理内存还大的应用程序。而 STM32 没有 MMU,所以无法支持 Linux 和 Windows 系统。

当程序存储到 STM32 芯片的内部 FLASH 时(即 ROM 区),它占用的空间是 Code、RO-data 及 RW-data 的总和,所以如果这些内容比 STM32 芯片的 FLASH 空间大,程序就无法被正常保存了。当程序在执行的时候,需要占用内部 SRAM 空间(即 RAM 区),占用的空间包括 RW-data 和 ZI-data。应用程序在各个状态时各区域的组成见下表。

在这里插入图片描述
在 MDK 中,我们建立的工程一般会选择芯片型号,选择后就有确定的 FLASH 及 SRAM 大小,若代码超出了芯片的存储器的极限,编译器会提示错误,这时就需要裁剪程序了,裁剪时可针对超出的区域来优化。

4. 编译工具链

在前面编译过程中,MDK 调用了各种编译工具,平时我们直接配置 MDK,不需要学习如何使用它们,但了解它们是非常有好处的。例如,若希望使用 MDK 编译生成 bin 文件的,需要在 MDK 中输入指令控制 fromelf 工具;在本章后面讲解 AXF 及 .O 文件的时候,需要利用 fromelf 工具查看其文件信息,这都是无法直接通过 MDK 做到的。关于这些工具链的说明,在 MDK 的帮助手册《ARM Development Tools》都有详细讲解,点击 MDK 界面的 “help->uVision Help” 菜单可打开该文件。

4.1 设置环境变量

调用这些编译工具,需要用到 Windows 的"命令行提示符工具",为了让命令行方便地找到这些工具,我们先把工具链的目录添加到系统的环境变量中。查看本机工具链所在的具体目录可根据上一小节讲解的工程编译提示输出信息中找到,如本机的路径为 “D:\work\keil5\ARM\ARMCC\bin”。

4.1.1 添加路径到 PATH 环境变量

本文以 Win7 系统为例添加工具链的路径到 PATH 环境变量,其它系统是类似的。

(1) 右键电脑系统的"计算机图标",在弹出的菜单中选择"属性",见下图:

在这里插入图片描述(2) 在弹出的属性页面依次点击"高级系统设置 “->” 环境变量",在用户变量一栏中找到名为 “PATH” 的变量,若没有该变量,则新建一个。编辑 “PATH” 变量,在它的变量值中输入工具链的路径,如本机的是 “;D:\work\keil5\ARM\ARMCC\bin”,注意要使用"分号 “;” 让它与其它路径分隔开,输入完毕后依次点确定,见下图 ;

在这里插入图片描述
(3) 打开 Windows 的命令行,点击系统的"开始菜单",在搜索框输入 “cmd”,在搜索结果中点击 “cmd.exe” 即可打开命令行,见下图;

在这里插入图片描述
(4) 在弹出的命令行窗口中输入 “fromelf” 回车,若窗口打印出 formelf 的帮助说明,那么路径正常,就可以开始后面的工作了;若提示"不是内部名外部命令,也不是可运行的程序…"信息,说明路径不对,请重新配置环境变量,并确认该工作目录下有编译工具链。

这个过程本质就是让命令行通过 “PATH” 路径找到 “fromelf.exe” 程序运行,默认运行 “fromelf.exe” 时它会输出自己的帮助信息,这就是工具链的调用过程,MDK 本质上也是如此调用工具链的,只是它集成为 GUI,相对于命令行对用户更友好,毕竟上述配置环境变量的过程已经让新手烦躁了。

4.2 armcc、armasm及armlink

接下来我们看看各个工具链的具体用法,主要以 armcc 为例。

(1)armcc

armcc 用于把 c/c++ 文件编译成 ARM 指令代码,编译后会输出 ELF 格式的 .O 文件(对象、目标文件),在命令行中输入 “armcc” 回车可调用该工具,它会打印帮助说明,见下图。

在这里插入图片描述帮助提示中分三部分,第一部分是 armcc 版本信息,第二部分是命令的用法,第三部分是主要命令选项。

根据命令用法: armcc [options] file1 file2 … filen ,在 [option] 位置可输入下面的 “–arm”、"–cpu list" 选项,若选项带文件输入,则把文件名填充在 file1 file2…的位置,这些文件一般是 c/c++ 文件。

根据它的帮助说明,"–cpu list" 可列出编译器支持的所有 cpu,我们在命令行中输入 “armcc --cpu list”,可查看下图中的cpu列表。

在这里插入图片描述
打开 MDK 的 Options for Targe->c/c++ 菜单,可看到 MDK 对编译器的控制命令,见下图。

在这里插入图片描述从该图中的命令可看到,它调用了 -c、-cpu –D –g –O0 等编译选项,当我们修改 MDK 的编译配置时,可看到该控制命令也会有相应的变化。然而我们无法在该编译选项框中输入命令,只能通过 MDK 提供的选项修改。

了解这些,我们就可以查询具体的 MDK 编译选项的具体信息了,如 c/c++ 选项中的 " Optimization:Leve 1(-O1)" 是什么功能呢?首先可了解到它是 “-O” 命令,命令后还带个数字,查看 MDK 的帮助手册,在 armcc 编译器说明章节,可详细了解,如下图。

在这里插入图片描述利用 MDK,我们一般不需要自己调用 armcc 工具,但经过这样的过程我们就会对 MDK 有更深入的认识。

(2)armasm

armasm 是汇编器,它把汇编文件编译成 .O 文件。与 armcc 类似,MDK 对 armasm 的调用选项可在 “Option for Target->Asm” 页面进行配置,见下图。

在这里插入图片描述
(3)armlink

armlink 是链接器,它把各个 .O 文件链接组合在一起生成 ELF 格式的 AXF 文件,AXF 文件是可执行的,下载器把该文件中的指令代码下载到芯片后,该芯片就能运行程序了;利用 armlink 还可以控制程序存储到指定的 ROM 或 RAM 地址。在 MDK 中可在 “Option for Target->Linker” 页面配置 armlink 选项,见下图 。

在这里插入图片描述
链接器默认是根据芯片类型的存储器分布来生成程序的,该存储器分布被记录在工程里的 sct 后缀的文件中,有特殊需要的话可自行编辑该文件,改变链接器的链接方式。

4.3 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” 一栏。

5.MDK工程的文件类型

除了上述编译过程生成的文件,MDK 工程中还包含了各种各样的文件,下面我们统一介绍,MDK 工程的常见文件类型见下表。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述这些文件主要分为MDK相关文件、源文件以及编译、链接器生成的文件。我们以"多彩流水灯"工程为例讲解各种文件的功能。(参看:MDK的编译过程及文件类型全解——(二))


转载自——第48章 MDK的编译过程及文件类型全解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章