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

前言:

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

在这里插入图片描述


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


1. MDK 相关文件

1.1 uvprojx 文件

uvprojx 文件就是我们平时双击打开的工程文件,它记录了整个工程的结构,如芯片类型、工程包含了哪些源文件等内容,见下图 :

在这里插入图片描述

1.2 uvoptx 文件

uvoptx 文件记录了工程的配置选项,如下载器的类型、变量跟踪配置、断点位置以及当前已打开的文件等等,见下图:

在这里插入图片描述

1.3 uvguix 文件

uvguix文件记录了 MDK 软件的 GUI 布局,如代码编辑区窗口的大小、编译输出提示窗口的位置等等。

在这里插入图片描述

uvprojx、uvoptx 及 uvguix 都是使用 XML 格式记录的文件,若使用记事本打开可以看到 XML 代码,见下图。而当使用 MDK 软件打开时,它根据这些文件的 XML 记录加载工程的各种参数,使得我们每次重新打开工程时,都能恢复上一次的工作环境。

在这里插入图片描述
这些工程参数都是当 MDK 正常退出时才会被写入保存,若 MDK 错误退出时(如使用 Windows 的任务管理器强制关闭),工程配置参数的最新更改是不会被记录的,重新打开工程时要再次配置。根据这几个文件的记录类型,可以知道 uvprojx 文件是最重要的,删掉它我们就无法再正常打开工程了,而 uvoptx 及 uvguix 文件并不是必须的,可以删除,重新使用 MDK 打开 uvprojx 工程文件后,会以默认参数重新创建 uvoptx 及 uvguix 文件。(所以当使用 Git/SVN 等代码管理的时候,往往只保留 uvproj x文件)

2. 源文件

源文件是工程中我们最熟悉的内容了,它们就是我们编写的各种源代码,MDK 支持 c、cpp、h、s、inc 类型的源代码文件,其中 c、cpp 分别是 c/c++ 语言的源代码,h 是它们的头文件,s 是汇编文件,inc 是汇编文件的头文件,可使用 “$include” 语法包含。编译器根据工程中的源文件最终生成机器码。

3 . Output 目录下生成的文件

点击 MDK 中的编译按钮,它会根据工程的配置及工程中的源文件输出各种对象和列表文件,在工程的 “Options for Targe->Output->Select Folder for Objects” 和 “Options for Targe->Listing->Select Folder for Listings” 选项配置它们的输出路径::Output 输出路径、Listing 输出路径。

在这里插入图片描述

在这里插入图片描述

编译后 Output 和 Listing 目录下生成的文件见下图:

在这里插入图片描述

3.1 lib 库文件

在某些场合下我们希望提供给第三方一个可用的代码库,但不希望对方看到源码,这个时候我们就可以把工程生成 lib 文件 (Library file) 提供给对方,在 MDK 中可配置 "Options for Target->Create Library"选 项把工程编译成库文件,见下图:

在这里插入图片描述
工程中生成可执行文件或库文件只能二选一,默认编译是生成可执行文件的,可执行文件即我们下载到芯片上直接运行的机器码。

得到生成的 .lib 文件后,可把它像 C 文件一样添加到其它工程中,并在该工程调用 lib 提供的函数接口,除了不能看到 .lib 文件的源码,在应用方面它跟 C 源文件没有区别。

3.2 dep、d 依赖文件

.dep 和 .d 文件 (Dependency file) 记录的是工程或其它文件的依赖,主要记录了引用的头文件路径,其中 .dep 是整个工程的依赖,它以工程名命名,而 .d 是单个源文件的依赖,它们以对应的源文件名命名。这些记录使用文本格式存储,我们可直接使用记事本打开,如下图:

在这里插入图片描述
在这里插入图片描述

3.3 crf 交叉引用文件

.crf 是交叉引用文件 (Cross-Reference file),它主要包含了浏览信息 (browse information),即源代码中的宏定义、变量及函数的定义和声明的位置。

我们在代码编辑器中点击 “Go To Definition Of ‘xxxx’” 可实现浏览跳转,见下图,跳转的时候,MDK 就是通过 .crf 文件查找出跳转位置的。

在这里插入图片描述通过配置 MDK 中的 “Option for Target->Output->Browse Information” 选项可以设置编译时是否生成浏览信息,见下图。只有勾选该选项并编译后,才能实现上面的浏览跳转功能。

在这里插入图片描述
.crf 文件使用了特定的格式表示,直接用文本编辑器打开会看到大部分乱码,见下图,这里不作深入研究。

在这里插入图片描述

3.4 o、axf及elf文件

.o、.elf、.axf、.bin 及 .hex 文件都存储了编译器根据源代码生成的机器码,根据应用场合的不同,它们又有所区别。

3.4.1 ELF 文件说明

.o、.elf、.axf 以及前面提到的 lib 文件都是属于目标文件,它们都是使用 ELF 格式来存储的。

ELF 是Executable and Linking Format 的缩写,译为可执行链接格式,该格式用于记录目标文件的内容。在 Linux 及 Windows 系统下都有使用该格式的文件(或类似格式)用于记录应用程序的内容,告诉操作系统如何链接、加载及执行该应用程序。

目标文件主要有如下三种类型:

(1) 可重定位的文件 (Relocatable File):包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件。 这种文件一般由编译器根据源代码生成。

例如 MDK 的 armcc 和 armasm 生成的 .o 文件就是这一类,另外还有 Linux 的 .o 文件,Windows 的 .obj 文件。

(2) 可执行文件 (Executable File):它包含适合于执行的程序,它内部组织的代码数据都有固定的地址(或相对于基地址的偏移),系统可根据这些地址信息把程序加载到内存执行。这种文件一般由链接器根据可重定位文件链接而成,它主要是组织各个可重定位文件,给它们的代码及数据一一打上地址标号,固定其在程序内部的位置,链接后,程序内部各种代码及数据段不可再重定位(即不能再参与链接器的链接)。

例如 MDK 的 armlink 生成的 .elf 及 .axf 文件,(使用 gcc 编译工具可生成 .elf 文件,用 armlink 生成的是 .axf 文件, .axf 文件在 .elf 之外,增加了调试使用的信息,其余区别不大,后面我们仅讲解 .axf 文件),另外还有 Linux 的 /bin/bash 文件,Windows 的 .exe 文件。

(3) 共享目标文件 (Shared Object File): 它的定义比较难理解,我们直接举例,MDK 生成的 .lib 文件就属于共享目标文件,它可以继续参与链接,加入到可执行文件之中。另外,Linux 的 .so,如 /lib/ glibc-2.5.so,Windows 的 DLL 都属于这一类。

3.4.1.1 o文件与axf文件的关系

根据上面的分类,我们了解到, .axf 文件是由多个 .o 文件链接而成的,而 .o 文件由相应的源文件编译而成,一个源文件对应一个 .o 文件。它们的关系见下图:

在这里插入图片描述

上图中的中间代表的是 armlink 链接器,在它的右侧是输入链接器的 .o 文件,左侧是它输出的 .axf 文件。

可以看到,由于都使用 ELF 文件格式, .o 与 .axf 文件的结构是类似的,它们包含 ELF 文件头、程序头、节区 (section) 以及节区头部表。各个部分的功能说明如下:

(1)ELF 文件头用来描述整个文件的组织,例如数据的大小端格式,程序头、节区头在文件中的位置等。

(2)程序头告诉系统如何加载程序,例如程序主体存储在本文件的哪个位置,程序的大小,程序要加载到内存什么地址等等。MDK 的可重定位文件 .o 不包含这部分内容,因为它还不是可执行文件,而 armlink 输出的 .axf 文件就包含该内容了。

(3)节区是 .o 文件的独立数据区域,它包含提供给链接视图使用的大量信息,如指令 (Code)、数据 (RO、RW、ZI-data)、符号表(函数、变量名等)、重定位信息等,例如每个由 C 语言定义的函数在 .o 文件中都会有一个独立的节区;

(4)存储在最后的节区头则包含了本文件节区的信息,如节区名称、大小等等。

总的来说,链接器把各个 .o 文件的节区归类、排列,根据目标器件的情况编排地址生成输出,汇总到 .axf 文件。例如,见下图,“多彩流水灯” 工程中在 “bsp_led.c” 文件中有一个 LED_GPIO_Config 函数,而它内部调用了 “stm32f4xx_gpio.c” 的 GPIO_Init 函数,经过 armcc 编译后,LED_GPIO_Config 及 GPIO_Iint 函数都成了指令代码,分别存储在 bsp_led.o 及 stm32f4xx_gpio.o 文件中,这些指令在 .o 文件都没有指定地址,仅包含了内容、大小以及调用的链接信息,而经过链接器后,链接器给它们都分配了特定的地址,并且把地址根据调用指向链接起来。

在这里插入图片描述

3.4.1.2 ELF 文件头

接下来我们看看具体文件的内容,使用 fromelf 文件可以查看 .o、 .axf 及 .lib 文件的 ELF 信息。

使用命令行,切换到文件所在的目录,输入 “fromelf –text –v bsp_led.o” 命令,可控制输出 bsp_led.o 的详细信息,见下图。利用 “-c、-z” 等选项还可输出反汇编指令文件、代码及数据文件等信息,请亲手尝试一下。

在这里插入图片描述为了便于阅读,已使用 fromelf 指令生成了 “多彩流水灯.axf”、“bsp_led” 及"多彩流水灯 .lib" 的 ELF 信息,并已把这些信息保存在独立的文件中,参见下表。

在这里插入图片描述

========================================================================
 ** ELF Header Information

 File Name:

 .\bsp_led.o         //bsp_led.o文件

 Machine class: ELFCLASS32 (32-bit) //32位机

 Data encoding: ELFDATA2LSB (Little endian) //小端格式

 Header version: EV_CURRENT (Current version)

 Operating System ABI: none

 ABI Version: 0

 File Type: ET_REL (Relocatable object) (1) //可重定位文件类型

 Machine: EM_ARM (ARM)

 Entry offset (in SHF_ENTRYSECT section): 0x00000000

 Flags: None (0x05000000)

 ARM ELF revision: 5 (ABI version 2)

 Built with

 Component: ARM Compiler 5.06 (build 20) Tool: armasm [4d35a2]

 Component: ARM Compiler 5.06 (build 20) Tool: armlink [4d35a3]

 Header size: 52 bytes (0x34)

 Program header entry size: 0 bytes (0x0)    //程序头大小

 Section header entry size: 40 bytes (0x28)

 Program header entries: 0

 Section header entries: 246

 Program header offset: 0 (0x00000000) //程序头在文件中的位置(没有程序头)

 Section header offset: 507224 (0x0007bd58) //节区头在文件中的位置

 Section header string table index: 243


=====================================================================

在上述代码中已加入了部分注释,解释了相应项的意义,值得一提的是在这个 .o 文件中,它的 ELF 文件头中告诉我们它的程序头 (Program header) 大小为 “0 bytes”,且程序头所在的文件位置偏移也为 “0”,这说明它是没有程序头的。

3.4.1.3 程序头
 ===================================================================



 ** ELF Header Information



 File Name:
 .\多彩流水灯.axf //多彩流水灯.axf 文件



 Machine class: ELFCLASS32 (32-bit) //32位机

 Data encoding: ELFDATA2LSB (Little endian) //小端格式

 Header version: EV_CURRENT (Current version)

 Operating System ABI: none

 ABI Version: 0

 File Type: ET_EXEC (Executable) (2) //可执行文件类型

 Machine: EM_ARM (ARM)


 Image Entry point: 0x080001ad

 Flags: EF_ARM_HASENTRY + EF_ARM_ABI_FLOAT_SOFT (0x05000202)



 ARM ELF revision: 5 (ABI version 2)



 Conforms to Soft float procedure-call standard



 Built with

 Component: ARM Compiler 5.06 (build 20) Tool: armasm [4d35a2]

 Component: ARM Compiler 5.06 (build 20) Tool: armlink [4d35a3]



 Header size: 52 bytes (0x34)

 Program header entry size: 32 bytes (0x20)

 Section header entry size: 40 bytes (0x28)



 Program header entries: 1

 Section header entries: 15



 Program header offset: 335252 (0x00051d94) //程序头在文件中的位置

 Section header offset: 335284 (0x00051db4) //节区头在文件中的位置



 Section header string table index: 14



 =================================================================


 ** Program header #0



 Type : PT_LOAD (1) //表示这是可加载的内容

 File Offset : 52 (0x34) //在文件中的偏移

 Virtual Addr : 0x08000000 //虚拟地址(此处等于物理地址)

 Physical Addr : 0x08000000 //物理地址

 Size in file : 1456 bytes (0x5b0) //程序在文件中占据的大小

 Size in memory: 2480 bytes (0x9b0) //若程序加载到内存,占据的内存空间

 Flags : PF_X + PF_W + PF_R + PF_ARM_ENTRY (0x80000007)

 Alignment : 8 //地址对齐


===============================================================

对比之下,可发现 .axf 文件的 ELF 文件头对程序头的大小说明为非 0 值,且给出了它在文件的偏移地址,在输出信息之中,包含了程序头的详细信息。可看到,程序头的 “Physical Addr” 描述了本程序要加载到的内存地址 “0x0800 0000”,正好是 STM3 2内部 FLASH 的首地址;“size in file” 描述了本程序占据的空间大小为 “1456 bytes”,它正是程序烧录到 FLASH 中需要占据的空间。

3.4.1.4 节区头

在 ELF 的原文件中,紧接着程序头的一般是节区的主体信息,在节区主体信息之后是描述节区主体信息的节区头.

 ====================================
 
 // Section #4

 Name : i.LED_GPIO_Config //节区名

 //此节区包含程序定义的信息,其格式和含义都由程序来解释。

 Type : SHT_PROGBITS (0x00000001)

 //此节区在进程执行过程中占用内存。节区包含可执行的机器指令。

 Flags :SHF_ALLOC + SHF_EXECINSTR (0x00000006)

 Addr : 0x00000000 //地址

 File Offset : 68 (0x44)        //在文件中的偏移

 Size : 116 bytes (0x74) //大小

 Link : SHN_UNDEF

 Info : 0

 Alignment : 4 //字节对齐

 Entry Size : 0

 ====================================
 

这个节区的名称为 LED_GPIO_Config,它正好是我们在 bsp_led.c 文件中定义的函数名,这个节区头描述的是该函数被编译后的节区信息,其中包含了节区的类型(指令类型)、节区应存储到的地址 (0x00000000)、它主体信息在文件位置中的偏移 (68) 以及节区的大小 (116 bytes)。

由于 .o 文件是可重定位文件,所以它的地址并没有被分配,是 0x00000000(假如文件中还有其它函数,该函数生成的节区中,对应的地址描述也都是 0)。当链接器链接时,根据这个节区头信息,在文件中找到它的主体内容,并根据它的类型,把它加入到主程序中,并分配实际地址,链接后生成的 .axf 文件,我们再来看看它的内容:

 ========================================================================

 ** Section #1

 Name : ER_IROM1 //节区名

 //此节区包含程序定义的信息,其格式和含义都由程序来解释。

 Type : SHT_PROGBITS (0x00000001)


 //此节区在进程执行过程中占用内存。节区包含可执行的机器指令

 Flags : SHF_ALLOC + SHF_EXECINSTR (0x00000006)

 Addr : 0x08000000 //地址

 File Offset : 52 (0x34)

 Size : 1456 bytes (0x5b0) //大小

 Link : SHN_UNDEF

 Info : 0

 Alignment : 4

 Entry Size : 0

 ====================================

 ** Section #2


 Name : RW_IRAM1 //节区名


 //包含将出现在程序的内存映像中的为初始

 //化数据。根据定义,当程序开始执行,系统

 //将把这些数据初始化为 0。

 Type : SHT_NOBITS (0x00000008)


 //此节区在进程执行过程中占用内存。节区包含进程执行过程中将可写的数据。

 Flags : SHF_ALLOC + SHF_WRITE (0x00000003)

 Addr : 0x20000000 //地址

 File Offset : 1508 (0x5e4)

 Size : 1024 bytes (0x400) //大小

 Link : SHN_UNDEF

 Info : 0

 Alignment : 8

 Entry Size : 0

 ====================================
 

在 .axf 文件中,主要包含了两个节区,一个名为 ER_IROM1,一个名为 RW_IRAM1,这些节区头信息中除了具有 .o 文件中节区头描述的节区类型、文件位置偏移、大小之外,更重要的是它们都有具体的地址描述,其中 ER_IROM1 的地址为 0x08000000,而 RW_IRAM1 的地址为 0x20000000,它们正好是内部 FLASH 及 SRAM 的首地址,对应节区的大小就是程序需要占用 FLASH 及 SRAM 空间的实际大小。

也就是说,经过链接器后,它生成的 .axf 文件已经汇总了其它 .o 文件的所有内容,生成的 ER_IROM1 节区内容可直接写入到 STM32 内部 FLASH 的具体位置。例如,前面 .o 文件中的 i.LED_GPIO_Config 节区已经被加入到 .axf 文件的ER_IROM1 节区的某地址。

3.4.1.5 节区主体及反汇编代码

使用 fromelf 的 -c 选项可以查看部分节区的主体信息,对于指令节区,可根据其内容查看相应的反汇编代码。


//.o文件的LED_GPIO_Config节区及反汇编代码

 ** Section #4 'i.LED_GPIO_Config' (SHT_PROGBITS) [SHF_ALLOC + SHF_EXECINSTR]

 Size : 116 bytes (alignment 4)

 Address: 0x00000000

 $t

 i.LED_GPIO_Config

 LED_GPIO_Config

 // 地址内容 (ASCII码) 内容对应的代码

 // (无意义)

 0x00000000: e92d41fc -..A PUSH {r2-r8,lr}

 0x00000004: 2101 .! MOVS r1,#1

 0x00000006: 2088 . MOVS r0,#0x88

 0x00000008: f7fffffe .... BL RCC_AHB1PeriphClockCmd

 0x0000000c: f44f6580 O..e MOV r5,#0x400
 
 0x00000010: 9500 .. STR r5,[sp,#0]

 0x00000012: 2101 .! MOVS r1,#1

 0x00000014: f88d1004 .... STRB r1,[sp,#4]

 0x00000018: 2000 . MOVS r0,#0

 0x0000001a: f88d0006 .... STRB r0,[sp,#6]

 0x0000001e: f88d1007 .... STRB r1,[sp,#7]

 0x00000022: f88d0005 .... STRB r0,[sp,#5]

 0x00000026: 4f11 .O LDR r7,[pc,#68] ;

 0x00000028: 4669 iF MOV r1,sp

 0x0000002a: 4638 8F MOV r0,r7

 0x0000002c: f7fffffe .... BL GPIO_Init

 0x00000030: 006c l. LSLS r4,r5,#1

 /*....以下省略**/
 

可看到,由于这是 .o 文件,它的节区地址还是没有分配的,基地址为 0x00000000,接着在 LED_GPIO_Config 标号之后,列出了一个表,表中包含了地址偏移、相应地址中的内容以及根据内容反汇编得到的指令。细看汇编指令,还可看到它包含了跳转到 RCC_AHB1PeriphClockCmd 及 GPIO_Init 标号的语句,而且这两个跳转语句原来的内容都是 “f7fffffe”,这是因为还 .o 文件中并没有 RCC_AHB1PeriphClockCmd 及 GPIO_Init 标号的具体地址索引,在 .axf 文件中,这是不一样的。


//.axf文件的LED_GPIO_Config反汇编代码
 i.LED_GPIO_Config

 LED_GPIO_Config

 0x080002a4: e92d41fc -..A PUSH {r2-r8,lr}

 0x080002a8: 2101 .! MOVS r1,#1

 0x080002aa: 2088 . MOVS r0,#0x88

 0x080002ac: f000f838 ..8. BL RCC_AHB1PeriphClockCmd ; 0x8000320

 0x080002b0: f44f6580 O..e MOV r5,#0x400

 0x080002b4: 9500 .. STR r5,[sp,#0]

 0x080002b6: 2101 .! MOVS r1,#1

 0x080002b8: f88d1004 .... STRB r1,[sp,#4]

 0x080002bc: 2000 . MOVS r0,#0

 0x080002be: f88d0006 .... STRB r0,[sp,#6]

 0x080002c2: f88d1007 .... STRB r1,[sp,#7]

 0x080002c6: f88d0005 .... STRB r0,[sp,#5]

 0x080002ca: 4f11 .O LDR r7,[pc,#68] ; [0x8000310] = 0x40021c00

 0x080002cc: 4669 iF MOV r1,sp

 0x080002ce: 4638 8F MOV r0,r7

 0x080002d0: f7ffffa5 .... BL GPIO_Init ; 0x800021e

 0x080002d4: 006c l. LSLS r4,r5,#1

 /*....以下省略**/
 

可看到,除了基地址以及跳转地址不同之外,LED_GPIO_Config 中的内容跟 .o 文件中的一样。另外,由于 .o 是独立的文件,而 .axf 是整个工程汇总的文件,所以在 .axf 中包含了所有调用到 .o 文件节区的内容。

在 .axf 文件中,跳转到 RCC_AHB1PeriphClockCmd 及 GPIO_Init 标号的这两个指令后都有注释,分别是 “; 0x8000320” 及 “; 0x800021e”,它们是这两个标号所在的具体地址,而且这两个跳转语句的跟 .o 中的也有区别,内容分别为 “f000f838e” 及 “f7ffffa5”( .o中的均为f7fffffe)。这就是链接器链接的含义,它把不同 .o 中的内容链接起来了。

3.4.1.6 分散加载代码

学习至此,还有一个疑问,前面提到程序有存储态及运行态,它们之间应有一个转化过程,把存储在 FLASH 中的 RW-data 数据拷贝至 SRAM。然而我们的工程中并没有编写这样的代码,在汇编文件中也查不到该过程,芯片是如何知道 FLASH 的哪些数据应拷贝到 SRAM 的哪些区域呢?程序中具有一段名为 “__scatterload” 的分散加载代码,它是由 armlink 链接器自动生成的.


//分散加载代码

 .text

 __scatterload

 __scatterload_rt2

 0x080001e4: 4c06 .L LDR r4,[pc,#24] ; [0x8000200] = 0x80005a0

 0x080001e6: 4d07 .M LDR r5,[pc,#28] ; [0x8000204] = 0x80005b0

 0x080001e8: e006 .. B 0x80001f8 ; __scatterload + 20

 0x080001ea: 68e0 .h LDR r0,[r4,#0xc]

 0x080001ec: f0400301 @... ORR r3,r0,#1

 0x080001f0: e8940007 .... LDM r4,{r0-r2}

 0x080001f4: 4798 .G BLX r3

 0x080001f6: 3410 .4 ADDS r4,r4,#0x10

 0x080001f8: 42ac .B CMP r4,r5

 0x080001fa: d3f6 .. BCC 0x80001ea ; __scatterload + 6

 0x080001fc: f7ffffda .... BL __main_after_scatterload ; 0x80001b4

 $d

 0x08000200: 080005a0 .... DCD 134219168

 0x08000204: 080005b0 .... DCD 134219184
 

这段分散加载代码包含了拷贝过程( LDM 复制指令),而 LDM 指令的操作数中包含了加载的源地址,这些地址中包含了内部 FLASH 存储的 RW-data 数据。而 "__scatterload " 的代码会被 “__main” 函数调用,__main 在启动文件中的 “Reset_Handler” 会被调用,因而,在主体程序执行前,已经完成了分散加载过程。


 _main
 
 _main_stk

 0x080001ac: f8dfd00c .... LDR sp,__lit__00000000 ; [0x80001bc] = 0x20000400

 .ARM.Collect$$$$00000004

 _main_scatterload

 0x080001b0: f000f818 .... BL __scatterload ; 0x80001e4
 

3.5 hex文件及bin文件

若编译过程无误,即可把工程生成前面对应的 .axf 文件,而在 MDK 中使用下载器 (DAP/JLINK/ULINK 等)下载程序或仿真的时候,MDK 调用的就是 .axf 文件,它解释该文件,然后控制下载器把 .axf 中的代码内容下载到 STM32 芯片对应的存储空间,然后复位后芯片就开始执行代码了。

然而,脱离了 MDK 或 IAR 等工具,下载器就无法直接使用 .axf 文件下载代码了,它们一般仅支持 hex 和 bin 格式的代码数据文件。默认情况下 MDK 都不会生成 hex 及 bin 文件,需要配置工程选项或使用 fromelf 命令。

3.5.1 生成hex文件

生成 hex 文件的配置比较简单,在 “Options for Target->Output->Create Hex File” 中勾选该选项,然后编译工程即可,见下图 :

在这里插入图片描述

3.5.2 生成bin文件

使用 MDK 生成 bin 文件需要使用 fromelf 命令,在 MDK 的 “Options For Target->Users” 中加入下图的命令。

在这里插入图片描述

图中的指令内容为:

“fromelf --bin --output …\Output\多彩流水灯.bin …\Output\多彩流水灯.axf”

该指令是根据本机及工程的配置而写的,在不同的系统环境或不同的工程中,指令内容都不一样,我们需要理解它,才能为自己的工程定制指令,首先看看 fromelf 的帮助,见下图:

在这里插入图片描述

我们在 MDK 输入的指令格式是遵守 fromelf 帮助里的指令格式说明的,其格式为:

“fromelf [options] input_file”

其中 optinos 是指令选项,一个指令支持输入多个选项,每个选项之间使用空格隔开,我们的实例中使用 “–bin” 选项设置输出 bin 文件,使用 “–output file” 选项设置输出文件的名字为 “…\Output\多彩流水灯.bin”,这个名字是一个相对路径格式,如果不了解如何使用 “…” 表示路径,可使用 MDK 命令输入框后面的文件夹图标打开文件浏览器选择文件,在命令的最后使用 “…\Output\多彩流水灯.axf” 作为命令的输入文件。具体的格式分解见下图:

在这里插入图片描述
fromelf 需要根据工程的 .axf 文件输入来转换得到 bin 文件,所以在命令的输入文件参数中要选择本工程对应的 .axf 文件,在 MDK 命令输入栏中,我们把 fromelf 指令放置在 “After Build/Rebuild” (工程构建完成后执行)一栏也是基于这个考虑,这样设置后,工程构建完成生成了最新的 .axf 文件,MDK 再执行 fromelf 指令,从而得到最新的 bin 文件。

设置完成生成 hex 的选项或添加了生成 bin 的用户指令后,点击工程的编译 (build)按钮,重新编译工程,成功后可看到下图中的输出。打开相应的目录即可找到文件,若找不到 bin 文件,请查看提示输出栏执行指令的信息,根据信息改正 fromelf 指令。

在这里插入图片描述其中 bin 文件是纯二进制数据,无特殊格式,接下来我们了解一下 hex 文件格式。

3.5.3 hex文件格式

hex 是 Intel 公司制定的一种使用 ASCII 文本记录机器码或常量数据的文件格式,这种文件常常用来记录将要存储到 ROM 中的数据,绝大多数下载器支持该格式。

一个 hex 文件由多条记录组成,而每条记录由五个部分组成,格式形如 “: ll aaaatt[dd…]cc”,例如本"多彩流水灯"工程生成的 hex 文件前几条记录如下:

:020000040800F2

:1000000000040020C10100081B030008A30200082F

:100010001903000809020008690400080000000034

:100020000000000000000000000000003D03000888

:100030000B020008000000001D0300081504000862

:10004000DB010008DB010008DB010008DB01000820

记录的各个部分介绍如下:

(1) “:” :每条记录的开头都使用冒号来表示一条记录的开始;

(2) ll :以 16 进制数表示这条记录的主体数据区的长度(即后面[dd…]的长度);

(3) aaaa:表示这条记录中的内容应存放到 FLASH 中的起始地址;

(4) tt:表示这条记录的类型,它包含中的各种类型;

在这里插入图片描述

(5) dd:表示一个字节的数据,一条记录中可以有多个字节数据,ll 区表示了它有多少个字节的数据;

(6) 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:表示这条记录数据区的长度为 16 字节;

(2) 0000:表示这条记录所在的地址,与前面的扩展记录结合,表示这条记录要存储的 FLASH 首地址为(0x0800 0000+0x0000);

(3) 00:表示这是一条数据记录,数据区的是地址;

(4) 00040020C10100081B030008A3020008:这是要按地址存储的数据;

(5) 2F:校验和

为了更清楚地对比 bin、hex 及 axf 文件的差异,我们来查看这些文件内部记录的信息来进行对比。

3.5.4 hex、bin及axf文件的区别与联系

bin、hex 及 axf 文件都包含了指令代码,但它们的信息丰富程度是不一样的。

(1) bin 文件是最直接的代码映像,它记录的内容就是要存储到 FLASH 的二进制数据(机器码本质上就是二进制数据),在 FLASH 中是什么形式它就是什么形式,没有任何辅助信息,包括大小端格式也没有,因此下载器需要有针对芯片 FLASH 平台的辅助文件才能正常下载(一般下载器程序会有匹配的这些信息);

(2) hex 文件是一种使用十六进制符号表示的代码记录,记录了代码应该存储到 FLASH 的哪个地址,下载器可以根据这些信息辅助下载;

(3) axf 文件在前文已经解释,它不仅包含代码数据,还包含了工程的各种信息,因此它也是三个文件中最大的。

同一个工程生成的 bin、hex 及 axf 文件的大小见下图:

在这里插入图片描述

实际上,这个工程要烧写到 FLASH 的内容总大小为 1456 字节,然而在 Windows 中查看的 bin 文件却比它大( bin 文件是 FLASH 的代码映像,大小应一致),这是因为 Windows 文件显示单位的原因,使用右键查看文件的属性,可以查看它实际记录内容的大小,见下图:

在这里插入图片描述

接下来我们打开本工程的 “多彩流水灯.bin”、“多彩流水灯 .hex “及由"多彩流水灯.axf” 使用 fromelf 工具输出的反汇编文件"多彩流水灯 _axf_elfInfo_c.txt” 文件,清晰地对比它们的差异,见下图。如果您想要亲自阅读自己电脑上的 bin 文件,推荐使用 sublime 软件打开,它可以把二进制数以 ASCII 码呈现出来,便于阅读。

在这里插入图片描述
在 hex 文件中包含了地址信息以及地址中的内容,而在 bin 文件中仅包含了内容,连存储的地址信息都没有。观察可知,bin、hex及axf 文件中的数据内容都是相同的,它们存储的都是机器码。这就是它们三都之间的区别与联系。

由于文件中存储的都是机器码,见下图,该图是根据 axf 文件的 GPIO_Init 函数的机器码,在 bin 及 hex 中找到的对应位置。所以经验丰富的人是有可能从 bin 或 hex 文件中恢复出汇编代码的,只是成本较高,但不是不可能。

在这里插入图片描述如果芯片没有做任何加密措施,使用下载器可以直接从芯片读回它存储在 FLASH 中的数据,从而得到 bin 映像文件,根据芯片型号还原出部分代码即可进行修改,甚至不用修改代码,直接根据目标产品的硬件 PCB,抄出一样的板子,再把 bin 映像下载芯片,直接山寨出目标产品,所以在实际的生产中,一定要注意做好加密措施。由于 axf 文件中含有大量的信息,且直接使用 fromelf 即可反汇编代码,所以更不要随便泄露 axf 文件。lib 文件也能反使用 fromelf 文件反汇编代码,不过它不能还原出 C 代码,由于 lib 文件的主要目的是为了保护 C 源代码,也算是达到了它的要求。

3.5.5 htm静态调用图文件

Output 目录下,有一个以工程文件命名的后缀为 .bulid_log.htm 及 .htm 文件,如"多彩流水灯 .bulid_log.htm" 及"多彩流水灯 .htm",它们都可以使用浏览器打开。其中 .build_log.htm 是工程的构建过程日志,而 .htm 是链接器生成的静态调用图文件。

在静态调用图文件中包含了整个工程各种函数之间互相调用的关系图,而且它还给出了静态占用最深的栈空间数量以及它对应的调用关系链。

在这里插入图片描述

该文件说明了本工程的静态栈空间最大占用 56 字节 (Maximum Stack Usage:56bytes),这个占用最深的静态调用为 “main->LED_GPIO_Config->GPIO_Init”。注意这里给出的空间只是静态的栈使用统计,链接器无法统计动态使用情况,例如链接器无法知道递归函数的递归深度。在本文件的后面还可查询到其它函数的调用情况及其它细节。

利用这些信息,我们可以大致了解工程中应该分配多少空间给栈,有空间余量的情况下,一般会设置比这个静态最深栈使用量大一倍,在 STM32 中可修改启动文件改变堆栈的大小;如果空间不足,可从该文件中了解到调用深度的信息,然后优化该代码。

注意:

查看了各个工程的静态调用图文件统计后,我们发现本书提供的一些比较大规模的工程例子,静态栈调用最大深度都已超出 STM32 启动文件默认的栈空间大小 0x00000400,即 1024 字节,但在当时的调试过程中却没有发现错误,因此我们也没有修改栈的默认大小(有一些工程调试时已发现问题,它们的栈空间就已经被我们改大了),虽然这些工程实际运行并没有错误,但这可能只是因为它使用的栈溢出 RAM 空间恰好没被程序其它部分修改而已。所以,建议您在实际的大型工程应用中(特别是使用了各种外部库时,如 Lwip/emWin/Fatfs 等),要查看本静态调用图文件,了解程序的栈使用情况,给程序分配合适的栈空间。(关于 Listing 目录下的介绍。请看下文——MDK的编译过程及文件类型全解——(三))


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

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值