linker: find AT_BASE using AT_PHDR/AT_PHNUM
When the linker is invoked directly, rather than as an interpreter for a
real program, the AT_BASE value is 0. To find the linker's base address,
the linker currently relies on the static linker populating the target of
a RELA relocation with an offset rather than leaving it zero. (With lld,
it will require a special flag, --apply-dynamic-relocs.)
Instead, do something more straightforward: the linker already finds the
executable's base address using its PHDR table, so do the same thing when
the linker is run by itself.
Bug: http://b/72789859
Test: boots, run linker/linker64 by itself
Change-Id: I4da5c346ca164ea6f4fbc011f8c3db4e6a829456
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 43f12d3..3410f90 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -56,6 +56,9 @@
static ElfW(Addr) get_elf_exec_load_bias(const ElfW(Ehdr)* elf);
+static void get_elf_base_from_phdr(const ElfW(Phdr)* phdr_table, size_t phdr_count,
+ ElfW(Addr)* base, ElfW(Addr)* load_bias);
+
// These should be preserved static to avoid emitting
// RELATIVE relocations for the part of the code running
// before linker links itself.
@@ -321,24 +324,8 @@
si->phdr = reinterpret_cast<ElfW(Phdr)*>(args.getauxval(AT_PHDR));
si->phnum = args.getauxval(AT_PHNUM);
- /* Compute the value of si->base. We can't rely on the fact that
- * the first entry is the PHDR because this will not be true
- * for certain executables (e.g. some in the NDK unit test suite)
- */
- si->base = 0;
+ get_elf_base_from_phdr(si->phdr, si->phnum, &si->base, &si->load_bias);
si->size = phdr_table_get_load_size(si->phdr, si->phnum);
- si->load_bias = 0;
- for (size_t i = 0; i < si->phnum; ++i) {
- if (si->phdr[i].p_type == PT_PHDR) {
- si->load_bias = reinterpret_cast<ElfW(Addr)>(si->phdr) - si->phdr[i].p_vaddr;
- si->base = reinterpret_cast<ElfW(Addr)>(si->phdr) - si->phdr[i].p_offset;
- break;
- }
- }
-
- if (si->base == 0) {
- async_safe_fatal("Could not find a PHDR: broken executable?");
- }
si->dynamic = nullptr;
@@ -503,6 +490,23 @@
return 0;
}
+/* Find the load bias and base address of an executable or shared object loaded
+ * by the kernel. The ELF file's PHDR table must have a PT_PHDR entry.
+ *
+ * A VDSO doesn't have a PT_PHDR entry in its PHDR table.
+ */
+static void get_elf_base_from_phdr(const ElfW(Phdr)* phdr_table, size_t phdr_count,
+ ElfW(Addr)* base, ElfW(Addr)* load_bias) {
+ for (size_t i = 0; i < phdr_count; ++i) {
+ if (phdr_table[i].p_type == PT_PHDR) {
+ *load_bias = reinterpret_cast<ElfW(Addr)>(phdr_table) - phdr_table[i].p_vaddr;
+ *base = reinterpret_cast<ElfW(Addr)>(phdr_table) - phdr_table[i].p_offset;
+ return;
+ }
+ }
+ async_safe_fatal("Could not find a PHDR: broken executable?");
+}
+
static ElfW(Addr) __attribute__((noinline))
__linker_init_post_relocation(KernelArgumentBlock& args,
ElfW(Addr) linker_addr,
@@ -524,22 +528,17 @@
__libc_init_sysinfo(args);
#endif
- // AT_BASE is set to 0 in the case when linker is run by iself
- // so in order to link the linker it needs to calcuate AT_BASE
- // using information at hand. The trick below takes advantage
- // of the fact that the value of linktime_addr before relocations
- // are run is an offset and this can be used to calculate AT_BASE.
- static uintptr_t linktime_addr = reinterpret_cast<uintptr_t>(&linktime_addr);
- ElfW(Addr) linker_addr = reinterpret_cast<uintptr_t>(&linktime_addr) - linktime_addr;
-
-#if defined(__clang_analyzer__)
- // The analyzer assumes that linker_addr will always be null. Make it an
- // unknown value so we don't have to mark N places with NOLINTs.
- //
- // (`+=`, rather than `=`, allows us to sidestep a potential "unused store"
- // complaint)
- linker_addr += reinterpret_cast<uintptr_t>(raw_args);
-#endif
+ ElfW(Addr) linker_addr = args.getauxval(AT_BASE);
+ if (linker_addr == 0) {
+ // When the linker is run by itself (rather than as an interpreter for
+ // another program), AT_BASE is 0. In that case, the AT_PHDR and AT_PHNUM
+ // aux values describe the linker, so use the phdr to find the linker's
+ // base address.
+ ElfW(Addr) load_bias;
+ get_elf_base_from_phdr(
+ reinterpret_cast<ElfW(Phdr)*>(args.getauxval(AT_PHDR)), args.getauxval(AT_PHNUM),
+ &linker_addr, &load_bias);
+ }
ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr);
ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);