Android so加载深入分析

1~so文件在java层的函数调用关系图


image.png

2~System.loadLibrary()分析

987 public  static  void  loadLibrary(String  libName) {
    //是调用了 Runtime 类的 loadLibrary()
988     Runtime.getRuntime(). loadLibrary( libName, VMStack.getCallingClassLoader());
989}

3~ Runtime 类的 loadLibrary()分析

351 public  void  loadLibrary(String n n ickname) {
        //调 用 了 它 的一 个 重 载 函 数loadLibrary(String libraryName, ClassLoader loader)
352     loadLibrary( nickname, VMStack.getCallingClassLoader());
353 }

4~重 载 函 数loadLibrary(String libraryName, ClassLoader loader)函数分析

358 void loadLibrary(String libraryName, ClassLoader loader) {
359 if (loader != null) {
    //获得 so 文件的绝对路径 filename
360 String filename = loader.findLibrary(libraryName);//so 路径
361 … …
    //调用 doLoad()来加载 so 文件
369 String error =  doLoad( filename, loader);//加载
370 … …
373 return;
374 }
376 … …
396 }

5~doLoad(String name, ClassLoader loader)函数分析

400 private String  doLoad(String  name, ClassLoader  loader) {
420 String l l dLibraryPath = null;
421 if (loader != null && loader i i nstanceof BaseDexClassLoader) {
422 ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
423 }
427 synchronized ( this) {
    //调用 nativeLoad()来加载 name 指向的.so 文件
    //nativeLoad()是 Runtime类的一个 native 函数,native 层对应的函数为 Runtime_nativeLoad()
428 return  nativeLoad( name, loader,  ldLibraryPath);
429 }
430 }

6~Runtime_nativeLoad()函数

46 static jstring  Runtime_nativeLoad(JNIEnv*  env, jclass, jstring  javaFilename,
jobject  javaLoader, jstring  javaLdLibraryPath) {
47 ScopedUtfChars filename(env,  javaFilename);
51 … …
67 std::string  detail;
68 {
69 ScopedObjectAccess soa(env);
70 StackHandleScope<1> hs(soa.Self());
71 Handle classLoader(
72 hs.NewHandle(soa.Decode( javaLoader)));
73 JavaVMExt*  vm = Runtime::Current()->GetJavaVM();
    
    //调用 JavaVMExt 类的 LoadNativeLibrary()函数来加载.so 文件
    //filename 是.so 文件的路径,detail 用于存储加载过程中的 log 信息
74 bool  success =  vm-> LoadNativeLibrary(filename.c_str(), classLoader,& detail);//加载 so
75 if ( success) {
76 return nullptr;
77 }
78 }

7~LoadNativeLibrary()函数分析


image.png

image.png
image.png
image.png

8~LoadNativeLibrary()函数加载一个so文件的简单流程以及读JNI_onload的加载
-1判断该.so 文件是否已经加载了,如果已经加载了,检查class_loader 是否一样;
-2 如果没有加载,调用 dlopen()函数加载该.so 文件;
-3 调用 dlsym()找到 JNI_OnLoad 函数的地址;
-4 调用 JNI_OnLoad 函数。
9~dlopen()函数分析

82 void*  dlopen( const  char*  filename,  int  flags) {
83 return  dlopen_ext(filename, flags, nullptr);
84

10~ dlopen_ext()函数分析

68 static  void*  dlopen_ext( const  char*  filename,  int  flags,  const
android_dlextinfo*  extinfo) {//extinfo 为 null
69 ScopedPthreadMutexLocker locker(& g_dl_mutex);
    
    //调用 do_dlopen 来加载 filename 指向的.so 文件
    //,返回值为soinfo对象的指针
    // dlopen()函数的返回的指针指向一个 soinfo 对象
70 soinfo *  result =  do_dlopen( (f filename ,  flags ,  extinfo );
71 if ( result == nullptr) {
72 __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
73 return nullptr;
74 }
75 return  result;
76}

11~do_dlopen()函数分析

1041 soinfo* do_dlopen( const  char* name,  int flags,  const android_dlextinfo*
extinfo) { //extinfo 为 null
1042 …  …
1057 protect_data(PROT_READ | PROT_WRITE);
    //调用 find_library()函数得到 soinfo 的对象
1058 soinfo* si =  find_library(name, flags, extinfo);//加载链接 name
1059 if (si != nullptr) {
    //调用si->CallConstructors()进行初始化
1060 si-> CallConstructors();//初始化
1061 }
1062 protect_data(PROT_READ);
1063 return si;
1064}

12~find_library函数分析

968 static  soinfo* find_library( const  char* name,  int dlflags,  const
android_dlextinfo* extinfo) {
969 … …
974 soinfo* si;
    //调用 find_libraries()
976 if (! find_libraries(&name, 1, &si, nullptr, 0, dlflags, extinfo)) {
977 return nullptr;
978 }
980 return si;
981}

13~ find_libraries()分析
作用:find_libraries()将数组 library_names[]中的 so 文件加载到内存,并进行链接。


image.png
image.png

image.png
image.png

14~find_libraries三个部分分析
find_libraries()分为三个部分来进行分析。第一部分(899-908行):初始化阶段;第二部分(922-949 行):采用宽度优先搜索加载 so;第三部分(952-960 行):对加载的 so 进行链接。
15~find_libraries()第一部分:初始化阶段
要加载的 so 可能依赖于其他库,linker 采用宽度优先搜索依次加载 so 及其依赖库。搜索树中父节点的依赖库为其子节点,根节点是待加载的.so 文件
16~

899 行:load_tasks 是用于宽度优先搜索的栈
LoadTaskList load_tasks;

900-903 :行对其进行初始化。
for (size_t i = 0; i < library_names_size; ++i) {
     const  char* name = library_names[i];
    load_tasks.push_back(LoadTask:: create(name, nullptr));
    }
907:found_libs 是.so 文件和其依赖库的列表
SoinfoLinkedList found_libs;//

17~ find_library_internal()函数分析

865 static  soinfo*  find_library_internal( LoadTaskList&  load_tasks,  const  char*
name,  int  dlflags,  const android_dlextinfo*  extinfo) {//extinfo=null
    //会检查 name 指向的.so 是否已经加载
867 soinfo*  si =  find_loaded_library_by_name(name);//检查是否被加载过
871 if (si == nullptr) {//加载过就直接返回 si,否则,调用 load_library 加载
872 TRACE("[ '%s' has not been found by name. Trying harder...]", name);
    //如果没有,就调用 load_library()加载
873 si =  load_library(load_tasks, name, dlflags, extinfo);
874 }
876 return si;
877}

18~load_library()函数原型分析,分为3部分;
主要作用是打开.so 文件,并判断是否已经加载;
加载.so文件的可加载段;
创建 soinfo 对象,解析.dynamic section,并将该.so 文件的依赖库添加到待加载的队列中。
19~load_library函数第一部分(782-820 行):主要作用是打开.so 文件,并判断是否已经加载;


image.png
image.png
image.png

20~load_library函数第二部分(828-831 行):加载.so文件的可加载段;

image.png

21~load_library函数第三部分(832-850 行):创建 soinfo 对象,解析.dynamic section,并将该.so 文件的依赖库添加到待加载的队列中。

image.png

22~PAGE_SIZE 的大小以及获取
PAGE_SIZE 为 4096 ,最好通过 sysconf(_SC_PAGE_SIZE)来获取 PAGE_SIZE 的值;
23~为什么要检查so文件是否被不同文件夹加载过?
检查.so 文件是否以不同的文件名被加载过了。Linux 下一个文件可以有多个链接文件(1对多),因而不同的文件名可能指向的是同一个文件(多对1)。
24~load_library函数的第二部分中的成员函数 Load()分析

135 bool  ElfReader:: Load( const android_dlextinfo*  extinfo) {
136 return  ReadElfHeader() &&//ReadElfHeader()用于读取 ELF 的头
137 VerifyElfHeader() &&    //检查 ELF 头某些字段是否合法
138 ReadProgramHeader() &&  // program header table 从.so 文件通过 mmap64 映射到只读私有匿名内存
139 ReserveAddressSpace(extinfo) && //通过 mmap 创建足够大的匿名内存空间,以便能够容纳所有可以加载的段
140 LoadSegments() &&   //加载段
141 FindPhdr(); //检查可加载段中是否包含program header table
142}

25~ReadElfHeader函数的作用
ReadElfHeader()用于读取 ELF 的头,并将结果赋给 ElfReader 的成员变量Elf32_Ehdr header_
26~VerifyElfHeader函数分析(作用)
检查 ELF 头某些字段是否合法
27~ReadProgramHeader函数分析
28~ReserveAddressSpace函数分析
29~mmap函数的作用
通过 mmap 创建足够大的匿名内存空间,以便能够容纳所有可以加载的段
30~VerifyElfHeader函数分析

image.png

image.png

image.png

31~在ELF头中进行校验的时候,bytee _ident[16]这个字段有何特点?
ELF 头中,bytee_ident[16]字段的后 10 位并没有进行校验。
32~VerifyElfHeader中223-227 行代码分析
作用:用于计算映射 program header table 需要的内存大小,以及 偏 移 。

223 ElfW(Addr) page_min = PAGE_START(header_.e_phoff); //0
224 ElfW(Addr) page_max = PAGE_END(header_.e_phoff + (phdr_num_ *
sizeof(ElfW(Phdr))));
225 ElfW(Addr) page_offset = PAGE_OFFSET(header_.e_phoff);//pht 在页中的偏移
226
227 phdr_size_ = page_max - page_min;//需要为 pht 映射内存的大小

33~宏 PAGE_START 在源码中的定 义分析

define PAGE_START(x) ((x) & PAGE_MASK)

由前面的分析可以知道,内存页的大小是 4096(2^12 ,4M),因而内存页的起始地址低 12 位应该为 0。PAGE_START(x)就是计算地址 x 所在内存页的起始地址,其中 PAGE_MASK 的低 12 位为 0,其他位是 1。
34~PAGE_OFFSET 在源码中的定 义分析

define PAGE_OFFSET(x) ((x) & ~PAGE_MASK)

PAGE_OFFSET(x)用于计算地址 x 在其所在内存页中的偏移。
35~PAGE_END在源码中的定 义
PAGE_END(x)用于计算地址x所在页的结束地址(注意不包含 PAGE_END(x)),其实就是地址 x 所在内存页的下一页的起始地址。
例如地址 x=0x1243B3C1,那么它所在内存页 A 的起始地址 PAGE_START(x)=0x1243B000,它在页 A 中的偏移
PAGE_OFFSET(x)=0x3C1,页 A 的结束地址 PAGE_END(x)=0x1243C000,即页 A 最后一个字节的地址为 0x1243BFFF。
36~ReserveAddressSpace函数分析
作用:通过 mmap 创建足够大的匿名内存空间,以便能够容纳所有可以加载的段


image.png
image.png

37~ELF文件头的可加载段的标志以及对该段的操作
ELF 文件的 program header table 中包含了一个或者多个可加载段,可加载段的标志为 PT_LOAD,这些段需要被映射到该进程的地址空间中;
38~可加载段重要的一些属性如下:

p_offset:段在文件中的偏移
p_filesz:段在文件中的大小
p_memsz:段在内存中的大小,总是大于或等于 p_filesz
p_vaddr:段的虚拟地址
p_flags:段 flags(读、写、执行等)

39~LoadSegments()函数分析
作用:遍历 programheader table,找到可加载段,并通过 mmap 将可加载段从文件映射到内存。


image.png
image.png

image.png

40~FindPhdr()函数分析
作用:函数检查 program headertable是否已经在内存中了,即检查可加载段中是否包含program header table。

image.png
image.png

41~FindPhdr()通过两种策略来确定 program header table 是否在内存中
1)首先检查是否有类型是 PT_PHDR 的段,即 program header table
2)否则,检查第一个可加载段。如果它在文件中的偏移是 0,那么该段以 ELF 头开始,我们通过 ELF 头能计算 program header table 的地址
42~CheckPhdr函数分析
作用:最终确定 program header table 是否在内存中是通过 CheckPhdr()来实现的

image.png
image.png

43~总结CheckPhdr()的逻辑
检查 program header table 的地址范围是否包含在被加载的段中
44~ ElfReader.Load()是如何加载.so 文件的可加载段
-1. ReadElfHeader():从.so 文件中读取 ELF 头;
-2. VerifyElfHeader():校验 ELF 头;
-3. ReadProgramHeader():将.so 文件的 program header table 映射到内存;
-4. ReserveAddressSpace():开辟匿名内存空间;
-5. LoadSegments():将可加载段加载到 ReserveAddressSpace 开辟的空间中;
-6. FindPhdr():校验 program header table 是否在内存中
45~load_library函数第三部分:833-841 行分析
作用:创建一个 soinfo 对象,并对相关字段进行赋值:

//创建一个 soinfo 对象si,并对相关字段进行赋值:
833 soinfo*  si =  soinfo_alloc(SEARCH_NAME(name), &file_stat, file_offset);

834 if (si == nullptr) {
835 return nullptr;
836 }
837 si->base = elf_reader.load_start();//加载 so 文件时,mmap 得到的空间的首地址
838 si-> size = elf_reader.load_size();//ReserveAddressSpace 中开辟的内存空间的大小
//加载段时的基址,load_bias+p_vaddr 为段的实际内存地址
839 si->load_bias = elf_reader.load_bias();
840 si->phnum = elf_reader.phdr_count();//program header 的个数
841 si->phdr = elf_reader.loaded_phdr();//program header table 在内存中的起始地址

补充:字段详解
a. si->base:加载 so 文件时,mmap 得到的内存空间的首地址。由于.so 文件中
第一个可加载段的偏移通常是 0,从 ReserveAddressSpace()的实现可知,
si->load_bias 等于 si->base,因而 si->base 也是第一个可加载段的起始地址;
b. si->size:ReserveAddressSpace 中开启的内存空间的大小;
c. si->load_bias : 加 载 的 偏 移 地 址 , 对 于 一 个 可 加 载 段 来 说 ,
si->load_bias+p_vaddr 是它在内存中的地址;
d. si->phnum:program header 的个数;
e. si->phdr:program header table 在内存中的起始地址。

46~load_library函数第三部分:843 行分析
作用:调用 PrelinkImage()解析.so 文件的.dynamic section

843 if (!si->PrelinkImage()) {//解析.dynamic section
844 soinfo_free(si);
845 return nullptr;
846 }

47~load_library函数第三部分:848-850 行
作用:将该.so 文件依赖的库添加到待加载的队列中。

847 //将该.so 文件依赖的库添加到待加载队列中
848 for_each_dt_needed(si, [&] ( const  char* name) {
849 load_tasks.push_back(LoadTask:: create(name, si)); //si 依赖于名为 name 的库
850 });

48~.dynamic section 中 entry 的数据结构

1620// Dynamic table entry for ELF32.
1621 struct  Elf32_Dyn
1622{
1623 Elf32_Sword  d_tag; // Type of dynamic table entry.
1624 union
1625 {
1626 Elf32_Word  d_val; // Integer value of entry.
1627 Elf32_Addr  d_ptr; // Pointer value of entry.
1628 }  d_un;
1629};
补充:其中 d_un 值的意义与 d_tag 的取值有关。

49~ PrelinkImage()函数分析:1861行
作用: phdr_table_get_dynamic_section()来获取.dynamic section在内存中的地址

1861 phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic,
                        &dynamic_flags);//根据 program header table 找到.dynamic section

50~为什么要遍历program header table就可以找到dynamic section ?
dynamic section 对应的段的类型是 PT_DYNAMIC,且类型为PT_DYNAMIC的段中只有.dynamic一个section。因而只需要遍历program headertable , 找 到 类 型 为 PT_DYNAMIC 的 段 即 可 , 其 在 内 存 中 的 地 址 就 是load_bias+p_vaddr。
51~PrelinkImage()函数分析: 1881-1884行代码分析
作用: 调 用phdr_table_get_arm_exidx()来获取.ARM.exidx section 在内存中的地址;因为类型为 PT_ARM_EXIDX 的段中只有.ARM.exidx 一个 section。

1881# if  defined(__arm__) //找到.ARM.exidx sectioin 在内存中的地址
1882 ( void) phdr_table_get_arm_exidx(phdr, phnum, load_bias,
1883 &ARM_exidx, &ARM_exidx_count);
1884# 

52~PrelinkImage()函数分析:1888-2150 行代码分析:
作用:是个 for 循环,遍历.dynamic section 中每一条 entry,根据d_tag 的值,用 d_un 做相应的操作。
53~加载so是在哪个文件呢?
是在linker.so;在该linker.so下断,就可以找到JNI_onload函数;
补充:libc.so,是C语言函数库的集合;
解析 dynamic section分析
54~find_libraries()第三部分(952-960 行)分析:
作用:对加载的 so 进行链接。

//链接加载的库
951 // Step 2: link libraries.
952 soinfo* si;
953 while ((si = found_libs.pop_front()) != nullptr) {
954 if ((si->flags & FLAG_LINKED) == 0) {//如果 si 没有链接,对 si 进行链接
955 if (!si->LinkImage(extinfo)) {//extinfo=null    //调用
956 return  false;
957 }
958 si->flags |= FLAG_LINKED;
959 }
960 }
961 … …

55~ LinkImage()函数 2207 行和 2213 行分析
这里可知:.rel.dyn 和.rel.plt 两个重定位表都是调用Relocate()来进行重定位的。

if (rel != nullptr) {
2206 DEBUG("[ relocating %s ]", name);
2207    if (Relocate(rel, rel_count)) {//对重定位表中所指的符号进行重定位
2208 return  false;
2209 }
2210 }
2211 if (plt_rel != nullptr) {//与调用导入函数相关
2212 DEBUG("[ relocating %s plt ]", name);
2213     if (Relocate(plt_rel, plt_rel_count)) {//对重定位表中所指的符号进行重定位
2214 return  false;
2215 }
2216 }

56~重定位表项的数据结构

164 typedef  struct  elf32_rel {
165  Elf32_Addr  r_offset; //在文件中的偏移或者虚拟地址
166  Elf32_Word  r_info; //符号表的索引和重定位类型
167}  Elf32_Rel ;
159# define  ELF32_R_SYM(x) ((x) >> 8)
160# define  ELF32_R_TYPE(x) ((x) & 0xff)
    
该结构体元素的说明:
    符号表的索引是 r_info 的高 24 位,可通过宏 ELF32_R_SYM 获取,表示重定位的符号;
    重定位类型是 r_info 的低 8 位,可通过宏 ELF32_R_TYPE 获取
    r_offset 根据重定位类型的不同有不同的解释。

57~ Relocate函数1364 行分析

    //变量 reloc 是重定位的地址
1364 ElfW(Addr) reloc =  static_cast(rel->r_offset + load_bias);

补充:
    即 reloc 处的值需要重新计算,对于导入函数来说,地址 reloc 在 got 表中,reloc 处应该是函数的
实际地址,代码中函数的地址其实是其在 got 表中的偏移,再从 got 表中跳转到函数的实际地址。

58~ Relocate函数1378 行分析

1376 if (sym != 0) {
1377 sym_name = get_string(symtab[sym].st_name);//得到符号的名称
    //调用soinfo_do_lookup()查找符号的定义 so。
1378 s =  soinfo_do_lookup( this, sym_name, & lsi);//查找 sym_name 定义在哪个 so
1379 if (s == nullptr) {//如果该符号没有定义,那么它的绑定类型必须是弱引用
1380 // We only allow an undefined symbol if this is a weak reference...
1381 s = &symtab[sym];
1382 if (ELF_ST_BIND(s->st_info) != STB_WEAK) {
1383 DL_ERR("cannot locate symbol \"%s\" referenced by \"%s\"...",
sym_name, name);
1384 return -1;
1385 }

59~ soinfo_do_lookup()函数分析
作用:soinfo_do_look()分别在其自身、预加载库和依赖库中查找符号的定义,查找函数是 soinfo_elf_lookup()

546 if (s == nullptr) {//在其依赖库(子结点)中递归查找符号
547 si->get_children().visit([&]( soinfo* child) {
548 DEBUG("%s: looking up %s in %s", si->name, name, child->name);
    //通过soinfo_elf_lookup()函数在其自身、预加载库和依赖库中查找符号的定义
549 s =  soinfo_elf_lookup(child, elf_hash, name);//分析
550 if (s != nullptr) {
551 * lsi = child;
552 return  false;
553 }
554 return  true;
555 });
556 }

60~ soinfo_elf_lookup()函数430-439 行分析
作用:用于确定符号是否在 si 中定义并且符号的绑定类型是否为 STB_GLOBAL 或 STB_WEAK

429 switch (ELF_ST_BIND(s->st_info)) {
430 case STB_GLOBAL:
431 case STB_WEAK:
432 if (s->st_shndx == SHN_UNDEF) {//符号未定义
433 continue;
434 }
435
436 TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
437 name, si->name,  reinterpret_cast< void*>(s->st_value),
438 static_cast(s->st_size));
439 return s;//在 si 中找到符号的定义
440 case STB_LOCAL:
441 continue;
442 default:
443 __libc_fatal("ERROR: Unexpected ST_BIND value: %d for '%s' in '%s'",
444 ELF_ST_BIND(s->st_info), name, si->name);
445 }

61~ Relocate函数1435行分析
作用: 调 用 resolve_symbol_address() 来 计 算 符 号 的 地 址

  else {//找到了符号的定义 so,计算该符号的地址
      //调 用 resolve_symbol_address() 来 计 算 符 号 的 地 址 
1435 sym_addr =  lsi->resolve_symbol_address(s);    //调用
1436 }
1437 count_relocation( kRelocSymbol);
1438 }//end if (sym != 0)

62~resolve_symbol_address函数分析:

1781 ElfW(Addr)  soinfo::resolve_symbol_address(ElfW(Sym)* s) {
1782 if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) {//符号的类型是 gnu indirect
func
1783 return  call_ifunc_resolver(s->st_value + load_bias);  //call_ifunc_resolver()调用
1784 }
1786 return  static_cast(s->st_value + load_bias);
1787}

简单逻辑分析:
    从上可知,如果符号的类型不是 STT_GNU_IFUNC(GNU indirect function),如
STT_FUNC(可执行代码,如函数)、STT_OBJECT(数据对象,如变量)等,直接返回符
号的地址,即 s->st_value + load_bias,否者调用 call_ifunc_resolver()计算符号的地
址(关于 GNU indirect function,简单来说就是根据函数名字符串来调用函数,感
觉和反射类似,

63~call_ifunc_resolver函数分析

    //resolver_addr 其实是一个函数的地址
1072 static ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr) {
1073 typedef  ElfW( ( Addr) ) (* ifunc_resolver_t )( void);
1074 ifunc_resolver_t ifunc_resolver =
reinterpret_cast(resolver_addr);//将 resolver_addr 转为函数指针
    //执行ifunc_resolver这个函数,其返回值ifunc_addr就是符号的地址。
1075 ElfW(Addr) ifunc_addr =  ifunc_resolver();//执行 resoler_addr 处的函数
1076 TRACE_TYPE(RELO, "Called ifunc_resolver@%p. The result is %p", ifunc_resolver,
reinterpret_cast< void*>(ifunc_addr));
1078 return ifunc_addr;
1079}

64~Relocate()函数中 1440-1557 行分析

作用:上面函数call_ifunc_resolver函数中ifunc_resolver的返回值是一个符号的地址,Relocate()函数中 1440-1557 行根据该符号的重定位类型重新计算 reloc 处的值。

1400 switch (type) {
//没有定义的弱引用,它的 sym_addr 是 0,或者重定位的时候不关心 sym_addr 的值
1401# if  defined(__arm__)
1402 case R_ARM_JUMP_SLOT:
1403 case R_ARM_GLOB_DAT:
1404 case R_ARM_ABS32:
1405 case R_ARM_RELATIVE: /* Don't care. */
1406 // sym_addr was initialized to be zero above or relocation
1407 // code below does not care about value of sym_addr.
1408 // No need to do anything.
1409 break;
1411 … …

65~1 重定位类型与重定位值的计算方式对应表


image.png

66~重定位类型为 R_ARM_RELATIVE 的重定位值有何特点?
注意重定位类型为 R_ARM_RELATIVE 的重定位项,其 r_info 中符号表的索引必须为 0,即不需要搜索符号,其重定位值的计算也与 sym_addr 没有关系。
67~find_libraries()的第三部分链接过程的总结
遍历重定位表,根据重定项的 r_info 获得重定位类型和重定位项对应的符号在符号表中的索引;然后利用 so 中的 hash 表,根据符号名快速地查找符号在哪个 so中定义;当找到了符号的定义,计算符号的地址 sym_addr;最后根据符号的重定位类型,结合 sym_addr 计算重定位值。
68~so文件被加载到内存后,然后做什么?
so 文件加载到内存,并链接完成后,就开始调用 so 中的初始化函数
69~ do_dlopen()函数分析

1041 soinfo* do_dlopen( const  char* name,  int flags,  const android_dlextinfo*
extinfo) { //extinfo 为 null
1042 … …
1057 protect_data(PROT_READ | PROT_WRITE);
1058 soinfo* si =  find_library(name, flags, extinfo);//加载链接 name
1059 if (si != nullptr) {
    ////初始化
1060 si-> CallConstructors();//调用
1061 }
1062 protect_data(PROT_READ);
1063 return si;
1064}

70~CallConstructors函数分析
作用:CallConstructors() 主 要 是 执 行 了 两 段 初 始 化 代 码 : init_func 和init_array,

1656 void  soinfo::CallConstructors() {
1657 if (constructors_called) {
1658 return;
1659 }
1660 … …
1679 get_children(). for_each([] ( soinfo* si) {
1680 si->CallConstructors();
1681 });
1682 … …
1685 // DT_INIT should be called before DT_INIT_ARRAY if both are present.
1686 CallFunction("DT_INIT", init_func);//调用 init_func 函数
//调用 init_array 数组中的函数
1687 CallArray("DT_INIT_ARRAY", init_array, init_array_count,  false);
1688}

71~init_func和init_array这两个变量何时赋值?
这两个变量是在 PrelinkImage()中解析 dynamic section 时赋值的。
72~init_func和init_array的应用以及执行特点?
加壳逻辑就放在 init_func 或 init_array 中,它们先于 jni_onLoad 执行

你可能感兴趣的:(Android so加载深入分析)