这周分析了一下安卓源码中 linker 的部分,了解了 Android
平台下是如何加载动态链接库的。
 
Android源码linker的实现 
首先下载安卓源码,过程参考
下载源代码|Android
开源项目|Android Open Source Project 
我们这次分析的是android-4.0.1_r1
Linker的代码位于linker.c和linker.h。我们从dlfcn.c中的dlopen入手:
dlopen 
首先将dl_lock锁住。dlopen 主要函数为 find_library。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void  *dlopen (const  char  *filename, int  flag) {     soinfo *ret;     pthread_mutex_lock(&dl_lock);     ret = find_library(filename);     if  (unlikely(ret == NULL )) {         set_dlerror(DL_ERR_CANNOT_LOAD_LIBRARY);     } else  {         ret->refcount++;     }     pthread_mutex_unlock(&dl_lock);     return  ret; } 
 
find_library 
先从solist中搜索是否已加载还没加载则调用load_library加载so,和init_library初始化库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bname = strrchr (name, '/' ); bname = bname ? bname + 1  : name; for (si = solist; si != 0 ; si = si->next){    if (!strcmp (bname, si->name)) {         if (si->flags & FLAG_ERROR) {             DL_ERR("%5d '%s' failed to load previously" , pid, bname);             return  NULL ;         }         if (si->flags & FLAG_LINKED) return  si;         DL_ERR("OOPS: %5d recursive link to '%s'" , pid, si->name);         return  NULL ;     } } TRACE("[ %5d '%s' has not been loaded yet.  Locating...]\n" , pid, name); si = load_library(name); if (si == NULL )    return  NULL ; return  init_library(si);
 
load_library 
首先open_library打开共享库
open_library 
如果是绝对路径,则直接打开
否则从ldpath和sopath中搜索然后打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if  ((name[0 ] == '/' ) && ((fd = _open_lib(name)) >= 0 ))    return  fd; for  (path = ldpaths; *path; path++) {    n = format_buffer(buf, sizeof (buf), "%s/%s" , *path, name);     if  (n < 0  || n >= (int )sizeof (buf)) {         WARN("Ignoring very long library path: %s/%s\n" , *path, name);         continue ;     }     if  ((fd = _open_lib(buf)) >= 0 )         return  fd; } for  (path = sopaths; *path; path++) {    n = format_buffer(buf, sizeof (buf), "%s/%s" , *path, name);     if  (n < 0  || n >= (int )sizeof (buf)) {         WARN("Ignoring very long library path: %s/%s\n" , *path, name);         continue ;     }     if  ((fd = _open_lib(buf)) >= 0 )         return  fd; } 
 
读取so的第一页,即header部分的内容
1 2 3 4 5 6 7 8 9 if  (lseek(fd, 0 , SEEK_SET) < 0 ) {    DL_ERR("lseek() failed!" );     goto  fail; } if  ((cnt = read(fd, &__header[0 ], PAGE_SIZE)) < 0 ) {    DL_ERR("read() failed!" );     goto  fail; } 
 
get_lib_extents 
这里负责设定共享库的基地址和加载时的大小,大小通过参数total_sz传入并设定
1 2 static  unsigned get_lib_extents (int  fd, const  char  *name, void  *__hdr, unsigned  *total_sz) 
 
如果没有prelink过,则基址设为0,后续mmap时交给自动指定合适的机制
1 2 3 4 5 6 7 8 9 req_base = (unsigned ) is_prelinked(fd, name); if  (req_base == (unsigned )-1 )    return  -1 ; else  if  (req_base != 0 ) {    TRACE("[ %5d - Prelinked library '%s' requesting base @ 0x%08x ]\n" ,           pid, name, req_base); } else  {     TRACE("[ %5d - Non-prelinked library '%s' found. ]\n" , pid, name); } 
 
遍历PT_LOAD的程序头,计算最大地址和最小地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 phdr = (Elf32_Phdr *)(_hdr + ehdr->e_phoff); for  (cnt = 0 ; cnt < ehdr->e_phnum; ++cnt, ++phdr) {    if  (phdr->p_type == PT_LOAD) {         if  ((phdr->p_vaddr + phdr->p_memsz) > max_vaddr)             max_vaddr = phdr->p_vaddr + phdr->p_memsz;         if  (phdr->p_vaddr < min_vaddr)             min_vaddr = phdr->p_vaddr;     } } min_vaddr &= ~PAGE_MASK; max_vaddr = (max_vaddr + PAGE_SIZE - 1 ) & ~PAGE_MASK; *total_sz = (max_vaddr - min_vaddr); return  (unsigned )req_base;
 
alloc_mem_region 
先初始化soinfo结构体,然后分配初始内存
1 2 3 4 5 6 7 8 9 10 si->base = req_base; si->size = ext_sz; si->flags = 0 ; si->entry = 0 ; si->dynamic = (unsigned  *)-1 ; if  (alloc_mem_region(si) < 0 )    goto  fail; TRACE("[ %5d allocated memory for %s @ %p (0x%08x) ]\n" ,       pid, name, (void  *)si->base, (unsigned ) ext_sz); 
 
使用mmap映射匿名空间,权限为读取和执行
1 2 3 4 5 6 7 8 9 10 11 12 void  *base = mmap(NULL , si->size, PROT_READ | PROT_EXEC,                  MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); if  (base == MAP_FAILED) {    DL_ERR("%5d mmap of library '%s' failed: %d (%s)\n" ,           pid, si->name,           errno, strerror(errno));     goto  err; } si->base = (unsigned ) base; PRINT("%5d mapped library '%s' to %08x via kernel allocator.\n" ,       pid, si->name, si->base); return  0 ;
 
load_segments 
将每一个段加载进内存
自然是遍历每个PT_LOAD的程序头
1 2 3 for  (cnt = 0 ; cnt < ehdr->e_phnum; ++cnt, ++phdr) {    if  (phdr->p_type == PT_LOAD) {          
 
将该段映射到地址空间,大小对齐到页,权限为程序头中的权限。此时的映射就不是匿名影射了,直接将so的文件映射过去,所以会在/proc/[pid]/maps中看到路径。
1 2 3 4 5 6 7 8 9 10 11 12 13          tmp = base + (phdr->p_vaddr & (~PAGE_MASK));                    len = phdr->p_filesz + (phdr->p_vaddr & PAGE_MASK);          TRACE("[ %d - Trying to load segment from '%s' @ 0x%08x "                 "(0x%08x). p_vaddr=0x%08x p_offset=0x%08x ]\n" , pid, si->name,                (unsigned )tmp, len, phdr->p_vaddr, phdr->p_offset);          pbase = mmap(tmp, len, PFLAGS_TO_PROT(phdr->p_flags),                       MAP_PRIVATE | MAP_FIXED, fd,                       phdr->p_offset & (~PAGE_MASK));          if  ((len & PAGE_MASK) && (phdr->p_flags & PF_W))              memset ((void  *)(pbase + len), 0 , PAGE_SIZE - (len & PAGE_MASK)); 
 
如果整个段的长度大于页的大小,则还要额外映射额外的长度。
比如在这个例子中,第二个段的结束地址0x2d48+0x2c0 =
0x3008,超出了当前页的大小0x3000,需要额外8字节的页
1 2 3 4 5 6 7 Program Headers:   Type           Offset             VirtAddr           PhysAddr                  FileSiz            MemSiz              Flags  Align   LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000                  0x0000000000000d60 0x0000000000000d60  R E    0x1000   LOAD           0x0000000000001d48 0x0000000000002d48 0x0000000000002d48                  0x00000000000002c0 0x00000000000002c0  RW     0x1000 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 tmp = (unsigned  char  *)(((unsigned )pbase + len + PAGE_SIZE - 1 ) &                                  (~PAGE_MASK));          if  (tmp < (base + phdr->p_vaddr + phdr->p_memsz)) {              extra_len = base + phdr->p_vaddr + phdr->p_memsz - tmp;              TRACE("[ %5d - Need to extend segment from '%s' @ 0x%08x "                     "(0x%08x) ]\n" , pid, si->name, (unsigned )tmp, extra_len);                            extra_base = mmap((void  *)tmp, extra_len,                                PFLAGS_TO_PROT(phdr->p_flags),                                MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS,                                -1 , 0 );              if  (extra_base == MAP_FAILED) {                  DL_ERR("[ %5d - failed to extend segment from '%s' @ 0x%08x"                          " (0x%08x) ]" , pid, si->name, (unsigned )tmp,                        extra_len);                  goto  fail;              }                            TRACE("[ %5d - Segment from '%s' extended @ 0x%08x "                     "(0x%08x)\n" , pid, si->name, (unsigned )extra_base,                    extra_len);          } 
 
如果有写权限还要加上写权限:
这里的pflag和prot中的读和执行权限和map的是反的,所以用了一个宏反了过来。
1 2 3 4 5 6 7 8 if  (!(phdr->p_flags & PF_W)) {    if  ((unsigned )pbase < si->wrprotect_start)         si->wrprotect_start = (unsigned )pbase;     if  (((unsigned )pbase + len) > si->wrprotect_end)         si->wrprotect_end = (unsigned )pbase + len;     mprotect(pbase, len,              PFLAGS_TO_PROT(phdr->p_flags) | PROT_WRITE); } 
 
全部segments load完毕后,关闭文件,然后进行初始化。
init_library 
这里主要要做的工作就是对共享库重定向,将其中的地址修改为正确地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static  soinfo *init_library (soinfo *si) {     unsigned  wr_offset = 0xffffffff ;          TRACE("[ %5d init_library base=0x%08x sz=0x%08x name='%s') ]\n" ,           pid, si->base, si->size, si->name);     if (link_image(si, wr_offset)) {                      munmap((void  *)si->base, si->size);         return  NULL ;     }     return  si; } 
 
link_image 
link_image主要做这些事:
首先遍历程序头,获取dynmic段的地址:
1 2 3 4 5 6 7 8 9 10 11 } else  if  (phdr->p_type == PT_DYNAMIC) {              if  (si->dynamic != (unsigned  *)-1 ) {                  DL_ERR("%5d multiple PT_DYNAMIC segments found in '%s'. "                         "Segment at 0x%08x, previously one found at 0x%08x" ,                        pid, si->name, si->base + phdr->p_vaddr,                        (unsigned )si->dynamic);                  goto  fail;              }              DEBUG_DUMP_PHDR(phdr, "PT_DYNAMIC" , pid);              si->dynamic = (unsigned  *) (si->base + phdr->p_vaddr);          } 
 
遍历Elf32_Dyn结构体数组,设置soinfo结构体成员。比较重要的内容有(注意这里说的地址都是相对虚拟地址):
DT_STRTAB:符号表字符串地址
DT_SYMTAB:符号表地址
DT_JMPREL,DT_PLTRELSZ:跳转重定向表及大小
DT_RELA,DT_RELASZ:重定向表地址及大小
DT_INIT_ARRAY,DT_INIT_ARRAYSZ,DT_FINI_ARRAY,DT_FINI_ARRAYSZ:init_array和fini_array的地址及信息
这些地址信息都是在加载时需要重定向的,所以这里记录重定向后的地址,以便后续修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111     for (d = si->dynamic; *d; d++){         DEBUG("%5d d = %p, d[0] = 0x%08x d[1] = 0x%08x\n" , pid, d, d[0 ], d[1 ]);         switch (*d++){         case  DT_HASH:             si->nbucket = ((unsigned  *) (si->base + *d))[0 ];             si->nchain = ((unsigned  *) (si->base + *d))[1 ];             si->bucket = (unsigned  *) (si->base + *d + 8 );             si->chain = (unsigned  *) (si->base + *d + 8  + si->nbucket * 4 );             break ;         case  DT_STRTAB:             si->strtab = (const  char  *) (si->base + *d);             break ;         case  DT_SYMTAB:             si->symtab = (Elf32_Sym *) (si->base + *d);             break ; #if  !defined(ANDROID_SH_LINKER)         case  DT_PLTREL:             if (*d != DT_REL) {                 DL_ERR("DT_RELA not supported" );                 goto  fail;             }             break ; #endif  #ifdef  ANDROID_SH_LINKER         case  DT_JMPREL:             si->plt_rela = (Elf32_Rela*) (si->base + *d);             break ;         case  DT_PLTRELSZ:             si->plt_rela_count = *d / sizeof (Elf32_Rela);             break ; #else          case  DT_JMPREL:             si->plt_rel = (Elf32_Rel*) (si->base + *d);             break ;         case  DT_PLTRELSZ:             si->plt_rel_count = *d / 8 ;             break ; #endif          case  DT_REL:             si->rel = (Elf32_Rel*) (si->base + *d);             break ;         case  DT_RELSZ:             si->rel_count = *d / 8 ;             break ; #ifdef  ANDROID_SH_LINKER         case  DT_RELASZ:             si->rela_count = *d / sizeof (Elf32_Rela);              break ; #endif          case  DT_PLTGOT:                          si->plt_got = (unsigned  *)(si->base + *d);             break ;         case  DT_DEBUG:                          *d = (int ) &_r_debug;             break ; #ifdef  ANDROID_SH_LINKER         case  DT_RELA:             si->rela = (Elf32_Rela *) (si->base + *d);             break ; #else           case  DT_RELA:             DL_ERR("%5d DT_RELA not supported" , pid);             goto  fail; #endif          case  DT_INIT:             si->init_func = (void  (*)(void ))(si->base + *d);             DEBUG("%5d %s constructors (init func) found at %p\n" ,                   pid, si->name, si->init_func);             break ;         case  DT_FINI:             si->fini_func = (void  (*)(void ))(si->base + *d);             DEBUG("%5d %s destructors (fini func) found at %p\n" ,                   pid, si->name, si->fini_func);             break ;         case  DT_INIT_ARRAY:             si->init_array = (unsigned  *)(si->base + *d);             DEBUG("%5d %s constructors (init_array) found at %p\n" ,                   pid, si->name, si->init_array);             break ;         case  DT_INIT_ARRAYSZ:             si->init_array_count = ((unsigned )*d) / sizeof (Elf32_Addr);             break ;         case  DT_FINI_ARRAY:             si->fini_array = (unsigned  *)(si->base + *d);             DEBUG("%5d %s destructors (fini_array) found at %p\n" ,                   pid, si->name, si->fini_array);             break ;         case  DT_FINI_ARRAYSZ:             si->fini_array_count = ((unsigned )*d) / sizeof (Elf32_Addr);             break ;         case  DT_PREINIT_ARRAY:             si->preinit_array = (unsigned  *)(si->base + *d);             DEBUG("%5d %s constructors (preinit_array) found at %p\n" ,                   pid, si->name, si->preinit_array);             break ;         case  DT_PREINIT_ARRAYSZ:             si->preinit_array_count = ((unsigned )*d) / sizeof (Elf32_Addr);             break ;         case  DT_TEXTREL:                                       DEBUG("%5d Text segment should be writable during relocation.\n" ,                   pid);             break ;         }     } 
 
接下来处理动态链接库引入的动态链接库:
找到type为DT_NEEDED的Dyn结构体,使用find_library加载该库。
一开始这个函数名让我很困扰,实际上这就是上文中的find_library,这个函数的作用不过是像他名字一样寻找(已加载)的库,未找到时还要再加载它。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 for (d = si->dynamic; *d; d += 2 ) {    if (d[0 ] == DT_NEEDED){         DEBUG("%5d %s needs %s\n" , pid, si->name, si->strtab + d[1 ]);         soinfo *lsi = find_library(si->strtab + d[1 ]);         if (lsi == 0 ) {             strlcpy(tmp_err_buf, linker_get_error(), sizeof (tmp_err_buf));             DL_ERR("%5d could not load needed library '%s' for '%s' (%s)" ,                    pid, si->strtab + d[1 ], si->name, tmp_err_buf);             goto  fail;         }                  d[1 ] = (unsigned )lsi;         lsi->refcount++;     } } 
 
接下来是比较重要的重定向部分。我们知道,动态链接库里存放的都是相对虚拟地址,以及外部函数的符号。我们需要修复这些地址,将他改成真正的虚拟地址。
安卓没有dl_resolve这个过程,load的时候JMPREL Relocation
Table的过程都要直接加载好,所以还要修改这些地址:
1 2 3 4 5 6 7 8 9 10 11 12 #ifdef  ANDROID_SH_LINKER     if (si->plt_rela) {         DEBUG("[ %5d relocating %s plt ]\n" , pid, si->name );         if (reloc_library_a(si, si->plt_rela, si->plt_rela_count))             goto  fail;     }     if (si->rela) {         DEBUG("[ %5d relocating %s ]\n" , pid, si->name );         if (reloc_library_a(si, si->rela, si->rela_count))             goto  fail;     } #endif   
 
分析reloc_library_a:
reloc_library_a 
遍历Elf32_Rela结构体数组。这里用到两个宏获取type和符号表的索引,并计算需要重定向的内容所在的地址
1 2 3 unsigned  type = ELF32_R_TYPE(rela->r_info);unsigned  sym = ELF32_R_SYM(rela->r_info);unsigned  reloc = (unsigned )(rela->r_offset + si->base);
 
如果是符号,获取符号字符串,然后_do_lookup寻找该符号对应的位置。这里使用哈希表实现的存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24         if (sym != 0 ) {             sym_name = (char  *)(strtab + symtab[sym].st_name);             s = _do_lookup(si, sym_name, &base);             if (s == 0 ) {                 DL_ERR("%5d cannot locate '%s'..." , pid, sym_name);                 return  -1 ;             } #if  0             if ((base == 0 ) && (si->base != 0 )){                                      DL_ERR("%5d cannot locate '%s'..." ,                        pid, strtab + symtab[sym].st_name);                 return  -1 ;             } #endif              if  ((s->st_shndx == SHN_UNDEF) && (s->st_value != 0 )) {                 DL_ERR("%5d In '%s', shndx=%d && value=0x%08x. We do not "                        "handle this yet" , pid, si->name, s->st_shndx,                       s->st_value);                 return  -1 ;             }             sym_addr = (unsigned )(s->st_value + base);             COUNT_RELOC(RELOC_SYMBOL);         } 
 
最后根据type对内容进行修改,起作用的最重要的一句话为*((unsigned*)reloc) += si->base;,这句换完成了RVA到VA的转换,以及写入外部函数的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 switch (type){case  R_SH_JUMP_SLOT:    COUNT_RELOC(RELOC_ABSOLUTE);     MARK(rela->r_offset);     TRACE_TYPE(RELO, "%5d RELO JMP_SLOT %08x <- %08x %s\n" , pid,                reloc, sym_addr, sym_name);     *((unsigned *)reloc) = sym_addr;     break ; case  R_SH_GLOB_DAT:    COUNT_RELOC(RELOC_ABSOLUTE);     MARK(rela->r_offset);     TRACE_TYPE(RELO, "%5d RELO GLOB_DAT %08x <- %08x %s\n" , pid,                reloc, sym_addr, sym_name);     *((unsigned *)reloc) = sym_addr;     break ; case  R_SH_DIR32:    COUNT_RELOC(RELOC_ABSOLUTE);     MARK(rela->r_offset);     TRACE_TYPE(RELO, "%5d RELO DIR32 %08x <- %08x %s\n" , pid,                reloc, sym_addr, sym_name);     *((unsigned *)reloc) += sym_addr;     break ; case  R_SH_RELATIVE:    COUNT_RELOC(RELOC_RELATIVE);     MARK(rela->r_offset);     if (sym){         DL_ERR("%5d odd RELATIVE form..." , pid);         return  -1 ;     }     TRACE_TYPE(RELO, "%5d RELO RELATIVE %08x <- +%08x\n" , pid,                reloc, si->base);     *((unsigned *)reloc) += si->base;     break ; default :    DL_ERR("%5d unknown reloc type %d @ %p (%d)" ,           pid, type, rela, (int ) (rela - start));     return  -1 ; } 
 
link_image的最后结尾,导入调试信息,这里暂时不分析了
 
这里是导入的动态链接库的构造函数,调用init_func,init_array。
 
自定义的dlopen与dlsym实现 
目标 
在这个demo中,我们需要了三个库libhelloworld.so、liblua.so、liblink.so。其中liblink.so是正常从Dalvic调用进来的,已经加载到了内存。libhelloworld.so中依赖liblua.so。libhellowrold.so中的函数helloworld()会调用liblua.so的函数,并且需要执行lua脚本。
我们需要实现自己的dlopen和dlsym,加载libhelloworld.so,调用其中的helloworld,运行任意lua脚本。同时加载的libhelloworld.so不能在maps中显示。由于libhelloworld.so依赖了liblua.so,也需要加载这个so并且不能再maps显示。
思路 
理解了源码中linker的实现,自己在实现一个dlopen就不难了。经过分析,maps中之所以会显示动态链接库的路径,是因为第二次map每一个segments的时候是将文件映射到内存,并非匿名映射,所以maps中会显示地址。我们可以参考源码中dlopen的实现,在load_segment的时候不采用文件映射的方法,而是读取文件并写入内存,这样这段内存仍然是匿名映射,就不会显示我们加载的动态链接库了。
需要实现的过程中很多地方需要注意,最重要的一点应该是对于重定向地址的修复,往往会忽略一些内容。对于data段存放的一些也需要重定向。注意考虑RELA和JMPREL中所有的类型。
第二点,如果我们加载的动态链接库也需要加载新的动态链接库,这些库如果原先没加载,我们也要加载它并隐藏,所以要用同样的自己实现的dlopen加载它。目前的方案是,如果在maps中没搜到这个库,统一用my_dlopen加载。
遇到的问题 
忽略了.data段也存放了需要重定向的地址,实际上就是要对重定向表中的每一个类型都进行处理。 
共享库加载共享库的问题。比如我们加载的库A,A还依赖库B,所以也要加载库B,否则对库B的引用都是RVA,无法正常运行。这里处理的时候是从maps中寻找,如果已经加载过这个库,则用libc的dlopen加载(经过分析dlopen的源码,同一个库是不会重复加载的)。如果(操作系统)没有加载过,则使用我们实现的mydlopen去加载这个库。 
编译时遇到的一些问题。主要是不太熟悉Cmake和C++。lua的源代码是用C写的,所以编译出来的符号表是正常的函数符号。而我在写libhelloworld.so时用的是C++,那么定义的函数名会附带符号修饰,那么就不能在C写的.so中找到对应的符号,所以link的时候会失败(比如我们要找一个叫lua_tointegerx的符号,而实际编译出来的符号是_Z14lua_tointegerxP9lua_StateiPi。 
 
结果 
我们在apk的data目录下存放了一个add.lua脚本:
1 2 3 function  add (x,y) return  x+yend 
 
libhelloworld.so中的helloworld函数负责调用liblua.so的lua函数,运行这个脚本并返回结果,然后在log中打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void  helloworld ()  {    LOGD("call helloworld." );     LOGD("gettimeofday at .data: %p" , fun1);     int  sum;     lua_State* L;     L = luaL_newstate();     luaopen_base(L);     luaL_dofile(L, "/data/user/0/fun.apeng.test2/add.lua" );     lua_getglobal(L, "add" );     lua_pushnumber(L, 1 );     lua_pushnumber(L, 2 );     lua_call(L, 2 , 1 );     sum = (int )lua_tointeger(L, -1 );     lua_pop(L, 1 );     LOGD("lua res:%d" , sum);     lua_close(L); } 
 
运行apk后,查看pid并查看/proc/[pid]/maps,grep一下只有liblinker.so,没有libhelloworld.so和liblua.so。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 sagit:/  USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME root          6689  4552   33528   1960 SyS_rt_si+          0 S sh root          6693  6689   36004   2312 0                   0 R ps sagit:/  u0_a121       6642   685 1 20:47:52 ?     00:00:00 fun.apeng.test2 root          6695  6689 0 20:48:37 pts/1 00:00:00 grep test2 sagit:/  75e6f53000-75e7030000 r-xp 00000000 103:01 1310751                       /data/app/fun.apeng.test2-VB49mv5fOQMEsLfO7U-EnA==/lib/arm64/liblinker.so 75e7031000-75e7039000 r--p 000dd000 103:01 1310751                       /data/app/fun.apeng.test2-VB49mv5fOQMEsLfO7U-EnA==/lib/arm64/liblinker.so 75e7039000-75e703a000 rw-p 000e5000 103:01 1310751                       /data/app/fun.apeng.test2-VB49mv5fOQMEsLfO7U-EnA==/lib/arm64/liblinker.so sagit:/  1|sagit:/  1|sagit:/  
 
调用一下helloworld函数,log中正确显示了内容,表明正确调用了helloworld,执行了lua脚本
1 2 3 2020-07-10 15:50:36.433 6642-6642/fun.apeng.test2 D/HELLO: call helloworld. 2020-07-10 15:50:36.433 6642-6642/fun.apeng.test2 D/HELLO: gettimeofday at .data: 0x76d09589fc 2020-07-10 15:50:36.434 6642-6642/fun.apeng.test2 D/HELLO: lua res:3 
 
再次查看maps,并没有发现libhelloworld.so和liblua.so
1 2 3 sagit:/  1|sagit:/  1|sagit:/  
 
缺陷 
第一个主要缺陷:同一个库可能会重复加载,对于比较复杂的库可能会占用大量内存。在操作系统中保存了加载过的所有库,而我实现的dlopen都是分离的,暂时无法共享大家都加载过什么库。后续可以继续优化,考虑记录下我们加载过的库,再去这些库中寻找。
第二个主要缺陷:新引入的库暂时是从同目录下寻找的,这样如果还要引入一些系统自带的库可能加载不到(比如测试时需要加载libc++_shared.so,就没有成功加载),可以考虑添加为在path中寻找并加载。