阅读源码版本:6.0.1
阅读工具:Android Studio 2.2.2 Notepad++
参考链接:
http://www.tuicool.com/articles/AfMZRbZ
http://docs.oracle.com/cd/E19253-01/819-7050/6n918j8nq/index.html#chapter6-71736
上层调用的终点找到了,之后就开始对加载链接的工作内容进行分析。
通过类对象对文件内容进行解析,对应文件为bionic/linker/linker_phdr.h/.cpp.
动态库的加载过程:
bool ElfReader::Load(constandroid_dlextinfo* extinfo) {
return ReadElfHeader() &&
VerifyElfHeader() &&
ReadProgramHeader() &&
ReserveAddressSpace(extinfo) &&
LoadSegments() &&
FindPhdr();
}
ELFReader提供外部接口函数调用私有函数对动态库文件进行操作,接下来对调用的私有函数进行分析。
bool ElfReader::ReadElfHeader()
没啥好说的,就是读取文件头部。
bool ElfReader::VerifyElfHeader()
相关文件:art\runtime\elf.h bionic\libc\kernel\uapi\linux\elf.h
校验头部信息,Android Linker只针对了部分属性做校验。
1、校验前4个字节对应7F 45 4C 46(\x7f,ELF)
2、根据获取设备系统位数校验动态库对应系统位数,第5个字节:1 32位 2(64位)
3、第六个字节对应:ELFDATA2LSB(0x1)
4、第17~18字节对应文件类型,只能为ET_DYN(3)表示动态链接库
5、第21~24字节对应版本,只能为EV_CURRENT(1)
6、第19~20字节对应机器架构,具体到机型来定,ARM对应0x28
可以看出,Linker只是简单的对头部信息做了校验,很多加固因此利用了这一特性对头部做了修改。
bool ElfReader::ReadProgramHeader()
操作程序段头部信息,读取头部表数目,计算头部表占据的页面大小,然后将程序头部表映射到内存中,并将内存地址赋值。
...
ElfW(Addr) page_min = PAGE_START(header_.e_phoff);//程序头部表在页边界对齐后的起始地址
ElfW(Addr) page_max = PAGE_END(header_.e_phoff + (phdr_num_ * sizeof(ElfW(Phdr))));//结束地址
ElfW(Addr) page_offset = PAGE_OFFSET(header_.e_phoff);//偏移地址
页面大小 = page_max - page_min
bool ElfReader::ReserveAddressSpace(constandroid_dlextinfo* extinfo)
这里就是创建内存空间,用来加载所有segments。
调用phdr_table_get_load_size获取加载大小
phdr_table_get_load_size:遍历phdr表,获取最小虚拟地址和最大虚拟地址,相差就是加载大小。
mmap(mmap_hint, load_size_, PROT_NONE, mmap_flags, -1, 0);
之后调用mmap开始分配私有匿名内存映射,私有映射表示对该映射区域写入操作会产生一个映射文件的复制,做的任何修改都不会被写会原来的文件。
bool ElfReader::LoadSegments()
开始加载PT_Load段
中间会有一个对内存偏移和文件偏移的判断,如果内存偏移大于文件偏移,会将中间多出内存设为0
bool ElfReader::FindPhdr()
查找PT_PHDR段是否存在,存在直接使用该段的地址,不存在就查找第一个可加载段。
动态库整个加载过程大致如此了,对后面两个函数的详细过程可能需要好好理解。
动态库的链接过程:
ELFReader对象创建完成后,开始创建新的soinfo对象。
对应文件bionic/linker/linker.cpp
soinfo* si = soinfo_alloc(realpath.c_str(), &file_stat, file_offset, rtld_flags);
开始进行动态链接:
si->prelink_image()
bool soinfo::prelink_image()
调用phdr_table_get_dynamic_section函数,查找存在的动态节区,没有dynamic就设置为空。
遍历动态链接节区,根据标记值进行相应操作。
初始化完成,开始做解析、重定位操作
调用prelink_image函数
该函数主要是获取PT_DYNAMIC段,然后解析并保存相关内容,包括了init_array、string表、system table表等等,这是linker比较关键的一步操作。
for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {
.....
}
还有对arm会解析异常处理段
bool soinfo::link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group,const android_dlextinfo* extinfo)
返回到find_libraries函数中,获取到soinfo结构对象,做相应判断操作后调用link_image函数,对上面获取信息进行处理,进行重定位,定位涉及了动态链接段解析的相关参数,这里简单标注下。
->->void soinfo::call_constructors()
紧接着调用了call_constructors函数,该函数会判断是否首次加载,若首次加载则会获取所有依赖动态库接着就开始执行init段
至此,linker的工作基本完成。