-
-
[分享]SO文件格式笔记
-
发表于: 2025-4-3 20:21 1485
-
ELF 文件结构关键组成
一、符号表 (Symbol Table: .symtab
)
符号表用于存储程序中定义的和引用的各种符号(如函数、变量、节区等)的信息。
C
typedef struct { Elf32_Word st_name; // 符号名在字符串表中的偏移量 unsigned char st_info; // 符号类型和绑定属性 unsigned char st_other; // 其他信息(如可见性) Elf32_Section st_shndx; // 关联的节区索引 Elf32_Addr st_value; // 符号的地址或值 Elf32_Word st_size; // 符号的大小(如函数/变量占用的字节数)} Elf32_Sym;
1. st_name
: 符号名称偏移量
- 表示符号名称在 ELF String Table (通常是
.strtab
) 中的偏移量。 - 若值为
0
,则表示该符号没有名称(属于匿名符号)。
2. st_info
: 符号类型和绑定属性
该字段的高 4 位表示绑定属性 (Binding),低 4 位表示符号类型 (Type)。
(1) 绑定属性 (高 4 位)
STB_LOCAL
(0x1
): 局部符号,作用域仅限于当前目标文件。STB_GLOBAL
(0x2
): 全局符号,可以被其他目标文件引用。STB_WEAK
(0x3
): 弱符号,优先级低于全局符号,可以被同名的全局符号覆盖。常用于提供默认或备用实现。STB_LOPROC
~STB_HIPROC
(0xd
~0xf
): 处理器特定的绑定类型,具体含义需要参考目标架构的文档。
(2) 符号类型 (低 4 位)
STT_NOTYPE
(0x0
): 未指定类型。STT_OBJECT
(0x1
): 表示一个数据对象,例如变量。STT_FUNC
(0x2
): 表示一个函数或可执行代码。STT_SECTION
(0x3
): 与某个节区关联的符号,通常表示该节区的起始地址。STT_FILE
(0x4
): 文件名符号,通常用于记录源文件名。STT_COMMON
(0x5
): 表示一个未初始化的全局变量(在链接时需要分配空间)。STT_TLS
(0x6
): 线程局部存储 (Thread-Local Storage) 变量。STT_LOPROC
~STT_HIPROC
(0xd
~0xf
): 处理器特定的符号类型。
3. st_other
: 其他信息
- 通常默认值为
0
。 - 低 3 位表示符号的可见性 (Visibility),可以通过宏
ELF32_ST_VISIBILITY
提取。 STV_DEFAULT
(0x0
): 默认可见性,遵循标准的动态链接规则。STV_HIDDEN
(0x1
): 隐藏符号,在动态链接过程中,其他模块无法直接引用。STV_INTERNAL
(0x2
): 内部符号,比隐藏符号更严格,可能在优化时被删除。STV_PROTECTED
(0x3
): 受保护符号,可以被其他模块引用,但不能被覆盖。
4. st_shndx
: 节区索引 (Section Header Index)
- 表示该符号关联的节区的索引,例如
.text
(代码段)、.data
(数据段) 等。 - 特殊值:
SHN_UNDEF
(0
): 符号未定义,通常表示该符号是在其他目标文件中定义的(外部符号)。SHN_ABS
(0xfff1
): 符号是绝对地址,其值不会因为重定位而改变。
5. st_value
: 符号的地址或值
- 对于已定义的符号(例如函数或变量):
st_value
表示符号在其关联节区内的偏移量(虚拟地址)。 - 例外:如果符号类型为
STT_FILE
(文件名符号),st_value
通常为0
。 - 对于未定义的符号(外部引用):
st_value
通常为0
。这类符号的地址需要在链接阶段通过动态链接器在 Global Offset Table (GOT) 中解析。
6. st_size
: 符号的大小
- 表示符号所占用的字节数。例如,对于函数,表示函数代码的大小;对于变量,表示变量占用的内存大小。
二、字符串表 (String Table: .strtab
, .dynstr
)
- 字符串表是连续的以
\0
(null) 结尾的字符串集合,这些字符串按顺序排列。 - ELF 文件中的其他结构(如符号表)通过偏移量 (offset) 来引用字符串表中的特定字符串,从而实现对名称的存储和查找。通常有
.strtab
(用于静态链接) 和.dynstr
(用于动态链接) 两种字符串表。
三、RELA 重定位表 (Relocation Table with Addends: .rela.text
, .rela.data
等)
RELA 重定位表主要用于处理需要进行地址修正的情况,尤其针对数据引用和绝对地址的修正。常见的应用场景包括全局变量、静态数据等非函数类符号的重定位。
C
typedef struct { Elf32_Addr r_offset; // 需要重定位的位置(目标偏移地址),一般表示 <需要重定位的 offset> 的 offset Elf32_Word r_info; // 低 32 位表示重定位类型,高 32 位表示符号表索引 Elf32_Sword r_addend; // 加数(直接参与计算的常量)} Elf32_Rela;
1. r_offset
: 重定位偏移量
- 表示需要进行地址修正的目标地址在其所属节区(例如
.text
或.data
)中的偏移量。简单来说,它指明了需要修改的那个内存位置相对于节区起始地址的偏移。
2. r_info
: 重定位信息
- 包含符号索引 (Symbol Index) 和重定位类型 (Relocation Type) 的组合信息。
- 符号索引 (高 32 位): 指向符号表中一个条目的索引,用于确定参与重定位计算的符号。
- 重定位类型 (低 32 位): 定义了如何计算需要写入到
r_offset
所指向的内存位置的值。不同的处理器架构有不同的重定位类型。可以参考相关的架构文档或您提供的 Android ELF 重定位详解 了解更多重定位类型。
3. r_addend
: 加数
- 一个有符号的常量值,在进行重定位计算时会直接加到符号的值上,用于更精细地调整最终的地址。
四、JMPREL 重定位表 (Jump Relocation Table: .rel.plt
或 .rela.plt
)
JMPREL 表是 Procedure Linkage Table (PLT) 和 Global Offset Table (GOT) 机制的核心,专门用于处理外部函数调用的延迟绑定。
- 对比:
- RELA 重定位: 在程序启动时由动态链接器统一处理,确保所有数据引用的正确性。
- JMPREL 重定位: 采用延迟绑定策略,函数地址的解析被推迟到函数首次被调用时,从而减少程序的启动时间开销。
JMPREL 表的条目结构与 RELA 表类似(在 x86-64 等架构中通常使用 RELA 格式,即包含 r_addend
)。
1. r_offset
: 目标地址
- 表示需要重定位的目标地址,通常是 Global Offset Table for PLT (GOT.PLT) 中的一个条目的地址。
2. r_info
: 重定位信息
- 符号索引 (高 32 位): 与 RELA 表相同,指向符号表中对应的外部函数符号。
- 重定位类型 (低 32 位): 指示如何进行重定位。常见的类型包括:
R_X86_64_JUMP_SLOT
(7
for x86-64): 将 GOT.PLT 条目修正为目标函数的真实地址。R_ARM_JUMP_SLOT
(22
for ARM32): 类似地,修正 GOT.PLT 条目。R_AARCH64_JUMP_SLOT
(1026
for AArch64): 同样用于修正 GOT.PLT 条目。
3. r_addend
: 加数
- 通常为
0
,因为 PLT 的延迟绑定机制直接使用解析出的函数地址来填充 GOT.PLT 条目,不需要额外的加数。
五、GOT.PLT (Global Offset Table for PLT)
GOT.PLT 是动态链接机制的关键组成部分,用于存储外部函数的地址。其理论上的初始布局如下:
PLT.GOT[0]
: 保留条目,通常指向动态段 (.dynamic
) 的地址,动态链接器可以通过它找到自身需要的信息。PLT.GOT[1]
: 保留条目,通常指向link_map
结构,这是动态链接器内部用于管理已加载模块的数据结构。PLT.GOT[2]
: 保留条目,通常指向_dl_runtime_resolve
函数的入口地址。这是一个动态链接器提供的函数,用于在首次调用外部函数时解析其真实地址。PLT.GOT[3...]
: 后续的每个条目对应一个通过 PLT 调用的外部函数。- 初始状态: 这些条目通常指向 PLT 中与该函数对应的解析代码段。当函数首次被调用时,PLT 代码会调用
_dl_runtime_resolve
来查找函数的真实地址,并将该地址写回到对应的 GOT.PLT 条目中。 - 解析后: 再次调用该函数时,PLT 代码会直接跳转到 GOT.PLT 中存储的真实函数地址,避免了重复解析的开销,这就是所谓的延迟绑定 (Lazy Binding)。
Android 11 测试中的观察
您在 Android 11 测试中观察到的现象与标准的延迟绑定行为有所不同:
PLT.GOT[0...2]
三个保留条目都为0
。PLT.GOT[3...]
中对应外部函数的条目已经被解析,直接指向外部函数的真实地址。- 这表明在您的测试环境中,可能并不存在函数调用的延迟绑定。
可能的链接过程 (Prelinking)
- Prelink Image 阶段: Android 系统在应用启动前或安装时,可能会执行一个预链接 (prelink) 或类似优化的过程。在这个阶段,linker (动态链接器) 会加载 RELA 和 JMPREL 表,并开始执行重定位操作。
- 提前解析: 在这个预链接阶段,linker 可能会主动解析所有需要重定位的变量和函数地址,包括外部函数的地址。
- 填充 GOT.PLT: 因此,在应用真正启动并执行代码之前,
PLT.GOT[3...]
中的条目就已经被填充了外部函数的真实地址。
这种预链接的优化可以显著减少应用启动时的动态链接开销,提高应用的启动速度。将保留的 GOT 条目设置为 0
可能是一种内部优化手段,具体原因需要更深入地了解 Android linker 的实现细节。
最后于 6天前
被null0bj编辑
,原因: 补充
赞赏
他的文章
- [分享]SO文件格式笔记 1486
- [原创]Frida检测思路 15865
赞赏
雪币:
留言: