Fix jump to unmapped memory on atexit

 Split d-tor calls and soinfo_free to 2 separate steps

Bug: 18338888
Change-Id: Idbcb7242ade16fa18cba7fe30505ebd8d6023622
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 11d7b94..1d68db5 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -1135,28 +1135,27 @@
   return si;
 }
 
-static void soinfo_unload(soinfo* si) {
+static void soinfo_unload_schedule(soinfo::soinfo_list_t& unload_list, soinfo* si) {
   if (!si->can_unload()) {
     TRACE("not unloading '%s' - the binary is flagged with NODELETE", si->name);
     return;
   }
 
   if (si->ref_count == 1) {
-    TRACE("unloading '%s'", si->name);
-    si->call_destructors();
+    unload_list.push_back(si);
 
     if (si->has_min_version(0)) {
       soinfo* child = nullptr;
       while ((child = si->get_children().pop_front()) != nullptr) {
         TRACE("%s needs to unload %s", si->name, child->name);
-        soinfo_unload(child);
+        soinfo_unload_schedule(unload_list, child);
       }
     } else {
       for_each_dt_needed(si, [&] (const char* library_name) {
         TRACE("deprecated (old format of soinfo): %s needs to unload %s", si->name, library_name);
         soinfo* needed = find_library(library_name, RTLD_NOLOAD, nullptr);
         if (needed != nullptr) {
-          soinfo_unload(needed);
+          soinfo_unload_schedule(unload_list, needed);
         } else {
           // Not found: for example if symlink was deleted between dlopen and dlclose
           // Since we cannot really handle errors at this point - print and continue.
@@ -1165,15 +1164,28 @@
       });
     }
 
-    notify_gdb_of_unload(si);
     si->ref_count = 0;
-    soinfo_free(si);
   } else {
     si->ref_count--;
     TRACE("not unloading '%s', decrementing ref_count to %zd", si->name, si->ref_count);
   }
 }
 
+static void soinfo_unload(soinfo* root) {
+  soinfo::soinfo_list_t unload_list;
+  soinfo_unload_schedule(unload_list, root);
+  unload_list.for_each([](soinfo* si) {
+    si->call_destructors();
+  });
+
+  soinfo* si = nullptr;
+  while ((si = unload_list.pop_front()) != nullptr) {
+    TRACE("unloading '%s'", si->name);
+    notify_gdb_of_unload(si);
+    soinfo_free(si);
+  }
+}
+
 void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
   // Use basic string manipulation calls to avoid snprintf.
   // snprintf indirectly calls pthread_getspecific to get the size of a buffer.
diff --git a/tests/libs/dlopen_check_order_reloc_nephew_answer.cpp b/tests/libs/dlopen_check_order_reloc_nephew_answer.cpp
index 065d1be..d6d1f09 100644
--- a/tests/libs/dlopen_check_order_reloc_nephew_answer.cpp
+++ b/tests/libs/dlopen_check_order_reloc_nephew_answer.cpp
@@ -19,3 +19,19 @@
 extern "C" int check_order_reloc_nephew_get_answer() {
   return check_order_reloc_get_answer_impl();
 }
+
+namespace {
+// The d-tor for this class is called on dlclose() -> __on_dlclose() -> __cxa_finalize()
+// We use it to detect calls to prematurely unmapped libraries during dlclose.
+// See also b/18338888
+class CallNephewInDtor {
+ public:
+  ~CallNephewInDtor() {
+    check_order_reloc_get_answer_impl();
+  }
+} instance;
+};
+
+extern "C" void* get_instance() {
+  return &instance;
+}