ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Cortex A移植概念备忘录

2026/8/12 14:48:17 拓冰建站 浏览量
Cortex A移植概念备忘录

目录

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去除)—— 程序的“源码索引”

编译后的机器码全是01,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):体积很小,只记录外部依赖的库函数(比如printfmalloc)和导出的接口。

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格式的可执行文件