From e5f1d635677b297ca422fb981124fffa9386872c Mon Sep 17 00:00:00 2001 From: Martin Stjernholm Date: Wed, 16 Nov 2022 15:28:12 +0000 Subject: [PATCH] Make classloader namespace names unique, for debugging. Also take the opportunity to switch to shorter names, when they are changing anyway. These namespaces aren't visible, so renaming them shouldn't be a compat issue. Test: atest libnativeloader_test libnativeloader_e2e_tests Test: adb shell "setprop debug.ld.all dlopen,dlerror && setprop log.tag.nativeloader :v && stop && start" Then check logcat that the classloader namespaces get the expected unique names (per process). Bug: 258340826 Change-Id: Ic5d6ba850d8cd9cdfb3a66e175dfc87046c0ba31 --- libnativeloader/README.md | 10 ++++---- libnativeloader/library_namespaces.cpp | 33 ++++++++++++++++---------- libnativeloader/native_loader_test.cpp | 21 ++++++++-------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/libnativeloader/README.md b/libnativeloader/README.md index c5ace61ba5..919feff8db 100644 --- a/libnativeloader/README.md +++ b/libnativeloader/README.md @@ -10,11 +10,11 @@ by the platform. The most typical use case of this library is calling `System.loadLibrary(name)`. When the method is called, the ART runtime delegates the call to this library -along with the reference to the classloader where the call was made. Then this -library finds the linker namespace (named `classloader-namespace`) that is -associated with the given classloader, and tries to load the requested library -from the namespace. The actual searching, loading, and linking of the library -is performed by the dynamic linker. +along with the reference to the classloader where the call was made. Then this +library finds the linker namespace (typically with the name `clns-` followed by +a number to make it unique) that is associated with the given classloader, and +tries to load the requested library from that namespace. The actual searching, +loading, and linking of the library is performed by the dynamic linker. The linker namespace is created when an APK is loaded into the process, and is associated with the classloader that loaded the APK. The linker namespace is diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp index aaf724153f..2a6febdd80 100644 --- a/libnativeloader/library_namespaces.cpp +++ b/libnativeloader/library_namespaces.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "nativeloader/dlext_namespaces.h" @@ -55,19 +56,20 @@ constexpr const char* kVndkNamespaceName = "vndk"; // vndk_product namespace for unbundled product apps constexpr const char* kVndkProductNamespaceName = "vndk_product"; -// classloader-namespace is a linker namespace that is created for the loaded -// app. To be specific, it is created for the app classloader. When -// System.load() is called from a Java class that is loaded from the -// classloader, the classloader-namespace namespace associated with that -// classloader is selected for dlopen. The namespace is configured so that its -// search path is set to the app-local JNI directory and it is linked to the -// system namespace with the names of libs listed in the public.libraries.txt. -// This way an app can only load its own JNI libraries along with the public libs. -constexpr const char* kClassloaderNamespaceName = "classloader-namespace"; -// Same thing for unbundled vendor APKs. -constexpr const char* kVendorClassloaderNamespaceName = "vendor-classloader-namespace"; -// Same thing for unbundled product APKs. -constexpr const char* kProductClassloaderNamespaceName = "product-classloader-namespace"; +// clns-XX is a linker namespace that is created for normal apps installed in +// the data partition. To be specific, it is created for the app classloader. +// When System.load() is called from a Java class that is loaded from the +// classloader, the clns namespace associated with that classloader is selected +// for dlopen. The namespace is configured so that its search path is set to the +// app-local JNI directory and it is linked to the system namespace with the +// names of libs listed in the public.libraries.txt and other public libraries. +// This way an app can only load its own JNI libraries along with the public +// libs. +constexpr const char* kClassloaderNamespaceName = "clns"; +// Same thing for unbundled APKs in the vendor partition. +constexpr const char* kVendorClassloaderNamespaceName = "vendor-clns"; +// Same thing for unbundled APKs in the product partition. +constexpr const char* kProductClassloaderNamespaceName = "product-clns"; // If the namespace is shared then add this suffix to help identify it in debug // messages. A shared namespace (cf. ANDROID_NAMESPACE_TYPE_SHARED) has // inherited all the libraries of the parent classloader namespace, or the @@ -294,6 +296,11 @@ Result LibraryNamespaces::Create(JNIEnv* env, uint32_t t namespace_name = namespace_name + kSharedNamespaceSuffix; } + // Append a unique number to the namespace name, to tell them apart when + // debugging linker issues, e.g. with debug.ld.all set to "dlopen,dlerror". + static int clns_count = 0; + namespace_name = android::base::StringPrintf("%s-%d", namespace_name.c_str(), ++clns_count); + ALOGD( "Configuring %s for %s %s. target_sdk_version=%u, uses_libraries=%s, library_path=%s, " "permitted_path=%s", diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp index 3825614545..6c0c8b17a7 100644 --- a/libnativeloader/native_loader_test.cpp +++ b/libnativeloader/native_loader_test.cpp @@ -32,6 +32,7 @@ namespace nativeloader { using ::testing::Eq; using ::testing::NotNull; +using ::testing::StartsWith; using ::testing::StrEq; using internal::ConfigEntry; // NOLINT - ConfigEntry is actually used using internal::ParseApexLibrariesConfig; @@ -191,7 +192,7 @@ class NativeLoaderTest_Create : public NativeLoaderTest { std::string permitted_path = "/data/app/foo/" LIB_DIR; // expected output (.. for the default test inputs) - std::string expected_namespace_name = "classloader-namespace"; + std::string expected_namespace_prefix = "clns"; uint64_t expected_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS; std::string expected_library_path = library_path; @@ -227,7 +228,7 @@ class NativeLoaderTest_Create : public NativeLoaderTest { EXPECT_CALL(*mock, NativeBridgeInitialized()).Times(testing::AnyNumber()); EXPECT_CALL(*mock, mock_create_namespace( - Eq(IsBridged()), StrEq(expected_namespace_name), nullptr, + Eq(IsBridged()), StartsWith(expected_namespace_prefix + "-"), nullptr, StrEq(expected_library_path), expected_namespace_flags, StrEq(expected_permitted_path), NsEq(expected_parent_namespace.c_str()))) .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(dex_path.c_str())))); @@ -322,7 +323,7 @@ TEST_P(NativeLoaderTest_Create, BundledSystemApp) { dex_path = "/system/app/foo/foo.apk"; is_shared = true; - expected_namespace_name = "classloader-namespace-shared"; + expected_namespace_prefix = "clns-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; SetExpectations(); RunTest(); @@ -332,7 +333,7 @@ TEST_P(NativeLoaderTest_Create, BundledVendorApp) { dex_path = "/vendor/app/foo/foo.apk"; is_shared = true; - expected_namespace_name = "classloader-namespace-shared"; + expected_namespace_prefix = "clns-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; SetExpectations(); RunTest(); @@ -342,7 +343,7 @@ TEST_P(NativeLoaderTest_Create, UnbundledVendorApp) { dex_path = "/vendor/app/foo/foo.apk"; is_shared = false; - expected_namespace_name = "vendor-classloader-namespace"; + expected_namespace_prefix = "vendor-clns"; expected_library_path = expected_library_path + ":/vendor/" LIB_DIR; expected_permitted_path = expected_permitted_path + ":/vendor/" LIB_DIR; expected_shared_libs_to_platform_ns = @@ -356,7 +357,7 @@ TEST_P(NativeLoaderTest_Create, BundledProductApp) { dex_path = "/product/app/foo/foo.apk"; is_shared = true; - expected_namespace_name = "classloader-namespace-shared"; + expected_namespace_prefix = "clns-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; SetExpectations(); RunTest(); @@ -366,7 +367,7 @@ TEST_P(NativeLoaderTest_Create, SystemServerWithApexJars) { dex_path = "/system/framework/services.jar:/apex/com.android.conscrypt/javalib/service-foo.jar"; is_shared = true; - expected_namespace_name = "classloader-namespace-shared"; + expected_namespace_prefix = "clns-shared"; expected_namespace_flags |= ANDROID_NAMESPACE_TYPE_SHARED; expected_link_with_conscrypt_ns = true; SetExpectations(); @@ -378,7 +379,7 @@ TEST_P(NativeLoaderTest_Create, UnbundledProductApp) { is_shared = false; if (is_product_vndk_version_defined()) { - expected_namespace_name = "product-classloader-namespace"; + expected_namespace_prefix = "product-clns"; expected_library_path = expected_library_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR; expected_permitted_path = expected_permitted_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR; @@ -416,7 +417,7 @@ TEST_P(NativeLoaderTest_Create, TwoApks) { const std::string second_app_permitted_path = "/data/app/bar/" LIB_DIR; const std::string expected_second_app_permitted_path = std::string("/data:/mnt/expand:") + second_app_permitted_path; - const std::string expected_second_app_parent_namespace = "classloader-namespace"; + const std::string expected_second_app_parent_namespace = "clns"; // no ALSO_USED_AS_ANONYMOUS const uint64_t expected_second_namespace_flags = ANDROID_NAMESPACE_TYPE_ISOLATED; @@ -429,7 +430,7 @@ TEST_P(NativeLoaderTest_Create, TwoApks) { // namespace for the second app is created. Its parent is set to the namespace // of the first app. EXPECT_CALL(*mock, mock_create_namespace( - Eq(IsBridged()), StrEq(expected_namespace_name), nullptr, + Eq(IsBridged()), StartsWith(expected_namespace_prefix + "-"), nullptr, StrEq(second_app_library_path), expected_second_namespace_flags, StrEq(expected_second_app_permitted_path), NsEq(dex_path.c_str()))) .WillOnce(Return(TO_MOCK_NAMESPACE(TO_ANDROID_NAMESPACE(second_app_dex_path.c_str()))));