记录 axf 转 bin python 脚本笔记

前言

记录制作 .axf 文件转换为 .bin 文件的 python 脚本过程遇到的难题和思考,脚本地址是nixgnauhcuy/python_script (github.com)

之前写过一篇 《关于嵌入式的bin、hex、axf、map》的文章,讲述了 .bin.hex.axf.map 文件的概念和用途,那个时候对 .axf 文件的理解过于片面,在制作脚本的过程中,发现了自己的不足,所以这次就来详细记录一下。

之前都是依赖于 fromelf.exe 工具来转换 .axf 文件为 .bin 文件,但是不知道具体的实现过程,就想自己体验一下这个流程,所以尝试用 python 实现。

准备工作

在制作脚本之前,我用 Keil 实现了某款芯片的程序,生成了 .axf 文件,然后使用 fromelf.exe 工具转换为 .bin 文件,这里生成的 .axf 文件和 .bin 文件就作为我后面验证脚本的输入文件和验证是否正确的输出文件。

初步实现

先分析 .axf 文件,打开后,第一反应,“WTF,这怎么看!”,

第一眼,一脸懵逼,无从下手,所以我用对比工具对比下,与 .bin 文件是否有关联,


对比完后,就发现了规律,.axf 的某一数据段正好完全等同于 .bin 文件。这就好办了,直接把 .axf 这一段数据取出,并且重新组成 .bin 文件即可。

从图里可以看出,.axf 开头的 0x00-0x34 这一段的内容是不需要的,所以要输出的 .bin 文件头索引,就要从 0x35 开始,而 .axf 输出的 .bin 文件的尾巴,内容是 0121002F0F0000022100000003010101,所以我匹配到这一段数据后,我就认为是到顶了即可。

OK,思路有了,但是还要担心是不是所有的 .axf 文件都是这样的,先不管了,实现了再说,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AXF_HEAD_SIZE = 0x34
BIN_END_MARK = b'\x01\x21\x00\x2F\x0F\x00\x00\x02\x21\x00\x00\x00\x03\x01\x01\`

with open(axf_file, 'rb') as input_file:
input_data = input_file.read()

start_index = AXF_HEAD_SIZE
end_index = input_data.find(BIN_END_MARK, AXF_HEAD_SIZE)
if end_index == -1:
raise ValueError("Convert data failed, can't find BIN_END_MARK.")

output_data = input_data[start_index:end_index]
with open(output_file, 'wb') as output:
output.write(output_data)

实现后,运行脚本后,结果一致,生成的文件符合预期,换了其他 MCU 的 .axf 文件和 .bin 文件,同样符合预期。

完善实现

虽然上面完成了,但是心里还是犯嘀咕,万一 AXF_HEAD_SIZE 不是从 0x34 开始,万一 BIN_END_MARK 刚好不匹配,岂不是执行不下,开始反思。

觉得自己对 .axf 没有理解,只是单纯的对比,发现逻辑并实现,跳过了对 .axf 文件的探索。

随后开始查询资料,查询 axf 如何 Convert 为 bin,发现好多资料都是提及使用 fromelf.exe 或者其他 xxx.exe 如何实现。

直到我看到了甲骨文 (oracle) 的文档,Chapter 7 Object File Format (Linker and Libraries Guide) (oracle.com),才发现之前的想法还是太幼稚了~

.axf 文件的文件格式如图,

根据给出的 ELF header 的结构体 Elf32_Ehdr 计算,长度正好符合前面提及的 0x34,并且将数据解析后得到的 e_ehsize 内容,也就是标头长度同样是 0x34,这样就验证了原先的逻辑,但是为了让程序更合理,我要获取的应该是 e_ehsize 的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define EI_NIDENT       16

typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;

.bin 文件的相关又要获取 Program Header: 的内容,过程有点长,我就省略部分篇幅了,最终修正的核心代码如下:

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
39
40
41
42
43
EI_NIDENT = 16
class Elf32_Struct(object):

# @see https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-43405/index.html
def __init__(self, data) -> None:
# ELF Header:
self.e_ident = data[:EI_NIDENT]
(
self.e_type,
self.e_machine,
self.e_version,
self.e_entry,
self.e_phoff,
self.e_shoff,
self.e_flags,
self.e_ehsize,
self.e_phentsize,
self.e_phnum,
self.e_shentsize,
self.e_shnum,
self.e_shstrndx
) = struct.unpack('<HHIIIIIHHHHHH', data[EI_NIDENT:EI_NIDENT+struct.calcsize('<HHIIIIIHHHHHH')])

# ELF Program Headers:
(
self.p_type,
self.p_flags,
self.p_offset,
self.p_vaddr,
self.p_paddr,
self.p_filesz,
self.p_memsz,
self.p_align
) = struct.unpack('<IIIIIIII', data[self.e_phoff:self.e_phoff+struct.calcsize('<IIIIIIII')])

with open(axf_file, 'rb') as input_file:
input_data = input_file.read()

elf_object = Elf32_Struct(input_data)

output_data = input_data[elf_object.e_ehsize:elf_object.p_paddr+elf_object.e_ehsize]
with open(output_file, 'wb') as output:
output.write(output_data)

最终根据 ELF HeaderELF Program Headers 可以稳定正确的获取 .bin 文件的内容,脚本也算正确的完成了。

结语

整个实现过程还是发现很多不足,偷懒没有对 .axf 进行一个稍微深度的理解,停留在一个片面理解的程度就进行开发,反而耗费了更长的时间,不过过程中还是学到了不少东西,这波不亏~