一个典型的嵌入式系统/固件开发流程。这个过程涉及多个工具链组件,最终将程序员编写的高级语言(或汇编)代码转换成可以烧录到 ROM 芯片中的原始二进制字节流(.bin 文件)。
核心流程:
源代码 (*.c, *.s, *.h) -> 编译 (Compiler) -> 目标文件 (*.o) -> 链接 (Linker) -> 带地址信息的可执行文件 (*.elf/axf) -> 格式转换 (Objcopy/工具链命令) -> 纯二进制镜像 (*.bin) -> 烧录到 BootROM 或 Flash。
详细步骤解析:
-
编写 Bootloader 源代码:
- 程序员使用 高级语言(如 C、少量 C++、Rust)和必要的 汇编语言(用于底层硬件初始化、异常向量表设置、栈指针设置等)编写 Bootloader 的程序逻辑。
- 关键功能通常包括:
- 初始化最关键的硬件(时钟、内存控制器、Cache)
- 设置初始堆栈指针 (
SP) - 设置中断向量表(
Exception Vector Table),通常是跳转指令。 - 将后续阶段(如操作系统内核、应用程序)从慢速存储(Nor Flash, NAND Flash, eMMC)加载到 RAM。
- 执行必要的自检(RAM Test 等)。
- 可选的安全启动验证(签名校验)。
- 跳转到 RAM 中的后续程序。
- 源代码文件包括
.c,.cpp,.s(汇编, 如 ARM 汇编可能是.s或.S),以及头文件.h。
-
编译 (Compilation):
- 目的: 将人类可读的源代码(高级语言或汇编)转换成特定处理器架构的 机器指令(
Machine Code或Opcode)以及初始化的数据。 - 工具: 编译器 (
Compiler)。嵌入式领域常见的有:- GCC 交叉编译器 (
arm-none-eabi-gcc,riscv64-unknown-elf-gcc,mips-elf-gcc等)。 - LLVM/Clang 交叉编译器。
- 商业编译器(ARM Compiler, IAR EWARM, Keil μVision)。
- GCC 交叉编译器 (
- 过程:
- 编译器解析源代码,进行词法分析、语法分析、语义分析、优化。
- 为每个
.c和.s文件生成对应的 目标文件 (Object File)(.o或.obj)。 - 目标文件内容:
.text段:存放编译后的机器指令。.data段:存放显式初始化的全局变量和静态变量(初始值不为零)。.rodata段:存放只读数据(如常量字符串)。.bss段:存储未初始化或显式初始化为零的全局/静态变量(只有大小信息,文件本身不占用空间,运行时清零)。- 重定位信息 (
Relocation Info): 最重要!记录了目标文件中哪些符号(函数名、变量名)的地址是未知的,需要在链接阶段由链接器解析填充。也包含跳转指令需要调整的目标地址信息。 - 符号表 (
Symbol Table): 定义了目标文件提供的外部可见的函数/变量(输出符号),以及它所引用的外部函数/变量(未定义符号/输入符号)。 - 调试信息(可选)。
- 特点:
- 目标文件包含机器码,但它不能直接执行。
- 目标文件内部的代码和数据的地址通常是基于
0的相对地址(.text,.data,.rodata等的起始地址假设从0开始)。最终的物理地址由链接器决定!
- 目的: 将人类可读的源代码(高级语言或汇编)转换成特定处理器架构的 机器指令(
-
链接 (Linking):
- 目的:
- 将所有编译好的目标文件(
.o)和库文件(如libc.a)合并成一个单一的可执行文件。 - 解析地址: 这是核心任务!链接器根据 链接脚本 (
Linker Script) 中定义的内存布局,为所有目标文件中的.text,.data,.rodata,.bss等段分配确切的物理地址或逻辑地址。 - 修改目标文件中的机器指令和数据引用,使其指向正确的最终地址(重定位 (
Relocation))。 - 解析所有符号引用,将符号地址绑定到符号定义上。
- 将所有编译好的目标文件(
- 工具: 链接器 (
Linker)。通常与编译器一起提供(如arm-none-eabi-ld,armlink,xlink, 或作为编译器调用的一部分arm-none-eabi-gcc -o output.elf ...)。 - 输入:
- 所有编译生成的目标文件(
.o)。 - 链接器脚本(
.ld文件)。 - 库文件(
.a或.lib)。
- 所有编译生成的目标文件(
- 输出: 一个 带完整地址信息的可执行文件。常见格式:
- ELF (
Executable and Linkable Format,.elf): Linux/现代嵌入式工具链(GCC)最常用。 - AXF (
.axf): ARM Keil MDK/DS-5 的输出格式(本质也是 ELF)。 - COFF (
.out或特定后缀): 较旧的格式。 - …
- ELF (
- 核心:链接器脚本 (
Linker Script)- 一个文本文件(
.ld),告诉链接器代码和数据应该放在芯片内存的什么位置。 - 关键作用:
- 定义内存区域 (
MEMORY): 精确描述芯片上可用的 RAM 和 ROM/Flash 的起始地址、大小和属性(x可执行,r可读,w可写)。 - 定义段布局 (
SECTIONS): 指定.text,.data,.rodata,.bss,.stack,.heap, 中断向量表等段应该放在哪个内存区域的哪个地址。例如:.text段放在ROM(或 BootROM)区域。.data段放在ROM中,但它会在初始化时被拷贝到 RAM。.bss段放在RAM中,初始化时清零。
- 指定入口点 (
ENTRY): 程序开始执行的第一个函数地址(通常就是 Bootloader 的入口,如Reset_Handler)。 - 控制特殊符号(如
__stack_top,_etext,_sdata,_edata,_sbss,_ebss),这些符号在启动代码中用来进行数据初始化和栈设置。 - 为向量表设置固定地址(通常为
0x00000000或芯片定义的 BootROM 起始地址)。
- 定义内存区域 (
- 例(简化 BootROM 链接脚本片段):
MEMORY { BOOTROM (rx) : ORIGIN = 0x00000000, LENGTH = 64K /* BootROM 位置和大小 */ SRAM (rwx) : ORIGIN = 0x40000000, LENGTH = 256K } SECTIONS { .vectors : { KEEP(*(.vectors)) /* 中断向量表放在最开头 */ } > BOOTROM .text : { *(.text*) /* 所有.text*段 (代码) */ *(.rodata*) /* 所有.rodata*段 (只读常量) */ . = ALIGN(4); } > BOOTROM .data : AT (ADDR(.text) + SIZEOF(.text)) /* .data的内容在ROM中,紧挨着.text */ { _sdata = .; /* 在RAM中.data起始地址的符号 */ *(.data*) _edata = .; /* 在RAM中.data结束地址的符号 */ } > SRAM /* .data在运行时位于RAM区域 */ .bss : { _sbss = .; /* .bss起始地址 */ *(.bss*) *(COMMON) _ebss = .; /* .bss结束地址 */ } > SRAM . = ALIGN(8); __stack_top = ORIGIN(SRAM) + LENGTH(SRAM); /* 栈顶在SRAM顶端 */ } ENTRY(Reset_Handler) /* 入口点函数 */
- 一个文本文件(
- 输出文件特点:
- 包含
.text,.data,.rodata段的 实际机器指令和数据(按链接脚本放置)。 - 包含
.bss段的信息(大小和RAM位置)。 - 包含所有地址: 代码、数据、函数、全局变量都有确定的地址(相对于内存基址)。例如
Reset_Handler的地址是0x00000004(如果向量表从0x00000000开始,第一条指令通常是向量表的第二条Reset_Handler地址)。 - 包含符号表和调试信息(便于调试,实际烧录可去除)。
- 不是纯二进制: 包含了ELF/AXF头部信息、段表、重定位信息(某些情况)、调试信息等。
- 包含
- 目的:
-
格式转换:生成
.bin文件- 目的: 从链接器生成的
.elf/.axf文件中,提取出可以直接烧录到 BootROM 或 Flash 芯片特定物理地址上的 纯原始二进制机器码和数据字节流。 - 为什么需要转换: BootROM/Flash 编程器只需要纯粹的二进制指令和数据按地址顺序排放。它们无法理解和处理
.elf/.axf文件中的头部、符号表、重定位信息、复杂的文件格式等额外元数据。 - 工具:
objcopy(通常是 GCC 工具链中的arm-none-eabi-objcopy).- 集成在工具链命令中(如 Keil MDK 的
fromelf --bin或--i32命令;IAR EWARM 的ielftool)。
- 核心命令 (Objcopy):
arm-none-eabi-objcopy -O binary input.elf output.bin-O binary: 指定输出格式为纯二进制。input.elf: 链接器生成的可执行文件。output.bin: 最终生成的纯二进制文件。
- 转换过程:
objcopy读取.elf/.axf文件。- 根据
.elf/.axf文件中的 段信息(由链接脚本定义),获取.text(代码)、.rodata(只读常量)、以及.data(初始化数据)段的 最终加载地址(Load Address LMA,在 ROM/BootROM 中的地址) 和 内容。.bss段只在运行时存在于 RAM,不包含任何需要在 ROM 中烧录的初始化数据(只有大小),所以不会出现在.bin文件中。 - 它会 忽略所有头信息、符号表、调试信息、重定位信息等非程序内容。
- 它会将这些段的内容(指令和数据)按照它们链接时的 加载地址(LMA) 在内存中的顺序,进行 填充 和 合并。链接器脚本中如果定义
.data的 LMA 在.text之后(如上面的例子AT (ADDR(.text) + SIZEOF(.text))),那么.bin文件中,.data段的内容就会紧接着.text段的最后一个字节开始排放。 - 它将填充和合并后的 连续字节流,从这些段的最低加载地址(通常是
0x00000000,BootROM 的物理起始地址)开始排列,并写入output.bin文件。该文件不包含任何地址信息,它代表着从 BootROM 物理起始地址0x00000000开始,连续的一串指令和数据的字节。
- 重要理解点:
.bin文件的第 0 个字节对应于 BootROM 地址0x00000000。.bin文件的长度等于链接时分配在 ROM/BootROM 区域中的.text,.rodata,.data(的加载地址部分)的总实际大小。
- 目的: 从链接器生成的
-
烧录到 BootROM:
- 目的: 将生成的
output.bin文件写入到实际的物理 BootROM 芯片中。 - 方式:
- 芯片生产阶段 (Mask ROM):
.bin文件被交付给晶圆代工厂,在制造阶段直接将二进制“图形”蚀刻进硅片。永久不可更改。 - OTP (One-Time Programmable): 通过特殊烧录器(编程器)一次性写入
.bin文件。无法擦除。 - NOR Flash (常见 BootROM 存储介质): 通过 JTAG/SWD 调试器、或者通过芯片自带的串口/Ethernet/USB DFU 固件升级方式(要求已经有一个Bootloader),将
.bin文件烧录到 NOR Flash 芯片的从0x00000000开始的区域。
- 芯片生产阶段 (Mask ROM):
- 目的: 将生成的
总结与关键概念:
Bootloader: 执行初期硬件初始化和加载后续程序的代码。BootROM: 物理存储介质(Mask ROM/OTP/NOR Flash),其物理地址通常映射到0x00000000(或芯片定义的固定位置)。Compiler: 翻译源代码为目标文件(含机器码和重定位信息)。Linker: 按链接脚本合并目标文件/库,分配绝对地址(链接地址),解析符号,生成带地址信息的可执行文件(.elf/.axf)。链接脚本 (Linker Script) 是控制内存布局(包括 BootROM 地址)的核心!Objcopy(或等效工具): 从.elf/.axf提取仅包含.text,.rodata,.data(按LMA)内容的原始字节流,生成.bin文件。.bin 文件字节流代表从 BootROM 物理起始地址开始连续存放的指令/数据。- 烧录: 将
.bin文件写入物理 BootROM/NOR Flash。芯片上电后,CPU 从0x00000000(BootROM映射地址) 开始取第一条指令执行。 .bss初始化:.bin文件不包含.bss(它全是0)。Bootloader 代码(通常在Reset_Handler)自己负责在.data从 ROM 拷贝到 RAM 后,再调用一段汇编/C 代码(比如__libc_init_array或手动编写的函数)去清零.bss段(依据链接脚本定义的_sbss/_ebss符号地址和长度)。
希望这个详细步骤解析让你清晰地了解了从代码到 BootROM 二进制数据的完整转换过程!在这个过程中,链接器脚本对于控制 Bootloader 在 BootROM 中的物理布局至关重要,objcopy 则是生成最终烧录镜像的关键转换工具。
1494

被折叠的 条评论
为什么被折叠?



