首页
社区
课程
招聘
1
[分享]SO文件格式笔记
发表于: 2025-4-3 20:21 1485

[分享]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编辑 ,原因: 补充
收藏
免费 1
支持
分享
赞赏记录
参与人
雪币
留言
时间
qqyspgj
这个讨论对我很有帮助,谢谢!
2025-4-5 11:26
最新回复 (0)
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册