(4.1.27.20)JNI原理

System的load和loadLibrary方法

System.loadLibrary或System.load会调用到so库中的JNI_OnLoad方法进行方法注册,但是这个说是这样说,对于读者依然很模糊,到底System.loadLibrary或System.load到底是怎样的一种流程进行加载的并且调用JNI_OnLoad方法进行注册的呢?

加载.so动态链接库需要用到System.load 与 System.loadLibrary

    @CallerSensitive
    public static void load(String filename) {
        Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
    }
    
    @CallerSensitive
    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
    }

getRuntime是Runtime.java中的方法,调用该方法返回Runtime对象,得到Java应用城的运行环境RunTime

  • System.load()
    • System.load 参数必须为库文件的绝对路径,可以是任意路径,例如:
    • System.load(“C:\Documents and Settings\TestJNI.dll”); //Windows
    • System.load("/usr/lib/TestJNI.so"); //Linux
  • System.loadLibrary
    • 参数为so的名称,用于加载App安装后自动冲apk包中赋值到/data/data/paclagename/lib下的so

System的load

    synchronized void load0(Class<?> fromClass, String filename) {
        if (!(new File(filename).isAbsolute())) {
            throw new UnsatisfiedLinkError(
                "Expecting an absolute path of the library: " + filename);
        }
        if (filename == null) {
            throw new NullPointerException("filename == null");
        }
        //【注释1】
        String error = doLoad(filename, fromClass.getClassLoader());
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
    }

在注释1处:调用doLoad方法,并传入了加载该类的类加载器

    private String doLoad(String name, ClassLoader loader) {
         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);
        }
    }

System的loadLibrary0

整体代码可以分为 ClassLoader不为null 和为null两个部分

synchronized void loadLibrary0(ClassLoader loader, String libname) {
	    //判断传入的库名称是否合法,比如我们的库是libxxx.so,我们只需要传入xxx就可以了
        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) {//如果类加载器不为空
            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;
        }

	//下面这些代码是类加载器为空的情况下才执行,正常情况下开发者app中开发者自己写的库文件加载时不会执行到这里
	//因为传入的类加载器不会为空,系统应用才有可能走这里,这时下面获取系统默认的库存放路径才是有用的
        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : getLibPaths()) {//getLibPaths()用来获取系统中存放so库的文件路径,下面有相关的实现代码和解释
            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);
    }

private String[] getLibPaths() {
        if (mLibPaths == null) {
            synchronized(this) {
                if (mLibPaths == null) {
                    mLibPaths = initLibPaths();//调用initLibPaths方法
                }
            }
        }
        return mLibPaths;
    }
  private static String[] initLibPaths() {
	//可以看出系统默认的库文件存放路径是在java.library.path属性中存储的
        String javaLibraryPath = System.getProperty("java.library.path");
        if (javaLibraryPath == null) {
            return EmptyArray.STRING;
        }
        String[] paths = javaLibraryPath.split(":");
        // Add a '/' to the end of each directory so we don't have to do it every time.
        for (int i = 0; i < paths.length; ++i) {
            if (!paths[i].endsWith("/")) {
                paths[i] += "/";
            }
        }
        return paths;
    }

  • 如果ClassLoader为null
    • 遍历getLibPaths()方法,该方法会返回"java.library.path"选项胚子的路径数组
    • 拼接出完整的so路径
    • 调用doLoad方法

打印"java.library.path",得到 pathList = /vendor/lib:/system/lib

  • 如果ClassLoader不为null
    • 调用loader的findLibrary方法得到完整路径
    • 调用doLoad方法

loader的findLibrary方法

常规开发的应用基本走的都是loader的findLibrary方法得到完整路径,android系统中的 ClassLoader真正的实现 在dalvik的dalvik.system.PathClassLoader。

打开libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java来看 PathClassLoader这个class 的实现,可以看到,就只是简单的继承 BaseDexClassLoader而已,没有任何实际的内容

public class BaseDexClassLoader extends ClassLoader {

     public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);

        this.originalPath = dexPath;
        this.originalLibraryPath = libraryPath;
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    
    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }
}

DexPathList的构造函数

然后是DexPathList的构造函数:

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }

        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }

        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }

            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }

        this.definingContext = definingContext;
        this.dexElements =
            makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        //【关注splitLibraryPath(),这个地方,实际上即是把传进来的libraryPath 又丢给splitLibraryPath来获取library path 的list】
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }
 
 //在用两个部分的library path list来由splitPaths构造最终的那个path list
 //一个部分是,传进来的library path
 //另外一个部分是,像我们前面看到的那个,是system property
 private static File[] splitLibraryPath(String path) {
        ArrayList<File> result = splitPaths(
                path, System.getProperty("java.library.path", "."), true);
        return result.toArray(new File[result.size()]);
    }

ClassLoader的那个findLibrary()实际上会在两个部分的folder中去寻找System.loadLibrary()要load的那个library,一个部分是,构造ClassLoader时,传进来的那个library path,即是app folder,另外一个部分是system property。在android系统中,查找要load的library,实际上会在如下3个folder中进行:

  1. /vendor/lib
  2. /system/lib
  3. /data/app-lib/com.qrcode.qrcode-1

DexPathList的findLibrary

public String findLibrary(String libraryName) {  
    //这个方法主要目的是给传进来的name添加前缀lib和后缀.so
    //最开始我们传进来的name是TestJni,所以此处处理后返回的是libTestJni.so.
    String fileName = System.mapLibraryName(libraryName);  
  
    for (Element element : nativeLibraryPathElements) {  
        String path = element.findNativeLibrary(fileName);  
  
        if (path != null) {  
            return path;  
        }  
    }  
  
    return null;  
}  

类似余DexPath的findClass方法:

  1. 每一个Element 对应一个so库
    1. findNativeLibrary可以返回so的路径
  2. nativeLibraryPathElements数组在构建DexPath时初始化

这里就提供了一种so修复的方案,就是将so补丁插入到nativeLibraryPathElements数组的前部

nativeLoaded方法

libcore/ojluni/src/main/native/Runtime.c中:

JNIEXPORT jstring JNICALL  
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,  
                   jobject javaLoader, jstring javaLibrarySearchPath)  
{  
    return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);  
} 

./art/runtime/openjdkjvm/OpenjdkJvm.cc中的JVM_NativeLoad:

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,  
                                 jstring javaFilename,  
                                 jobject javaLoader,  
                                 jstring javaLibrarySearchPath) {  
  //so文件名转化为ScopedUtfChars
  ScopedUtfChars filename(env, javaFilename);  
  if (filename.c_str() == NULL) {  
    return NULL;  
  }  
   
  std::string error_msg;  
  {  
    //获取当前云高兴的虚拟机
    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();  
    //虚拟机加载so
    bool success = vm->LoadNativeLibrary(env,  
                                         filename.c_str(),  
                                         javaLoader,  
                                         javaLibrarySearchPath,  
                                         &error_msg);  
    if (success) {  
      return nullptr;  
    }  
  }  

LoadNativeLibrary

  1. 判断是否加载过so,两次ClassLoader是否是同一个,避免重复加载
  2. 打开so,并得到so的举兵,失败就返回false。—>创建新的SharedLibrary,如果传入的path对应的library是空指针,就将新的SharedLibrary赋值给library存储到libraries中
  3. 查找JNI_OnLoad的函数指针,根据不同情况设置 was_successful的值,最终返回该值
    在这里插入图片描述

LoadNativeLibrary的part1

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,  
                                  const std::string& path,  
                                  jobject class_loader,  
                                  jstring library_path,  
                                  std::string* error_msg) {  
  error_msg->clear();  
   
  SharedLibrary* library;  
  Thread* self = Thread::Current();  
  {  
    MutexLock mu(self, *Locks::jni_libraries_lock_);  
    //【根据so的名称,从 libraries_获取对应的SharedLibrary指针】
    library = libraries_->Get(path);  
  }  
  。。。。。。。
  if (library != nullptr) {    //【标示此前加载过该so】
    // Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.  
    if (library->GetClassLoaderAllocator() != class_loader_allocator) {  //【如果两次传入的ClassLoader不同】
      StringAppendF(error_msg, "Shared library \"%s\" already opened by "  
          "ClassLoader %p; can't open in ClassLoader %p",  
          path.c_str(), library->GetClassLoader(), class_loader);  
      LOG(WARNING) << error_msg;  
      return false;  
    }  
    VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "  
              << " ClassLoader " << class_loader << "]";  
    if (!library->CheckOnLoadResult()) {  //【判断上次加载so的结果,如果有异常也返回so】
      StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "  
          "to load \"%s\"", path.c_str());  
      return false;  
    }  
    return true;  //【不再重复加载so】
  }  

LoadNativeLibrary的part2

  Locks::mutator_lock_->AssertNotHeld(self);  
  const char* path_str = path.empty() ? nullptr : path.c_str();  
  /**
  * 【1. 打开路径 path_str的so库,得到so的句柄handle】
  */
  void* handle = android::OpenNativeLibrary(env,  
                                            runtime_->GetTargetSdkVersion(),  
                                            path_str,  
                                            class_loader,  
                                            library_path);  
   
  bool needs_native_bridge = false;  
  if (handle == nullptr) {  
    if (android::NativeBridgeIsSupported(path_str)) {  
      handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);  
      needs_native_bridge = true;  
    }  
  }  
   
  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";  
 /**
  * 【2,失败则返回中断】
  */
  if (handle == nullptr) {  
    *error_msg = dlerror();  
    VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;  
    return false;  
  }  
   
  if (env->ExceptionCheck() == JNI_TRUE) {  
    LOG(ERROR) << "Unexpected exception:";  
    env->ExceptionDescribe();  
    env->ExceptionClear();  
  }  

  bool created_library = false;  
  {  
    /**
    * 【3 创建 SharedLibrary,并将so句柄作为参数传入】
   */
    // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.  
    std::unique_ptr<SharedLibrary> new_library(  
        new SharedLibrary(env, self, path, handle, class_loader, class_loader_allocator));  
    MutexLock mu(self, *Locks::jni_libraries_lock_);  
    library = libraries_->Get(path);  
    //【4. 获取传入path对应的library,如果为空指针,就将新的SharedLibrary赋值给library存储到ibraries_中】
    if (library == nullptr) {  // We won race to get libraries_lock.  
      library = new_library.release();  
      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;  
    return library->CheckOnLoadResult();  
  }  
  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";  

LoadNativeLibrary的part3

   bool was_successful = false;  
  void* sym;  
  if (needs_native_bridge) {  
    library->SetNeedsNativeBridge();  
  }  
  /**
  * 【1 查找JNI_OnLoad函数指针(该函数朱永用于native方法的动态注册),并复制俄格sym】
  */
  sym = library->FindSymbol("JNI_OnLoad", nullptr);  
  if (sym == nullptr) {  
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";  
    //【2 没找到对应函数,直接标识为成功】
    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);  
    int version = (*jni_on_load)(this, nullptr);  
   
    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {  
      fault_manager.EnsureArtActionInFrontOfSignalChain();  
    }  
   
    self->SetClassLoaderOverride(old_class_loader.get());  
   
    if (version == JNI_ERR) {  
      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());  
    } else if (IsBadJniVersion(version)) {  
      StringAppendF(error_msg, "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;  

参考文献

猜你喜欢

转载自blog.csdn.net/fei20121106/article/details/85004604
JNI