为什么会有 .axf、.out、.elf、.hex、.bin?——嵌入式程序文件格式详解我们在做嵌入式开发时经常会遇到编译工具输出各种格式的文件比如.axf、.out、.elf、.hex、.bin。心里是不是难免会嘀咕“为什么会有这么多种文件它们的作用是什么”本文就来详细介绍它们之间到底有什么区别。一、这些文件从里来这些文件都是不同的IDE工具在程序构建过程中不同阶段输出的产物:IDE工具Keil MDKIAR EWARMGNU GCC链接输出文件.axf.out.elf烧录文件.hex/.bin.hex/.bin.hex/.bin二、这些文件的作用上述五种文件按用途可分为两类。第一类调试文件.axf/.out/.elf该类文件由链接器生成是包含完整信息的可执行文件通常涵盖以下内容程序代码与数据.text、.rodata、.data、.bss等段分别存放机器指令、只读常量、已初始化变量和未初始化变量。符号表.symtab、.strtab保存函数名、全局变量名及其地址调试器靠它把地址映射回源码符号。调试信息.debug_info、.debug_line、.debug_abbrev等 DWARF 段记录源文件、行号、变量类型、调用关系等。重定位与加载信息程序头表、节头表、重定位表等用于加载器或调试器确定各段应放到哪里。属性与注释如.ARM.attributes、.comment记录架构、工具链版本等信息。调试工具如调试器、链接器、分析器使用这类文件可以完成符号解析和源码级调试。.axf、.out、.elf这三种后缀只是不同工具链或历史习惯造成的扩展名差异其内部结构基本一致通常采用 ELF 格式。三者的常见差异如下.elf标准 ELF 格式GCC、Clang、LLVM 等工具链常用通用性最好可用readelf、objdump、addr2line分析。.axfARM 工具链常见扩展名本质通常也是 ELFKeil MDK、Arm Compiler 常用供 Keil、ULINK 等调试器加载。.out历史通用输出名现代工具链中多数也是 ELF早期可能对应 a.out 格式IAR 等工具链也常用.out作为调试输出。第二类烧录文件.hex/.bin烧录文件通常由上面的调试文件经过专用工具转换生成其中的程序机器码和数据最终会被写入存储介质供CPU在启动和执行过程中读取使用。.hex文件 与.bin文件 的区别如下.hex带地址和校验信息的文本格式烧录器可自行解析地址.bin纯二进制镜像不含地址信息烧录时必须指定起始地址。三、ELF 文件格式ELFExecutable and Linkable Format可执行与可链接格式是一种二进制目标文件格式用于描述程序代码、数据、符号、重定位信息以及程序加载所需的信息。3.1 整体布局在 ELF 文件格式中还有ELF32 和 ELF64两种格式区分。两者整体设计基本一致都包含ELF HeaderProgram Header TableSection DataSection Header Table主要区别在于地址和偏移字段的位宽不同ELF Header、Program Header、Section Header 的结构大小不同可表示的地址空间不同部分数据类型和结构体布局不同面向的处理器架构和 ABI 可能不同。本文后续都以嵌入式中更常见EFL32为基础进行介绍。3.2 ELF Header位于 ELF 文件的起始位置用于描述文件类型、目标架构、程序入口地址以及 Program Header Table 和 Section Header Table 的位置与大小。字段含义e_identELF 标识信息包括魔数、位数、字节序和 ELF 版本e_typeELF 文件类型e_machine目标机器架构e_versionELF 文件版本e_entry程序入口地址e_phoffProgram Header Table 的文件偏移e_shoffSection Header Table 的文件偏移e_flags与目标架构相关的处理器标志e_ehsizeELF Header 自身的大小e_phentsize每个 Program Header 的大小e_phnumProgram Header 的数量e_shentsize每个 Section Header 的大小e_shnumSection Header 的数量e_shstrndxSection Name String Table 的索引3.3 Program Header Table位于 ELF Header 之后(偏移位置由e_phoff指出),是一个由固定大小表项组成的数组,表项数量为e_phnum,每项大小为e_phentsize。每个表项描述一个 Segment,告诉加载器把文件的哪一部分映射到内存的哪里、赋予什么权限。字段含义p_type段类型, 参见常见p_tpye定义p_offset该段在文件中的偏移p_vaddr该段在内存中的虚拟地址(VMA,运行地址)p_paddr该段的物理地址(LMA,加载地址),在裸机/MCU 中用于指明存放在 Flash 中的位置p_filesz该段在文件中占用的大小p_memsz该段在内存中占用的大小,大于p_filesz的部分由加载器/启动代码清零(对应.bss)p_flags段权限:R(4)、W(2)、X(1)p_align段对齐要求常见p_type:类型含义PT_LOAD需要加载到内存的段,最核心的类型PT_DYNAMIC动态链接信息(对应.dynamic)PT_INTERP动态链接器路径(对应.interp)PT_NOTE附加的备注信息PT_TLS线程局部存储模板PT_GNU_STACK栈的权限,用于控制栈是否可执行(NX)PT_GNU_RELRO重定位完成后设为只读的区域3.4 Section Header Table位于文件末尾附近(偏移位置由e_shoff指出),同样是固定大小表项的数组,数量为e_shnum,每项大小为e_shentsize。每个表项描述一个 Section 的名称、类型、地址、文件位置和大小。字段含义sh_name节名,值是在.shstrtab中的偏移sh_type节类型, 参见常见sh_type定义sh_flags节属性:SHF_WRITE(可写)、SHF_ALLOC(运行时占用内存)、SHF_EXECINSTR(含可执行指令)sh_addr节在运行时的虚拟地址,不需要加载的节为 0sh_offset节内容在文件中的偏移sh_size节的大小sh_link关联节的索引,含义随sh_type而变(如符号表关联其字符串表)sh_info附加信息,含义随sh_type而变(如重定位节指向被重定位的节)sh_addralign节的对齐要求sh_entsize若节由固定大小表项构成(如符号表),表示每项大小,否则为 0常见sh_type:类型含义SHT_NULL无效表项,Section Header Table 的第 0 项固定为此类型SHT_PROGBITS程序定义的内容,如代码、数据(.text、.data、.rodata)SHT_NOBITS运行时占内存但文件中不占空间(.bss)SHT_SYMTAB / SHT_DYNSYM符号表 / 动态符号表SHT_STRTAB字符串表(.strtab、.shstrtab)SHT_REL / SHT_RELA重定位表(不带 / 带显式加数)SHT_DYNAMIC动态链接信息SHT_NOTE备注信息3.5 Segment/Section数据(中间的数据区)位于 Program Header Table 与 Section Header Table 之间,是 ELF 文件的主体,保存代码、数据、符号、重定位、调试信息等实际内容。节名含义.text机器指令.rodata只读数据,如字符串常量、const全局变量.data已初始化的全局/静态变量,初始值存于文件.bss未初始化(或初始化为 0)的全局/静态变量,运行前由启动代码清零.symtab符号表,记录函数和变量的名称、地址、大小、类型.strtab符号名字符串表,供.symtab引用.shstrtab节名字符串表,供 Section Header 的sh_name引用.rel.* / .rela.*重定位信息,如.rela.text(仅.o中常见).init_array / .fini_array构造函数 / 析构函数指针数组.dynamic动态链接信息,由(tag, value)键值对组成.dynsym / .dynstr动态符号表及其字符串表.plt / .got过程链接表 / 全局偏移表,用于动态链接和延迟绑定.interp动态链接器路径字符串.debug_*DWARF 调试信息.eh_frame栈展开信息,用于异常处理和回溯.comment编译器版本信息.ARM.exidx / .ARM.extabARM 异常展开表(ARM 特有).ARM.attributesARM 架构属性,如 CPU 型号、FPU 类型(ARM 特有).isr_vector中断向量表,名称由链接脚本决定(Cortex-M 常见)扩展arm elf文件解读arm-none-eabi-readelf-hproject.elf# 查看 ELF Header$ arm-none-eabi-readelf-lproject.elf# 查看 Program Header$ arm-none-eabi-readelf-Sproject.elf# 查看 Section Header基于Cortex-M4生成的project.elf解读展示ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V Type: EXEC (Executable file) Machine: ARM Entry point address: 0x80001c5 Start of program headers: 52 (bytes into file) Start of section headers: 85204 (bytes into file) Flags: 0x5000400, Version5 EABI, hard-float ABI Number of program headers: 3 Number of section headers: 22 Section header string table index: 21Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align ARM_EXIDX 0x00c2c4 0x0800b2c4 0x0800b2c4 0x00008 0x00008 R 0x4 LOAD 0x001000 0x08000000 0x08000000 0x0b2cc 0x0b2cc R E 0x1000 LOAD 0x00d000 0x20000000 0x0800b2cc 0x00918 0x00ed8 RW 0x1000 Section to Segment mapping: 00 .ARM.exidx 01 .isr_vector .text .eh_frame .ARM.exidx 02 .data .init_array .fini_array .init_array.00000 .bssThere are 22 section headers, starting at offset 0x14cd4: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .isr_vector PROGBITS 08000000 001000 000040 00 A 0 0 4 [ 2] .text PROGBITS 08000040 001040 00b280 00 AX 0 0 64 [ 3] .eh_frame PROGBITS 0800b2c0 00c2c0 000004 00 A 0 0 4 [ 4] .ARM.exidx ARM_EXIDX 0800b2c4 00c2c4 000008 00 AL 2 0 4 [ 5] .data PROGBITS 20000000 00d000 00090c 00 WA 0 0 8 [ 6] .init_array INIT_ARRAY 2000090c 00d90c 000004 04 WA 0 0 4 [ 7] .fini_array FINI_ARRAY 20000910 00d910 000004 04 WA 0 0 4 [ 8] .init_array.00000 INIT_ARRAY 20000914 00d914 000004 04 WA 0 0 4 [ 9] .bss NOBITS 20000918 00d918 0005c0 00 WA 0 0 8 [10] .ARM.attributes ARM_ATTRIBUTES 00000000 00d918 000030 00 0 0 1 [11] .comment PROGBITS 00000000 00d948 000045 01 MS 0 0 1 [12] .debug_line_str PROGBITS 00000000 00d98d 000299 01 MS 0 0 1 [13] .debug_info PROGBITS 00000000 00dc26 000238 00 0 0 1 [14] .debug_abbrev PROGBITS 00000000 00de5e 00018d 00 0 0 1 [15] .debug_aranges PROGBITS 00000000 00dfeb 000040 00 0 0 1 [16] .debug_line PROGBITS 00000000 00e02b 0001ff 00 0 0 1 [17] .debug_str PROGBITS 00000000 00e22a 00016d 01 MS 0 0 1 [18] .debug_frame PROGBITS 00000000 00e398 001f30 00 0 0 4 [19] .symtab SYMTAB 00000000 0102c8 0036a0 10 20 598 4 [20] .strtab STRTAB 00000000 013968 00127d 00 0 0 1 [21] .shstrtab STRTAB 00000000 014be5 0000ed 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), y (purecode), p (processor specific)四、 HEX 文件HEX 文件是一种以 ASCII 文本形式表示程序二进制数据及其存储地址的文件格式。它的特点是每行都是一条独立的记录, 并携带地址和校验信息。4.1 文件格式: LL AAAA TT DD...DD CC字段长度含义:1 字符记录起始标志LL1 字节数据字段长度AAAA2 字节16 位偏移地址(大端)TT1 字节记录类型DD…LL 字节数据CC1 字节校验和记录类型展开TT名称作用00数据记录携带实际数据01文件结束固定为:00000001FF02扩展段地址用于 20 位寻址(8086 时代)03起始段地址8086 入口04扩展线性地址给出 32 位地址的高 16 位,ARM 必用05起始线性地址32 位入口地址(是否生成因工具而异)4.2 实例拆解还是以Cortex-M4生成的project.hex文件为例:020000040800F2 :1000000000000120C50100082102000821020008AB :10001000210200082102000821020008000000005F :1000200000000000000000000000000021020008A5 :10003000210200080000000021020008210200083F :100040000348044B834202D0034B03B11847704767 :100050001409002014090020000000000548064B88 :100060001B1AD90F01EBA301491002D0034B03B1B6 :1000700018477047140900201409002000000000F0 :1000800010B5064C237843B9FFF7DAFF044B13B1E0 :100090000448AFF300800123237010BD180900202D :1000A00000000000C0B2000808B5054B1BB10549AF :1000B0000548AFF30080BDE80840FFF7CFBF00BFA1 :1000C000000000001C090020C0B20008002A01D076 :1000D00002F5807A704700BF162031A1ABBE3048D0 :1000E0004168002901D0364A11600168002901D118 :1000F000324901608168C2682A4B002900D00B4652 :100100009D46FFF7E3FF00218B460F462648274A0E :10011000121A00F0BFFA09F0B1FD15202649ABBE56 :1001200025490968002001B40B780131002B15D056第一行: 02 0000 04 0800 F2 │ │ │ │ │ │ │ │ │ │ │ └─ Checksum │ │ │ │ └────────────────────────── Data │ │ │ │ │ │ │ └─ Record Type 04扩展线性地址记录 │ │ └─ Address 0x0000 │ └─ Data Length 0x022 字节 └─ Record Start数据Data区域解读线性地址的高16位值为0x0800第二行: 10 0000 00 00000120C50100082102000821020008 AB │ │ │ │ │ │ │ │ │ │ │ └─ Checksum │ │ │ │ └────────────────────────── Data │ │ │ │ │ │ │ └─ Record Type 00数据记录 │ │ └─ Address 0x0000叠加上高地址区域后该数据记录的基址为0x08000000 │ └─ Data Length 0x1016 字节 └─ Record Start数据Data区域解读原始字节小端还原含义000001200x20010000初始 MSP 栈顶值C50100080x080001C5Reset_Handlerbit01 表示 Thumb 态210200080x08000221NMI → Default_Handler210200080x08000221HardFault → Default_Handler五、BIN 文件BIN文件是一种以原始二进制字节序列保存程序或数据内容的文件格式。它的文件内容里不含任何地址、类型、校验和信息。还是以之前Cortex-M4的例子使用二进制查看工具打开project.bin文件:00000000 00 00 0120c5 01 00 082102 00 082102 00 08|.......!...!...|000000102102 00 082102 00 082102 00 08 00 00 00 00|!...!...!.......|00000020 00 00 00 00 00 00 00 00 00 00 00 002102 00 08|............!...|000000302102 00 08 00 00 00 002102 00 082102 00 08|!.......!...!...|00000040 034804 4b834202 d0 03 4b 03 b118477047|.H.K.B...K...GpG|000000501409 00201409 002000 00 00 00 054806 4b|...........H.K|00000060 1b 1a d9 0f 01 eb a3 01491002 d0 03 4b 03 b1|........I....K..|00000070184770471409 00201409 002000 00 00 00|.GpG..........|0000008010b5 06 4c237843b9 ff f7 da ff 04 4b13b1|...L#xC......K..|00000090 0448af f3 00800123237010bd1809 0020|.H.....##p..... |000000a0 00 00 00 00 c0 b2 00 08 08 b5 05 4b 1b b1 0549|...........K...I|000000b0 0548af f3 0080bd e8 0840ff f7 cf bf 00 bf|.H.............|000000c0 00 00 00 00 1c 09 0020c0 b2 00 08 00 2a 01 d0|............*..|000000d0 02 f5807a704700 bf162031a1 ab be3048|...zpG...1...0H|000000e04168002901 d0364a11600168002901 d1|Ah.)..6J..h.)..|000000f0324901608168c2682a 4b 002900 d0 0b46|2I..h.h*K.)...F|00000100 9d46ff f7 e3 ff 00218b460f462648274a|.F.....!.F.FHJ| 00000110 12 1a 00 f0 bf fa 09 f0 b1 fd 15 20 26 49 ab be |........... I..| 00000120 25 49 09 68 00 20 01 b4 0b 78 01 31 00 2b 15 d0 |%I.h. ...x.1...| 00000130 20 2b f9 d0 22 2b 01 d0 27 2b 01 d1 1a 00 01 e0 | ..........|可以看到里面的每一行的数据和HEX文件的数据Data区域都是匹配的BIN文件第一行的 00 00 01 20 c5 01 00 08 21 02 00 08 21 02 00 08 |… …!..!..|HEX文件第二行的数据Data区域 00000120C50100082102000821020008扩展Cortex-M4 异常向量表和复位序列Cortex-M4 Flash中起始位置放置的异常向量表的内容和作用可以参考博文Cortex-M 系列复位序列六、小插曲a.out在 Linux 或 GCC 环境中,你可能见过这个文件名:$ gcc hello.c# 没有 -o默认输出 a.out$ arm-none-eabi-gcc main.c...# 交叉编译同理a.out其实有两层含义。默认文件名a.out是链接器默认的输出文件名,来自 assembler output 的缩写。在现代工具链中,它的内容通常就是一个 ELF 文件:$filea.out a.out: ELF32-bit LSB executable, ARM, EABI5 version1(SYSV), statically linked, with debug_info, not stripped.out文件格式它是早期 Unix 使用的一种简单可执行文件格式 ,结构大致为:exec header text 段 data 段 重定位信息 符号表。这种格式结构简单,但段的扩展能力有限,对共享库的支持也很不方便,因此逐渐被 ELF 取代,如今只在少数老系统或特殊场景中还能见到。因此在现代工具链中看到 a.out 时别被名字迷惑它只是一个沿用了历史名称的 ELF 文件与 .elf 没有本质区别。