关于嵌入式的bin、hex、axf、map

别在注释里陷得太深——注释很可能会误导你,你要调试的只是代码。

前言

记录工作中学习到的知识,在这里做些笔记,方便自己后面温习。


bin、hex、axf

bin、hex、axf 之间的关系

bin hex axf
数据 数据 数据
地址 地址
调试信息

所以同一工程中 bin、hex、axf 的文件大小为 .bin < .hex < .axf

假设 bin 文件是一个三无产品,那么hex就是一个带有信息的产品,而 axf 文件则是带有信息并且附了一张使用说明的产品。(我也不知道这样举例合不合理,意思到位就行)

因为 bin 文件没有地址信息,而 hex 文件带了地址信息,所以实际上我们使用烧录软件 (J-Flash.exe) 是这样的:

导入 .bin 时,因为没有地址信息,所以我们需要为它指定烧写的起始地址

导入 .hex 时,因为包含了地址信息,所以我们不需要指定起始地址,烧录工具会自动读取要烧录的地址


bin 文件

上面已经说了 bin 文件起始不包含地址信息,所以 bin 文件只是单纯的二进制文件,是没有格式的程序文件,只是包含了程序数据。我们看到烧录到单片机的是 .hex 文件,但是实际上,烧录软件会帮我们将 hex 文件的地址解析提取,最后还是烧录的 .bin 文件

下面这张图可以看出,J-Flash 解析 test.hex 地址信息后,实际上要烧录的还是 test.bin 的内容:


hex 文件

Hex 是由 Intel 制定的一种十六进制标准文件格式,是由编译器转换而成的一种用于下载到处理器里面的文件。

Hex 文件格式是由一行一行的十六进制数据组成,每行包含:开始、长度、数据、类型、校验和等重要信息。

一般 Hex 文件的记录格式如下:

Record mark Length Load offset Record type Info or Data Chksum
冒号 byte(1) byte(2~3) byte(4) byte(5~n) byte(n+1)
: 数据长度 起始地址 数据类型 数据 校验和
  • Record mark:标记头(数据头)
  • Length:表示本行的数据长度(Info or Data)
  • Load offset:表示本行数据的起始地址
  • Record type:数据类型,共分:

0x00 - Data Rrecord,数据记录
0x01 - End of FileRecord,用来标识文件结束,放在文件的最后,标识HEX文件的结尾
0x02 - Extended Segment Address Record,用来标识扩展段地址的记录,扩展段地址记录(HEX86),它包含4~19位数据地址段。由于普通的 Intel 的 HEX 记录文件只能记录 64K 的地址范围,所以大于 64K 的地址数据要靠扩展段地址记录
0x03 - Start Segment Address Record,开始段地址记录
0x04 - Extended Linear Address Record,用来标识扩展线性地址的记录,扩展线性地址记录也叫 32 位地址记录或者 HEX386 记录,这些记录包含了数据在存储器里真实地址的高 16 位。 当一个扩展线性地址记录被读取后,将一直保持有效,直到它被另一个扩展地址记录改变。因为它记录的是后面数据在存储器里存放的真实起始地址,所以它的起始地址偏移量 (Load offset) 总是 0000
0x05 - Start Linear Address Record,开始线性地址记录

  • Info or Data:数据代表一个字节的数据,一个记录可以有许多数据字节,数据字节数量应等于 Length
  • Chksum:一个字节,先将此字节前所有字节相加得到 sum,校验和=(0x100-sum & 0xFF) & 0xFF

说了这么多,还是来实际验证一下,这里我贴出了test.hex的前3行和后3行,中间的其他内容省略:

1
2
3
4
5
6
7
:020000040002F8
:1060000070F60320A1640200A96402008D640200FE
:10601000AD640200AF640200B16402000000000041
...(略n行)
:0CA2F000FFBF915A010401789495F0051D
:040000050002620192
:00000001FF

:02|0000|04|0002|F8(头):

  • 02 - 本行数据长度为 2
  • 0000 - 本行数据起始地址偏移为 0x0000
  • 04 - 数据类型是标识扩展线性地址的记录
  • 0002 - 本行 2 个数据为0x00和0x02,(这里 0x0002<<16=0x00020000,为基地址)
  • F8 - 校验和=0x100-(0x02+0x00+0x00+0x04+0x00+0x02)&0xFF=0xF8

:10|6000|00|70F60320A1640200A96402008D640200|FE

  • 10 - 本行数据长度为16
  • 6000 - 本行数据起始地址偏移为0x6000,所以这里记录的地址是0x000020000+0x6000=0x00026000(这个可以看上面J-Flash,test.hex的起始地址刚好是0x26000)
  • 00 - 数据类型是标识扩展线性地址的记录
  • 70F60320A1640200A96402008D640200 - 本行16个数据为0x70,0xF6…0x02,0x00
  • FE - 校验和=0x100-(0x10+0x60+0x00…+0x02+0x00)&0xFF=0xFE

:00|0000|01|FF(尾):

  • 00 - 本行数据长度为0
  • 0000 - 本行数据起始地址偏移为0x0000
  • 01 - 数据类型是标识文件结束
  • FF - 校验和=0x100-(0x00+0x00+0x00+0x01)&0xFF=0xFF

从上面这些可以验证出,hex 文件确实保存了 bin 文件的地址信息

实际上 hex 文件会大于 2 倍的 bin 文件大小的,bin 文件一个 byte 在 hex 文件中用 Ascill 编码则需要用两个字符来表示一个字节,而且 hex 又包括了其他信息,所以一般 hex > 2bin


axf 文件

axf,全称 ARM Executable File,它是由 ARM 编译器产生,除了包含 bin 的内容之外,还附加其他调试信息,这些调试信息加在可执行的二进制数据之前。调试时这些调试信息不会下载到 RAM 中,真正下载到 RAM 中的信息仅仅是可执行代码。

调试信息作用:

  1. 可将源代码包括注释夹在反汇编代码中,这样我们可随时切换到源代码中进行调试
  2. 我们还可以对程序中的函数调用情况进行跟踪(Keil可以通过Watch & Call Stack Window查看)
  3. 对变量进行跟踪(Keil可以通过Watch & Call Stack Window查看)

axf 文件转 bin 文件

fromelf 格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
fromelf [options] input_file  (命令的格式)

Options:
--help display this help screen (显示帮助信息)
--vsn display version information (显示版本信息)
--output file the output file. (defaults to stdout for -text format) (输出文件(默认的输出为文本格式))
--nodebug do not put debug areas in the output image (在生成的映象中不包含调试信息)
--nolinkview do not put sections in the output image (在生成的映象中不包含段的信息)

Binary Output Formats:
--bin Plain Binary (生成Plain Binary格式的文件)
--m32 Motorola 32 bit Hex (生成Motorola 32位十六进制格式的文件)
--i32 Intel 32 bit Hex (生成Intel 32位十六进制格式的文件)
--vhx Byte Oriented Hex format (面向字节的位十六进制格式的文件t)

--base addr Optionally set base address for m32,i32 (设置m32,i32格式文件的基地址)

Output Formats Requiring Debug Information (需要调试信息的格式)
--fieldoffsets Assembly Language Description of Structures/Classes (结构/类的汇编语言描述)
--expandarrays Arrays inside and outside structures are expanded (扩展数组内部和外部结构被扩展)

Other Output Formats:
--elf ELF
--text Text Information (显示文本信息)

Flags for Text Information
-v verbose (打印详细信息)
-a print data addresses (For images built with debug) (打印数据地址(针对带调试信息的映象))
-c disassemble code (打印反汇编代码)
-d print contents of data section (打印数据段的内容)
-e print exception tables (打印表达式表)
-g print debug tables (打印调试表)
-r print relocation information (打印重定位信息)
-s print symbol table (打印字符表)
-t print string table (打印字符串表)
-y print dynamic segment contents (打印动态段的内容)
-z print code and data size information (打印代码和数据大小的信息)

将 axf 文件转 bin 文件

在 Keil 的安装目录下,我的装在了 C 盘,在 C:\Keil_v5\ARM\ARMCC\bin 中,可以找到 fromelf.exe 这个可执行文件

使用命令行工具,例如 win10 自带的或者 git 等,这里考虑大家不一定有其他命令行工具,所以直接用 win10 自带的命令行。

win+R 打开运行,输入 cmd 打开运行,进入 fromelf.exe 路径

这里我将要转化的文件放在 F 盘 test 文件夹中

命令格式为:

1
[fromelf.exe文件路径] --bin -o [BIN路径] [AXF文件路径]

命令行输入:

fromelf.exe --bin -o F:\test\test_bin_out.bin F:\test\test.axf

可以看到在输出目录 F:\test 中,将 axf 文件转化生成为了 bin 文件,并且输出的 bin 文件和 keil 生成 bin 文件大小一致,说明是相同的文件

hex 文件和bin文件相互转换

srecord 软件

在找软件时,在Keil官网看到 BINARY to Motorola S-Record Converter Utility

翻译一下: srec_cat.exe 应用程序是 HEX2BIN,BIN2HEX,BIN2MOT 和 MOT2BIN 的绝佳替代品,用途更广泛。该工具是在 sourceforge.net 上托管的 SRecord 项目的一部分。您可以从 https://sourceforge.net/projects/srecord/files/srecord-win32 下载。

后面我就下载下来研究了一下,发现可以替代 hex2bin 和 bin2hex,所以直接拿来使用。

hex 转 bin

命令:

srec_cat.exe *.hex -intel -offset -0x00000 -o *.bin -binary

*.hex指定要转换的hex文件
*.bin指定要输出的文件名
-offset -0x00000这个指定偏移要根据工程指定来

这里我的偏移是0x26000,所以我输入的命令是:

srec_cat.exe test.hex -intel -offset -0x26000 -o test_out.bin -binary

上面转 axf 时,已经有了 test 文件,这里我把 srec_cat.exe 放在了 F:/test1/srecord 中,并且把要转换的 test.hex 文件放在了 srecord 文件夹中

可以看到生成的 test_out.bin 和 keil 生成的 test.bin 文件大小一致。

bin 转 hex

参考Sorting Intel HEX Files,这里我的命令是:

srec_cat.exe test_out.bin -Binary -o test_hex_out.hex -intel -Output-Block-Size=16 -Disable_Sequence_Warnings

这里实际上和 keil 生的 hex 文件有稍稍区别,因为我试了多种方式,都没有办法和 Keil 生成文件一样,虽然 binw 文件存储的数据一样,但是 crc 及偏移还是稍微有区别,

图左边是生成的hex文件,图右边是原先的hex文件

map

map 文件

map文件是我们通过编译器编译生成的映射文件,map文件包含了五个部分:

A. Section Cross References(模块、段的交叉引用关系)
B. Removing Unused input sections from the image(移除未使用的段)
C. Image Symbol Table(映射符号表,列出了各个段所存储的对应地址)
D. Memory Map of the image(内存(映射)分布)
E. Image component sizes(映像组成大小)

这里也提一下关于map的基本概念

section(段):描述映像文件的代码和数据块
RO:Read-Only的缩写,包括RO-data(只读数据)和RO-code(代码)
RW:Read-Write的缩写,主要是RW-data,Rw-data由程序初始化初始值
ZI:Zero-initialized的缩写,主要是ZI-data,由编程器初始化为0
.text:与RO-code同义
.constdata:与RO-data同义
.bss:与ZI-data同义,通常是指存放未初始化的全局变量的区域
.data:与RW-data同义

在Keil中,我们在 Project -> Options for Target -> Listing 中可以看到我们生成的map中链接的内容,

它包含了:

  • Memory Map:内存映射
  • Callgraph:图像映射
  • Symbols:符号
  • Cross Reference:交叉引用
  • Size Info:大小信息
  • Totals Info:统计信息
  • Unused Section Info:未调用模块(段)信息
  • Veneers Info:装饰信息

Section Cross References

模块、段的交叉引用关系:这里主要是各个源文件之间生成的模块、段之间相互引用的关系。

这里我随便找了个简单的LED延时闪灯程序,编译后打开生成的 map 文件

这里指出了 test.c 中模块调用其他模块之间的关系:

test.o(i.main) refers to sys.o(i.Stm32_Clock_Init) for Stm32_Clock_Init
test.o(i.main) refers to delay.o(i.delay_init) for delay_init
test.o(i.main) refers to led.o(i.LED_Init) for LED_Init
test.o(i.main) refers to delay.o(i.delay_ms) for delay_ms

因为我的 main 写在 test.c 中,所以 test.c 中因为 main 函数调用了 sys.c 中的 Stm32_Clock_Init 函数、调用了 delay.c 中的 delay_init 函数和 delay_ms 函数、调用了 led.c 的 LED_Init 函数,所以, 生成的 map 中 Section Cross References 指出了之前的相互引用关系。

Removing Unused input sections from the image

移除未使用的段:这个是我们工程中没有被调用的模块,会被编译器移除并标识出来。

图里列出了被移除的模块信息,例如

Removing delay.o(i.delay_us), (60 bytes).
Removing usart.o(i.uart_init), (220 bytes).
13 unused section(s) (total 786 bytes) removed from the image.

从上面 main 函数的执行中,可以看出我没有调用 delay_us 和 uart_init 串口的初始化函数,所以编译器把他们找出来并移除,共 13 个段没有被调用,大小为 786 字节。

Image Symbol Table

映射符号表,列出了各个段所存储的对应地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Image Symbol Table

Local Symbols

Symbol Name Value Ov Type Size Object(Section)

../clib/angel/boardlib.s 0x00000000 Number 0 boardinit3.o ABSOLUTE
....
..\Project\LED\led.c 0x00000000 Number 0 led.o ABSOLUTE
..\SYSTEM\delay\delay.c 0x00000000 Number 0 delay.o ABSOLUTE
..\SYSTEM\sys\sys.c 0x00000000 Number 0 sys.o ABSOLUTE
..\SYSTEM\usart\usart.c 0x00000000 Number 0 usart.o ABSOLUTE
..\\SYSTEM\\sys\\sys.c 0x00000000 Number 0 sys.o ABSOLUTE
....
RESET 0x08000000 Section 304 startup_stm32f10x_hd.o(RESET)
!!!main 0x08000130 Section 8 __main.o(!!!main)
....
fac_us 0x20000000 Data 1 delay.o(.data)
fac_ms 0x20000002 Data 2 delay.o(.data)

Global Symbols

Symbol Name Value Ov Type Size Object(Section)
....
....
delay_init 0x08000455 Thumb Code 56 delay.o(i.delay_init)
delay_ms 0x08000495 Thumb Code 56 delay.o(i.delay_ms)
main 0x080004d1 Thumb Code 114 test.o(i.main)
LED_Init 0x08000269 Thumb Code 74 led.o(i.LED_Init)
USART_RX_STA 0x20000008 Data 2 usart.o(.data)
USART_RX_BUF 0x2000000c Data 200 usart.o(.bss)

这里我截取了一些 map 文件中 Image Symbol Table 的内容出来分析

Symbol 分为两类

Local Symbols(局部)
局部就是在函数内部用 static 声明的变量,还有用 static 声明的函数,基本上都是属于局部,汇编文件里面的变量如果作用域是本文件的就是局部。

Global Symbols(全局)
全局就是不是用 static 声明的变量和函数,是 auto 声明的全局变量和 C 文件函数就属于全局。汇编文件里面作用域是全工程的就是全局。

Symbol 符号名称

Symbol Name:符号名

Value:存储对应的地址

0x0800xxxx:指存储在 flash 中的代码和变量等
0x2000xxxx:指存储在 RAM 中的变量 Data 等

Ov Type:符号类型

Number、Section、Thumb Code、Data等

Size:大小,指当前Symbol占用的大小

Object(Section):段目标,指当前段所在的模块及源文件

Memory Map of the image

内存(映射)分布:映像文件可以分为加载域(Load Region)和运行域(Execution Region):加载域反映了ARM可执行映像文件的各个段存放在存储器中的位置关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Memory Map of the image

Image Entry point : 0x08000131

Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00000578, Max: 0x00080000, ABSOLUTE)

Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x0000056c, Max: 0x00080000, ABSOLUTE)

Base Addr Size Type Attr Idx E Section Name Object

0x08000000 0x00000130 Data RO 3 RESET startup_stm32f10x_hd.o
0x08000130 0x00000008 Code RO 213 * !!!main c_w.l(__main.o)
....
....

Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000538, Max: 0x00010000, ABSOLUTE)

Base Addr Size Type Attr Idx E Section Name Object

0x20000000 0x00000004 Data RW 49 .data delay.o
0x20000004 0x00000006 Data RW 159 .data usart.o
....
....

Image Entry point : 0x08000131:指映射入口地址。

Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00000578, Max: 0x00080000, ABSOLUTE):指加载域 LR_IROM1 起始地址为 0x08000000,大小是 0x00000578,加载域最大大小为 0x00080000

Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x0000056c, Max: 0x00080000, ABSOLUTE)

Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000538, Max: 0x00010000, ABSOLUTE)

运行域 ER_IROM1 起始地址 0x08000000,大小是 0x0000056c,加载域最大大小为 0x00080000

运行域 RW_IRAM1 起始地址 0x20000000,大小是 0x00000538,加载域最大大小为 0x00010000

对应Keil中设定的IROM1和IRAM1:

Image component sizes

映像组成大小:各个映像模块在各个文件中的代码大小,主要就是对模块进行汇总存储大小信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Image component sizes

Code (inc. data) RO Data RW Data ZI Data Debug Object Name

124 12 0 4 0 1196 delay.o
84 10 0 0 0 435 led.o
....
....
==============================================================================


Code (inc. data) RO Data RW Data ZI Data Debug

1052 108 336 12 1324 215049 Grand Totals
1052 108 336 12 1324 215049 ELF Image Totals
1052 108 336 12 0 0 ROM Totals

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

Total RO Size (Code + RO Data) 1388 ( 1.36kB)
Total RW Size (RW Data + ZI Data) 1336 ( 1.30kB)
Total ROM Size (Code + RO Data + RW Data) 1400 ( 1.37kB)

==============================================================================
Code RO Data RW Data ZI Data Debug Object Name
指代码的大小 指除了内联数据之外的常量数据 指可读写、已初始化的变量数据 指未初始化的变量数据 显示调试数据占用了多少字节 目标名

Total RW Size (RW Data + ZI Data) 1336 ( 1.30kB):是我们程序RAM所占的字节总数,

Total ROM Size (Code + RO Data + RW Data) 1400 ( 1.37kB):是我们程序ROM所占的字节总数,也就是我们程序所下载到ROM Flash中的大小。

写的可能有点乱,因为我晚上才有空整理这些,上面这些是花了好几个晚上才整理出来,所以有点乱也希望大家理解。有什么错误也希望能在留言处指出,我会及时修改的!