Android system — 链接器命名空间共享库配置方法(Android 11后)

Android system — 链接器命名空间共享库配置方法

  • 1. 应用进程
    • 1.1 应用进程类加载器的命名空间初始化
      • 1.1.1 OpenNativeLibrary
      • 1.1.2 LibraryNamespaces::Create
    • 1.2 配置共享库位置
  • 2. native进程
    • 2.1 native 命名空间配置初始化
      • 2.1.1 android_namespace_t::is_accessible
      • 2.1.2 init_default_namespaces
    • 2.2 配置共享库位置

1. 应用进程

1.1 应用进程类加载器的命名空间初始化

  在应用程序对应的可执行文件app_process根据/linkerconfig/ld.config.txt配置文件初始化命名空间之后,每当应用程序创建一个类加载器classloader并调用System.loadLibrary加载so库时都会创建一个与此类加载器对应的命名空间。从源码角度分析一下这个过程,System.loadLibrary函数最后会调用OpenNativeLibrary函数。

1.1.1 OpenNativeLibrary

System.loadLibrary()-->nativeLoad()-->Runtime.c::Runtime_nativeLoad()-->JVM_NativeLoad()-->Openjdkjvm.cc::JVM_NativeLoad()-->java_vm_ext.cc::LoadNativeLibrary()-->native_loader.cpp::OpenNativeLibrary() 也就是java层的System.loadLibrary()最终会调用libnativeloader.so的OpenNativeLibrary函数。

  • OpenNativeLibrary先判断classloader类加载器是否为空,如果为空直接调用android_dlopen_ext加载库文件
  • 如果判断classloader类加载器不为空,并且classloader类加载器没有对应的命名空间(第一次调用System.loadLibrary)就调用LibraryNamespaces::Create创建新的命名空间。
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) { //如果类加载器为空直接调用android_dlopen_ext加载库文件*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,};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;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);

CreateClassLoaderNamespaceLocked中我们可以看到调用了LibraryNamespaces::Create创建新的命名空间


Result<NativeLoaderNamespace*> CreateClassLoaderNamespaceLocked(JNIEnv* env,int32_t target_sdk_version,jobject class_loader,bool is_shared,jstring dex_path,jstring library_path,jstring permitted_path,jstring uses_library_list)REQUIRES(g_namespaces_mutex) {Result<NativeLoaderNamespace*> ns = g_namespaces->Create(env,target_sdk_version,class_loader,is_shared,dex_path,library_path,permitted_path,uses_library_list);if (!ns.ok()) {return ns;}Result<void> linked = CreateNativeloaderDefaultNamespaceLibsLink(*ns.value());if (!linked.ok()) {return linked.error();}return ns;
}

1.1.2 LibraryNamespaces::Create

  • 先调用android_create_namespace创建一个clns命名空间
  • 调用android_linker_namespace设置新创建的命名空间链接到system、APEX和vendor等命名空间
  • 设置链接到system、APEX等命名空间的共享库

Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,jobject class_loader, bool is_shared,jstring dex_path_j,jstring java_library_path,jstring java_permitted_path,jstring uses_library_list) {std::string library_path;  // empty string by default.std::string dex_path;......// Create the app namespaceNativeLoaderNamespace* parent_ns = FindParentNamespaceByClassLoader(env, class_loader);// Heuristic: the first classloader with non-empty library_path is assumed to// be the main classloader for app// TODO(b/139178525) remove this heuristic by determining this in LoadedApk (or its// friends) and then passing it down to here.bool is_main_classloader = app_main_namespace_ == nullptr && !library_path.empty();// Policy: the namespace for the main classloader is also used as the// anonymous namespace.bool also_used_as_anonymous = is_main_classloader;// Note: this function is executed with g_namespaces_mutex held, thus no// racing here.//创建命名空间,namespace_name为kClassloaderNamespaceName = "clns"auto app_ns = NativeLoaderNamespace::Create(namespace_name, library_path, permitted_path, parent_ns, is_shared,target_sdk_version < 24 /* is_exempt_list_enabled */, also_used_as_anonymous);if (!app_ns.ok()) {return app_ns.error();}// ... and link to other namespaces to allow access to some public librariesbool is_bridged = app_ns->IsBridged();auto system_ns = NativeLoaderNamespace::GetSystemNamespace(is_bridged);if (!system_ns.ok()) {return system_ns.error();}// 新创建的命名空间链接到system,共享库设置为system_exposed_librariesauto linked = app_ns->Link(&system_ns.value(), system_exposed_libraries);if (!linked.ok()) {return linked.error();}for (const auto&[apex_ns_name, public_libs] : apex_public_libraries()) {auto ns = NativeLoaderNamespace::GetExportedNamespace(apex_ns_name, is_bridged);// Even if APEX namespace is visible, it may not be available to bridged.if (ns.ok()) {// 新创建的命名空间链接到APEX,共享库设置为public_libslinked = app_ns->Link(&ns.value(), public_libs);if (!linked.ok()) {return linked.error();}}}// Give access to VNDK-SP libraries from the 'vndk' namespace for unbundled vendor apps.if (unbundled_app_origin == APK_ORIGIN_VENDOR && !vndksp_libraries_vendor().empty()) {auto vndk_ns = NativeLoaderNamespace::GetExportedNamespace(kVndkNamespaceName, is_bridged);if (vndk_ns.ok()) {// 新创建的命名空间链接到vndk,共享库设置为vndksp_libraries_vendor()linked = app_ns->Link(&vndk_ns.value(), vndksp_libraries_vendor());if (!linked.ok()) {return linked.error();}}}// Give access to VNDK-SP libraries from the 'vndk_product' namespace for unbundled product apps.if (unbundled_app_origin == APK_ORIGIN_PRODUCT && !vndksp_libraries_product().empty()) {auto vndk_ns = NativeLoaderNamespace::GetExportedNamespace(kVndkProductNamespaceName, is_bridged);if (vndk_ns.ok()) {linked = app_ns->Link(&vndk_ns.value(), vndksp_libraries_product());if (!linked.ok()) {return linked.error();}}}for (const std::string& each_jar_path : android::base::Split(dex_path, ":")) {auto apex_ns_name = FindApexNamespaceName(each_jar_path);if (apex_ns_name.ok()) {const auto& jni_libs = apex_jni_libraries(*apex_ns_name);if (jni_libs != "") {auto apex_ns = NativeLoaderNamespace::GetExportedNamespace(*apex_ns_name, is_bridged);if (apex_ns.ok()) {linked = app_ns->Link(&apex_ns.value(), jni_libs);if (!linked.ok()) {return linked.error();}}}}}auto vendor_libs = filter_public_libraries(target_sdk_version, uses_libraries,vendor_public_libraries());if (!vendor_libs.empty()) {auto vendor_ns = NativeLoaderNamespace::GetExportedNamespace(kVendorNamespaceName, is_bridged);// when vendor_ns is not configured, link to the system namespaceauto target_ns = vendor_ns.ok() ? vendor_ns : system_ns;if (target_ns.ok()) {linked = app_ns->Link(&target_ns.value(), vendor_libs);if (!linked.ok()) {return linked.error();}}}auto product_libs = filter_public_libraries(target_sdk_version, uses_libraries,product_public_libraries());if (!product_libs.empty()) {auto target_ns = system_ns;if (is_product_vndk_version_defined()) {// If ro.product.vndk.version is defined, product namespace provides the product libraries.target_ns = NativeLoaderNamespace::GetExportedNamespace(kProductNamespaceName, is_bridged);}if (target_ns.ok()) {linked = app_ns->Link(&target_ns.value(), product_libs);if (!linked.ok()) {return linked.error();}} else {// The linkerconfig must have a problem on defining the product namespace in the system// section. Skip linking product namespace. This will not affect most of the apps. Only the// apps that requires the product public libraries will fail.ALOGW("Namespace for product libs not found: %s", target_ns.error().message().c_str());}}auto& emplaced = namespaces_.emplace_back(std::make_pair(env->NewWeakGlobalRef(class_loader), *app_ns));if (is_main_classloader) {app_main_namespace_ = &emplaced.second;}return &emplaced.second;
}

1.2 配置共享库位置

通过上面的代码跟踪我们会发现,其实在Android 11后,应用进程共享库的配置位置都在public_libraries.cpp

// art/libnativeloader/public_libraries.cpp
namespace {constexpr const char* kDefaultPublicLibrariesFile = "/etc/public.libraries.txt";
constexpr const char* kExtendedPublicLibrariesFilePrefix = "public.libraries-";
constexpr const char* kExtendedPublicLibrariesFileSuffix = ".txt";
constexpr const char* kApexLibrariesConfigFile = "/linkerconfig/apex.libraries.config.txt";
constexpr const char* kVendorPublicLibrariesFile = "/vendor/etc/public.libraries.txt";
constexpr const char* kLlndkLibrariesFile = "/apex/com.android.vndk.v{}/etc/llndk.libraries.{}.txt";
constexpr const char* kVndkLibrariesFile = "/apex/com.android.vndk.v{}/etc/vndksp.libraries.{}.txt";

只要在命名空间对应的配置文件中加上自己需要的共享库即可

2. native进程

2.1 native 命名空间配置初始化

native进程的so库是直接通过dlopen打开的,而其对应的命名空间也是直接通过linker进行判断的,并没有调用到libnativeloader.so,因此native进程的配置方式也与应用进程不同

2.1.1 android_namespace_t::is_accessible

前一篇Android system — 链接器命名空间(linker namespace)源码分析 介绍了dlopen时会通过is_accessible进行权限判断

// bionic/linker/linker_namespaces.cpp
// Given an absolute path, can this library be loaded into this namespace?
bool android_namespace_t::is_accessible(const std::string& file) {//判断命名空间的is_isolated_,即是否为严格隔离,如果不是则权限检查通过。if (!is_isolated_) {return true;}//判断是否在允许名单中if (!allowed_libs_.empty()) {const char *lib_name = basename(file.c_str());if (std::find(allowed_libs_.begin(), allowed_libs_.end(), lib_name) == allowed_libs_.end()) {return false;}}//判断是否在ld_library_paths中(LD_LIBRARY_PATH设置)for (const auto& dir : ld_library_paths_) {if (file_is_in_dir(file, dir)) {return true;}}//判断是否在default_library_paths中for (const auto& dir : default_library_paths_) {if (file_is_in_dir(file, dir)) {return true;}}//判断是否在特权路径permitted_paths中for (const auto& dir : permitted_paths_) {if (file_is_under_dir(file, dir)) {return true;}}return false;
}

我们可以看到这里有个allowed_libs_,我们跟踪代码可以发现linker_namespaces.h中有set_allowed_libs接口

// bionic/linker/linker_namespaces.h const std::vector<std::string>& get_allowed_libs() const { return allowed_libs_; }void set_allowed_libs(std::vector<std::string>&& allowed_libs) {allowed_libs_ = std::move(allowed_libs);}void set_allowed_libs(const std::vector<std::string>& allowed_libs) {allowed_libs_ = allowed_libs;}

2.1.2 init_default_namespaces

通过这个思路我们发现调用其实在init_default_namespaces

  • 初始化default命名空间
  • 初始化vndk等其他命名空间
  • 在命名空间之间建立链接
std::vector<android_namespace_t*> init_default_namespaces(const char* executable_path) {g_default_namespace.set_name("(default)");soinfo* somain = solist_get_somain();const char *interp = phdr_table_get_interpreter_name(somain->phdr, somain->phnum,somain->load_bias);const char* bname = (interp != nullptr) ? basename(interp) : nullptr;g_is_asan = bname != nullptr &&(strcmp(bname, "linker_asan") == 0 ||strcmp(bname, "linker_asan64") == 0);const Config* config = nullptr;{std::string ld_config_file_path = get_ld_config_file_path(executable_path);INFO("[ Reading linker config \"%s\" ]", ld_config_file_path.c_str());ScopedTrace trace(("linker config " + ld_config_file_path).c_str());std::string error_msg;if (!Config::read_binary_config(ld_config_file_path.c_str(), executable_path, g_is_asan,&config, &error_msg)) {if (!error_msg.empty()) {DL_WARN("Warning: couldn't read '%s' for '%s' (using default configuration instead): %s",ld_config_file_path.c_str(), executable_path, error_msg.c_str());}config = nullptr;}}if (config == nullptr) {return init_default_namespace_no_config(g_is_asan);}const auto& namespace_configs = config->namespace_configs();std::unordered_map<std::string, android_namespace_t*> namespaces;// 1. Initialize default namespaceconst NamespaceConfig* default_ns_config = config->default_namespace_config();g_default_namespace.set_isolated(default_ns_config->isolated());g_default_namespace.set_default_library_paths(default_ns_config->search_paths());g_default_namespace.set_permitted_paths(default_ns_config->permitted_paths());namespaces[default_ns_config->name()] = &g_default_namespace;if (default_ns_config->visible()) {g_exported_namespaces[default_ns_config->name()] = &g_default_namespace;}// 2. Initialize other namespacesfor (auto& ns_config : namespace_configs) {if (namespaces.find(ns_config->name()) != namespaces.end()) {continue;}android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();ns->set_name(ns_config->name());ns->set_isolated(ns_config->isolated());ns->set_default_library_paths(ns_config->search_paths());ns->set_permitted_paths(ns_config->permitted_paths());ns->set_allowed_libs(ns_config->allowed_libs());namespaces[ns_config->name()] = ns;if (ns_config->visible()) {g_exported_namespaces[ns_config->name()] = ns;}}// 3. Establish links between namespacesfor (auto& ns_config : namespace_configs) {auto it_from = namespaces.find(ns_config->name());CHECK(it_from != namespaces.end());android_namespace_t* namespace_from = it_from->second;for (const NamespaceLinkConfig& ns_link : ns_config->links()) {auto it_to = namespaces.find(ns_link.ns_name());CHECK(it_to != namespaces.end());android_namespace_t* namespace_to = it_to->second;if (ns_link.allow_all_shared_libs()) {link_namespaces_all_libs(namespace_from, namespace_to);} else {link_namespaces(namespace_from, namespace_to, ns_link.shared_libs().c_str());}}}// we can no longer rely on the fact that libdl.so is part of default namespace// this is why we want to add ld-android.so to all namespaces from ld.config.txtsoinfo* ld_android_so = solist_get_head();// we also need vdso to be available for all namespaces (if present)soinfo* vdso = solist_get_vdso();for (auto it : namespaces) {if (it.second != &g_default_namespace) {it.second->add_soinfo(ld_android_so);if (vdso != nullptr) {it.second->add_soinfo(vdso);}// somain and ld_preloads are added to these namespaces after LD_PRELOAD libs are linked}}set_application_target_sdk_version(config->target_sdk_version());std::vector<android_namespace_t*> created_namespaces;created_namespaces.reserve(namespaces.size());for (const auto& kv : namespaces) {created_namespaces.push_back(kv.second);}return created_namespaces;
}
}

这里面我们发现配置文件是通过读取配置文件进行配置的,配置文件ld_config_file_path在Android 11后便改为/linkerconfig/ld.config.txt

static const char* const kLdConfigArchFilePath = "/system/etc/ld.config." ABI_STRING ".txt";static const char* const kLdConfigFilePath = "/system/etc/ld.config.txt";
static const char* const kLdConfigVndkLiteFilePath = "/system/etc/ld.config.vndk_lite.txt";static const char* const kLdGeneratedConfigFilePath = "/linkerconfig/ld.config.txt";static std::string get_ld_config_file_path(const char* executable_path) {
#ifdef USE_LD_CONFIG_FILE// This is a debugging/testing only feature. Must not be available on// production builds.const char* ld_config_file_env = getenv("LD_CONFIG_FILE");if (ld_config_file_env != nullptr && file_exists(ld_config_file_env)) {return ld_config_file_env;}
#endifstd::string path = get_ld_config_file_apex_path(executable_path);if (!path.empty()) {if (file_exists(path.c_str())) {return path;}DL_WARN("Warning: couldn't read config file \"%s\" for \"%s\"",path.c_str(), executable_path);}path = kLdConfigArchFilePath;if (file_exists(path.c_str())) {return path;}if (file_exists(kLdGeneratedConfigFilePath)) {return kLdGeneratedConfigFilePath;}if (is_linker_config_expected(executable_path)) {DL_WARN("Warning: failed to find generated linker configuration from \"%s\"",kLdGeneratedConfigFilePath);}path = get_ld_config_file_vndk_path();if (file_exists(path.c_str())) {return path;}return kLdConfigFilePath;
}

2.2 配置共享库位置

上一章我们提到了配置文件位置为/linkerconfig/ld.config.txt,
在Android 11以前ld.config.txt是通过静态文件配置的,而Android 11是改为linkerconfig动态生成

我们以一个vendor/bin下的native 程序为例,要使用system/lib下的库

在Android system — Android链接器命名空间(Android 11后)中,我们提到了“目录-区段”映射属性dir.name,指向 [name] 区段所应用到的目录的路径,其实就是baseconfig.cc中定义的dirToSection

// system/linkerconfig/contents/configuration/baseconfig.ccandroid::linkerconfig::modules::Configuration CreateBaseConfiguration(Context& ctx) {std::vector<Section> sections;ctx.SetCurrentLinkerConfigType(LinkerConfigType::Default);// Don't change the order here. The first pattern that matches with the// absolute path of an executable is selected.std::vector<DirToSection> dirToSection = {{"/system/bin/", "system"},{"/system/xbin/", "system"},{Var("SYSTEM_EXT") + "/bin/", "system"},// Processes from the product partition will have a separate section if// PRODUCT_PRODUCT_VNDK_VERSION is defined. Otherwise, they are run from// the "system" section.{Var("PRODUCT") + "/bin/", "product"},{"/odm/bin/", "vendor"},{"/vendor/bin/", "vendor"},{"/data/nativetest/odm", "vendor"},{"/data/nativetest64/odm", "vendor"},{"/data/benchmarktest/odm", "vendor"},{"/data/benchmarktest64/odm", "vendor"},{"/data/nativetest/vendor", "vendor"},{"/data/nativetest64/vendor", "vendor"},{"/data/benchmarktest/vendor", "vendor"},{"/data/benchmarktest64/vendor", "vendor"},{"/data/nativetest/unrestricted", "unrestricted"},{"/data/nativetest64/unrestricted", "unrestricted"},// Create isolated namespace for development purpose.// This isolates binary from the system so binaries and libraries from// this location can be separated from system libraries.{"/data/local/tmp/isolated", "isolated"},// Create directories under shell-writable /data/local/tests for// each namespace in order to run tests.{"/data/local/tests/product", "product"},{"/data/local/tests/system", "system"},{"/data/local/tests/unrestricted", "unrestricted"},{"/data/local/tests/vendor", "vendor"},// TODO(b/123864775): Ensure tests are run from one of the subdirectories// above.  Then clean this up.{"/data/local/tmp", "unrestricted"},{"/postinstall", "postinstall"},// Fallback entry to provide APEX namespace lookups for binaries anywhere// else. This must be last.{"/data", "system"},// TODO(b/168556887): Remove this when we have a dedicated section for// binaries in APKs{Var("PRODUCT") + "/app/", "system"},};sections.emplace_back(BuildSystemSection(ctx));if (ctx.IsVndkAvailable()) {sections.emplace_back(BuildVendorSection(ctx));if (android::linkerconfig::modules::IsProductVndkVersionDefined()) {sections.emplace_back(BuildProductSection(ctx));} else {RedirectSection(dirToSection, "product", "system");}} else {RemoveSection(dirToSection, "product");RemoveSection(dirToSection, "vendor");}sections.emplace_back(BuildUnrestrictedSection(ctx));sections.emplace_back(BuildPostInstallSection(ctx));sections.emplace_back(BuildIsolatedSection(ctx));return android::linkerconfig::modules::Configuration(std::move(sections),dirToSection);
}

因为我们是vendor/bin下面的程序,所以可以看出来我们是vendor section,因此我们看BuildVendorSection即可。

// system/linkerconfig/contents/section/vendor.ccSection BuildVendorSection(Context& ctx) {ctx.SetCurrentSection(SectionType::Vendor);std::vector<Namespace> namespaces;namespaces.emplace_back(BuildVendorDefaultNamespace(ctx));namespaces.emplace_back(BuildVndkNamespace(ctx, VndkUserPartition::Vendor));namespaces.emplace_back(BuildSystemNamespace(ctx));namespaces.emplace_back(BuildRsNamespace(ctx));if (android::linkerconfig::modules::IsVndkInSystemNamespace()) {namespaces.emplace_back(BuildVndkInSystemNamespace(ctx));}std::set<std::string> visible_apexes;// APEXes with public libs should be visiblefor (const auto& apex : ctx.GetApexModules()) {if (apex.public_libs.size() > 0) {visible_apexes.insert(apex.name);}}android::linkerconfig::modules::LibProviders libs_providers = {};if (ctx.IsVndkAvailable()) {libs_providers[":vndk"] = android::linkerconfig::modules::LibProvider{"vndk",std::bind(BuildVndkNamespace, ctx, VndkUserPartition::Vendor),{Var("VNDK_SAMEPROCESS_LIBRARIES_VENDOR"),Var("VNDK_CORE_LIBRARIES_VENDOR")},};}return BuildSection(ctx, "vendor", std::move(namespaces), visible_apexes, libs_providers);
}

我们可以看到最后调用了BuildSection,在其中调用了AddStandardSystemLinks 添加system的共享库

// system/linkerconfig/contents/section/sectionbuilder.ccSection BuildSection(const Context& ctx, const std::string& name,std::vector<Namespace>&& namespaces,const std::set<std::string>& visible_apexes,const LibProviders& providers) {// add additional visible APEX namespacesfor (const auto& apex : ctx.GetApexModules()) {if (visible_apexes.find(apex.name) == visible_apexes.end() &&!apex.visible) {continue;}if (auto it = std::find_if(namespaces.begin(),namespaces.end(),[&apex](auto& ns) { return ns.GetName() == apex.namespace_name; });it == namespaces.end()) {auto ns = ctx.BuildApexNamespace(apex, true);namespaces.push_back(std::move(ns));} else {// override "visible" when the apex is already createdit->SetVisible(true);}}// resolve provide/require constraintsSection section(std::move(name), std::move(namespaces));if (auto res = section.Resolve(ctx, providers); !res.ok()) {LOG(ERROR) << res.error();}AddStandardSystemLinks(ctx, &section); // 添加通用的system link共享库return section;
}

在kBionicLibs 加上我们需要的库即可

 // system/linkerconfig/contents/common/system_links.ccnamespace {
const std::vector<std::string> kBionicLibs = {"libc.so","libdl.so","libdl_android.so","libm.so",
};
}  // namespacenamespace android {
namespace linkerconfig {
namespace contents {using android::linkerconfig::modules::Namespace;
using android::linkerconfig::modules::Section;void AddStandardSystemLinks(const Context& ctx, Section* section) {const bool debuggable = android::base::GetBoolProperty("ro.debuggable", false);const std::string system_ns_name = ctx.GetSystemNamespaceName();const bool is_section_vndk_enabled = ctx.IsSectionVndkEnabled();section->ForEachNamespaces([&](Namespace& ns) {if (ns.GetName() != system_ns_name) {ns.GetLink(system_ns_name).AddSharedLib(kBionicLibs);if (!is_section_vndk_enabled || ns.GetName() != "default") {// TODO(b/185199923) remove the default valuens.GetLink(system_ns_name).AddSharedLib(Var("SANITIZER_RUNTIME_LIBRARIES", ""));}if (debuggable) {// Library on the system image that can be dlopened for debugging purposes.ns.GetLink(system_ns_name).AddSharedLib("libfdtrack.so");}}});
}}  // namespace contents
}  // namespace linkerconfig
}  // namespace android

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/4864.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

清理docker缓存

要清理Docker缓存&#xff0c;你可以使用以下命令&#xff1a; docker system prune 这个命令会删除所有未使用的容器、网络、镜像(默认不包括Tag为none的)和悬空的镜像。如果你也想要删除所有未使用的镜像&#xff0c;不仅仅是悬空的&#xff0c;可以添加-a参数&#xff1a;…

swift语言学习总结

Var 表示变量&#xff0c; let表示常量。数组和map&#xff0c; 都用中括号[].可以直接赋值。可以用下标或键访问。 var shoppingList ["catfish", "water", "tulips", "blue paint”]//最后一个可以加逗号。 shoppingList[1] "bo…

Web开发中的网络安全: 常见攻击及防范策略

在Web开发的世界里&#xff0c;网络攻击是一种常见且潜在的威胁。理解这些攻击如何运作并采取措施防范它们对于构建安全的Web应用至关重要。本文将介绍几种常见的Web攻击&#xff0c;提供防范策略&#xff0c;并以实例说明如何防止这些攻击。 SQL注入 概要: SQL注入攻击是攻击…

Python+Selenium基于PO模式的Web自动化测试框架

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、什么是Selenium&#xff1f; Selenium是一个基于浏览器的自动化测试工具&#xff0c;它提供…

Linux pppstats命令教程:如何详解PPP连接的统计信息(附案例详解和注意事项)

Linux pppstats命令介绍 pppstats命令用于显示活动的PPP&#xff08;点对点协议&#xff09;连接的统计信息。这些统计信息包括发送和接收的数据包数量、压缩前和压缩后的数据量、IPCP和LCP协议的状态等。 Linux pppstats命令适用的Linux版本 pppstats命令在大多数Linux发行…

吴恩达机器学习笔记:第 8 周-14降维(Dimensionality Reduction) 14.3-14.5

目录 第 8 周 14、 降维(Dimensionality Reduction)14.3 主成分分析问题14.4 主成分分析算法14.5 选择主成分的数量 第 8 周 14、 降维(Dimensionality Reduction) 14.3 主成分分析问题 主成分分析(PCA)是最常见的降维算法。 在 PCA 中&#xff0c;我们要做的是找到一个方向…

C++与或运算规则

文章目录 前言问题1问题2 前言 在笔试中遇到c或与运算的问题&#xff0c;在这记录 问题1&#xff1a;2024.4.28 问题1 下面代码的运行结果&#xff1a; #include <iostream> using namespace std; bool fun1(char ch){cout<<ch<<" ";if(ch >a…

react-lib 读取本地模板创建PDF

读取本地文件和读取远程的一样&#xff0c;都使用fetch去获取 async function modifyPdf() {let url ./template.pdflet existingPdfBytes await fetch(url).then(res > res.arrayBuffer()) // 这里也有问题要转一下const d new Uint8Array(existingPdfBytes)const pdfDo…

dremio数据湖sql行列转换及转置

1、行转列 (扁平化) 数据准备 表 aa 1.1 cross join unnest 在Dremio中&#xff0c;UNNEST 函数用于将数组或复杂类型的列&#xff08;如JSON、Map或Array类型&#xff09;中的值“炸裂”&#xff08;分解&#xff09;成多行. with aa as ( select 上海 as city, ARRAY[浦东…

Centos7_miniconda_devtools安装_R语言入门之R包的安装

因为有同事反馈安装R包很慢或卡住&#xff0c;提供了一个安装R包的命令给我测试&#xff0c;在安装过程中复现报错信息&#xff0c;把下载慢或卡顿的链接中的域名在防火墙中调整出口。 devtools::install_github("GreenleafLab/ArchR", ref"master", repo…

LLaMA Factory多卡微调的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

How to solve matplotlib Chinese garbled characters in Ubuntu 22.04

conda create -n huizhou python3.8conda activate huizhouconda install numpy matplotlibpip install mplfontsmplfonts init# 导入必要的库 import numpy as np import matplotlib.pyplot as plt# 创建角度数组&#xff0c;从0到2π x np.linspace(0, 2 * np.pi, 100)# 计算…

使用逆滤波算法deconvwnr恢复图像回复图像时,产生了很多横竖条纹。解决办法

使用逆滤波算法deconvwnr恢复图像回复图像时&#xff0c;产生了很多横竖条纹。解决办法 原来的代码 % 清除工作空间并关闭所有图形窗口 clear; clc; close all;% 读取原始图像 original_image imread(pic3.jpg);% 显示原始图像 subplot(131); imshow(original_image); title…

密码学python库PBC安装使用

初始化 使用环境云服务器&#xff08;移动云可以免费使用一个月&#xff09; 选择ubuntu18.04-64位 第一次进入linux命令行之后是没有界面显示的&#xff0c;需要在命令行下载。 这里按照其他云平台操作即可&#xff1a;Ubuntu18.04 首次使用配置教程(图形界面安装) 记录好登录…

用Stream流方式合并两个list集合(部分对象属性重合)

一、合并出共有部分 package com.xu.demo.test;import java.util.Arrays; import java.util.List; import java.util.stream.Collectors;public class ListMergeTest1 {public static void main(String[] args) {List<User> list1 Arrays.asList(new User(1, "Alic…

跨境电商亚马逊、虾皮等平台做测评要用什么IP?

IP即IP地址&#xff0c;IP地址是指互联网协议地址&#xff08;英语&#xff1a;Internet Protocol Address&#xff0c;又译为网际协议地址&#xff09;&#xff0c;是IP Address的缩写&#xff0c;IP地址是IP协议提供的一种统一的地址格式 功能&#xff1a;它为互联网上的每一…

Java中一个汉字究竟占几个字节?

前言 在今天&#xff0c;“Java中一个汉字占几个字符”的问题&#xff0c;让我提起了兴趣 在我的记忆中&#xff0c;一个字符应该是占两个字符的。但看了他人的回答 发现自己对这方面了解非常片面&#xff0c;于是痛定思痛潜心学习&#xff0c;写下这篇博客 总结不足文章目录 …

NLP transformers - 翻译

from transformers import AutoTokenizer#加载编码器 tokenizer AutoTokenizer.from_pretrained(Helsinki-NLP/opus-mt-en-ro,use_fastTrue)print(tokenizer)#编码试算 tokenizer.batch_encode_plus([[Hello, this one sentence!, This is another sentence.]])PreTrainedToke…

ubuntu18.04系统编译openwrt21.02.3

搭建ubuntu18.04环境 使用虚拟机安装ubuntu环境网上教程很多&#xff0c;这里不做赘述&#xff0c;主要是安装一些我们在编译openwrt时可能会用到的一些工具环境 sudo apt-get update sudo apt instll libncurses-dev gawk sudo apt-get install build-essential libncurses5…

【python技术】使用akshare抓取东方财富所有概念板块,并把指定板块概念的成分股保存excel 简单示例

最近有个想法&#xff0c;分析A股某个概念成分股情况进行分析&#xff0c;第一反应是把对应概念板块的成分股爬取下来。说干就干 下面是简单示例 import akshare as ak import pandas as pddef fetch_and_save_concept_stocks(name):# 获取指定股票概念的成分股&#xff0c;并…