Merge "Reland "Use the dynamic table instead of __rela?_iplt_* to find the linker's IRELATIVE relocs." with a fix."
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 2a690e9..9e5be34 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -39,6 +39,7 @@
 #include "linker_globals.h"
 #include "linker_phdr.h"
 #include "linker_relocate.h"
+#include "linker_relocs.h"
 #include "linker_tls.h"
 #include "linker_utils.h"
 
@@ -596,31 +597,66 @@
   }
 }
 
-// TODO: There is a similar ifunc resolver calling loop in libc_init_static.cpp, but that version
-// uses weak symbols, which don't work in the linker prior to its relocation. This version also
-// supports a load bias. When we stop supporting the gold linker in the NDK, then maybe we can use
-// non-weak definitions and merge the two loops.
 #if defined(USE_RELA)
-extern __LIBC_HIDDEN__ ElfW(Rela) __rela_iplt_start[], __rela_iplt_end[];
-
-static void call_ifunc_resolvers(ElfW(Addr) load_bias) {
-  for (ElfW(Rela) *r = __rela_iplt_start; r != __rela_iplt_end; ++r) {
-    ElfW(Addr)* offset = reinterpret_cast<ElfW(Addr)*>(r->r_offset + load_bias);
-    ElfW(Addr) resolver = r->r_addend + load_bias;
-    *offset = __bionic_call_ifunc_resolver(resolver);
-  }
-}
+using RelType = ElfW(Rela);
+const unsigned kRelTag = DT_RELA;
+const unsigned kRelSzTag = DT_RELASZ;
 #else
-extern __LIBC_HIDDEN__ ElfW(Rel) __rel_iplt_start[], __rel_iplt_end[];
+using RelType = ElfW(Rel);
+const unsigned kRelTag = DT_REL;
+const unsigned kRelSzTag = DT_RELSZ;
+#endif
 
-static void call_ifunc_resolvers(ElfW(Addr) load_bias) {
-  for (ElfW(Rel) *r = __rel_iplt_start; r != __rel_iplt_end; ++r) {
-    ElfW(Addr)* offset = reinterpret_cast<ElfW(Addr)*>(r->r_offset + load_bias);
-    ElfW(Addr) resolver = *offset + load_bias;
+extern __LIBC_HIDDEN__ ElfW(Ehdr) __ehdr_start;
+
+static void call_ifunc_resolvers_for_section(RelType* begin, RelType* end) {
+  auto ehdr = reinterpret_cast<ElfW(Addr)>(&__ehdr_start);
+  for (RelType *r = begin; r != end; ++r) {
+    if (ELFW(R_TYPE)(r->r_info) != R_GENERIC_IRELATIVE) {
+      continue;
+    }
+    ElfW(Addr)* offset = reinterpret_cast<ElfW(Addr)*>(ehdr + r->r_offset);
+#if defined(USE_RELA)
+    ElfW(Addr) resolver = ehdr + r->r_addend;
+#else
+    ElfW(Addr) resolver = ehdr + *offset;
+#endif
     *offset = __bionic_call_ifunc_resolver(resolver);
   }
 }
-#endif
+
+static void call_ifunc_resolvers() {
+  // Find the IRELATIVE relocations using the DT_JMPREL and DT_PLTRELSZ, or DT_RELA? and DT_RELA?SZ
+  // dynamic tags.
+  auto ehdr = reinterpret_cast<ElfW(Addr)>(&__ehdr_start);
+  auto* phdr = reinterpret_cast<ElfW(Phdr)*>(ehdr + __ehdr_start.e_phoff);
+  for (size_t i = 0; i != __ehdr_start.e_phnum; ++i) {
+    if (phdr[i].p_type != PT_DYNAMIC) {
+      continue;
+    }
+    auto *dyn = reinterpret_cast<ElfW(Dyn)*>(ehdr + phdr[i].p_vaddr);
+    ElfW(Addr) pltrel = 0, pltrelsz = 0, rel = 0, relsz = 0;
+    for (size_t j = 0, size = phdr[i].p_filesz / sizeof(ElfW(Dyn)); j != size; ++j) {
+      if (dyn[j].d_tag == DT_JMPREL) {
+        pltrel = dyn[j].d_un.d_ptr;
+      } else if (dyn[j].d_tag == DT_PLTRELSZ) {
+        pltrelsz = dyn[j].d_un.d_ptr;
+      } else if (dyn[j].d_tag == kRelTag) {
+        rel = dyn[j].d_un.d_ptr;
+      } else if (dyn[j].d_tag == kRelSzTag) {
+        relsz = dyn[j].d_un.d_ptr;
+      }
+    }
+    if (pltrel && pltrelsz) {
+      call_ifunc_resolvers_for_section(reinterpret_cast<RelType*>(ehdr + pltrel),
+                                       reinterpret_cast<RelType*>(ehdr + pltrel + pltrelsz));
+    }
+    if (rel && relsz) {
+      call_ifunc_resolvers_for_section(reinterpret_cast<RelType*>(ehdr + rel),
+                                       reinterpret_cast<RelType*>(ehdr + rel + relsz));
+    }
+  }
+}
 
 // Usable before ifunc resolvers have been called. This function is compiled with -ffreestanding.
 static void linker_memclr(void* dst, size_t cnt) {
@@ -682,14 +718,13 @@
   ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);
 
   // string.h functions must not be used prior to calling the linker's ifunc resolvers.
-  const ElfW(Addr) load_bias = get_elf_exec_load_bias(elf_hdr);
-  call_ifunc_resolvers(load_bias);
+  call_ifunc_resolvers();
 
   soinfo tmp_linker_so(nullptr, nullptr, nullptr, 0, 0);
 
   tmp_linker_so.base = linker_addr;
   tmp_linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
-  tmp_linker_so.load_bias = load_bias;
+  tmp_linker_so.load_bias = get_elf_exec_load_bias(elf_hdr);
   tmp_linker_so.dynamic = nullptr;
   tmp_linker_so.phdr = phdr;
   tmp_linker_so.phnum = elf_hdr->e_phnum;