天天看點

Android源碼學習——linker(1)

本文學習的源碼參考AndroidXRef,版本為Lollipop 5.1.0_r1。

在Android開發過程中,我們要想使用JNI機制調用一個native方法時,首先會需要調用一個system的方法,把定義了native方法的庫加載進來。今天,就從System.loadLibrary(“XXX”)來詳細看一下一個動态庫的加載過程。

public static void loadLibrary(String libName) {
        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
    }
           

這個loadLibrary是System的一個方法,同時也說明了JNI标準本身就是Java語言的一部分。

可以看到這個方法裡實際調用了Runtime的loadLibrary方法。

void loadLibrary(String libraryName, ClassLoader loader) {
        if (loader != null) {
            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;
        }

        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : mLibPaths) {
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }
           

loadLibrary首先根據傳入的loader,調用它的

findLibrary(libraryName)

方法去擷取要加載的so的檔案路徑,然後判斷一下獲得的路徑是否為空,不為空,則調用

doLoad(filename, loader)

去執行so的加載連結等等過程。

若傳入的loader為空,則首先去通過System的

mapLibraryName(libraryName)

方法擷取so的檔案名,然後在一個mLibPaths的變量裡面去找這個so檔案,并最終拼接形成最終的so檔案路徑,最後還是調用

doLoad(candidate, loader)

去執行so的加載連結過程。

看下doLoad的實作:

private String doLoad(String name, ClassLoader loader) {
        String ldLibraryPath = null;
        if (loader != null && loader instanceof BaseDexClassLoader) {
            ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
        }

        synchronized (this) {
            return nativeLoad(name, loader, ldLibraryPath);
        }
    }
           

注釋中提到,Android應用從Zygote程序中建立出來,是以他們沒有自定義的LD_LIBRARY_PATH,so檔案的預設路徑肯定也不在這個變量中;而PathClassLoader知道正确的路徑,是以我們可以暫時先不加載依賴庫,但是後面需要按依賴順序加載。我們需要将API添加到Android的動态連結器中,這樣我們就可以為目前運作的程序更新所使用的庫路徑,此處就是将classloader中的路徑傳遞給nativeload。

可以看到doLoad先提取了ldLibraryPath的值,然後和so檔案的名字以及loader一起傳遞給了nativeLoad函數。很顯然,這是一個native方法:

static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == NULL) {
    return NULL;
  }

  if (javaLdLibraryPath != NULL) {
    ScopedUtfChars ldLibraryPath(env, javaLdLibraryPath);
    if (ldLibraryPath.c_str() == NULL) {
      return NULL;
    }
    void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");
    if (sym != NULL) {
      typedef void (*Fn)(const char*);
      Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);
      (*android_update_LD_LIBRARY_PATH)(ldLibraryPath.c_str());
    } else {
      LOG(ERROR) << "android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!";
    }
  }

  std::string detail;
  {
    ScopedObjectAccess soa(env);
    StackHandleScope<> hs(soa.Self());
    Handle<mirror::ClassLoader> classLoader(
        hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
    JavaVMExt* vm = Runtime::Current()->GetJavaVM();
    bool success = vm->LoadNativeLibrary(filename.c_str(), classLoader, &detail);
    if (success) {
      return nullptr;
    }
  }

  // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
  env->ExceptionClear();
  return env->NewStringUTF(detail.c_str());
}
           

這個方法首先做了javaFilename和javaLdLibraryPath的類型轉換,然後更新了LD_LIBRARY_PATH,就是我們上面說的路徑設定。接下來,最主要的,調用

GetJavaVM()

擷取對應的android虛拟機,調用它的LoadNativeLibrary方法去完成so的加載。

bool JavaVMExt::LoadNativeLibrary(const std::string& path,
                                  Handle<mirror::ClassLoader> class_loader,
                                  std::string* detail) {
  detail->clear();

  // See if we've already loaded this library.  If we have, and the class loader
  // matches, return successfully without doing anything.
  // TODO: for better results we should canonicalize the pathname (or even compare
  // inodes). This implementation is fine if everybody is using System.loadLibrary.
  SharedLibrary* library;
  Thread* self = Thread::Current();
  {
    // TODO: move the locking (and more of this logic) into Libraries.
    MutexLock mu(self, libraries_lock);
    library = libraries->Get(path);
  }
  if (library != nullptr) {
    if (library->GetClassLoader() != class_loader.Get()) {
      // The library will be associated with class_loader. The JNI
      // spec says we can't load the same library into more than one
      // class loader.
      StringAppendF(detail, "Shared library \"%s\" already opened by "
          "ClassLoader %p; can't open in ClassLoader %p",
          path.c_str(), library->GetClassLoader(), class_loader.Get());
      LOG(WARNING) << detail;
      return false;
    }
    VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
              << "ClassLoader " << class_loader.Get() << "]";
    if (!library->CheckOnLoadResult()) {
      StringAppendF(detail, "JNI_OnLoad failed on a previous attempt "
          "to load \"%s\"", path.c_str());
      return false;
    }
    return true;
  }

  // Open the shared library.  Because we're using a full path, the system
  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
  // resolve this library's dependencies though.)

  // Failures here are expected when java.library.path has several entries
  // and we have to hunt for the lib.

  // Below we dlopen but there is no paired dlclose, this would be necessary if we supported
  // class unloading. Libraries will only be unloaded when the reference count (incremented by
  // dlopen) becomes zero from dlclose.

  // This can execute slowly for a large library on a busy system, so we
  // want to switch from kRunnable while it executes.  This allows the GC to ignore us.
  self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  void* handle = dlopen(path_str, RTLD_LAZY);
  bool needs_native_bridge = false;
  if (handle == nullptr) {
    if (android::NativeBridgeIsSupported(path_str)) {
      handle = android::NativeBridgeLoadLibrary(path_str, RTLD_LAZY);
      needs_native_bridge = true;
    }
  }
  self->TransitionFromSuspendedToRunnable();

  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]";

  if (handle == nullptr) {
    *detail = dlerror();
    LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *detail;
    return false;
  }

  // Create a new entry.
  // TODO: move the locking (and more of this logic) into Libraries.
  bool created_library = false;
  {
    MutexLock mu(self, libraries_lock);
    library = libraries->Get(path);
    if (library == nullptr) {  // We won race to get libraries_lock
      library = new SharedLibrary(path, handle, class_loader.Get());
      libraries->Put(path, library);
      created_library = true;
    }
  }
  if (!created_library) {
    LOG(INFO) << "WOW: we lost a race to add shared library: "
        << "\"" << path << "\" ClassLoader=" << class_loader.Get();
    return library->CheckOnLoadResult();
  }

  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader.Get()
      << "]";

  bool was_successful = false;
  void* sym = nullptr;
  if (UNLIKELY(needs_native_bridge)) {
    library->SetNeedsNativeBridge();
    sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
  } else {
    sym = dlsym(handle, "JNI_OnLoad");
  }

  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.)
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    StackHandleScope<> hs(self);
    Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader.Get());

    int version = ;
    {
      ScopedThreadStateChange tsc(self, kNative);
      VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
      version = (*jni_on_load)(this, nullptr);
    }

    if (runtime->GetTargetSdkVersion() !=  && runtime->GetTargetSdkVersion() <= ) {
      fault_manager.EnsureArtActionInFrontOfSignalChain();
    }
    self->SetClassLoaderOverride(old_class_loader.Get());

    if (version == JNI_ERR) {
      StringAppendF(detail, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
    } else if (IsBadJniVersion(version)) {
      StringAppendF(detail, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
                    path.c_str(), version);
      // It's unwise to call dlclose() here, but we can mark it
      // as bad and ensure that future load attempts will fail.
      // We don't know how far JNI_OnLoad got, so there could
      // be some partially-initialized stuff accessible through
      // newly-registered native method calls.  We could try to
      // unregister them, but that doesn't seem worthwhile.
    } else {
      was_successful = true;
    }
    VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
              << " from JNI_OnLoad in \"" << path << "\"]";
  }

  library->SetResult(was_successful);
  return was_successful;
}
           

有點長,大緻分為四個部分:

第一部分,檢查我們是否已經加載了這個so庫,并且對應的classloader是否比對,如果都通過了則直接傳回成功;

第二部分,去打開那個so庫,并傳回一個handle句柄,也就是我們很熟悉的

dlopen(path_str, RTLD_LAZY)

第三部分,建立一個SharedLibrary對象,後面去執行裡面的方法都需要通過這個對象來完成。例如,我們可以通過

FindSymbolWithNativeBridge("JNI_OnLoad", nullptr)

獲得JNI_OnLoad的函數指針。當然,也可以通過

dlsym(handle, "JNI_OnLoad")

的方式來擷取;

最後,當然就是去執行JNI_OnLoad方法了。

剛剛提到的dlopen,就是so加載的核心函數了,我們看下這個方法的實作:

void* dlopen(const char* filename, int flags) {
  return dlopen_ext(filename, flags, nullptr);
}

static void* dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo) {
  ScopedPthreadMutexLocker locker(&g_dl_mutex);
  soinfo* result = do_dlopen(filename, flags, extinfo);
  if (result == nullptr) {
    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
    return nullptr;
  }
  return result;
}
           

終于進入到linker的源碼了。

可以看到這個函數最終是調用了

do_dlopen(filename, flags, extinfo)

這裡提一下dlopen的那個flag的參數,總共可以取三個值:

  • RTLD_LAZY:在dlopen傳回前,對于動态庫中存在的未定義的變量不執行解析;
  • RTLD_NOW:與上面相反,這個值表示在dlopen傳回前,立即解析每個未定義變量的位址,否則會傳回undefined symbol的錯誤;
  • RTLD_GLOBAL:它的含義是使得庫中的解析的變量具有全局性,也即随後的其它的連結庫中也可以使用。

繼續看do_dlopen的實作:

soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) {
  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NOLOAD)) != ) {
    DL_ERR("invalid flags to dlopen: %x", flags);
    return nullptr;
  }
  if (extinfo != nullptr) {
    if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != ) {
      DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
      return nullptr;
    }
    if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) ==  &&
        (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != ) {
      DL_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags);
      return nullptr;
    }
  }
  protect_data(PROT_READ | PROT_WRITE);
  soinfo* si = `find_library(name, flags, extinfo)`;
  if (si != nullptr) {
    si->CallConstructors();
  }
  protect_data(PROT_READ);
  return si;
}
           

前面是做了下參數的判斷,重點在後面,先修改記憶體權限為可讀可寫,因為我們要加載so庫了;

然後調用

find_library(name, flags, extinfo)

進行so的加載,傳回一個soinfo的指針,這個soinfo就是包含了我們加載後的so的所有資訊的一個結構體;

接下來,若si不為空,也即so加載成功了,去調用

CallConstructors()

方法,後面我們就會知道這裡是去執行了so的init和init_array段,完成一些初始化的操作;

最後,将記憶體改回可讀。