【硬件】高级语言转换为.bin文件的过程【个人草稿】

一个典型的嵌入式系统/固件开发流程。这个过程涉及多个工具链组件,最终将程序员编写的高级语言(或汇编)代码转换成可以烧录到 ROM 芯片中的原始二进制字节流(.bin 文件)。

核心流程:
源代码 (*.c, *.s, *.h) -> 编译 (Compiler) -> 目标文件 (*.o) -> 链接 (Linker) -> 带地址信息的可执行文件 (*.elf/axf) -> 格式转换 (Objcopy/工具链命令) -> 纯二进制镜像 (*.bin) -> 烧录到 BootROM 或 Flash。

详细步骤解析:

  1. 编写 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
  2. 编译 (Compilation):

    • 目的: 将人类可读的源代码(高级语言或汇编)转换成特定处理器架构的 机器指令Machine CodeOpcode)以及初始化的数据。
    • 工具: 编译器 (Compiler)。嵌入式领域常见的有:
      • GCC 交叉编译器 (arm-none-eabi-gcc, riscv64-unknown-elf-gcc, mips-elf-gcc 等)。
      • LLVM/Clang 交叉编译器。
      • 商业编译器(ARM Compiler, IAR EWARM, Keil μVision)。
    • 过程:
      • 编译器解析源代码,进行词法分析、语法分析、语义分析、优化。
      • 为每个 .c.s 文件生成对应的 目标文件 (Object File)(.o.obj)。
      • 目标文件内容:
        • .text 段:存放编译后的机器指令。
        • .data 段:存放显式初始化的全局变量和静态变量(初始值不为零)。
        • .rodata 段:存放只读数据(如常量字符串)。
        • .bss 段:存储未初始化或显式初始化为零的全局/静态变量(只有大小信息,文件本身不占用空间,运行时清零)。
        • 重定位信息 (Relocation Info): 最重要!记录了目标文件中哪些符号(函数名、变量名)的地址是未知的,需要在链接阶段由链接器解析填充。也包含跳转指令需要调整的目标地址信息。
        • 符号表 (Symbol Table): 定义了目标文件提供的外部可见的函数/变量(输出符号),以及它所引用的外部函数/变量(未定义符号/输入符号)。
        • 调试信息(可选)。
    • 特点:
      • 目标文件包含机器码,但它不能直接执行。
      • 目标文件内部的代码和数据的地址通常是基于 0 的相对地址(.text, .data, .rodata 等的起始地址假设从 0 开始)。最终的物理地址由链接器决定!
  3. 链接 (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 或特定后缀): 较旧的格式。
    • 核心:链接器脚本 (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头部信息、段表、重定位信息(某些情况)、调试信息等。
  4. 格式转换:生成 .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: 最终生成的纯二进制文件。
    • 转换过程:
      1. objcopy 读取 .elf/.axf 文件。
      2. 根据 .elf/.axf 文件中的 段信息(由链接脚本定义),获取 .text(代码)、 .rodata(只读常量)、以及 .data(初始化数据)段的 最终加载地址(Load Address LMA,在 ROM/BootROM 中的地址)内容.bss 段只在运行时存在于 RAM,不包含任何需要在 ROM 中烧录的初始化数据(只有大小),所以不会出现在 .bin 文件中。
      3. 它会 忽略所有头信息、符号表、调试信息、重定位信息等非程序内容
      4. 它会将这些段的内容(指令和数据)按照它们链接时的 加载地址(LMA) 在内存中的顺序,进行 填充合并。链接器脚本中如果定义 .data 的 LMA 在 .text 之后(如上面的例子 AT (ADDR(.text) + SIZEOF(.text))),那么 .bin 文件中,.data 段的内容就会紧接着 .text 段的最后一个字节开始排放。
      5. 它将填充和合并后的 连续字节流,从这些段的最低加载地址(通常是 0x00000000,BootROM 的物理起始地址)开始排列,并写入 output.bin 文件。该文件不包含任何地址信息,它代表着从 BootROM 物理起始地址 0x00000000 开始,连续的一串指令和数据的字节。
    • 重要理解点: .bin 文件的第 0 个字节对应于 BootROM 地址 0x00000000.bin 文件的长度等于链接时分配在 ROM/BootROM 区域中的 .text, .rodata, .data(的加载地址部分)的总实际大小。
  5. 烧录到 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 开始的区域。

总结与关键概念:

  1. Bootloader: 执行初期硬件初始化和加载后续程序的代码。
  2. BootROM: 物理存储介质(Mask ROM/OTP/NOR Flash),其物理地址通常映射到 0x00000000 (或芯片定义的固定位置)。
  3. Compiler: 翻译源代码为目标文件(含机器码和重定位信息)。
  4. Linker: 按链接脚本合并目标文件/库,分配绝对地址(链接地址),解析符号,生成带地址信息的可执行文件(.elf/.axf)。链接脚本 (Linker Script) 是控制内存布局(包括 BootROM 地址)的核心!
  5. Objcopy (或等效工具):.elf/.axf 提取仅包含 .text, .rodata, .data(按 LMA)内容的原始字节流,生成 .bin 文件。.bin 文件字节流代表从 BootROM 物理起始地址开始连续存放的指令/数据。
  6. 烧录:.bin 文件写入物理 BootROM/NOR Flash。芯片上电后,CPU 从 0x00000000 (BootROM映射地址) 开始取第一条指令执行。
  7. .bss 初始化: .bin 文件不包含 .bss(它全是0)。Bootloader 代码(通常在 Reset_Handler自己负责.data 从 ROM 拷贝到 RAM 后,再调用一段汇编/C 代码(比如 __libc_init_array 或手动编写的函数)去清零 .bss 段(依据链接脚本定义的 _sbss/_ebss 符号地址和长度)。

希望这个详细步骤解析让你清晰地了解了从代码到 BootROM 二进制数据的完整转换过程!在这个过程中,链接器脚本对于控制 Bootloader 在 BootROM 中的物理布局至关重要,objcopy 则是生成最终烧录镜像的关键转换工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值