Mini2440移植uC/OS-II笔记(三).elf文件解读
预处理 (Pre-processing)main.c被处理,头文件展开。汇编 (Compilation): 编译器将其变成汇编,然后变成.o此时全是碎片,地址未定。链接 (Linking)ld将main.oos_core.ostartup.o等聚在一起。根据link.ld脚本,决定大家住哪(0x30000000解决所有的符号引用。生成 Program Headers,规划内存布局。此刻,mini2
0.序言
以UCOS II为例,我要把它移植到mini2440。
在VScode中打开一个elf文件,一般是不能直接查看的。

点击中间的仍然打开,可以在上方选择十六进制编辑器。

进入显示了16进制的页面,那么我们面对依然比较难解读的代码如何解读呢。一般我们采用readelf命令进行解读。

1.elf header
在WSL terminal中使用命令,cmd或者powershell用这个没有用,以我make出来的mini2440_rtos.elf文件举例。
在vscode terminal中点击+号下拉菜单选择WSL

输入命令,注意要先cd到你的elf文件目录
readelf -h mini2440_rtos.elf
终端显示结果

ELF 文件头位于文件偏移 0x00 至 0x34 (52字节) 处,定义了文件的基本属性与解释方式。
1.1 标识与架构 (Identification & Machine)
-
Magic Number:
7f 45 4c 46 ...,确认为标准的 ELF 格式文件。 -
Class:
ELF32。表明该系统采用 32 位地址空间,指针与长整型宽度为 4 字节,适配 ARM9 32位 RISC 架构。 -
Data:
2's complement, little endian。数据采用小端序存储(低字节在低地址),这与Makefile中未指定大端参数(默认小端)的行为一致,且符合 S3C2440 通常的运行模式。 -
Machine:
ARM。指定目标指令集架构为 ARM。 -
Flags:
0x5000200, Version5 EABI, soft-float ABI。-
Version5 EABI: 遵循 ARM 嵌入式应用二进制接口 (EABI) 第 5 版标准,规范了函数调用约定(如寄存器传参 R0-R3)、堆栈对齐(8字节对齐)等。
-
soft-float ABI: 表明二进制代码不使用硬件浮点指令(ARM920T 无 FPU),所有浮点运算均通过软件库函数(如
__aeabi_fadd)模拟实现。
-
1.2 文件类型与入口 (Type & Entry)
-
Type:
EXEC (Executable file)。表明文件已完成重定位(Relocation)与符号解析,包含绝对虚拟地址,可直接加载运行。 -
Entry point address:
0x30000000。-
物理意义: 这是 CPU 获取控制权后执行的第一条指令地址。
-
构建溯源: 链接脚本
link.ld通过ENTRY(_start)指令指定入口符号,而.text段被脚本强制定位在0x30000000。汇编源码startup.S将_start标号置于代码段之首,从而确立了该入口地址。
-
2.Section Headers
如果 Header 是封面,Section Headers 就是目录。它告诉我们要去哪里找代码、找数据。
运行命令:
arm-none-eabi-readelf -S build/mini2440_rtos.elf

可以看到有15个Section。这些是编译器归类放置东西的区域划分。
2.1 代码段 (.text)
-
范围:
0x30000000-0x3000800d -
大小:
0x00800d -
属性:
AX(Alloc, Execute)。 -
内容: 包含异常向量表、启动代码、操作系统内核代码及用户应用代码。
-
对齐现象: 代码段结束于非 4 字节对齐地址
0x3000800d。
2.2 填充 (Padding)
-
现象: 下一节区
.data起始于0x30008010。 -
分析: 存在 3 字节 (
0x30008010 - 0x3000800d = 3) 的空隙。 -
成因:
link.ld脚本在.data段定义前使用了. = ALIGN(4);指令。这是为了确保数据段起始地址满足 ARM 处理器的 32 位访问对齐要求,防止非对齐访问异常(Data Abort)。
2.3 已初始化数据段 (.data)
-
范围:
0x30008010-0x30008118 -
大小:
0x000108(264 Bytes)。 -
属性:
WA(Write, Alloc)。 -
内容: 包含代码中显式赋值的全局变量,如
os_core.c中的查找表。
2.4 未初始化数据段 (.bss)
-
范围:
0x30008118-0x3000b8e8 -
大小:
0x0037d0(14,288 Bytes)。 -
属性:
WA(Write, Alloc),NOBITS。 -
验证: 结束地址
0x30008118 + 0x37d0 = 0x3000b8e8,计算无误。此区域包含大型数据结构,如任务堆栈数组。
2.5 调试信息段 (.debug_*)
-
特征: 地址均为
00000000,不占用目标机内存。 -
debug_line: 映射机器码地址与 C 源码行号。
-
debug_info: 存储 DWARF 格式的类型信息与变量作用域。
-
debug_frame: 存储调用栈帧信息(CFI),用于调试器回溯函数调用栈。
3. Program Headers
程序头表描述了系统加载器(Loader)如何将文件映射到物理内存。本镜像仅包含一个 LOAD 类型的段(Segment),表明其为单一连续的内存镜像。
3.1 静态加载地址映射
-
VirtAddr (虚拟地址):
0x30000000 -
PhysAddr (物理地址):
0x30000000 -
分析: 虚拟地址与物理地址恒等,表明该程序被设计为直接在 SDRAM 控制器映射的基地址(S3C2440 的 Bank 6起始地址)处运行,不开启 MMU 地址转换,或 MMU 采用平坦映射(Flat Mapping)。
3.2 文件镜像与内存镜像的差异
-
FileSiz (文件占用):
0x08118(33,048 Bytes)。包含代码指令与已初始化的全局变量。 -
MemSiz (内存占用):
0x0b8e8(47,336 Bytes)。包含代码、已初始化数据及未初始化数据(BSS)。 -
BSS 空间计算:
MemSiz - FileSiz = 0x37D0(14,288 Bytes)。-
该差值精确对应了程序运行时所需的未初始化数据区大小。系统启动时,必须由启动代码(Startup Code)将这
0x37D0字节的内存区域清零,否则未初始化变量将含有随机值。
-
CPU 并不关心你有多少个小抽屉(Section),它只关心内存块(Segment)。加载器会把属性相似的 Section 合并成一个 Segment (段)。 命令:readelf -l
让我们看看 mini2440_rtos.elf 的 Program Headers:

-
Type: LOAD: 这表示这个段必须被加载到内存里。
-
VirtAddr (虚拟地址/运行地址 VMA): 代码运行时应该在的地方。对于 mini2440,是在 SDRAM (
0x30000000)。 -
PhysAddr (物理地址/加载地址 LMA): 代码如果不运行,静静躺着时应该在的地方。
-
注意:在裸机开发中,如果你的代码在 NOR Flash 运行,VMA 可能等于 LMA。但如果你的代码要从 NAND 拷贝到 SDRAM 运行,Linker Script 里就要仔细处理这两个地址的差异。你的
mini2440_rtos似乎是直接链接到 SDRAM 地址,这意味着你可能通过 J-Link 直接把程序写到了 SDRAM,或者 Bootloader 帮你完成了搬运。
-
-
Flg (Flags):
-
R E(Read, Execute): 这是代码段。只读,可执行。对应.text。 -
RW(Read, Write): 这是数据段。可读写。对应.data和.bss。
-
关键点:操作系统加载程序时,根本不看 Section Headers,只看 Program Headers。它按照这里的指示,把文件切块,“映射”到内存里。
4. Symbol Table(符号表)
符号表提供了内存地址与源码变量/函数的直接映射关系。可以用 arm-none-eabi-nm 或 readelf -s 查看。
arm-none-eabi-objdump -s mini2440_rtos.elf > 1.txt
4.1 启动与异常处理
-
_start(0x30000000): 物理入口。对应startup.S中的异常向量表基址。-
向量表布局:
-
0x00:b reset -
0x04:b undef -
...
-
-
-
reset(0x30000110): 复位异常处理程序实体入口。-
偏移分析: 从
0x30000000到0x30000110之间的0x110(272) 字节空间,包含了 8 条跳转指令(32字节)以及其后的 LED 错误指示处理代码(undef, swi, pabort 等处理程序)。这表明复位代码被安排在其他异常处理桩代码之后。
-
4.2 链接脚本边界符号
以下符号由 link.ld 自动生成,用于运行时内存管理:
-
_etext/_data(0x30008010): 准确标记了只读代码区与可读写数据区的分界线。 -
_bss_start_(0x30008118): 标记清零区的起始。 -
_bss_end_(0x3000b8e8): 标记清零区的结束。
4.3 关键变量布局
-
StartStk(0x30008118):-
定义:
main.c中定义的OS_STK StartStk[...]。 -
位置: 位于
.bss段的绝对首地址。 -
风险评估: 由于
StartStk紧邻.data段(0x30008118 之前即为 .data),若该栈发生向下溢出 (Stack Overflow),将直接破坏.data段末尾的全局变量,导致系统出现难以调试的逻辑错误。这是内存布局上的一个高风险点。
-
-
OSCPUUsage(0x3000ac0d):-
大小: 1 字节 (
OBJECT类型)。 -
位置: 位于
.bss段深处,表明其为未初始化的统计变量。
-
4.4 绝对地址符号 (ABS)
-
BWSCON(0x48000000),GPFCON(0x56000050) 等。 -
来源: 这些符号源自
2440addr.inc等头文件中的宏定义 (.equ)。 -
性质: 它们不代表内存中的变量,而是直接指向 S3C2440 处理器的特殊功能寄存器 (SFR) 物理地址,用于外设控制。
5.调试信息的迷宫(DWARF)
ELF 文件里有几个巨大的段:.debug_info, .debug_abbrev, .debug_str。它们使用的是 DWARF 格式(Debugging With Attributed Record Formats)。
它像是一棵树。
-
编译单元 (CU): 根节点。比如
main.c。 -
子节点: 函数
main。-
属性:起始 PC
0x30000458,结束 PC0x3000047c。 -
属性:源文件行号。
-
-
子节点: 变量
key。-
属性:类型
int(4字节)。 -
属性:位置
fp - 16(栈帧指针减16的位置)。
-
当你用 GDB 调试时输入 print key:
-
GDB 查 DWARF 表,找到
key的定义。 -
GDB 发现它是
int,且在栈上。 -
GDB 读取寄存器
fp的值,减去 16,得到内存地址。 -
GDB 读取那个内存地址的 4 个字节,按整数解释,显示给你。
如果没有这些段(比如你用了 strip 命令或者没加 -g),GDB 就只会对着一堆寄存器和十六进制发呆。
6.总结
mini2440_rtos.elf 的全处理流程如下:
-
预处理 (Pre-processing):
main.c被处理,头文件展开。 -
汇编 (Compilation): 编译器将其变成汇编,然后变成
.o(Relocatable ELF)。此时全是碎片,地址未定。 -
链接 (Linking):
ld将main.o,os_core.o,startup.o等聚在一起。-
根据
link.ld脚本,决定大家住哪(0x30000000)。 -
解决所有的符号引用。
-
生成 Program Headers,规划内存布局。
-
此刻,mini2440_rtos.elf 诞生了。
-
-
加载 (Loading):
-
你通过 J-Link 点击“Download”。
-
J-Link 软件读取 ELF Header,发现是 ARM 程序。
-
读取 Program Headers,看到有一个 LOAD 段要放在
0x30000000。 -
它把文件里对应位置的二进制数据通过 JTAG 协议烧写到板子的 SDRAM 中。
-
最后,它读取 ELF Header 的 Entry Point,把 CPU 的 PC 寄存器强行设置为
0x30000000。
-
-
运行 (Execution):
-
板子复位,CPU 从
0x30000000开始取指。 -
第一条指令通常是跳转到 Reset Handler。
-
建立栈,初始化
.bss,搬运.data。 -
跳转到
main()。 -
uC/OS-II 启动,LED 开始闪烁。
-
7.题外话
当然对于静态调试而言反汇编生成一个.dis文件,人类会更容易看懂,可以直接在Makefile文件中添加相关语法。elf的更多作用是对项目架构有一个整体感、配合GDB调试。
arm-none-eabi-objdump -S mini2440_rtos.elf > mixed_view.dis

更多推荐


所有评论(0)