Merge "Fix SYSCALLS.TXT now clock_getres comes from the vdso."
diff --git a/libc/Android.bp b/libc/Android.bp
index 3ec75ee..a02139a 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -2036,6 +2036,7 @@
     license: "NOTICE",
 }
 
+// Not actually used in the NDK, but needed to build AOSP for mips.
 ndk_headers {
     name: "libc_asm_mips",
     from: "kernel/uapi/asm-mips",
@@ -2044,6 +2045,7 @@
     license: "NOTICE",
 }
 
+// Not actually used in the NDK, but needed to build AOSP for mips64.
 ndk_headers {
     name: "libc_asm_mips64",
     from: "kernel/uapi/asm-mips",
diff --git a/linker/linker.cpp b/linker/linker.cpp
index abfd00f..4ad44fa 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -328,9 +328,7 @@
   TRACE("name %s: freeing soinfo @ %p", si->get_realpath(), si);
 
   if (!solist_remove_soinfo(si)) {
-    // TODO (dimitry): revisit this - for now preserving the logic
-    // but it does not look right, abort if soinfo is not in the list instead?
-    return;
+    async_safe_fatal("soinfo=%p is not in soinfo_list (double unload?)", si);
   }
 
   // clear links to/from si
@@ -718,13 +716,11 @@
 // walk_dependencies_tree returns false if walk was terminated
 // by the action and true otherwise.
 template<typename F>
-static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, F action) {
+static bool walk_dependencies_tree(soinfo* root_soinfo, F action) {
   SoinfoLinkedList visit_list;
   SoinfoLinkedList visited;
 
-  for (size_t i = 0; i < root_soinfos_size; ++i) {
-    visit_list.push_back(root_soinfos[i]);
-  }
+  visit_list.push_back(root_soinfo);
 
   soinfo* si;
   while ((si = visit_list.pop_front()) != nullptr) {
@@ -760,7 +756,7 @@
   const ElfW(Sym)* result = nullptr;
   bool skip_lookup = skip_until != nullptr;
 
-  walk_dependencies_tree(&root, 1, [&](soinfo* current_soinfo) {
+  walk_dependencies_tree(root, [&](soinfo* current_soinfo) {
     if (skip_lookup) {
       skip_lookup = current_soinfo != skip_until;
       return kWalkContinue;
@@ -1478,7 +1474,6 @@
           if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks, rtld_flags, false)) {
             return true;
           }
-          // lib was not found in the namespace. Try next linked namespace.
         } else {
           // lib is already loaded
           return true;
@@ -1491,7 +1486,6 @@
 }
 
 static void soinfo_unload(soinfo* si);
-static void soinfo_unload(soinfo* soinfos[], size_t count);
 
 static void shuffle(std::vector<LoadTask*>* v) {
   for (size_t i = 0, size = v->size(); i < size; ++i) {
@@ -1516,9 +1510,9 @@
                     const android_dlextinfo* extinfo,
                     bool add_as_children,
                     bool search_linked_namespaces,
-                    std::unordered_map<const soinfo*, ElfReader>& readers_map,
                     std::vector<android_namespace_t*>* namespaces) {
   // Step 0: prepare.
+  std::unordered_map<const soinfo*, ElfReader> readers_map;
   LoadTaskList load_tasks;
 
   for (size_t i = 0; i < library_names_count; ++i) {
@@ -1548,11 +1542,6 @@
     }
   });
 
-  auto failure_guard = android::base::make_scope_guard([&]() {
-    // Housekeeping
-    soinfo_unload(soinfos, soinfos_count);
-  });
-
   ZipArchiveCache zip_archive_cache;
 
   // Step 1: expand the list of load_tasks to include
@@ -1565,7 +1554,6 @@
     task->set_extinfo(is_dt_needed ? nullptr : extinfo);
     task->set_dt_needed(is_dt_needed);
 
-    // try to find the load.
     // Note: start from the namespace that is stored in the LoadTask. This namespace
     // is different from the current namespace when the LoadTask is for a transitive
     // dependency and the lib that created the LoadTask is not found in the
@@ -1583,10 +1571,6 @@
 
     if (is_dt_needed) {
       needed_by->add_child(si);
-
-      if (si->is_linked()) {
-        si->increment_ref_count();
-      }
     }
 
     // When ld_preloads is not null, the first
@@ -1662,77 +1646,116 @@
     }
   }
 
-  // Step 5: link libraries that are not destined to this namespace.
-  // Do this by recursively calling find_libraries on the namespace where the lib
-  // was found during Step 1.
+  // Step 5: Collect roots of local_groups.
+  // Whenever needed_by->si link crosses a namespace boundary it forms its own local_group.
+  // Here we collect new roots to link them separately later on. Note that we need to avoid
+  // collecting duplicates. Also the order is important. They need to be linked in the same
+  // BFS order we link individual libraries.
+  std::vector<soinfo*> local_group_roots;
+  if (start_with != nullptr && add_as_children) {
+    local_group_roots.push_back(start_with);
+  } else {
+    CHECK(soinfos_count == 1);
+    local_group_roots.push_back(soinfos[0]);
+  }
+
   for (auto&& task : load_tasks) {
     soinfo* si = task->get_soinfo();
-    if (si->get_primary_namespace() != ns) {
-      const char* name = task->get_name();
-      if (find_libraries(si->get_primary_namespace(), task->get_needed_by(), &name, 1,
-                         nullptr /* soinfos */, nullptr /* ld_preloads */, 0 /* ld_preload_count */,
-                         rtld_flags, nullptr /* extinfo */, false /* add_as_children */,
-                         false /* search_linked_namespaces */, readers_map, namespaces)) {
-        // If this lib is directly needed by one of the libs in this namespace,
-        // then increment the count
-        soinfo* needed_by = task->get_needed_by();
-        if (needed_by != nullptr && needed_by->get_primary_namespace() == ns && si->is_linked()) {
-          si->increment_ref_count();
-        }
-      } else {
-        return false;
+    soinfo* needed_by = task->get_needed_by();
+    bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
+    android_namespace_t* needed_by_ns =
+        is_dt_needed ? needed_by->get_primary_namespace() : ns;
+
+    if (!si->is_linked() && si->get_primary_namespace() != needed_by_ns) {
+      auto it = std::find(local_group_roots.begin(), local_group_roots.end(), si);
+      LD_LOG(kLogDlopen,
+             "Crossing namespace boundary (si=%s@%p, si_ns=%s@%p, needed_by=%s@%p, ns=%s@%p, needed_by_ns=%s@%p) adding to local_group_roots: %s",
+             si->get_realpath(),
+             si,
+             si->get_primary_namespace()->get_name(),
+             si->get_primary_namespace(),
+             needed_by == nullptr ? "(nullptr)" : needed_by->get_realpath(),
+             needed_by,
+             ns->get_name(),
+             ns,
+             needed_by_ns->get_name(),
+             needed_by_ns,
+             it == local_group_roots.end() ? "yes" : "no");
+
+      if (it == local_group_roots.end()) {
+        local_group_roots.push_back(si);
       }
     }
   }
 
-  // Step 6: link libraries in this namespace
-  soinfo_list_t local_group;
-  walk_dependencies_tree(
-      (start_with != nullptr && add_as_children) ? &start_with : soinfos,
-      (start_with != nullptr && add_as_children) ? 1 : soinfos_count,
+  // Step 6: Link all local groups
+  for (auto root : local_group_roots) {
+    soinfo_list_t local_group;
+    android_namespace_t* local_group_ns = root->get_primary_namespace();
+
+    walk_dependencies_tree(root,
       [&] (soinfo* si) {
-    if (ns->is_accessible(si)) {
-      local_group.push_back(si);
-      return kWalkContinue;
-    } else {
-      return kWalkSkip;
-    }
-  });
+        if (local_group_ns->is_accessible(si)) {
+          local_group.push_back(si);
+          return kWalkContinue;
+        } else {
+          return kWalkSkip;
+        }
+      });
 
-  soinfo_list_t global_group = ns->get_global_group();
-  bool linked = local_group.visit([&](soinfo* si) {
-    if (!si->is_linked()) {
-      if (!si->link_image(global_group, local_group, extinfo) ||
-          !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
-        return false;
+    soinfo_list_t global_group = local_group_ns->get_global_group();
+    bool linked = local_group.visit([&](soinfo* si) {
+      // Even though local group may contain accessible soinfos from other namesapces
+      // we should avoid linking them (because if they are not linked -> they
+      // are in the local_group_roots and will be linked later).
+      if (!si->is_linked() && si->get_primary_namespace() == local_group_ns) {
+        if (!si->link_image(global_group, local_group, extinfo) ||
+            !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
+          return false;
+        }
       }
-    }
 
-    return true;
-  });
-
-  if (linked) {
-    local_group.for_each([](soinfo* si) {
-      if (!si->is_linked()) {
-        si->set_linked();
-      }
+      return true;
     });
 
-    failure_guard.Disable();
+    if (!linked) {
+      return false;
+    }
   }
 
-  return linked;
+  // Step 7: Mark all load_tasks as linked and increment refcounts
+  // for references between load_groups (at this point it does not matter if
+  // referenced load_groups were loaded by previous dlopen or as part of this
+  // one on step 6)
+  if (start_with != nullptr && add_as_children) {
+    start_with->set_linked();
+  }
+
+  for (auto&& task : load_tasks) {
+    soinfo* si = task->get_soinfo();
+    si->set_linked();
+  }
+
+  for (auto&& task : load_tasks) {
+    soinfo* si = task->get_soinfo();
+    soinfo* needed_by = task->get_needed_by();
+    if (needed_by != nullptr &&
+        needed_by != start_with &&
+        needed_by->get_local_group_root() != si->get_local_group_root()) {
+      si->increment_ref_count();
+    }
+  }
+
+
+  return true;
 }
 
 static soinfo* find_library(android_namespace_t* ns,
                             const char* name, int rtld_flags,
                             const android_dlextinfo* extinfo,
                             soinfo* needed_by) {
-  soinfo* si;
+  soinfo* si = nullptr;
 
-  // readers_map is shared across recursive calls to find_libraries.
-  // However, the map is not shared across different threads.
-  std::unordered_map<const soinfo*, ElfReader> readers_map;
   if (name == nullptr) {
     si = solist_get_somain();
   } else if (!find_libraries(ns,
@@ -1745,8 +1768,10 @@
                              rtld_flags,
                              extinfo,
                              false /* add_as_children */,
-                             true /* search_linked_namespaces */,
-                             readers_map)) {
+                             true /* search_linked_namespaces */)) {
+    if (si != nullptr) {
+      soinfo_unload(si);
+    }
     return nullptr;
   }
 
@@ -1755,18 +1780,26 @@
   return si;
 }
 
-static void soinfo_unload(soinfo* si) {
-  soinfo* root = si->is_linked() ? si->get_local_group_root() : si;
+static void soinfo_unload(soinfo* unload_si) {
+  // Note that the library can be loaded but not linked;
+  // in which case there is no root but we still need
+  // to walk the tree and unload soinfos involved.
+  //
+  // This happens on unsuccessful dlopen, when one of
+  // the DT_NEEDED libraries could not be linked/found.
+  bool is_linked = unload_si->is_linked();
+  soinfo* root = is_linked ? unload_si->get_local_group_root() : unload_si;
 
   LD_LOG(kLogDlopen,
          "... dlclose(realpath=\"%s\"@%p) ... load group root is \"%s\"@%p",
-         si->get_realpath(),
-         si,
+         unload_si->get_realpath(),
+         unload_si,
          root->get_realpath(),
          root);
 
   ScopedTrace trace((std::string("unload ") + root->get_realpath()).c_str());
 
+  size_t ref_count = is_linked ? root->decrement_ref_count() : 0;
   if (!root->can_unload()) {
     LD_LOG(kLogDlopen,
            "... dlclose(root=\"%s\"@%p) ... not unloading - the load group is flagged with NODELETE",
@@ -1775,48 +1808,17 @@
     return;
   }
 
-  soinfo_unload(&root, 1);
-}
-
-static void soinfo_unload(soinfo* soinfos[], size_t count) {
-  // Note that the library can be loaded but not linked;
-  // in which case there is no root but we still need
-  // to walk the tree and unload soinfos involved.
-  //
-  // This happens on unsuccessful dlopen, when one of
-  // the DT_NEEDED libraries could not be linked/found.
-  if (count == 0) {
+  if (ref_count > 0) {
+    LD_LOG(kLogDlopen,
+           "... dlclose(root=\"%s\"@%p) ... not unloading - decrementing ref_count to %zd",
+           root->get_realpath(),
+           root,
+           ref_count);
     return;
   }
 
   soinfo_list_t unload_list;
-  for (size_t i = 0; i < count; ++i) {
-    soinfo* si = soinfos[i];
-
-    if (si->can_unload()) {
-      size_t ref_count = si->is_linked() ? si->decrement_ref_count() : 0;
-      if (ref_count == 0) {
-        unload_list.push_back(si);
-      } else {
-        LD_LOG(kLogDlopen,
-               "... dlclose(root=\"%s\"@%p) ... not unloading - decrementing ref_count to %zd",
-               si->get_realpath(),
-               si,
-               ref_count);
-      }
-    } else {
-      LD_LOG(kLogDlopen,
-             "... dlclose(root=\"%s\"@%p) ... not unloading - the load group is flagged with NODELETE",
-             si->get_realpath(),
-             si);
-      return;
-    }
-  }
-
-  // This is used to identify soinfos outside of the load-group
-  // note that we cannot have > 1 in the array and have any of them
-  // linked. This is why we can safely use the first one.
-  soinfo* root = soinfos[0];
+  unload_list.push_back(root);
 
   soinfo_list_t local_unload_list;
   soinfo_list_t external_unload_list;
@@ -1900,12 +1902,17 @@
     soinfo_free(si);
   }
 
-  while ((si = external_unload_list.pop_front()) != nullptr) {
-    LD_LOG(kLogDlopen,
-           "... dlclose: unloading external reference \"%s\"@%p ...",
-           si->get_realpath(),
-           si);
-    soinfo_unload(si);
+  if (is_linked) {
+    while ((si = external_unload_list.pop_front()) != nullptr) {
+      LD_LOG(kLogDlopen,
+             "... dlclose: unloading external reference \"%s\"@%p ...",
+             si->get_realpath(),
+             si);
+      soinfo_unload(si);
+    }
+  } else {
+      LD_LOG(kLogDlopen,
+             "... dlclose: unload_si was not linked - not unloading external references ...");
   }
 }
 
@@ -3368,6 +3375,10 @@
 
 bool soinfo::link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group,
                         const android_dlextinfo* extinfo) {
+  if (is_image_linked()) {
+    // already linked.
+    return true;
+  }
 
   local_group_root_ = local_group.front();
   if (local_group_root_ == nullptr) {
@@ -3510,6 +3521,7 @@
   }
 
   notify_gdb_of_load(this);
+  set_image_linked();
   return true;
 }
 
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 317f0d2..dc1fa75 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -384,9 +384,6 @@
   const char** needed_library_names = &needed_library_name_list[0];
   size_t needed_libraries_count = needed_library_name_list.size();
 
-  // readers_map is shared across recursive calls to find_libraries so that we
-  // don't need to re-load elf headers.
-  std::unordered_map<const soinfo*, ElfReader> readers_map;
   if (needed_libraries_count > 0 &&
       !find_libraries(&g_default_namespace,
                       si,
@@ -399,7 +396,6 @@
                       nullptr,
                       true /* add_as_children */,
                       true /* search_linked_namespaces */,
-                      readers_map,
                       &namespaces)) {
     __linker_cannot_link(g_argv[0]);
   } else if (needed_libraries_count == 0) {
diff --git a/linker/linker_main.h b/linker/linker_main.h
index 2cf30c2..5641696 100644
--- a/linker/linker_main.h
+++ b/linker/linker_main.h
@@ -65,7 +65,6 @@
                     const android_dlextinfo* extinfo,
                     bool add_as_children,
                     bool search_linked_namespaces,
-                    std::unordered_map<const soinfo*, ElfReader>& readers_map,
                     std::vector<android_namespace_t*>* namespaces = nullptr);
 
 void solist_add_soinfo(soinfo* si);
diff --git a/linker/linker_soinfo.cpp b/linker/linker_soinfo.cpp
index fbff7cf..dd91752 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -657,6 +657,10 @@
   return (flags_ & FLAG_LINKED) != 0;
 }
 
+bool soinfo::is_image_linked() const {
+  return (flags_ & FLAG_IMAGE_LINKED) != 0;
+}
+
 bool soinfo::is_main_executable() const {
   return (flags_ & FLAG_EXE) != 0;
 }
@@ -669,6 +673,10 @@
   flags_ |= FLAG_LINKED;
 }
 
+void soinfo::set_image_linked() {
+  flags_ |= FLAG_IMAGE_LINKED;
+}
+
 void soinfo::set_linker_flag() {
   flags_ |= FLAG_LINKER;
 }
@@ -677,8 +685,8 @@
   flags_ |= FLAG_EXE;
 }
 
-void soinfo::increment_ref_count() {
-  local_group_root_->ref_count_++;
+size_t soinfo::increment_ref_count() {
+  return ++local_group_root_->ref_count_;
 }
 
 size_t soinfo::decrement_ref_count() {
diff --git a/linker/linker_soinfo.h b/linker/linker_soinfo.h
index 6f472b5..273c36c 100644
--- a/linker/linker_soinfo.h
+++ b/linker/linker_soinfo.h
@@ -41,6 +41,17 @@
 #define FLAG_GNU_HASH         0x00000040 // uses gnu hash
 #define FLAG_MAPPED_BY_CALLER 0x00000080 // the map is reserved by the caller
                                          // and should not be unmapped
+#define FLAG_IMAGE_LINKED     0x00000100 // Is image linked - this is a guard on link_image.
+                                         // The difference between this flag and
+                                         // FLAG_LINKED is that FLAG_LINKED
+                                         // means is set when load_group is
+                                         // successfully loaded whereas this
+                                         // flag is set to avoid linking image
+                                         // when link_image called for the
+                                         // second time. This situation happens
+                                         // when load group is crossing
+                                         // namespace boundary twice and second
+                                         // local group depends on the same libraries.
 #define FLAG_NEW_SOINFO       0x40000000 // new soinfo format
 
 #define SOINFO_VERSION 3
@@ -243,7 +254,7 @@
   void set_main_executable();
   void set_nodelete();
 
-  void increment_ref_count();
+  size_t increment_ref_count();
   size_t decrement_ref_count();
 
   soinfo* get_local_group_root() const;
@@ -273,6 +284,9 @@
   void* to_handle();
 
  private:
+  bool is_image_linked() const;
+  void set_image_linked();
+
   bool elf_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
   ElfW(Sym)* elf_addr_lookup(const void* addr);
   bool gnu_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index 249d341..04b83f2 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -1051,6 +1051,87 @@
             "\" wasn't loaded and RTLD_NOLOAD prevented it", dlerror());
 }
 
+TEST(dlext, ns_unload_between_namespaces_missing_symbol_direct) {
+  ASSERT_TRUE(android_init_anonymous_namespace(g_core_shared_libs.c_str(), nullptr));
+
+  const std::string public_ns_search_path =  get_testlib_root() + "/public_namespace_libs";
+  const std::string private_ns_search_path = get_testlib_root() + "/private_namespace_libs";
+
+  android_namespace_t* ns_public =
+          android_create_namespace("public",
+                                   nullptr,
+                                   public_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_public, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_namespace_t* ns_private =
+          android_create_namespace("private",
+                                   nullptr,
+                                   private_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_private, ns_public, "libtest_missing_symbol.so")) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_private, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_dlextinfo extinfo;
+  extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+  extinfo.library_namespace = ns_private;
+
+  void* handle = android_dlopen_ext((public_ns_search_path + "/libtest_missing_symbol.so").c_str(),
+                                    RTLD_NOW,
+                                    &extinfo);
+  ASSERT_TRUE(handle == nullptr);
+  ASSERT_EQ(std::string("dlopen failed: cannot locate symbol \"dlopen_testlib_missing_symbol\" referenced by \"") +
+            public_ns_search_path + "/libtest_missing_symbol.so\"...",
+            dlerror());
+}
+
+TEST(dlext, ns_unload_between_namespaces_missing_symbol_indirect) {
+  ASSERT_TRUE(android_init_anonymous_namespace(g_core_shared_libs.c_str(), nullptr));
+
+  const std::string public_ns_search_path =  get_testlib_root() + "/public_namespace_libs";
+  const std::string private_ns_search_path = get_testlib_root() + "/private_namespace_libs";
+
+  android_namespace_t* ns_public =
+          android_create_namespace("public",
+                                   nullptr,
+                                   public_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_public, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_namespace_t* ns_private =
+          android_create_namespace("private",
+                                   nullptr,
+                                   private_ns_search_path.c_str(),
+                                   ANDROID_NAMESPACE_TYPE_ISOLATED,
+                                   nullptr,
+                                   nullptr);
+
+  ASSERT_TRUE(android_link_namespaces(ns_private,
+                                      ns_public,
+                                      "libnstest_public.so:libtest_missing_symbol_child_public.so")
+              ) << dlerror();
+  ASSERT_TRUE(android_link_namespaces(ns_private, nullptr, g_core_shared_libs.c_str())) << dlerror();
+
+  android_dlextinfo extinfo;
+  extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
+  extinfo.library_namespace = ns_private;
+
+  void* handle = android_dlopen_ext("libtest_missing_symbol_root.so", RTLD_NOW, &extinfo);
+  ASSERT_TRUE(handle == nullptr);
+  ASSERT_EQ(std::string("dlopen failed: cannot locate symbol \"dlopen_testlib_missing_symbol\" referenced by \"") +
+            private_ns_search_path + "/libtest_missing_symbol_root.so\"...",
+            dlerror());
+}
+
 TEST(dlext, ns_greylist_enabled) {
   ASSERT_TRUE(android_init_anonymous_namespace(g_core_shared_libs.c_str(), nullptr));
 
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 8d0271a..e45eb6e 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -113,6 +113,62 @@
 }
 
 // -----------------------------------------------------------------------------
+// Library used by dlext direct unload on the namespace boundary tests
+// -----------------------------------------------------------------------------
+cc_test_library {
+    name: "libtest_missing_symbol",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["dlopen_testlib_missing_symbol.cpp"],
+    allow_undefined_symbols: true,
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+}
+
+
+// -----------------------------------------------------------------------------
+// Library used by dlext indirect unload on the namespace boundary tests
+//
+// These libraries produce following dependency graph:
+// libtest_missing_symbol_root (private ns)
+// +-> libbnstest_public (public ns)
+// +-> libtest_missing_symbol_child_public (public ns)
+//     +-> libnstest_public (public ns)
+// +-> libtest_missing_symbol_child_private (private_ns)
+//     +-> libnstest_public (public_ns)
+//
+// All libraries except libtest_missing_symbol are located in
+// private_namespace_libs/
+// -----------------------------------------------------------------------------
+cc_test_library {
+    name: "libtest_missing_symbol_child_public",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["empty.cpp"],
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+    shared_libs: ["libnstest_public"],
+}
+
+cc_test_library {
+    name: "libtest_missing_symbol_child_private",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["empty.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+    shared_libs: ["libnstest_public"],
+}
+
+cc_test_library {
+    name: "libtest_missing_symbol_root",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["dlopen_testlib_missing_symbol.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+    allow_undefined_symbols: true,
+    shared_libs: [
+        "libnstest_public",
+        "libtest_missing_symbol_child_public",
+        "libtest_missing_symbol_child_private",
+    ],
+}
+
+// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
 // Library used by dlfcn nodelete tests
 // -----------------------------------------------------------------------------
 cc_test_library {
@@ -142,8 +198,65 @@
 
 // -----------------------------------------------------------------------------
 // Build test helper libraries for linker namespaces
+//
+// This set of libraries is 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
+// 4. Check that having access to shared library (libnstest_public.so)
+//    does not expose symbols from dependent library (libnstest_public_internal.so)
+//
+// Dependency tree (visibility)
+// libnstest_root.so (this should be local to the namespace)
+// +-> libnstest_public.so
+//     +-> libnstest_public_internal.so
+// +-> libnstest_private.so
+//
+// libnstest_dlopened.so (library in private namespace dlopened from libnstest_root.so)
 // -----------------------------------------------------------------------------
-// include $(LOCAL_PATH)/Android.build.linker_namespaces.mk
+cc_test_library {
+    name: "libnstest_root",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_root.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+    shared_libs: [
+        "libnstest_public",
+        "libnstest_private",
+    ],
+}
+
+cc_test_library {
+    name: "libnstest_public_internal",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_public_internal.cpp"],
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+}
+
+cc_test_library {
+    name: "libnstest_public",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_public.cpp"],
+    relative_install_path: "bionic-loader-test-libs/public_namespace_libs",
+    shared_libs: ["libnstest_public_internal"],
+}
+
+cc_test_library {
+    name: "libnstest_private",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_private.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+}
+
+cc_test_library {
+    name: "libnstest_dlopened",
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["namespaces_dlopened.cpp"],
+    relative_install_path: "bionic-loader-test-libs/private_namespace_libs",
+}
 
 // -----------------------------------------------------------------------------
 // Build DT_RUNPATH test helper libraries
diff --git a/tests/libs/Android.build.linker_namespaces.mk b/tests/libs/Android.build.linker_namespaces.mk
index cd9d7f1..e2b01a7 100644
--- a/tests/libs/Android.build.linker_namespaces.mk
+++ b/tests/libs/Android.build.linker_namespaces.mk
@@ -19,51 +19,6 @@
 # -----------------------------------------------------------------------------
 
 # -----------------------------------------------------------------------------
-# 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
-# 4. Check that having access to shared library (libnstest_public.so)
-#    does not expose symbols from dependent library (libnstest_public_internal.so)
-#
-# Dependency tree (visibility)
-# libnstest_root.so (this should be local to the namespace)
-# +-> libnstest_public.so
-#     +-> libnstest_public_internal.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_relative_install_path := private_namespace_libs
-module := libnstest_root
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_public_internal_src_files := namespaces_public_internal.cpp
-module := libnstest_public_internal
-libnstest_public_internal_relative_install_path := public_namespace_libs
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_public_src_files := namespaces_public.cpp
-libnstest_public_shared_libraries := libnstest_public_internal
-module := libnstest_public
-libnstest_public_relative_install_path := public_namespace_libs
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_private_src_files := namespaces_private.cpp
-libnstest_private_relative_install_path := private_namespace_libs
-module := libnstest_private
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-libnstest_dlopened_src_files := namespaces_dlopened.cpp
-libnstest_dlopened_relative_install_path := private_namespace_libs
-module := libnstest_dlopened
-include $(LOCAL_PATH)/Android.build.testlib.target.mk
-
-# -----------------------------------------------------------------------------
 # This set of libraries is to test isolated namespaces
 #
 # Isolated namespaces do not allow loading of the library outside of
diff --git a/tests/libs/dlopen_testlib_missing_symbol.cpp b/tests/libs/dlopen_testlib_missing_symbol.cpp
new file mode 100644
index 0000000..0f73c60
--- /dev/null
+++ b/tests/libs/dlopen_testlib_missing_symbol.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+extern "C" void dlopen_testlib_missing_symbol();
+
+extern "C" bool dlopen_testlib_simple_func() {
+  dlopen_testlib_missing_symbol();
+  return true;
+}