本文分析so加载的步骤,其实在之前dalvik浅析二中也有提及,但那重点关注的是jni。android中so库的加载,代码如下:

loadLibrary("nanosleep");   

  我们来看下它的执行流程吧:

先调用dlopen来载入so文件;find_library在soinfo结构(进程加载的so链)中查找当前so是否已载入,否则去执行so载入流程。so载入后,find_library会返回soinfo,去执行so的CallConstructors函数;如果so包含init、init_array段,则此函数会先执行这init和init_array。dlopen函数执行完毕表示系统对so操作告一段落,接着通过dlsym获取地址去执行JNI_LOAD。对一般而言so的加载和执行到此为止了,下图是我们通常会关注到的执行流程:

  普通而言就这样了,但这并不能满足我们的求知欲。下面以android4.4的linker代码来分析下后续的流程:

  上面说到通过find_library函数来判断so是否已加载,而在该函数中实质是调用find_library_internal函数去实现全部功能的。来看下find_library_internal的执行流程,

在find_loaded_library中通过soinfo结构提取出so的name比较来进行判别so是否已加载。load_library函数分三步执行:1.打开so文件得到文件描述符;2.ElfReader(linker_phdr.h)结构体去加载(mmap)elfheader、elf_phdr、elf_segment;3.将ElfReader解析到的elf信息填充到soinfo结构体中。这里我们先看下load函数,究竟是load了哪些东东

bool ElfReader::Load(const android_dlextinfo* extinfo) {
return ReadElfHeader() && //读取elf头到rc
VerifyElfHeader() && //验证rc是否为elf头,主要是验证magic、type、version
ReadProgramHeader() && //mmap映射segment 头
ReserveAddressSpace(extinfo) && //
LoadSegments() && //映射load segment:代码段和数据段
FindPhdr(); //判断是否有PT_PHDR segment
}

  当然这里soinfo_link_image是重头戏:

phdr_table_get_dynamic_section函数得到.dynamic节区(注意这里是根据segment类型为PT_DYNAMIC来得到.dynamic section),再通过.dynamic section解析出其他section;关于如何定位.dynamic节区和解析其他节区请参考elf文件格式(强烈建议在看本文之前观看)。不知各位看官注意到没有,对于载入so来说,只需segment就可以开工了(elf_shdr在这里没有用)。再来看so加载的重定位操作。

  从rel节中取出elf32_rel并解析其type和elf32_sym,接着根据elf32_sym.name调用soinfo_do_lookup在其他so库(先前已加载)中找到对应的符号。soinfo_elf_lookup先将name hash并将此值作为hash表的索引得到funIndex,然后判断elf32_sym[funIndex].name是否相同。没说清楚,直接看源码吧

static Elf32_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) {
Elf32_Sym* symtab = si->symtab;
const char* strtab = si->strtab; TRACE_TYPE(LOOKUP, "SEARCH %s in %s@0x%08x %08x %d",
name, si->name, si->base, hash, hash % si->nbucket); for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]) {
Elf32_Sym* s = symtab + n;
if (strcmp(strtab + s->st_name, name)) continue; /* only concern ourselves with global and weak symbol definitions */
switch(ELF32_ST_BIND(s->st_info)){
case STB_GLOBAL:
case STB_WEAK:
if (s->st_shndx == SHN_UNDEF) {  //SHN_UNDEF表示为外部调用,不是本so的函数,故还是没找到真正地址,继续找
continue;
} TRACE_TYPE(LOOKUP, "FOUND %s in %s (%08x) %d",
name, si->name, s->st_value, s->st_size);
return s;
}
} return NULL;
}

  ok,从其他共享库中找到了对应的elf32_sym立刻保存其函数地址elf32_sym.st_value为sys_addr。然后根据type,将sys_addr处理后赋值给elf32_rel.r_offset所指向的值(这一步就是修改.got的值,.got保存着so外部调用符号的地址,类似于PE文件的IAT)。以上就是重定位的全部流程,就是帮外部调用找到其执行位置。

find_library就这样啦,来看看CallConstructors函数

void soinfo::CallConstructors() {
if (constructors_called) {
return;
} if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
name, preinit_array_count);
} if (dynamic != NULL) {
for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
if (d->d_tag == DT_NEEDED) {
const char* library_name = strtab + d->d_un.d_val;
TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
find_loaded_library(library_name)->CallConstructors();
}
}
} TRACE("\"%s\": calling constructors", name); // DT_INIT should be called before DT_INIT_ARRAY if both are present.
CallFunction("DT_INIT", init_func);
CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
} void soinfo::CallDestructors() {
TRACE("\"%s\": calling destructors", name); // DT_FINI_ARRAY must be parsed in reverse order.
CallArray("DT_FINI_ARRAY", fini_array, fini_array_count, true); // DT_FINI should be called after DT_FINI_ARRAY if both are present.
CallFunction("DT_FINI", fini_func);
}

CallConstructors里主要执行2个函数CallFunction、CallArray,即执行.init和.init_array节区的函数(可以在写so函数时指定在.init_array节):

通过__attribute__((constructor(num)))声明某一函数(num 值越小, 越先执行 ), 即指定了在.init_array 中的位置, 修改其顺序即可实现。 例如:void __attribute__((constructor(101))) kingcoming();       ——by ThomasKing

CallDestructors在卸载so调用,也是执行2个函数与CallConstructors相对应的。值得一提的是constructors_called,它防止so的Constructors被重复调用,当前so有调用其他共享库的函数,则先执行其他共享库的Constructors。

dlopen完结了,而dlsym实质就是soinfo_elf_lookup来找到对应的函数。

参考资料:

  1 android linker 浅析

【原创】内功修炼之路—链接深入剖析

android so加载的更多相关文章

  1. Android ListView加载更多

    先看效果: ListView的footer布局: <?xml version="1.0" encoding="utf-8"?> <Relati ...

  2. [转载] Android动态加载Dex机制解析

    本文转载自: http://blog.csdn.net/wy353208214/article/details/50859422 1.什么是类加载器? 类加载器(class loader)是 Java ...

  3. Android图片加载库的理解

    前言     这是“基础自测”系列的第三篇文章,以Android开发需要熟悉的20个技术点为切入点,本篇重点讲讲Android中的ImageLoader这个库的一些理解,在Android上最让人头疼是 ...

  4. Android中加载位图的方法

    Android中加载位图的关键的代码: AssetManager assets =context.getAssets(); //用一个AssetManager 对象来从应用程序包的已编译资源中为工程加 ...

  5. Android图片加载库:最全面的Picasso讲解

    前言 上文已经对当今 Android主流的图片加载库 进行了全面介绍 & 对比 如果你还没阅读,我建议你先移步这里阅读 今天我们来学习其中一个Android主流的图片加载库的使用 - Pica ...

  6. Android图片加载与缓存开源框架:Android Glide

    <Android图片加载与缓存开源框架:Android Glide> Android Glide是一个开源的图片加载和缓存处理的第三方框架.和Android的Picasso库类似,个人感觉 ...

  7. 演化理解 Android 异步加载图片

    原文:http://www.cnblogs.com/ghj1976/archive/2011/05/06/2038738.html#3018499 在学习"Android异步加载图像小结&q ...

  8. Android 动态加载 (二) 态加载机制 案例二

    探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法 重要说明 在实践的过程中大家都会发现资源引用的问题,这里重点声明两点: 1. 资源文件是不能直接inflate的,如果简单的话直接在程序 ...

  9. Android 动态加载 (一) 态加载机制 案例一

    在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本 ...

  10. 一起写一个Android图片加载框架

    本文会从内部原理到具体实现来详细介绍如何开发一个简洁而实用的Android图片加载缓存框架,并在内存占用与加载图片所需时间这两个方面与主流图片加载框架之一Universal Image Loader做 ...

随机推荐

  1. AntDesign Pro + .NET Core 实现基于JWT的登录认证

    很多同学说AgileConfig的UI实在是太丑了.我想想也是的,本来这个项目是我自己使用的,一开始甚至连UI都没有,全靠手动在数据库里修改数据.后来加上了UI也是使用了老掉牙的bootstrap3做 ...

  2. 2020年12月-第02阶段-前端基础-CSS Day02

    CSS Day02 复合选择器 后代选择器 并集选择器 1. CSS复合选择器 理解 理解css复合选择器分别的应用场景 为什么要学习css复合选择器 CSS选择器分为 基础选择器 和 复合选择器 , ...

  3. 2020年12月-第01阶段-前端基础-认识WEB

    认识WEB 1.认识网页 网页主要由文字.图像和超链接等元素构成.当然,除了这些元素,网页中还可以包含音频.视频以及Flash等. 思考: 网页是如何形成的呢? 总结 网页有图片.链接.文字等元素组成 ...

  4. mysql-canal-rabbitmq 安装部署教程

    原文 1.1. 开启 MySQL 的 binlog 日志 修改 my.cnf 或 my.ini(windows), 添加配置项: # binlog 日志存放路径 log-bin=D:\env\mysq ...

  5. 浅谈.Net Core后端单元测试

    目录 1. 前言 2. 为什么需要单元测试 2.1 防止回归 2.2 减少代码耦合 3. 基本原则和规范 3.1 3A原则 3.2 尽量避免直接测试私有方法 3.3 重构原则 3.4 避免多个断言 3 ...

  6. 再探命令行传参之c与python

    继上一次java命令行传参 python sys模块包括了一组非常实用的服务,内含很多函数方法和变量,用来处理Python运行时配置以及资源,从而可以与前当程序之外的系统环境交互,如:python解释 ...

  7. 说说SpringMVC从http流到Controller接口参数的转换过程

    一,前言 谈起springMVC框架接口请求过程大部分人可能会这样回答:负责将请求分发给对应的handler,然后handler会去调用实际的接口.核心功能是这样的,但是这样的回答未免有些草率.面试过 ...

  8. Elasticsearch 复合查询——多字符串多字段查询

    前言 有时我们在搜索电影的时候,包含了多个条件,比如主演是周星驰,打分8分以上,上映时间是1990年~2001年的,那么Elasticsearch又该如何帮我们做查询呢?这里我们可以用 bool 查询 ...

  9. Django 模板 render传参不转码

    今天通过Django后端向前端页面传递一行js代码,却发现符号被转码了导致代码不能执行 Django代码 HTML代码 实际生成页面代码 我们可以看到实际代码中的引号被转义,导致代码不能执行, 解决方 ...

  10. java例题_08 输入特定数字求和(n个a位数递增求和问题)

    1 /*8 [程序 8 输入数字求和] 2 题目:求 s=a+aa+aaa+aaaa+aa...a 的值,其中 a 是一个数字.例如 2+22+222+2222+22222(此时共有 5 个数相加), ...