目录
1.交叉编译工具链
2.工具链的配置与常用命令
2.1 ELF 头部信息(readelf -h 查看)—— 程序的“运行说明书”
2. 2 符号表(nm 查看,strip 去除)—— 程序的“源码索引”
为什么对最终可执行文件 strip,不影响链接(也不影响运行)?
3.生成二进制镜像文件
4.生成ELF可执行文件
Linux系统移植的核心步骤包括:搭建交叉编译工具链、编写Bootloader、移植Linux内核、构建根文件系统。
1.交叉编译工具链
概念:由于开发主机(PC)与目标机(嵌入式板子)CPU架构不同,需要使用在宿主机上运行,但能生成目标机可执行代码的工具链,称为交叉编译工具链。
核心组件:工具链通常包含gcc、ld、ar、objcopy等工具。
2.工具链的配置与常用命令
环境变量配置:将交叉编译工具链的路径添加到shell的环境变量PATH中,以便在终端中直接调用工具。
常用命令解析:
file:用于查看文件的类型和属性,确认可执行文件的架构(如x86, arm)。
readelf:用于分析ELF格式文件的头部信息,如入口地址、各段信息等。
size:用于查看目标文件各段(代码段、数据段、BSS段)的大小。
nm:用于查看目标文件中的符号表,帮助定位函数和变量。
strip:用于去除可执行文件中的符号表和调试信息,以减小文件体积,适用于最终部署到资源受限的嵌入式设备。
2.1 ELF 头部信息(readelf -h查看)—— 程序的“运行说明书”
CPU 本身是“傻子”,它不知道从哪里开始执行,也不知道内存怎么划分。ELF 头部就是解决这个问题的。
入口地址(Entry point address):
作用:告诉操作系统(或 BootLoader):“加载完程序后,请从内存的这一个地址开始执行第一条指令”。
实战意义:在嵌入式裸机开发中,这个地址通常由链接脚本(Linker Script)指定(比如 STM32 的
0x08000000)。如果你用readelf看到入口地址是0x0或者一个奇怪的地址,那程序一运行大概率会“跑飞”或崩溃。
各段信息(Program Headers / Section Headers):
头部里会写明
.text(代码段)、.data(数据段)、.bss(未初始化全局变量段)在文件中的偏移量、在内存中的大小以及加载地址。作用:系统加载器(Loader)根据这些信息,把代码搬运到 RAM/Flash 的指定位置,并留出足够的内存空间给变量。
实战意义:假设你的嵌入式板子 RAM 只有 64KB,你用
readelf -S看到.bss段占了 70KB,那程序一运行就会因为内存溢出而挂掉(HardFault)。提前用readelf检查,就能避免烧录进去后才知道出问题。
2. 2 符号表(nm查看,strip去除)—— 程序的“源码索引”
编译后的机器码全是0和1,CPU 只认地址(比如0x80001234),不认函数名(比如uart_send)。符号表就是将内存地址和源代码中的函数名、全局变量名一一对应起来的“密码本”。
符号表的作用(
nm):定位函数和变量:编译报错“未定义引用(undefined reference)”时,用
nm检查.o文件,能立刻看到哪些函数地址是U(未定义),哪些是T(已定义),帮你排查链接错误。分析内存占用:用
nm --size-sort可以按大小列出所有函数,让你一眼看出哪个函数编译后体积最大(比如某个数组占了 10KB),方便你优化代码。
为什么要去除(
strip):符号表里存着成千上万个函数名(比如
void init_gpio(void)这种字符串),这些名字在程序运行时完全没有用(CPU 只看地址,不看名字)。在资源受限的嵌入式设备中,Flash 空间寸土寸金。一个带符号表的程序可能有 500KB,
strip掉符号表和调试信息后,可能只剩下 200KB,文件体积直接缩小一半以上。注意:
strip后的程序无法再用 GDB 进行源码级单步调试,崩溃后只能看到地址(比如PC=0x8001234),所以通常是在最终量产发布前才执行strip,而保留一份带符号的版本用于后期维护排查问题。
如果是对“最终生成的可执行文件”执行
strip:完全不影响运行时动态链接,程序照常运行。如果是对“还没链接的中间文件(.o 目标文件)或静态库(.a)”执行
strip:会严重影响后续的链接,导致链接直接失败。
Linux 里的“符号表”拆分成“静态符号表(.symtab)”和“动态符号表(.dynsym)”,看完下面的对比,你就全明白了:
为什么对最终可执行文件 strip,不影响链接(也不影响运行)?
当你运行gcc main.c -o app生成最终程序后,文件里其实有两张表:
静态符号表(.symtab):体积巨大,记录了所有函数名、变量名、行号信息。这是专门给调试器(GDB)和链接器(ld)看的,CPU 和操作系统加载器完全无视它。
动态符号表(.dynsym):体积很小,只记录外部依赖的库函数(比如
printf、malloc)和导出的接口。
strip默认干的事:它只把那张巨大的“静态符号表”和调试信息(.debug 段)撕掉,通常不会动“动态符号表”。
所以,当程序运行时,动态链接器(ld-linux.so)依然能通过动态符号表找到libc.so里的printf地址,完成链接和跳转。程序跑起来完全不受影响,并且体积缩小了
3.生成二进制镜像文件
背景:裸机程序(如Bootloader、内核)需要直接运行在物理内存中,因此不能使用带ELF头的可执行文件,必须生成纯净的二进制镜像。
生成流程:
使用gcc编译C或汇编源码,生成目标文件(.o)。
使用ld命令链接目标文件,并通过链接脚本(linker script)指定各段(如代码段、数据段)的加载地址。
使用objcopy命令,将链接后生成的ELF文件中的代码段等实际内容剥离,生成最终的二进制镜像文件。
4.生成ELF可执行文件
a. 把高级语言 .c .S 生成 .o目标文件
b. 不能使用ld命令,而是使用gcc 将.o目标文件 做为 链接原材料进行链接 gcc会自动加载标准C库和OS初始化库的内容
c. 在链接时,交给默认链接脚本实现链接, 使用虚拟地址
d. 默认生成ELF格式的可执行文件