本文主要讲解下Android 9.0 System.loadLibrary
的源码实现。
libcore/ojluni/src/main/java/java/lang/System.java
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
//将libname加上前缀和后缀,即`lib.so`
public static native String mapLibraryName(String libname);
System 的 loadLibrary 最终调用的是 Runtime 中的 loadLibrary0 方法。 mapLibraryName 方法主要是给 libname 加上 “lib” 的前缀和 “.so” 的后缀。所以我们加载 so 库时,不用带 “lib” 和 “.so” 的字符串。
libcore/ojluni/src/main/native/System.c
static void cpchars(jchar *dst, char *src, int n)
{
int i;
for (i = 0; i < n; i++) {
dst[i] = src[i];
}
}
JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
int len;
int prefix_len = (int) strlen(JNI_LIB_PREFIX); //JNI_LIB_PREFIX="lib"
int suffix_len = (int) strlen(JNI_LIB_SUFFIX); //JNI_LIB_SUFFIX=".so"
jchar chars[256];
if (libname == NULL) {
JNU_ThrowNullPointerException(env, 0);
return NULL;
}
len = (*env)->GetStringLength(env, libname);
if (len > 240) {
JNU_ThrowIllegalArgumentException(env, "name too long");
return NULL;
}
cpchars(chars, JNI_LIB_PREFIX, prefix_len); //chars="lib"
(*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);//chars="lib"
len += prefix_len;
cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len); //chars="lib"".so"
len += suffix_len;
return (*env)->NewString(env, chars, len);
}
libcore/ojluni/src/main/java/java/lang/Runtime.java
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) { //loader 不为空就,调用的 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) + "\"");
}
//filename = /data/app/-xyz==/lib/arm64/lib.so
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
//filename=lib.so
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
//getLibPaths()=/system/lib64/
for (String directory : getLibPaths()) {
//candidate=/system/lib64/lib.so
String candidate = directory + filename;
candidates.add(candidate);
//如果存在/system/lib64/lib.so就加载so
if (IoUtils.canOpenReadOnly(candidate)) {
String error = nativeLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
//如果都没有找到so就抛出异常。
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
private volatile String[] mLibPaths = null;
private String[] getLibPaths() {
if (mLibPaths == null) {
synchronized(this) {
if (mLibPaths == null) {
mLibPaths = initLibPaths();
}
}
}
return mLibPaths;
}
private static String[] initLibPaths() {
String javaLibraryPath = System.getProperty("java.library.path"); //java.library.path="/system/lib64"
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;
}
loadLibrary0 在加载 so 时,先判断 loader 是否为 null 。如果不为 null,就通过 loader 去找到 so 的绝对路径,然后再加载。如果为 null,就从 /system/lib64/ 中去找是否存在要加载的 so 。如果都没有找到就抛出异常。下面分析下 loader.findLibrary 方法。loader 集成自 BaseDexClassLoader。所以它最终调用的 BaseDexClassLoader 的 findLibrary 方法。
libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
BaseDexClassLoader 的 findLibrary 方法最终由 DexPathList 的 findLibrary 实现。而 DexPathList 的 findLibrary 是在 nativeLibraryPathElements 中遍历查找 存在的 so。nativeLibraryPathElements 的值来自 librarySearchPath 和 System.getProperty(“java.library.path”) 。librarySearchPath 主要是 /data/app/
的路径。System.getProperty(“java.library.path”) 为 /system/lib64/
。所以 DexPathList 是在这几个目录下查找 so 是否存在。如果存在就返回其绝对路径即可。
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
...
//librarySearchPath="/data/app/-xyz==/lib/arm64:/data/app/-xyz==/base.apk!/lib/arm64-v8a"
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true); //"java.library.path"="/system/lib64"
List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
...
}
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
List<File> result = new ArrayList<>();
if (searchPath != null) {
for (String path : searchPath.split(File.pathSeparator)) {
if (directoriesOnly) {
try {
StructStat sb = Libcore.os.stat(path);
if (!S_ISDIR(sb.st_mode)) {
continue;
}
} catch (ErrnoException ignored) {
continue;
}
}
result.add(new File(path));
}
}
return result;
}
private static NativeLibraryElement[] makePathElements(List<File> files) {
NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
int elementsPos = 0;
for (File file : files) {
String path = file.getPath();
if (path.contains(zipSeparator)) { //zipSeparator="!/"
String split[] = path.split(zipSeparator, 2);
File zip = new File(split[0]);
String dir = split[1];
elements[elementsPos++] = new NativeLibraryElement(zip, dir);
} else if (file.isDirectory()) {
// We support directories for looking up native libraries.
elements[elementsPos++] = new NativeLibraryElement(file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (NativeLibraryElement element : nativeLibraryPathElements) {
String path = element.findNativeLibrary(fileName);
if (path != null) {
return path;
}
}
return null;
}
static class NativeLibraryElement {
public NativeLibraryElement(File dir) {
this.path = dir;
this.zipDir = null;
}
public NativeLibraryElement(File zip, String zipDir) {
this.path = zip;
this.zipDir = zipDir;
if (zipDir == null) {
throw new IllegalArgumentException();
}
}
public synchronized void maybeInit() {
if (initialized) {
return;
}
if (zipDir == null) {
initialized = true;
return;
}
try {
urlHandler = new ClassPathURLStreamHandler(path.getPath());
} catch (IOException ioe) {
System.logE("Unable to open zip file: " + path, ioe);
urlHandler = null;
}
initialized = true;
}
public String findNativeLibrary(String name) {
maybeInit();
if (zipDir == null) {
String entryPath = new File(path, name).getPath();
if (IoUtils.canOpenReadOnly(entryPath)) {
return entryPath;
}
} else if (urlHandler != null) {
String entryName = zipDir + '/' + name;
if (urlHandler.isEntryStored(entryName)) {
return path.getPath() + zipSeparator + entryName;
}
}
return null;
}
}
找到 so 的绝对路径后就是加载 so 了。so 的加载是通过 nativeLoad 方法实现的。java 层的 nativeLoad 对应的就是 c 层的 Runtime_nativeLoad 方法。
libcore/ojluni/src/main/native/Runtime.c
#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, (void*)(className ## _ ## functionName) }
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader)
{
return JVM_NativeLoad(env, javaFilename, javaLoader);
}
static JNINativeMethod gMethods[] = {
FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
NATIVE_METHOD(Runtime, gc, "()V"),
NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
NATIVE_METHOD(Runtime, nativeLoad,
"(Ljava/lang/String;Ljava/lang/ClassLoader;)"
"Ljava/lang/String;"),
//{"nativeLoad", "(Ljava/lang/String;Ljava/lang/ClassLoader;)"
"Ljava/lang/String;", (void*)Runtime_nativeLoad}
};
void register_java_lang_Runtime(JNIEnv* env) {
jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}
上面代码可以看出 Runtime_nativeLoad 调用的是 JVM_NativeLoad 方法。而 JVM_NativeLoad 真正实现so 的加载是在 vm->LoadNativeLibrary 方法中。
art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
&error_msg);
if (success) {
return nullptr;
}
}
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
std::string* error_msg) {
...
// 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.
// Retrieve the library path from the classloader, if necessary.
ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));
Locks::mutator_lock_->AssertNotHeld(self);
const char* path_str = path.empty() ? nullptr : path.c_str();
bool needs_native_bridge = false;
void* handle = android::OpenNativeLibrary(env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
library_path.get(),
&needs_native_bridge,
error_msg);
VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
if (handle == nullptr) {
VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
return false;
}
if (env->ExceptionCheck() == JNI_TRUE) {
LOG(ERROR) << "Unexpected exception:";
env->ExceptionDescribe();
env->ExceptionClear();
}
// Create a new entry.
// TODO: move the locking (and more of this logic) into Libraries.
bool created_library = false;
{
// Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
std::unique_ptr<SharedLibrary> new_library(
new SharedLibrary(env,
self,
path,
handle,
needs_native_bridge,
class_loader,
class_loader_allocator));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
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 << "]";
bool was_successful = false;
void* 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);
int version = (*jni_on_load)(this, nullptr);
if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
// Make sure that sigchain owns SIGSEGV.
EnsureFrontOfChain(SIGSEGV);
}
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 (JavaVMExt::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;
}
class SharedLibrary {
void SetNeedsNativeBridge(bool needs) {
needs_native_bridge_ = needs;
}
bool NeedsNativeBridge() const {
return needs_native_bridge_;
}
// No mutator lock since dlsym may block for a while if another thread is doing dlopen.
void* FindSymbol(const std::string& symbol_name, const char* shorty = nullptr)
REQUIRES(!Locks::mutator_lock_) {
return NeedsNativeBridge()
? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty)
: FindSymbolWithoutNativeBridge(symbol_name.c_str());
}
// No mutator lock since dlsym may block for a while if another thread is doing dlopen.
void* FindSymbolWithoutNativeBridge(const std::string& symbol_name)
REQUIRES(!Locks::mutator_lock_) {
CHECK(!NeedsNativeBridge());
return dlsym(handle_, symbol_name.c_str());
}
void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty)
REQUIRES(!Locks::mutator_lock_) {
CHECK(NeedsNativeBridge());
uint32_t len = 0;
return android::NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len);
}
}
LoadNativeLibrary 最终会通过 android::OpenNativeLibrary 去加载 so 库。然后判断 JNI_OnLoad 方法是否存在。如果存在就调用其方法。这也就是为什么我们在做 jni 开发时,要实现 JNI_OnLoad 方法来做一些初始化的操作。下面列出 OpenNativeLibrary 的源码实现,不在深入分析。现在新版本的加入了名字空间的概念,通过它来实现系统的私有 so 库,不被第三方加载。加载so不像之前的是用dlopen去加载 so 了。
system/core/libnativeloader/native_loader.cpp
void* OpenNativeLibrary(JNIEnv* env,
int32_t target_sdk_version,
const char* path,
jobject class_loader,
jstring library_path,
bool* needs_native_bridge,
std::string* error_msg) {
#if defined(__ANDROID__)
UNUSED(target_sdk_version);
if (class_loader == nullptr) {
*needs_native_bridge = false;
return dlopen(path, RTLD_NOW);
}
std::lock_guard<std::mutex> guard(g_namespaces_mutex);
NativeLoaderNamespace ns;
if (!g_namespaces->FindNamespaceByClassLoader(env, class_loader, &ns)) {
// This is the case where the classloader was not created by ApplicationLoaders
// In this case we create an isolated not-shared namespace for it.
if (!g_namespaces->Create(env,
target_sdk_version,
class_loader,
false /* is_shared */,
false /* is_for_vendor */,
library_path,
nullptr,
&ns,
error_msg)) {
return nullptr;
}
}
if (ns.is_android_namespace()) {
android_dlextinfo extinfo;
extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
extinfo.library_namespace = ns.get_android_ns();
void* handle = android_dlopen_ext(path, RTLD_NOW, &extinfo);
if (handle == nullptr) {
*error_msg = dlerror();
}
*needs_native_bridge = false;
return handle;
} else {
void* handle = NativeBridgeLoadLibraryExt(path, RTLD_NOW, ns.get_native_bridge_ns());
if (handle == nullptr) {
*error_msg = NativeBridgeGetError();
}
*needs_native_bridge = true;
return handle;
}
#else
UNUSED(env, target_sdk_version, class_loader);
// Do some best effort to emulate library-path support. It will not
// work for dependencies.
//
// Note: null has a special meaning and must be preserved.
std::string c_library_path; // Empty string by default.
if (library_path != nullptr && path != nullptr && path[0] != '/') {
ScopedUtfChars library_path_utf_chars(env, library_path);
c_library_path = library_path_utf_chars.c_str();
}
std::vector<std::string> library_paths = base::Split(c_library_path, ":");
for (const std::string& lib_path : library_paths) {
*needs_native_bridge = false;
const char* path_arg;
std::string complete_path;
if (path == nullptr) {
// Preserve null.
path_arg = nullptr;
} else {
complete_path = lib_path;
if (!complete_path.empty()) {
complete_path.append("/");
}
complete_path.append(path);
path_arg = complete_path.c_str();
}
void* handle = dlopen(path_arg, RTLD_NOW);
if (handle != nullptr) {
return handle;
}
if (NativeBridgeIsSupported(path_arg)) {
*needs_native_bridge = true;
handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
if (handle != nullptr) {
return handle;
}
*error_msg = NativeBridgeGetError();
} else {
*error_msg = dlerror();
}
}
return nullptr;
#endif
}
System.loadLibrary
的源码分析基本完成了。现在我们来重新整理下它们的调用关系。
System.loadLibrary
Runtime.loadLibrary0
Runtime_nativeLoad
JVM_NativeLoad
LoadNativeLibrary
OpenNativeLibrary
Android 7.0 开始,禁止加载非NDK库,也就是说系统禁止了应用去链接系统的私有库。它通过名字空间的方式来实现其方法。所以就看到了,我们加载 so 的时候是用 OpenNativeLibrary 方法,而不是以往的 dlopen 方法。