Android源码学习——linker(1)
来源:互联网 发布:java编写cad绘图工具 编辑:程序博客网 时间:2024/05/20 15:42
本文学习的源码参考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<1> 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<1> hs(self); Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader.Get()); int version = 0; { ScopedThreadStateChange tsc(self, kNative); VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; 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(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)) != 0) { DL_ERR("invalid flags to dlopen: %x", flags); return nullptr; } if (extinfo != nullptr) { if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) { DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags); return nullptr; } if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) { 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段,完成一些初始化的操作;
最后,将内存改回可读。
- Android源码学习——linker(1)
- Android源码学习——linker(3)
- Android源码学习——linker(2)
- Android源码学习——linker(4)
- 《Android系统学习》第六章:Android4.1 HAL段错误问题—linker与prelink
- 《Android系统学习》第六章:Android4.1 HAL段错误问题—linker与prelink
- 《Android系统学习》第六章:Android4.1 HAL段错误问题—linker与prelink
- Android Linker学习笔记
- Android Linker学习笔记
- Android Linker学习笔记
- Android Linker学习笔记
- ELF Linker学习篇(四)——初始化
- K60学习笔记3——Linker
- Android源码学习——ClassLoader(1)
- Android源码学习1
- BCB初学之常见错误1——Linker错误
- 错误分析—linker command failed with exit code 1
- android-jamendo源码学习(一)——SplashscreenActivity
- 菜鸟新手的知识汲取(3)JAVA基础之方法
- G
- caffe2 C++接口predictor测试demo.
- practice makes perfect
- 内存池、进程池、线程池
- Android源码学习——linker(1)
- Oracle如何实现定时操作
- [暑假专题]DP&贪心 小结(更新中)
- 博弈小节 NIM SG BASH GAME Wythoff Game
- Codeforces 838E IndiaHacks 2nd Elimination 2017 mirror Convex Countour :毛线DP啊
- Java菜鸟学习日记26
- Android apk签名
- 服务器验证
- 使用gulp-jshint遇到的小坑