Introducing linker namespaces
Bug: http://b/22548808
Change-Id: Ia3af3c0a167f1d16447a3d83bb045d143319b1e1
diff --git a/tests/Android.build.mk b/tests/Android.build.mk
index 7cac349..740c2f4 100644
--- a/tests/Android.build.mk
+++ b/tests/Android.build.mk
@@ -28,9 +28,17 @@
LOCAL_MODULE_STEM_32 := $(module)32
LOCAL_MODULE_STEM_64 := $(module)64
else
+
+ifneq ($($(module)_install_to_out_data_dir),)
+ $(module)_install_to_out_data := true
+endif
+
ifeq ($($(module)_install_to_out_data),true)
- LOCAL_MODULE_PATH_32 := $($(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_DATA_NATIVE_TESTS)/$(module)
- LOCAL_MODULE_PATH_64 := $(TARGET_OUT_DATA_NATIVE_TESTS)/$(module)
+ ifeq ($($(module)_install_to_out_data_dir),)
+ $(module)_install_to_out_data_dir := $(module)
+ endif
+ LOCAL_MODULE_PATH_32 := $($(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_OUT_DATA_NATIVE_TESTS)/$($(module)_install_to_out_data_dir)
+ LOCAL_MODULE_PATH_64 := $(TARGET_OUT_DATA_NATIVE_TESTS)/$($(module)_install_to_out_data_dir)
endif
endif
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index c7d4d46..83bd5cc 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -52,14 +52,14 @@
#define LIBSIZE 1024*1024 // how much address space to reserve for it
#if defined(__LP64__)
-#define LIBPATH_PREFIX "/nativetest64/"
+#define NATIVE_TESTS_PATH "/nativetest64"
#else
-#define LIBPATH_PREFIX "/nativetest/"
+#define NATIVE_TESTS_PATH "/nativetest"
#endif
-#define LIBPATH LIBPATH_PREFIX "libdlext_test_fd/libdlext_test_fd.so"
-#define LIBZIPPATH LIBPATH_PREFIX "libdlext_test_zip/libdlext_test_zip_zipaligned.zip"
-#define LIBZIPPATH_WITH_RUNPATH LIBPATH_PREFIX "libdlext_test_runpath_zip/libdlext_test_runpath_zip_zipaligned.zip"
+#define LIBPATH NATIVE_TESTS_PATH "/libdlext_test_fd/libdlext_test_fd.so"
+#define LIBZIPPATH NATIVE_TESTS_PATH "/libdlext_test_zip/libdlext_test_zip_zipaligned.zip"
+#define LIBZIPPATH_WITH_RUNPATH NATIVE_TESTS_PATH "/libdlext_test_runpath_zip/libdlext_test_runpath_zip_zipaligned.zip"
#define LIBZIP_OFFSET PAGE_SIZE
@@ -602,3 +602,192 @@
ASSERT_EQ(0, WEXITSTATUS(status));
}
}
+
+// Testing namespaces
+static const char* g_public_lib = "libnstest_public.so";
+
+TEST(dlext, ns_smoke) {
+ static const char* root_lib = "libnstest_root.so";
+ std::string path = std::string("libc.so:libc++.so:libdl.so:libm.so:") + g_public_lib;
+
+ ASSERT_FALSE(android_init_public_namespace(path.c_str()));
+ ASSERT_STREQ("android_init_public_namespace failed: Error initializing public namespace: "
+ "\"libnstest_public.so\" was not found in the default namespace", dlerror());
+
+ const std::string lib_path = std::string(getenv("ANDROID_DATA")) + NATIVE_TESTS_PATH;
+
+ void* handle_public = dlopen((lib_path + "/public_namespace_libs/" + g_public_lib).c_str(), RTLD_NOW);
+ ASSERT_TRUE(handle_public != nullptr) << dlerror();
+
+ ASSERT_TRUE(android_init_public_namespace(path.c_str())) << dlerror();
+
+ // Check that libraries added to public namespace are NODELETE
+ dlclose(handle_public);
+ handle_public = dlopen((lib_path + "/public_namespace_libs/" + g_public_lib).c_str(), RTLD_NOW | RTLD_NOLOAD);
+ ASSERT_TRUE(handle_public != nullptr) << dlerror();
+
+ android_namespace_t* ns1 = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false);
+ ASSERT_TRUE(ns1 != nullptr) << dlerror();
+
+ android_namespace_t* ns2 = android_create_namespace("private_isolated", nullptr, (lib_path + "/private_namespace_libs").c_str(), true);
+ ASSERT_TRUE(ns2 != nullptr) << dlerror();
+
+ // This should not have affect search path for default namespace:
+ ASSERT_TRUE(dlopen(root_lib, RTLD_NOW) == nullptr);
+ void* handle = dlopen(g_public_lib, RTLD_NOW);
+ ASSERT_TRUE(handle != nullptr) << dlerror();
+ dlclose(handle);
+
+ android_dlextinfo extinfo;
+ extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+ extinfo.library_namespace = ns1;
+
+ void* handle1 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle1 != nullptr) << dlerror();
+
+ extinfo.library_namespace = ns2;
+ void* handle2 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle2 != nullptr) << dlerror();
+
+ ASSERT_TRUE(handle1 != handle2);
+
+ typedef const char* (*fn_t)();
+
+ fn_t ns_get_local_string1 = reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_local_string"));
+ ASSERT_TRUE(ns_get_local_string1 != nullptr) << dlerror();
+ fn_t ns_get_local_string2 = reinterpret_cast<fn_t>(dlsym(handle2, "ns_get_local_string"));
+ ASSERT_TRUE(ns_get_local_string2 != nullptr) << dlerror();
+
+ EXPECT_STREQ("This string is local to root library", ns_get_local_string1());
+ EXPECT_STREQ("This string is local to root library", ns_get_local_string2());
+
+ ASSERT_TRUE(ns_get_local_string1() != ns_get_local_string2());
+
+ fn_t ns_get_private_extern_string1 =
+ reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_private_extern_string"));
+ ASSERT_TRUE(ns_get_private_extern_string1 != nullptr) << dlerror();
+ fn_t ns_get_private_extern_string2 =
+ reinterpret_cast<fn_t>(dlsym(handle2, "ns_get_private_extern_string"));
+ ASSERT_TRUE(ns_get_private_extern_string2 != nullptr) << dlerror();
+
+ EXPECT_STREQ("This string is from private namespace", ns_get_private_extern_string1());
+ EXPECT_STREQ("This string is from private namespace", ns_get_private_extern_string2());
+
+ ASSERT_TRUE(ns_get_private_extern_string1() != ns_get_private_extern_string2());
+
+ fn_t ns_get_public_extern_string1 =
+ reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_public_extern_string"));
+ ASSERT_TRUE(ns_get_public_extern_string1 != nullptr) << dlerror();
+ fn_t ns_get_public_extern_string2 =
+ reinterpret_cast<fn_t>(dlsym(handle2, "ns_get_public_extern_string"));
+ ASSERT_TRUE(ns_get_public_extern_string2 != nullptr) << dlerror();
+
+ EXPECT_STREQ("This string is from public namespace", ns_get_public_extern_string1());
+ ASSERT_TRUE(ns_get_public_extern_string1() == ns_get_public_extern_string2());
+
+ // and now check that dlopen() does the right thing in terms of preserving namespace
+ fn_t ns_get_dlopened_string1 = reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_dlopened_string"));
+ ASSERT_TRUE(ns_get_dlopened_string1 != nullptr) << dlerror();
+ fn_t ns_get_dlopened_string2 = reinterpret_cast<fn_t>(dlsym(handle2, "ns_get_dlopened_string"));
+ ASSERT_TRUE(ns_get_dlopened_string2 != nullptr) << dlerror();
+
+ EXPECT_STREQ("This string is from private namespace (dlopened library)", ns_get_dlopened_string1());
+ EXPECT_STREQ("This string is from private namespace (dlopened library)", ns_get_dlopened_string2());
+
+ ASSERT_TRUE(ns_get_dlopened_string1() != ns_get_dlopened_string2());
+
+ dlclose(handle1);
+
+ // Check if handle2 is still alive (and well)
+ ASSERT_STREQ("This string is local to root library", ns_get_local_string2());
+ ASSERT_STREQ("This string is from private namespace", ns_get_private_extern_string2());
+ ASSERT_STREQ("This string is from public namespace", ns_get_public_extern_string2());
+ ASSERT_STREQ("This string is from private namespace (dlopened library)", ns_get_dlopened_string2());
+
+ dlclose(handle2);
+}
+
+TEST(dlext, ns_isolated) {
+ static const char* root_lib = "libnstest_root_not_isolated.so";
+ std::string path = std::string("libc.so:libc++.so:libdl.so:libm.so:") + g_public_lib;
+
+ const std::string lib_path = std::string(getenv("ANDROID_DATA")) + NATIVE_TESTS_PATH;
+ void* handle_public = dlopen((lib_path + "/public_namespace_libs/" + g_public_lib).c_str(), RTLD_NOW);
+ ASSERT_TRUE(handle_public != nullptr) << dlerror();
+
+ ASSERT_TRUE(android_init_public_namespace(path.c_str())) << dlerror();
+
+ android_namespace_t* ns_not_isolated = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false);
+ ASSERT_TRUE(ns_not_isolated != nullptr) << dlerror();
+
+ android_namespace_t* ns_isolated = android_create_namespace("private_isolated1", nullptr, (lib_path + "/private_namespace_libs").c_str(), true);
+ ASSERT_TRUE(ns_isolated != nullptr) << dlerror();
+
+ android_namespace_t* ns_isolated2 = android_create_namespace("private_isolated2", (lib_path + "/private_namespace_libs").c_str(), nullptr, true);
+ ASSERT_TRUE(ns_isolated2 != nullptr) << dlerror();
+
+ ASSERT_TRUE(dlopen(root_lib, RTLD_NOW) == nullptr);
+ ASSERT_STREQ("dlopen failed: library \"libnstest_root_not_isolated.so\" not found", dlerror());
+
+ std::string lib_private_external_path =
+ lib_path + "/private_namespace_libs_external/libnstest_private_external.so";
+
+ // Load lib_private_external_path to default namespace
+ // (it should remain invisible for the isolated namespaces after this)
+ void* handle = dlopen(lib_private_external_path.c_str(), RTLD_NOW);
+ ASSERT_TRUE(handle != nullptr) << dlerror();
+
+ android_dlextinfo extinfo;
+ extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+ extinfo.library_namespace = ns_not_isolated;
+
+ void* handle1 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle1 != nullptr) << dlerror();
+
+ extinfo.library_namespace = ns_isolated;
+
+ void* handle2 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle2 == nullptr);
+ ASSERT_STREQ("dlopen failed: library \"libnstest_private_external.so\" not found", dlerror());
+
+ // Check dlopen by absolute path
+ handle2 = android_dlopen_ext(lib_private_external_path.c_str(), RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle2 == nullptr);
+ ASSERT_EQ("dlopen failed: library \"" + lib_private_external_path + "\" not found", dlerror());
+
+ extinfo.library_namespace = ns_isolated2;
+
+ handle2 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle2 == nullptr);
+ ASSERT_STREQ("dlopen failed: library \"libnstest_private_external.so\" not found", dlerror());
+
+ // Check dlopen by absolute path
+ handle2 = android_dlopen_ext(lib_private_external_path.c_str(), RTLD_NOW, &extinfo);
+ ASSERT_TRUE(handle2 == nullptr);
+ ASSERT_EQ("dlopen failed: library \"" + lib_private_external_path + "\" not found", dlerror());
+
+ typedef const char* (*fn_t)();
+ fn_t ns_get_local_string = reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_local_string"));
+ ASSERT_TRUE(ns_get_local_string != nullptr) << dlerror();
+
+ ASSERT_STREQ("This string is local to root library", ns_get_local_string());
+
+ fn_t ns_get_private_extern_string =
+ reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_private_extern_string"));
+ ASSERT_TRUE(ns_get_private_extern_string != nullptr) << dlerror();
+
+ ASSERT_STREQ("This string is from private namespace", ns_get_private_extern_string());
+
+ fn_t ns_get_public_extern_string =
+ reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_public_extern_string"));
+ ASSERT_TRUE(ns_get_public_extern_string != nullptr) << dlerror();
+
+ ASSERT_STREQ("This string is from public namespace", ns_get_public_extern_string());
+
+ fn_t ns_get_dlopened_string = reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_dlopened_string"));
+ ASSERT_TRUE(ns_get_dlopened_string != nullptr) << dlerror();
+
+ ASSERT_STREQ("This string is from private namespace (dlopened library)", ns_get_dlopened_string());
+
+ dlclose(handle1);
+}
diff --git a/tests/libs/Android.build.linker_namespaces.mk b/tests/libs/Android.build.linker_namespaces.mk
new file mode 100644
index 0000000..f913780
--- /dev/null
+++ b/tests/libs/Android.build.linker_namespaces.mk
@@ -0,0 +1,84 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# -----------------------------------------------------------------------------
+# This set of libraries are used to verify linker namespaces.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# Test cases
+# 1. Check that private libraries loaded in different namespaces are
+# different. Check that dlsym does not confuse them.
+# 2. Check that public libraries loaded in different namespaces are shared
+# between them.
+# 3. Check that namespace sticks on dlopen
+#
+# Dependency tree (visibility)
+# libnstest_root.so (this should be local to the namespace)
+# +-> libnstest_public.so
+# +-> libnstest_private.so
+#
+# libnstest_dlopened.so (library in private namespace dlopened from libnstest_root.so)
+# -----------------------------------------------------------------------------
+libnstest_root_src_files := namespaces_root.cpp
+libnstest_root_shared_libraries := libnstest_public libnstest_private
+libnstest_root_install_to_out_data_dir := private_namespace_libs
+module := libnstest_root
+include $(LOCAL_PATH)/Android.build.target.testlib.mk
+
+libnstest_public_src_files := namespaces_public.cpp
+module := libnstest_public
+libnstest_public_install_to_out_data_dir := public_namespace_libs
+include $(LOCAL_PATH)/Android.build.target.testlib.mk
+
+libnstest_private_src_files := namespaces_private.cpp
+libnstest_private_install_to_out_data_dir := private_namespace_libs
+module := libnstest_private
+include $(LOCAL_PATH)/Android.build.target.testlib.mk
+
+libnstest_dlopened_src_files := namespaces_dlopened.cpp
+libnstest_dlopened_install_to_out_data_dir := private_namespace_libs
+module := libnstest_dlopened
+include $(LOCAL_PATH)/Android.build.target.testlib.mk
+
+# -----------------------------------------------------------------------------
+# This set of libraries is to test isolated namespaces
+#
+# Isolated namespaces do not allow loading of the library outside of
+# the search paths.
+#
+# This library cannot be loaded in isolated namespace because one of DT_NEEDED
+# libraries is outside of the search paths.
+#
+# libnstest_root_not_isolated.so (DT_RUNPATH = $ORIGIN/../private_namespace_libs_external/)
+# +-> libnstest_public.so
+# +-> libnstest_private_external.so (located in $ORIGIN/../private_namespace_libs_external/)
+#
+# Search path: $NATIVE_TESTS/private_namespace_libs/
+# -----------------------------------------------------------------------------
+libnstest_root_not_isolated_src_files := namespaces_root.cpp
+libnstest_root_not_isolated_shared_libraries := libnstest_public libnstest_private_external
+libnstest_root_not_isolated_install_to_out_data_dir := private_namespace_libs
+libnstest_root_not_isolated_ldflags := -Wl,--rpath,\$$ORIGIN/../private_namespace_libs_external \
+ -Wl,--enable-new-dtags
+
+module := libnstest_root_not_isolated
+include $(LOCAL_PATH)/Android.build.target.testlib.mk
+
+libnstest_private_external_src_files := namespaces_private.cpp
+libnstest_private_external_install_to_out_data_dir := private_namespace_libs_external
+module := libnstest_private_external
+include $(LOCAL_PATH)/Android.build.target.testlib.mk
diff --git a/tests/libs/Android.build.target.testlib.mk b/tests/libs/Android.build.target.testlib.mk
new file mode 100644
index 0000000..1e767c2
--- /dev/null
+++ b/tests/libs/Android.build.target.testlib.mk
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+build_target := SHARED_LIBRARY
+build_type := target
+include $(TEST_PATH)/Android.build.mk
+
diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk
index 3391d79..93d95ee 100644
--- a/tests/libs/Android.mk
+++ b/tests/libs/Android.mk
@@ -26,6 +26,7 @@
$(LOCAL_PATH)/Android.build.dlopen_check_order_dlsym.mk \
$(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_siblings.mk \
$(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_main_executable.mk \
+ $(LOCAL_PATH)/Android.build.linker_namespaces.mk \
$(LOCAL_PATH)/Android.build.pthread_atfork.mk \
$(LOCAL_PATH)/Android.build.testlib.mk \
$(LOCAL_PATH)/Android.build.versioned_lib.mk \
@@ -213,6 +214,11 @@
include $(LOCAL_PATH)/Android.build.testlib.mk
# -----------------------------------------------------------------------------
+# Build test helper libraries for linker namespaces
+# -----------------------------------------------------------------------------
+include $(LOCAL_PATH)/Android.build.linker_namespaces.mk
+
+# -----------------------------------------------------------------------------
# Build DT_RUNPATH test helper libraries
# -----------------------------------------------------------------------------
include $(LOCAL_PATH)/Android.build.dt_runpath.mk
diff --git a/tests/libs/namespaces_dlopened.cpp b/tests/libs/namespaces_dlopened.cpp
new file mode 100644
index 0000000..9d11689
--- /dev/null
+++ b/tests/libs/namespaces_dlopened.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const char* g_private_dlopened_string = "This string is from private namespace "
+ "(dlopened library)";
+
diff --git a/tests/libs/namespaces_private.cpp b/tests/libs/namespaces_private.cpp
new file mode 100644
index 0000000..07cab70
--- /dev/null
+++ b/tests/libs/namespaces_private.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const char* g_private_extern_string = "This string is from private namespace";
+
diff --git a/tests/libs/namespaces_public.cpp b/tests/libs/namespaces_public.cpp
new file mode 100644
index 0000000..bb2a8de
--- /dev/null
+++ b/tests/libs/namespaces_public.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const char* g_public_extern_string = "This string is from public namespace";
+
diff --git a/tests/libs/namespaces_root.cpp b/tests/libs/namespaces_root.cpp
new file mode 100644
index 0000000..0bb4611
--- /dev/null
+++ b/tests/libs/namespaces_root.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dlfcn.h>
+
+static const char* g_local_string = "This string is local to root library";
+extern "C" const char* g_private_extern_string;
+extern "C" const char* g_public_extern_string;
+
+bool g_dlopened = false;
+
+extern "C" const char* ns_get_local_string() {
+ return g_local_string;
+}
+
+extern "C" const char* ns_get_private_extern_string() {
+ return g_private_extern_string;
+}
+
+extern "C" const char* ns_get_public_extern_string() {
+ return g_public_extern_string;
+}
+
+extern "C" const char* ns_get_dlopened_string() {
+ void* handle = dlopen("libnstest_dlopened.so", RTLD_NOW | RTLD_GLOBAL);
+ if (handle == nullptr) {
+ return nullptr;
+ }
+
+ const char* result = *static_cast<const char**>(dlsym(handle, "g_private_dlopened_string"));
+ if (result != nullptr) {
+ g_dlopened = true;
+ }
+
+ return result;
+}