这周分析了一下安卓源码中 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中寻找并加载。