前语
so库的加载可是咱们日常开发都会用到的,因而系统也提供了十分便利的api给咱们进行调用
System.loadLibrary(xxxso);
当然,随着版别的改变,loadLibrary也是呈现了十分大的改变,最重要的是分水岭是androidN加入了namespace机制,可能很多人都是一头雾水噢!这是个啥?咱们在动态so加载计划中,会频频呈现这个名词,一起还有一个高频的词便是Linker,本期不触及杂乱的技术计划,咱们就来深入聊聊,Linker的概念,与namespace机制的加入,希望能协助更多开发者去了解so的加载进程。
Linker
咱们都知道,Linux渠道下有动态链接文件(.so)与静态链接文件(.a),两者其实都是一种ELF文件(相关的文件格式咱们不赘述)。为什么会有这么两种文件呢?咱们就从简单的角度来想一次,其实便是为了更多的代码复用,比如程序1,程序2都用到了同一个东西,比如xx.so
此刻就会呈现,程序1与程序2中调用fun common的地方,在没有链接之前,调用处的地址,咱们以“stub”,表明这其实是一个未确定的东西,而后续的这个地址填充(写入正确的common地址),其实便是Linker的责任。
咱们经过上面的例子,其实就能够理解,Linker,首要的责任,便是协助查找当时程序所依靠的动态库文件(ELF文件)。那么Linker自身是个什么呢,其实他跟.so文件都是同一种格式,也是ELF文件,那么Linker又由谁协助加载发动呢,这儿就会呈现存在一个(鸡生蛋,蛋生鸡)的问题,而ELF文件给出的答案便是,建立一个:interp 的段,当一个进程发动的时分(linux中经过execv发动),此刻就会经过load_elf_binary函数,先加载ELF文件,然后再调用load_elf_interp办法,直接加载了:interp 段地址的起点,从而能够构建咱们的大管家Linker,当然,Linker自身就不能像一般的so文件一样,去依靠另一个so,其实原因也很简单,没人帮他初始化呀!因而Linker是选用配置的办法先发动起来了!
当然,咱们首要的目标是建立概念,Linker自身触及的杂乱加载,咱们也不继续贴出来了
NameSpace
在以往的anroidN以下版别中,加载so库通常是直接选用dlopen的办法去直接加载的,对于非揭露的符号,假如被运用,就容易在之后迭代呈现问题,(相似java,运用了一个三方库的private办法,假如后续变更办法含义,就会呈现问题),因而引入了NameSpace机制
Android 7.0 为原生库引入了命名空间,以限制内部 API 可见性并解决运用意外运用渠道库而不是自己的渠道库的状况。
咱们说的NameSpace,首要对应着一个数据结构android_namespace_link_t
linker_namespaces.h
struct android_namespace_link_t
private:
std::string name_; namespace称号
bool is_isolated_; 是否隔离(大部分是true)
std::vector<std::string> ld_library_paths_; 链接途径
std::vector<std::string> default_library_paths_;默认可拜访途径
std::vector<std::string> permitted_paths_;已答应拜访途径
....
咱们来看一看,这个数据结构在哪里会被运用到,其实便是so库加载进程。当咱们调用System.loadLibrary的时分,其实终究调用的是
private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
文件名校验
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
// Android-note: BootClassLoader doesn't implement findLibrary(). http://b/111850480
// Android's class.getClassLoader() can return BootClassLoader where the RI would
// have returned null; therefore we treat BootClassLoader the same as null here.
if (loader != null && !(loader instanceof BootClassLoader)) {
String filename = loader.findLibrary(libraryName);
if (filename == null &&
(loader.getClass() == PathClassLoader.class ||
loader.getClass() == DelegateLastClassLoader.class)) {
// Don't give up even if we failed to find the library in the native lib paths.
// The underlying dynamic linker might be able to find the lib in one of the linker
// namespaces associated with the current linker namespace. In order to give the
// dynamic linker a chance, proceed to load the library with its soname, which
// is the fileName.
// Note that we do this only for PathClassLoader and DelegateLastClassLoader to
// minimize the scope of this behavioral change as much as possible, which might
// cause problem like b/143649498. These two class loaders are the only
// platform-provided class loaders that can load apps. See the classLoader attribute
// of the application tag in app manifest.
filename = System.mapLibraryName(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 = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
// We know some apps use mLibPaths directly, potentially assuming it's not null.
// Initialize it here to make sure apps see a non-null value.
getLibPaths();
String filename = System.mapLibraryName(libraryName);
终究调用nativeLoad
String error = nativeLoad(filename, loader, callerClass);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
这儿咱们留意到,抛出UnsatisfiedLinkError的时机,要么so文件名加载不合法,要么便是nativeLoad办法返回了错误信息,这儿是需要咱们留意的,咱们假如呈现这个异常,能够从这儿排查,nativeLoad办法终究经过LoadNativeLibrary,在native层真正进入so的加载进程
LoadNativeLibrary 十分长,咱们截取部分
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jclass caller_class,
std::string* error_msg) {
会判别是否现已加载过当时so,一起也要加锁,因为存在多线程加载的状况
SharedLibrary* library;
Thread* self = Thread::Current();
{
// TODO: move the locking (and more of this logic) into Libraries.
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
}
调用OpenNativeLibrary加载
void* handle = android::OpenNativeLibrary(
env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
(caller_location.empty() ? nullptr : caller_location.c_str()),
library_path.get(),
&needs_native_bridge,
&nativeloader_error_msg);
这儿又是漫长的native办法,OpenNativeLibrary,在这儿咱们终于见到namespace了
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
jobject class_loader, const char* caller_location, jstring library_path,
bool* needs_native_bridge, char** error_msg) {
#if defined(ART_TARGET_ANDROID)
UNUSED(target_sdk_version);
if (class_loader == nullptr) {
*needs_native_bridge = false;
if (caller_location != nullptr) {
android_namespace_t* boot_namespace = FindExportedNamespace(caller_location);
if (boot_namespace != nullptr) {
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = boot_namespace,
};
终究调用android_dlopen_ext翻开
void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
}
// Check if the library is in NATIVELOADER_DEFAULT_NAMESPACE_LIBS and should
// be loaded from the kNativeloaderExtraLibs namespace.
{
Result<void*> handle = TryLoadNativeloaderExtraLib(path);
if (!handle.ok()) {
*error_msg = strdup(handle.error().message().c_str());
return nullptr;
}
if (handle.value() != nullptr) {
return handle.value();
}
}
// Fall back to the system namespace. This happens for preloaded JNI
// libraries in the zygote.
// TODO(b/185833744): Investigate if this should fall back to the app main
// namespace (aka anonymous namespace) instead.
void* handle = OpenSystemLibrary(path, RTLD_NOW);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
std::lock_guard<std::mutex> guard(g_namespaces_mutex);
NativeLoaderNamespace* ns;
触及到了namespace,假如当时classloader没有,则创立,可是这归于异常状况
if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
// This is the case where the classloader was not created by ApplicationLoaders
// In this case we create an isolated not-shared namespace for it.
Result<NativeLoaderNamespace*> isolated_ns =
CreateClassLoaderNamespaceLocked(env,
target_sdk_version,
class_loader,
/*is_shared=*/false,
/*dex_path=*/nullptr,
library_path,
/*permitted_path=*/nullptr,
/*uses_library_list=*/nullptr);
if (!isolated_ns.ok()) {
*error_msg = strdup(isolated_ns.error().message().c_str());
return nullptr;
} else {
ns = *isolated_ns;
}
}
return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
这儿咱们打断一下,咱们看到上面代码剖析,假如当时classloader的namespace假如为null,则创立,这儿咱们也知道一个信息,namespace是跟classloader绑定的。一起咱们也知道,classloader在创立的时分,其实就会绑定一个namespace。咱们在app加载的时分,就会经过LoadedApk这个class去加载一个pathclassloader
frameworks/base/core/java/android/app/LoadedApk.java
if (!mIncludeCode) {
if (mDefaultClassLoader == null) {
StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
"" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
null /* classLoaderName */);
setThreadPolicy(oldPolicy);
mAppComponentFactory = AppComponentFactory.DEFAULT;
}
if (mClassLoader == null) {
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
new ApplicationInfo(mApplicationInfo));
}
return;
}
之后ApplicationLoaders.getDefault().getClassLoader会调用createClassLoader
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
List<ClassLoader> sharedLibrariesAfter) {
final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
classLoaderName, sharedLibraries, sharedLibrariesAfter);
String sonameList = "";
if (nativeSharedLibraries != null) {
sonameList = String.join(":", nativeSharedLibraries);
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
这儿就讲上述的属性传入,创立了一个归于该classloader的namespace
String errorMessage = createClassloaderNamespace(classLoader,
targetSdkVersion,
librarySearchPath,
libraryPermittedPath,
isNamespaceShared,
dexPath,
sonameList);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (errorMessage != null) {
throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
classLoader + ": " + errorMessage);
}
return classLoader;
}
这儿咱们得到的首要音讯是,咱们的classloader的namespace,里边的so检索途径,其实都在创立的时分就被定下来了(这个也是,为什么想要实现so动态加载,其间的一个计划便是替换classloader的原因,因为咱们当时运用的classloader的namespace检索途径,现已是固定了,后续对classloader自身的检索途径增加,是不会同步给namespace的,只要创立的时分才会同步)
好了,咱们继续回到OpenNativeLibrary,内部其实调用android_dlopen_ext翻开
void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) {
const void* caller_addr = __builtin_return_address(0);
return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}
这儿不知道大家有没有觉得眼熟,这儿肯定终究调用便是dlopen,只不过谷歌为了限制dlopen的调起方,选用了__builtin_return_address 内建函数作为卡口,限制了一般app调哟dlopen(这儿也是有破解办法的)
之后的阅历android_dlopen_ext -> dlopen_ext ->do_dlopen,终究到了最终加载的办法了
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);
ScopedTrace trace(trace_prefix.c_str());
ScopedTrace loading_trace((trace_prefix + " - loading and linking").c_str());
soinfo* const caller = find_containing_library(caller_addr);
// 找到调用者,归于哪个namespace
android_namespace_t* ns = get_caller_namespace(caller);
...
ProtectedDataGuard guard;
之后便是在namespace的加载列表找library的进程了
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
....
return nullptr;
}
总结
最终咱们先总结一下,Linker作用跟NameSpace的调用流程,能够发现其实内部十分杂乱,可是咱们捉住主干去看,NameSpace其实作用的功用,也便是标准了查找so的进程,需要在指定列表查找。
上篇就到此结束,咱们下篇再会