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;
+}