2024年HarmonyOS鸿蒙最全so加载 - Linker跟NameSpace知识 (上篇)_dlopen namespace,2024年最新面试的知识点有哪些

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

我们通过上面的例子,其实就可以明白,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::vectorstd::string ld_library_paths_; 链接路径
std::vectorstd::string default_library_paths_;默认可访问路径
std::vectorstd::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_guardstd::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

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

2698)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值