Merge "linker: Handle libraries with disjoint mappings correctly."
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 61cd818..56e85e4 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -415,11 +415,9 @@
 //
 // Intended to be called by libc's __gnu_Unwind_Find_exidx().
 _Unwind_Ptr do_dl_unwind_find_exidx(_Unwind_Ptr pc, int* pcount) {
-  for (soinfo* si = solist_get_head(); si != 0; si = si->next) {
-    if ((pc >= si->base) && (pc < (si->base + si->size))) {
-        *pcount = si->ARM_exidx_count;
-        return reinterpret_cast<_Unwind_Ptr>(si->ARM_exidx);
-    }
+  if (soinfo* si = find_containing_library(reinterpret_cast<void*>(pc))) {
+    *pcount = si->ARM_exidx_count;
+    return reinterpret_cast<_Unwind_Ptr>(si->ARM_exidx);
   }
   *pcount = 0;
   return 0;
@@ -938,8 +936,18 @@
 soinfo* find_containing_library(const void* p) {
   ElfW(Addr) address = reinterpret_cast<ElfW(Addr)>(p);
   for (soinfo* si = solist_get_head(); si != nullptr; si = si->next) {
-    if (address >= si->base && address - si->base < si->size) {
-      return si;
+    if (address < si->base || address - si->base >= si->size) {
+      continue;
+    }
+    ElfW(Addr) vaddr = address - si->load_bias;
+    for (size_t i = 0; i != si->phnum; ++i) {
+      const ElfW(Phdr)* phdr = &si->phdr[i];
+      if (phdr->p_type != PT_LOAD) {
+        continue;
+      }
+      if (vaddr >= phdr->p_vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) {
+        return si;
+      }
     }
   }
   return nullptr;
diff --git a/tests/Android.bp b/tests/Android.bp
index c1eaeec..ab11d47 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -642,6 +642,8 @@
         "libnstest_ns_a_public1_internal",
         "libnstest_ns_b_public2",
         "libnstest_ns_b_public3",
+        "libsegment_gap_inner",
+        "libsegment_gap_outer",
         "ld_preload_test_helper",
         "ld_preload_test_helper_lib1",
         "ld_preload_test_helper_lib2",
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index 8a3b6f3..ff5b1a9 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -49,6 +49,12 @@
 #pragma clang diagnostic pop
 #endif //  defined(__ANDROID__) && (defined(__arm__) || defined(__i386__))
 
+// Declared manually because the macro definitions in <elf.h> conflict with LLVM headers.
+#ifdef __arm__
+typedef uintptr_t _Unwind_Ptr;
+extern "C" _Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr, int*);
+#endif
+
 #define ASSERT_SUBSTR(needle, haystack) \
     ASSERT_PRED_FORMAT2(::testing::IsSubstring, needle, haystack)
 
@@ -1732,4 +1738,28 @@
   ASSERT_TRUE(handle != nullptr) << dlerror();
 }
 
+TEST(dlfcn, segment_gap) {
+  void* handle = dlopen("libsegment_gap_outer.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+
+  auto get_inner = reinterpret_cast<void* (*)()>(dlsym(handle, "get_inner"));
+  void* inner = get_inner();
+  (void)inner;
+
+#if __arm__
+  int count;
+  _Unwind_Ptr outer_exidx = dl_unwind_find_exidx(reinterpret_cast<_Unwind_Ptr>(get_inner), &count);
+  _Unwind_Ptr inner_exidx = dl_unwind_find_exidx(reinterpret_cast<_Unwind_Ptr>(inner), &count);
+  EXPECT_NE(0u, outer_exidx);
+  EXPECT_NE(0u, inner_exidx);
+  EXPECT_NE(inner_exidx, outer_exidx);
+#endif
+
+  Dl_info info;
+  int rc = dladdr(inner, &info);
+  ASSERT_NE(rc, 0);
+
+  EXPECT_NE(nullptr, strstr(info.dli_fname, "libsegment_gap_inner.so"));
+}
+
 #endif
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index d58b6b8..422cd75 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -918,3 +918,18 @@
     defaults: ["bionic_testlib_defaults"],
     srcs: ["exec_linker_helper_lib.cpp"],
 }
+
+cc_test_library {
+    name: "libsegment_gap_outer",
+    host_supported: false,
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["segment_gap_outer.cpp"],
+    ldflags: ["-Wl,-T,bionic/tests/libs/segment_gap_outer.lds"],
+}
+
+cc_test_library {
+    name: "libsegment_gap_inner",
+    host_supported: false,
+    defaults: ["bionic_testlib_defaults"],
+    srcs: ["segment_gap_inner.cpp"],
+}
diff --git a/tests/libs/segment_gap_inner.cpp b/tests/libs/segment_gap_inner.cpp
new file mode 100644
index 0000000..f23b065
--- /dev/null
+++ b/tests/libs/segment_gap_inner.cpp
@@ -0,0 +1 @@
+extern "C" void inner() {}
diff --git a/tests/libs/segment_gap_outer.cpp b/tests/libs/segment_gap_outer.cpp
new file mode 100644
index 0000000..fb448e7
--- /dev/null
+++ b/tests/libs/segment_gap_outer.cpp
@@ -0,0 +1,25 @@
+#include <android/dlext.h>
+#include <dlfcn.h>
+#include <jni.h>
+#include <stdlib.h>
+
+extern "C" void text_before_start_of_gap() {}
+char end_of_gap[0x1000];
+
+extern "C" void* get_inner() {
+  android_dlextinfo info = {};
+  info.flags = ANDROID_DLEXT_RESERVED_ADDRESS;
+
+  char* start_of_gap =
+      reinterpret_cast<char*>(reinterpret_cast<uintptr_t>(text_before_start_of_gap) & ~0xfffull) +
+      0x1000;
+  info.reserved_addr = start_of_gap;
+  info.reserved_size = end_of_gap - start_of_gap;
+
+  void *handle = android_dlopen_ext("libsegment_gap_inner.so", RTLD_NOW, &info);
+  if (!handle) {
+    __builtin_trap();
+  }
+
+  return dlsym(handle, "inner");
+}
diff --git a/tests/libs/segment_gap_outer.lds b/tests/libs/segment_gap_outer.lds
new file mode 100644
index 0000000..f326aab
--- /dev/null
+++ b/tests/libs/segment_gap_outer.lds
@@ -0,0 +1,27 @@
+SECTIONS {
+  # This starts off fairly normal: rodata, text, data, relro, bss with
+  # appropriate alignment between them.
+  . = SIZEOF_HEADERS;
+  .rodata : {}
+  . = ALIGN(0x1000);
+  .text : {}
+  . = ALIGN(0x1000);
+  .data : {}
+  . = ALIGN(0x1000);
+  .data.rel.ro : {}
+  . = ALIGN(0x1000);
+  .bss : {}
+
+  # Now create the gap. We need a text segment first to prevent the linker from
+  # merging .bss with .bss.end_of_gap.
+  . = ALIGN(0x1000);
+  .text.text_before_start_of_gap : {
+    *(.text.text_before_start_of_gap);
+  }
+
+  # Place end_of_gap at the end of the gap.
+  . = 0x1000000;
+  .bss.end_of_gap : {
+    *(.bss.end_of_gap);
+  }
+}