so加载详细流程解析
字数 2709 2025-09-01 11:26:17
SO文件加载流程深度解析
1. SO文件基础概念
1.1 SO文件定义
- 全称:Shared Object(共享对象)
- 性质:动态共享库,存放可被多个程序共享并在运行时链接的代码和数据
- 优势:
- 运行时加载和卸载
- 节省内存和磁盘空间
- 库更新时无需重新编译依赖程序
1.2 ELF格式基础
- 定义:Executable and Linkable Format,用于存储可执行文件、目标代码、共享库及核心转储文件
- 采用系统:所有现代Unix-like系统(Linux、Solaris、BSD等)
- 三种主要类型:
- 可重定位文件(.o):包含编译器生成的代码和数据,用于链接生成可执行文件或共享库
- 可执行文件:可直接执行的程序
- 共享目标文件(.so):即SO文件,包含代码和数据
1.3 SO文件与ELF的关系
- SO文件采用ELF格式组织和存储内容
- 统一使用ELF格式保证了跨平台兼容性和工具链统一性
2. SO文件生成过程
2.1 编译流程
- 使用C编译器将C文件编译成目标文件(.o)
- 使用gcc或ld命令将目标文件链接成SO文件(.so)
- 将生成的SO文件与程序进行动态链接
3. Android SO加载详细流程
3.1 Java层加载入口
System.loadLibrary()
- 调用路径:
System.loadLibrary()→Runtime.loadLibrary0() - Runtime.loadLibrary0()主要逻辑:
-
库名检查:
- 确保库名中不包含目录分隔符("/"或"")
- 检查方法:
libname.indexOf(File.separatorChar) != -1
-
ClassLoader处理:
- 非NULL情况:根据ClassLoader查找规则查找实际文件路径
- NULL情况:
- 使用
System.mapLibraryName(libraryName)将库名映射为实际文件名(如"libfirstndk.so") - 调用
getLibPaths()获取系统预设库搜索路径(如/vendor/lib、/system/lib等) - 遍历路径,构造候选路径并检查可读性(
IoUtils.canOpenReadOnly(candidate)) - 调用
nativeLoad(candidate, loader)尝试加载
- 使用
-
3.2 Native层加载流程
Runtime_nativeLoad
- Java层
nativeLoad对应的Native实现 - 核心流程由动态链接器(Linker)完成
动态链接器核心流程
-
dlopen系列函数调用
- 显式调用
dlopen或隐式依赖(静态链接时自动加载)
- 显式调用
-
动态链接器介入
- 解析库路径
- 加载库文件到内存
- 处理符号重定位
- 执行初始化代码
-
库文件查找与加载
- 搜索路径:
/system/lib[64]/vendor/lib[64]/data/app/.../lib- 通过
LD_LIBRARY_PATH环境变量扩展
- 加载步骤:
a. 检查库是否已加载(避免重复)
b. 读取ELF文件头验证有效性
c. 创建内存映射(mmap)将库载入进程地址空间
- 搜索路径:
-
ELF文件解析与内存映射
- 关键ELF结构:
- Program Header Table:描述段(Segment)信息
- Section Header Table:描述节(Section)信息
- 内存映射策略:
- 代码段(.text):
PROT_READ | PROT_EXEC - 数据段(.data、.bss):
PROT_READ | PROT_WRITE
- 代码段(.text):
- 关键ELF结构:
-
符号重定位(Relocation)
- 目的:解决跨库函数调用和数据引用的地址问题
- 关键步骤:
a. 解析动态符号表(.dynsym)和重定位表(.rel.dyn/.rel.plt)
b. 遍历所有需要重定位的项(ElfW(Rela)),修正地址
c. PLT/GOT机制:- PLT(Procedure Linkage Table):跳转桩代码
- GOT(Global Offset Table):存储函数实际地址
-
初始化函数执行顺序
.init段:编译器生成的初始化函数(通常由_init函数实现).init_array段:函数指针数组,按顺序执行每个初始化函数JNI_OnLoad(如果存在):用于动态注册JNI方法
3.3 关键函数分析
do_dlopen
- 调用两个重要函数:
find_library:完成SO的装载链接soinfo的CallConstructors:调用SO的初始化函数
find_library_internal
- 通过
find_loaded_library_by_name判断目标SO是否已加载 - 未加载则调用
load_library继续加载流程
load_library
- 核心加载函数:
- 通过
ElfReader对象的load方法将SO文件装载到内存 - 调用
soinfo_alloc为SO分配新的soinfo结构 - 调用
soinfo_link_image完成SO的装载
- 通过
soinfo结构体
- 作用:描述并管理已加载共享库(.so)的各种信息
- 核心功能:
- 记录库的基地址
- 管理符号表
- 处理依赖关系
- 维护重定位信息
CallConstructors
- 初始化函数指定方式:
- 链接选项
-init - 函数属性
__attribute__((constructor))
- 链接选项
- 调用时机:SO装载链接后被调用,之后才将
soinfo指针返回给dl_open调用者 - 安全应用:
- SO层面保护的两个介入点:
jni_onload- 初始化函数(常用于反调试、脱壳等)
- SO层面保护的两个介入点:
init和init_array函数
- 特性:SO文件在被加载/卸载时自动执行的函数
- 执行顺序:
init函数优先于init_array函数 - 安全应用:可通过hook这些早期执行的函数绕过关键检测点
4. 关键技术与应用
4.1 动态加载hook点
- 常见问题:传统hook时机可能错过
init初始化 - 解决方案:hook
android_dlopen_ext函数(SO加载的关键函数)
4.2 安全相关技术
- 反调试:在
init或JNI_OnLoad中实现 - 脱壳:利用初始化函数进行解密操作
- 防护:检测关键函数是否被hook