日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

so加載Linker跟NameSpace機制詳解_Android

作者:Pika ? 更新時間: 2023-03-05 編程語言

前言

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的過程,需要在指定列表查找。

原文鏈接:https://juejin.cn/post/7188486895570321468

欄目分類
最近更新