Android Native库的加载及动态链接的过程

Native库的装载过程

我们从一个简单的NDK Demo开始分析。

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

// Example of a call to a native method

final TextView tv = (TextView) findViewById(R.id.sample_text);

tv.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

tv.setText(stringFromJNI());

}

});

}

/**

* A native method that is implemented by the 'native-lib' native library,

* which is packaged with this application.

*/

public native String stringFromJNI();

// Used to load the 'native-lib' library on application startup.

// 动态库的装载及链接

static {

System.loadLibrary("native-lib");

}

}

Android 链接器Linker之前的工作

 

下面从 System.loadLibrary() 开始分析。

public static void loadLibrary(String libname) {

这里VMStack.getCallingClassLoader()返回应用的类加载器

Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);

}

下面看 loadLibrary0()

synchronized void loadLibrary0(ClassLoader loader, String libname) {

if (libname.indexOf((int)File.separatorChar) != -1) {

throw new UnsatisfiedLinkError(

"Directory separator should not appear in library name: " + libname);

}

String libraryName = libname;

if (loader != null) {

// findLibrary()返回库的全路径名

String filename = loader.findLibrary(libraryName);

if (filename == null) {

// It's not necessarily true that the ClassLoader used

// System.mapLibraryName, but the default setup does, and it's

// misleading to say we didn't find "libMyLibrary.so" when we

// actually searched for "liblibMyLibrary.so.so".

throw new UnsatisfiedLinkError(loader + " couldn't find \"" +

System.mapLibraryName(libraryName) + "\"");

}

// 装载动态库

String error = doLoad(filename, loader);

if (error != null) {

throw new UnsatisfiedLinkError(error);

}

return;

}

......

}

参数 loader 为Android的应用类加载器,它是 PathClassLoader 类型的对象,继承自 BaseDexClassLoader 对象,下面看 BaseDexClassLoader 的 findLibrary() 方法。

public String findLibrary(String name) {

// 调用DexPathList的findLibrary方法

return pathList.findLibrary(name);

}


下面看 DexPathList 的 findLibrary() 方法

public String findLibrary(String libraryName) {

// 产生平台相关的库名称这里返回libxxx.so

String fileName = System.mapLibraryName(libraryName);

for (Element element : nativeLibraryPathElements) {

// 查找动态库返回库的全路径名

String path = element.findNativeLibrary(fileName);

if (path != null) {

return path;

}

}

return null;

}

回到 loadLibrary0() ,有了动态库的全路径名就可以装载库了,下面看 doLoad() 。

private String doLoad(String name, ClassLoader loader) {

......

// 获取应用类加载器的Native库搜索路径

String librarySearchPath = null;

if (loader != null && loader instanceof BaseDexClassLoader) {

BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;

librarySearchPath = dexClassLoader.getLdLibraryPath();

}

// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless

// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized

// internal natives.

synchronized (this) {

return nativeLoad(name, loader, librarySearchPath);

}

}

nativeLoad() 最终调用 LoadNativeLibrary() ,下面直接分析 LoadNativeLibrary() 。

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,

const std::string& path,

jobject class_loader,

jstring library_path,

std::string* error_msg) {

......

SharedLibrary* library;

Thread* self = Thread::Current();

{

// TODO: move the locking (and more of this logic) into Libraries.

// 检查动态库是否已经装载过,如果已经装载过(类加载器也匹配)直接返回。

MutexLock mu(self, *Locks::jni_libraries_lock_);

library = libraries_->Get(path);

}

......

// 没有装载过,装载链接动态库

// 参数patch_str传递的是动态库的全路径,之所以还要传递搜索路径,是因为可能包含它的依赖库

void* handle = android::OpenNativeLibrary(env,

runtime_->GetTargetSdkVersion(),

path_str,

class_loader,

library_path);

......

// 查找动态库中的"JNI_OnLoad"函数

sym = library->FindSymbol("JNI_OnLoad", nullptr);

if (sym == nullptr) {

VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";

was_successful = true;

} else {

// Call JNI_OnLoad. We have to override the current class

// loader, which will always be "null" since the stuff at the

// top of the stack is around Runtime.loadLibrary(). (See

// the comments in the JNI FindClass function.)

ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));

self->SetClassLoaderOverride(class_loader);

VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";

typedef int (*JNI_OnLoadFn)(JavaVM*, void*);

JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);

// 调用库的JNI_OnLoad函数注册JNI, 本文暂不讨论

int version = (*jni_on_load)(this, nullptr);

......

}

......

}

对于JNI注册,这里暂不讨论,下面看 OpenNativeLibrary() 的实现。

void* OpenNativeLibrary(JNIEnv* env,

int32_t target_sdk_version,

const char* path,

jobject class_loader,

jstring library_path) {

#if defined(__ANDROID__)

UNUSED(target_sdk_version);

if (class_loader == nullptr) {

return dlopen(path, RTLD_NOW);

}

std::lock_guard<std::mutex> guard(g_namespaces_mutex);

// 找到类加载器映射的命名空间(Android应用类加载器创建时创建)

// 关于命名空间的动态链接请参考http://jackwish.net/namespace-based-dynamic-linking-chn.html

android_namespace_t* ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);

.......

android_dlextinfo extinfo;

// 在一个不同的命名空间中装载

extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;

extinfo.library_namespace = ns;

// RILD_NOW表示重定位在dlopen返回前完成,不会延迟到第一次执行(RTLD_LAZY)

return android_dlopen_ext(path, RTLD_NOW, &extinfo);

......

}

下面看 android_dlopen_ext() 的实现

void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo) {

// __builtin_return_address是编译器的内建函数,__builtin_return_address(0)表示当前函数的返回地址

void* caller_addr = __builtin_return_address(0);

return dlopen_ext(filename, flags, extinfo, caller_addr);

}

接下来就Android链接器linker的工作了。

Android 链接器Linker的装载工作

 

下面从 do_dlopen() 开始分析。

void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo,

void* caller_addr) {

// caller_addr在libnativeloader.so中

// 查找地址所在的动态库(采用遍历查找,可以优化查找)

soinfo* const caller = find_containing_library(caller_addr);

// ns为调用库所在命名空间

android_namespace_t* ns = get_caller_namespace(caller);

......

if (extinfo != nullptr) {

......

// extinfo->flags为ANDROID_DLEXT_USE_NAMESPACE

if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) {

if (extinfo->library_namespace == nullptr) {

DL_ERR("ANDROID_DLEXT_USE_NAMESPACE is set but extinfo->library_namespace is null");

return nullptr;

}

// 命名空间使用应用自身类加载器-命名空间

ns = extinfo->library_namespace;

}

}

......

// 在命名空间ns中装载库

soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);

......

}

find_library() 当参数translated_name不为空时,直接调用 find_libraries() ,这是装载链接的关键函数,下面看它的实现。

static bool find_libraries(android_namespace_t* ns,

soinfo* start_with,

const char* const library_names[],

size_t library_names_count, soinfo* soinfos[],

std::vector<soinfo*>* ld_preloads,

size_t ld_preloads_count, int rtld_flags,

const android_dlextinfo* extinfo,

bool add_as_children) {

// ns为应用类加载器-命名空间

// 这里start_with为libnativeloader.so的soinfo

// library_names为需要装载的动态库,不包含依赖库

// library_names_count需要装载的动态库的数量,这里为1

// ld_preloads为nullptr

// add_as_children为false

......

// 为需要装载的动态库创建LoadTask添加到load_tasks

// LoadTask用于管理动态库的装载

for (size_t i = 0; i < library_names_count; ++i) {

const char* name = library_names[i];

load_tasks.push_back(LoadTask::create(name, start_with, &readers_map));

}

// Construct global_group.

// 收集命名空间ns中设置了DF_1_GLOBAL(RTLD_GLOBAL:共享库中的符号可被后续装载的库重定位)标志的动态库

soinfo_list_t global_group = make_global_group(ns);

......

// Step 1: expand the list of load_tasks to include

// all DT_NEEDED libraries (do not load them just yet)

// load_tasks以广度优先遍历的顺序存储动态库依赖树

// 例如依赖树: 1

// / \

// 2 3

// / \

// 4 5

// load_tasks: 1->2->3->4->5

for (size_t i = 0; i<load_tasks.size(); ++i) {

LoadTask* task = load_tasks[i];

soinfo* needed_by = task->get_needed_by();

bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);

task->set_extinfo(is_dt_needed ? nullptr : extinfo);

task->set_dt_needed(is_dt_needed);

// 收集动态库的信息以及它的依赖库

if(!find_library_internal(ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) {

return false;

}

soinfo* si = task->get_soinfo();

if (is_dt_needed) {

// si添加到needed_by的依赖中

needed_by->add_child(si);

}

if (si->is_linked()) {

// 已经链接过的库增加引用计数

si->increment_ref_count();

}

......

if (soinfos_count < library_names_count) {

soinfos[soinfos_count++] = si;

}

}

// Step 2: Load libraries in random order (see b/24047022)

LoadTaskList load_list;

// 需要装载的库放到load_list中

for (auto&& task : load_tasks) {

soinfo* si = task->get_soinfo();

auto pred = [&](const LoadTask* t) {

return t->get_soinfo() == si;

};

if (!si->is_linked() &&

std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) {

load_list.push_back(task);

}

}

// 随机化load_list中库的顺序

shuffle(&load_list);

for (auto&& task : load_list) {

// 装载动态库

if (!task->load()) {

return false;

}

}

// Step 3: pre-link all DT_NEEDED libraries in breadth first order.

// 预链接load_tasks中没有链接过的库,见下文

for (auto&& task : load_tasks) {

soinfo* si = task->get_soinfo();

if (!si->is_linked() && !si->prelink_image()) {

return false;

}

}

// Step 4: Add LD_PRELOADed libraries to the global group for

// future runs. There is no need to explicitly add them to

// the global group for this run because they are going to

// appear in the local group in the correct order.

if (ld_preloads != nullptr) {

for (auto&& si : *ld_preloads) {

si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL);

}

}

// Step 5: link libraries.

// 链接过程,见下文

soinfo_list_t local_group;

// 广度优先遍历添加动态库依赖图到local_group中

walk_dependencies_tree(

(start_with != nullptr && add_as_children) ? &start_with : soinfos,

(start_with != nullptr && add_as_children) ? 1 : soinfos_count,

[&] (soinfo* si) {

local_group.push_back(si);

return true;

});

// We need to increment ref_count in case

// the root of the local group was not linked.

bool was_local_group_root_linked = local_group.front()->is_linked();

bool linked = local_group.visit([&](soinfo* si) {

if (!si->is_linked()) {

if (!si->link_image(global_group, local_group, extinfo)) {

return false;

}

}

return true;

});

if (linked) {

// 设置链接标志

local_group.for_each([](soinfo* si) {

if (!si->is_linked()) {

si->set_linked();

}

});

failure_guard.disable();

}

......

}

find_libraries() 中动态库的装载可以分为两部分

  • 收集动态库的信息及其依赖库
  • 装载动态库及依赖库
  • 收集动态库的信息及依赖库

下面从 find_library_internal() 开始分析。

static bool find_library_internal(android_namespace_t* ns,

LoadTask* task,

ZipArchiveCache* zip_archive_cache,

LoadTaskList* load_tasks,

int rtld_flags) {

soinfo* candidate;

// 在应用类加载器-命名空间中查找动态库是否已经装载过

if (find_loaded_library_by_soname(ns, task->get_name(), &candidate)) {

task->set_soinfo(candidate);

return true;

}

// 在默认命名空间中查找动态库是否已经装载过

if (ns != &g_default_namespace) {

// check public namespace

candidate = g_public_namespace.find_if([&](soinfo* si) {

return strcmp(task->get_name(), si->get_soname()) == 0;

});

......

}

......

// 装载库

if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags)) {

return true;

} else {

// In case we were unable to load the library but there

// is a candidate loaded under the same soname but different

// sdk level - return it anyways.

if (candidate != nullptr) {

task->set_soinfo(candidate);

return true;

}

}

return false;

}

下面分析 load_library()

static bool load_library(android_namespace_t* ns,

LoadTask* task,

ZipArchiveCache* zip_archive_cache,

LoadTaskList* load_tasks,

int rtld_flags) {

......

// 打开库文件返回文件描述符

int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);

if (fd == -1) {

DL_ERR("library \"%s\" not found", name);

return false;

}

task->set_fd(fd, true);

task->set_file_offset(file_offset);

// 装载库

return load_library(ns, task, load_tasks, rtld_flags, realpath);

}

下面看另一个 load_library() 的实现

static bool load_library(android_namespace_t* ns,

LoadTask* task,

LoadTaskList* load_tasks,

int rtld_flags,

const std::string& realpath) {

off64_t file_offset = task->get_file_offset();

const char* name = task->get_name();

const android_dlextinfo* extinfo = task->get_extinfo();

......

// 为动态库创建soinfo,用于记录动态链接信息等

soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags);

if (si == nullptr) {

return false;

}

task->set_soinfo(si);

// Read the ELF header and some of the segments.

// 读取ELF文件头以及一些段信息

if (!task->read(realpath.c_str(), file_stat.st_size)) {

soinfo_free(si);

task->set_soinfo(nullptr);

return false;

}

......

// 查找依赖库,创建LoadTask添加到load_tasks

for_each_dt_needed(task->get_elf_reader(), [&](const char* name) {

load_tasks->push_back(LoadTask::create(name, si, task->get_readers_map()));

});

return true;

}

下面分析ELF文件头以及段信息的读取过程,也就是LoadTask的 read() ,它直接调用ElfReader的 Read() 方法。

bool ElfReader::Read(const char* name, int fd, off64_t file_offset, off64_t file_size) {

CHECK(!did_read_);

CHECK(!did_load_);

name_ = name;

fd_ = fd;

file_offset_ = file_offset;

file_size_ = file_size;

if (ReadElfHeader() &&

VerifyElfHeader() &&

ReadProgramHeaders() &&

ReadSectionHeaders() &&

ReadDynamicSection()) {

did_read_ = true;

}

return did_read_;

}

ReadElfHeader() : 读取ELF文件头信息

VerifyElfHeader() : 校验ELF(文件类型等)

ReadProgramHeaders() : 根据ELF文件头信息获取程序头表

ReadSectionHeaders() : 根据ELF文件头信息获取段头表

ReadDynamicSection() : 获取Dynamic Section的信息

装载动态库

动态库的装载在LoadTask的 load() 中实现。

bool load() {

ElfReader& elf_reader = get_elf_reader();

// 映射动态库的可加载Segment到进程的虚拟地址空间中

if (!elf_reader.Load(extinfo_)) {

return false;

}

// 保存装载信息

// 动态库装载的起始地址

si_->base = elf_reader.load_start();

// 可装载的Segment大小之和

si_->size = elf_reader.load_size();

si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller());

// 动态库装载的期望起始地址,通常si_->load_bias = si_->base

si_->load_bias = elf_reader.load_bias();

// 动态库程序头表项数

si_->phnum = elf_reader.phdr_count();

// 动态库程序头表的地址

si_->phdr = elf_reader.loaded_phdr();

return true;

}

在实际的地址计算中,使用si_->load_bias,不使用si_->base。

下面看ElfReader的 Load() 方法

bool ElfReader::Load(const android_dlextinfo* extinfo) {

CHECK(did_read_);

CHECK(!did_load_);

if (ReserveAddressSpace(extinfo) &&

LoadSegments() &&

FindPhdr()) {

did_load_ = true;

}

return did_load_;

}

ReserveAddressSpace(): 保留虚拟地址空间为动态库(装载地址随机化)

LoadSegments() : 装载ELF文件中可装载的Segments

FindPhdr() : 确保程序头表包含在一个可加载的Segment中

动态库的装载已经完成,下面看链接过程。

Native库动态链接的过程

 

预链接

下面看 prelink_image()

bool soinfo::prelink_image() {

/* Extract dynamic section */

ElfW(Word) dynamic_flags = 0;

// 根据程序头表的地址计算dynamic section的地址

phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);

......

uint32_t needed_count = 0;

// 解析dynamic section获取动态链接信息

for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {

DEBUG("d = %p, d[0](tag) = %p d[1](val) = %p",

d, reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val));

switch (d->d_tag) {

......

case DT_STRTAB:

// 动态字符串表的地址

strtab_ = reinterpret_cast<const char*>(load_bias + d->d_un.d_ptr);

break;

case DT_STRSZ:

strtab_size_ = d->d_un.d_val;

break;

case DT_SYMTAB:

// 动态符号表的地址

symtab_ = reinterpret_cast<ElfW(Sym)*>(load_bias + d->d_un.d_ptr);

break;

......

case DT_JMPREL:

// 需重定位的函数表(.rela.plt)的地址

#if defined(USE_RELA)

plt_rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr);

#else

plt_rel_ = reinterpret_cast<ElfW(Rel)*>(load_bias + d->d_un.d_ptr);

#endif

break;

......

case DT_RELA:

// 需重定位的数据表(.rela.dyn)的地址

rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr);

break;

......

case DT_NEEDED:

// 依赖的动态库

++needed_count;

break;

}

}

......

// Sanity checks.

// 检查动态链接信息

if (relocating_linker && needed_count != 0) {

DL_ERR("linker cannot have DT_NEEDED dependencies on other libraries");

return false;

}

if (nbucket_ == 0 && gnu_nbucket_ == 0) {

DL_ERR("empty/missing DT_HASH/DT_GNU_HASH in \"%s\" "

"(new hash type from the future?)", get_realpath());

return false;

}

if (strtab_ == 0) {

DL_ERR("empty/missing DT_STRTAB in \"%s\"", get_realpath());

return false;

}

if (symtab_ == 0) {

DL_ERR("empty/missing DT_SYMTAB in \"%s\"", get_realpath());

return false;

}

......

}

链接

链接主要完成符号重定位工作,下面从 link_image() 开始分析

bool soinfo::link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group,

const android_dlextinfo* extinfo) {

......

#if defined(USE_RELA)

// rela_为重定位数据表的地址

if (rela_ != nullptr) {

DEBUG("[ relocating %s ]", get_realpath());

// 数据引用重定位

if (!relocate(version_tracker,

plain_reloc_iterator(rela_, rela_count_), global_group, local_group)) {

return false;

}

}

// plt_rela_为重定位函数表的地址

if (plt_rela_ != nullptr) {

DEBUG("[ relocating %s plt ]", get_realpath());

// 函数引用重定位

if (!relocate(version_tracker,

plain_reloc_iterator(plt_rela_, plt_rela_count_), global_group, local_group)) {

return false;

}

}

#else

......

}

下面以函数引用重定位为例分析 relocate() 方法

template<typename ElfRelIteratorT>

bool soinfo::relocate(const VersionTracker& version_tracker, ElfRelIteratorT&& rel_iterator,

const soinfo_list_t& global_group, const soinfo_list_t& local_group) {

for (size_t idx = 0; rel_iterator.has_next(); ++idx) {

const auto rel = rel_iterator.next();

if (rel == nullptr) {

return false;

}

// rel->r_info的低32位

ElfW(Word) type = ELFW(R_TYPE)(rel->r_info);

// rel->r_info的高32位

ElfW(Word) sym = ELFW(R_SYM)(rel->r_info);

// 重定位地址的存储位置

ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rel->r_offset + load_bias);

ElfW(Addr) sym_addr = 0;

const char* sym_name = nullptr;

ElfW(Addr) addend = get_addend(rel, reloc);

......

if (sym != 0) {

// sym为动态符号表项的索引

// symtab_[sym].st_name为符号在动态字符串表的索引

// sysm_name为需重定位的符号名

sym_name = get_string(symtab_[sym].st_name);

const version_info* vi = nullptr;

if (!lookup_version_info(version_tracker, sym, sym_name, &vi)) {

return false;

}

// 查找符号返回符号表项的地址

if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) {

return false;

}

if (s == nullptr) {

......

} else {

......

// 根据符号表项计算符号地址

sym_addr = lsi->resolve_symbol_address(s);

......

}

......

}

switch (type) {

// ELF64中R_GENERIC_JUMP_SLOT = R_AARCH64_JUMP_SLOT

case R_GENERIC_JUMP_SLOT:

count_relocation(kRelocAbsolute);

MARK(rel->r_offset);

TRACE_TYPE(RELO, "RELO JMP_SLOT %16p <- %16p %s\n",

reinterpret_cast<void*>(reloc),

reinterpret_cast<void*>(sym_addr + addend), sym_name);

// 符号地址更新到reloc(GOT表)中

*reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend);

break;

......

}

}

return true;

}

参考

Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification

总结

以上所述是小编给大家介绍的Android Native库的加载及动态链接的过程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!

以上是 Android Native库的加载及动态链接的过程 的全部内容, 来源链接: utcz.com/z/346534.html

回到顶部