Fix logic in loading dependencies crossing namespace boundaries

This change addresses multiple problems introduced by
02586a2a34e6acfccf359b94db840f422b6c0231

1. In the case of unsuccessful dlopen the failure guard is triggered
for two namespaces which leads to double unload.

2. In the case where load_tasks includes libraries from 3 and more
namespaces it results in incorrect linking of libraries shared between
second and third/forth and so on namespaces.

The root cause of these problems was recursive call to find_libraries.
It does not do what it is expected to do. It does not form new load_tasks
list and immediately jumps to linking local_group. Not only this skips
reference counting it also will include unlinked but accessible library
from third (and fourth and fifth) namespaces in invalid local group. The
best case scenario here is that for 3 or more namesapces this will
fail to link. The worse case scenario it will link the library
incorrectly with will lead to very hard to catch bugs.

This change removes recursive call and replaces it with explicit list of
local_groups which should be linked. It also revisits the way we do
reference counting - with this change the reference counts are updated after
after libraries are successfully loaded.

Also update soinfo_free to abort in case when linker tries to free same
soinfo for the second time - this makes linker behavior less undefined.

Test: bionic-unit-tests
Bug: http://b/69787209
Change-Id: Iea25ced181a98c6503cce6e2b832c91d697342d5
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;
+}