关于嵌入式的bin、hex、axf、map
关于嵌入式的bin、hex、axf、map
nixgnauhcuy别在注释里陷得太深——注释很可能会误导你,你要调试的只是代码。
前言
记录工作中学习到的知识,在这里做些笔记,方便自己后面温习。
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 | :020000040002F8 |
: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 中的信息仅仅是可执行代码。
调试信息作用:
- 可将源代码包括注释夹在反汇编代码中,这样我们可随时切换到源代码中进行调试
- 我们还可以对程序中的函数调用情况进行跟踪(Keil可以通过Watch & Call Stack Window查看)
- 对变量进行跟踪(Keil可以通过Watch & Call Stack Window查看)
axf 文件转 bin 文件
fromelf 格式
1 | fromelf [options] input_file (命令的格式) |
将 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 | Image Symbol Table |
这里我截取了一些 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 | Memory Map of the image |
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 | Image component sizes |
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中的大小。
结
写的可能有点乱,因为我晚上才有空整理这些,上面这些是花了好几个晚上才整理出来,所以有点乱也希望大家理解。有什么错误也希望能在留言处指出,我会及时修改的!