ELF Linker学习篇(四)——初始化

来源:互联网 发布:wps for ubuntu 16.04 编辑:程序博客网 时间:2024/06/05 06:28

在上几篇中总结了ELF的加载、链接之后,最后一篇我们继续关于初始化的过程。

正文篇:

首先我们看到源码为:

soinfo* do_dlopen(const char* name, int flags) {  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {    DL_ERR("invalid flags to dlopen: %x", flags);    return NULL;  }  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);  soinfo* si = find_library(name);  if (si != NULL) {    si->CallConstructors();  }  set_soinfo_pool_protection(PROT_READ);  return si;}
完成 SO 的装载链接后,返回到 do_dlopen 函数, do_open 获得 find_library 返回的刚刚加载的 SO 的 soinfo,在将 soinfo 返回给其他模块使用之前,最后还需要调用 soinfo 的成员函数 CallConstructors。

看源码为:

void soinfo::CallConstructors() {  if (constructors_called) {    return;  }  // We set constructors_called before actually calling the constructors, otherwise it doesn't  // protect against recursive constructor calls. One simple example of constructor recursion  // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:  // 1. The program depends on libc, so libc's constructor is called here.  // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.  // 3. dlopen() calls the constructors on the newly created  //    soinfo for libc_malloc_debug_leak.so.  // 4. The debug .so depends on libc, so CallConstructors is  //    called again with the libc soinfo. If it doesn't trigger the early-  //    out above, the libc constructor will be called again (recursively!).  constructors_called = true;  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);}
CallConstructors 函数会调用 SO 的首先调用所有依赖的 SO 的 soinfo 的 CallConstructors 函数,接着调用自己的 soinfo 成员变量 init 和 看 init_array 指定的函数,这两个变量在在解析 dynamic section 时赋值。

在这里重点说一下CallFunction()函数:

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {  if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {    return;  }  TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);  function();  TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);  // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures  // are still writable. This happens with our debug malloc (see http://b/7941716).  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);}
这也就明白了之前看的文章中为什么说在linker处下断点时,要搜素“
<span style="font-family:SimSun;">Calling %s @ %p for '%s'</span><span style="font-family: SimSun; background-color: rgb(255, 255, 255);">”这样的字符。</span>
<span style="font-family: SimSun; background-color: rgb(255, 255, 255);"></span>

初始化函数执行完以后,接着执行我们所说的JNI_Onload函数,此函数的作用为:

当Android的VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。
它的用途有二:
  (1)告诉VM此C组件使用那一个JNI版本。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的 JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
  (2)由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之(Initialization)。

JNI_Onload函数执行完以后,接着执行java_com_XX我们所熟悉的函数的身影。

因此这里也就跟之前所说的下断点有三层.init->.init_array->JNI_Onload->java_com_XX.

总结篇:

ELF的加载、链接、算是完了,也算不上很清楚,模模糊糊,以后用到再复习进阶,学东西不就这样嘛


0 0
原创粉丝点击