3. GNU Binutils 工具集:嵌入式开发的瑞士军刀

说实话,GNU Binutils 这套工具,我用了快二十年了。每次做 Rootfs 都离不开它们。有人觉得这些工具太底层、太枯燥,但在我看来,它们才是真正决定你系统能不能跑起来的关键。

Binutils 全称是 Binary Utilities,二进制工具集。它包含的每个小工具,都解决一个具体问题。今天我就挑最常用的六个,跟你聊聊我的实战经验。

核心知识点速览

  • as:汇编器,把汇编代码变成机器码
  • ld:链接器,把目标文件拼成可执行文件
  • objdump:反汇编,看二进制里到底有什么
  • readelf:查看 ELF 文件结构
  • strip:给二进制文件瘦身
  • ar:管理静态库
GNU Binutils 工具集 as 汇编器 汇编代码 → 机器码 ld 链接器 目标文件 → 可执行文件 objdump 反汇编 二进制 → 汇编代码 readelf 查看 ELF 解析 ELF 文件结构 strip 瘦身 移除符号表、调试信息 ar 静态库管理 打包、解包 .a 文件 六大工具覆盖:编译 → 链接 → 分析 → 优化 → 打包 每个工具在 Rootfs 制作中都有不可替代的作用

3.1 as 汇编器:从汇编到机器码

as 是 GNU 的汇编器。它的工作很简单:把你写的汇编代码,翻译成 CPU 能识别的机器码。嗯,听起来简单,但坑不少。

我记得刚做嵌入式那会儿,写了一段 ARM 汇编,用 as 编译死活报错。后来发现是语法版本问题。ARM 有 ARM 模式和 Thumb 模式,指令集不一样。as 默认用 AT&T 语法,跟 Intel 语法差别很大。

我的习惯:写汇编时,我会在文件开头明确指定架构。比如 ARM 平台,我会加一行 .arch armv7-a。这样 as 就知道该用哪套规则了。

# 一个简单的 ARM 汇编示例
.section .text
.global _start

_start:
    mov r0, #1      @ 系统调用号:1 = exit
    mov r7, #1      @ 系统调用号(ARM 用 r7)
    svc #0          @ 触发系统调用

编译命令很简单:

arm-linux-gnueabihf-as -o test.o test.s

你会得到一个目标文件 test.o。它还不是可执行文件,需要链接器来处理。

3.2 ld 链接器:把碎片拼成整体

ld 是链接器。它的任务是把多个目标文件、库文件,按照你指定的规则,拼成一个完整的可执行文件。

说白了,你写的代码可能分散在十几个 .c 文件里。编译器把它们变成 .o 文件。ld 负责把这些 .o 文件粘在一起,还要处理函数调用、变量地址这些细节。

我个人觉得,ld 最核心的是链接脚本。它告诉 ld:代码放哪里,数据放哪里,堆栈怎么安排。在嵌入式开发中,这尤其重要。因为你的程序要跑在特定的内存地址上。

我曾经踩过的坑:有一次做 Bootloader,链接脚本里把 .text 段地址写错了。结果程序下载到板子上,一运行就死机。查了两天才发现,是链接脚本里少了个对齐指令。从那以后,我每次写链接脚本都会反复检查地址对齐。

# 一个简单的链接命令
arm-linux-gnueabihf-ld -T link.ld -o test test.o start.o

其中 link.ld 是链接脚本,内容大致如下:

SECTIONS
{
    . = 0x8000;          /* 起始地址 */
    .text : { *(.text) } /* 代码段 */
    .data : { *(.data) } /* 数据段 */
    .bss : { *(.bss) }   /* BSS 段 */
}

3.3 objdump 反汇编:看穿二进制

objdump 是我用得最频繁的工具之一。它能反汇编二进制文件,把机器码还原成汇编代码。你想想看,当你拿到一个编译好的程序,没有源码,怎么知道它干了什么?objdump 就是你的眼睛。

我经常用它来检查编译器生成的代码质量。比如,看看某个函数有没有被正确优化,或者检查有没有多余的指令。

# 反汇编整个文件
arm-linux-gnueabihf-objdump -d test

# 只看某个段的内容
arm-linux-gnueabihf-objdump -s -j .rodata test

输出示例:

00008000 <_start>:
    8000:   e3a00001    mov r0, #1
    8004:   e3a07001    mov r7, #1
    8008:   ef000000    svc 0

左边是地址,中间是机器码,右边是汇编指令。一目了然。

一个小技巧:如果你想看某个函数被编译成了什么,可以用 objdump -d test | grep -A 20 "<function_name>:"。这样只显示那个函数的反汇编结果,省得翻半天。

3.4 readelf:ELF 文件的解剖刀

readelf 用来查看 ELF 文件的内部结构。ELF 是 Linux 下可执行文件和目标文件的格式。它包含文件头、段表、符号表、重定位表等等。

为什么需要 readelf?因为有时候程序跑不起来,你不知道是哪个段出了问题。readelf 能帮你把 ELF 文件的结构看得清清楚楚。

# 查看 ELF 文件头
arm-linux-gnueabihf-readelf -h test

# 查看所有段
arm-linux-gnueabihf-readelf -S test

# 查看符号表
arm-linux-gnueabihf-readelf -s test

输出示例:

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Machine:                           ARM
  Entry point address:               0x8000

看到 Entry point address 了吗?这就是程序入口地址。如果这个地址不对,程序肯定跑不起来。

实战经验:有一次我交叉编译一个程序,放到目标板上说"Exec format error"。用 readelf 一看,发现 Machine 字段显示的是 x86,不是 ARM。原来是交叉编译器没配好,编译出来的文件架构不对。readelf 帮我省了半天排查时间。

3.5 strip:给二进制文件瘦身

strip 的作用很简单:删除 ELF 文件中的符号表和调试信息。在嵌入式系统中,存储空间往往很宝贵。一个带调试信息的程序可能有几 MB,strip 之后能缩小到几百 KB。

你想想看,你的 Rootfs 总共就 32MB 的 Flash 空间。每个程序都带着调试信息,装不了几个就满了。strip 就是帮你省空间的利器。

# 瘦身一个可执行文件
arm-linux-gnueabihf-strip test

# 只删除调试信息,保留符号表
arm-linux-gnueabihf-strip --strip-debug test

# 查看瘦身前后的文件大小
ls -lh test
arm-linux-gnueabihf-strip test
ls -lh test

注意:strip 之后,你就没法用 gdb 调试这个程序了。所以我的习惯是:开发阶段保留调试信息,发布前再做 strip。另外,strip 不能恢复,操作前最好备份原始文件。

我曾经在一个项目里,用 strip 把整个 Rootfs 从 28MB 压缩到了 12MB。效果非常明显。但要注意,strip 只对 ELF 文件有效。如果你有脚本文件或者数据文件,strip 帮不了你。

3.6 ar:静态库的打包工

ar 是归档工具,用来创建和管理静态库(.a 文件)。静态库说白了,就是把一堆 .o 文件打包成一个文件。链接的时候,ld 会从 .a 文件里提取需要的 .o 文件。

我习惯把常用的函数封装成静态库。比如,驱动层的 GPIO 操作、I2C 通信、串口打印这些。这样多个项目可以复用,不用每次都重新编译。

# 创建静态库
arm-linux-gnueabihf-ar rcs libmylib.a gpio.o i2c.o uart.o

# 查看静态库里的内容
arm-linux-gnueabihf-ar t libmylib.a

# 从静态库中提取某个目标文件
arm-linux-gnueabihf-ar x libmylib.a gpio.o

ar 的参数说明:

参数 含义
r 替换或添加文件到库中
c 创建库(如果不存在)
s 创建索引,加快链接速度
t 列出库中的文件列表
x 从库中提取文件

我的建议:创建静态库时,一定要加 's' 参数生成索引。否则链接的时候 ld 会报 "ar: creating an index" 的警告。虽然不影响功能,但链接速度会变慢。尤其是库文件很大的时候,差别很明显。

使用静态库链接程序:

arm-linux-gnueabihf-gcc -o test test.c -L. -lmylib

这里 -L. 表示在当前目录找库,-lmylib 表示链接 libmylib.a。

小结

这六个工具,每个都有自己的专长。as 负责汇编,ld 负责链接,objdump 和 readelf 帮你分析,strip 帮你瘦身,ar 帮你管理代码。在 Rootfs 制作中,它们缺一不可。

我个人觉得,掌握这些工具的关键是多用。别光看书,打开终端,拿一个实际的 ELF 文件,用 readelf 看看它的结构,用 objdump 反汇编看看代码,用 strip 试试瘦身效果。动手做一遍,比看十遍都管用。

一句话总结:Binutils 是嵌入式开发的基石。你越了解它们,就越能掌控你的系统。

可可精品资料库