summaryrefslogtreecommitdiffstats
path: root/mozglue/linker/BaseElf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/linker/BaseElf.cpp')
-rw-r--r--mozglue/linker/BaseElf.cpp215
1 files changed, 215 insertions, 0 deletions
diff --git a/mozglue/linker/BaseElf.cpp b/mozglue/linker/BaseElf.cpp
new file mode 100644
index 000000000..8a78285ef
--- /dev/null
+++ b/mozglue/linker/BaseElf.cpp
@@ -0,0 +1,215 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BaseElf.h"
+#include "Elfxx.h"
+#include "Logging.h"
+#include "mozilla/RefPtr.h"
+
+using namespace Elf;
+using namespace mozilla;
+
+unsigned long
+BaseElf::Hash(const char *symbol)
+{
+ const unsigned char *sym = reinterpret_cast<const unsigned char *>(symbol);
+ unsigned long h = 0, g;
+ while (*sym) {
+ h = (h << 4) + *sym++;
+ g = h & 0xf0000000;
+ h ^= g;
+ h ^= g >> 24;
+ }
+ return h;
+}
+
+void *
+BaseElf::GetSymbolPtr(const char *symbol) const
+{
+ return GetSymbolPtr(symbol, Hash(symbol));
+}
+
+void *
+BaseElf::GetSymbolPtr(const char *symbol, unsigned long hash) const
+{
+ const Sym *sym = GetSymbol(symbol, hash);
+ void *ptr = nullptr;
+ if (sym && sym->st_shndx != SHN_UNDEF)
+ ptr = GetPtr(sym->st_value);
+ DEBUG_LOG("BaseElf::GetSymbolPtr(%p [\"%s\"], \"%s\") = %p",
+ reinterpret_cast<const void *>(this), GetPath(), symbol, ptr);
+ return ptr;
+}
+
+const Sym *
+BaseElf::GetSymbol(const char *symbol, unsigned long hash) const
+{
+ /* Search symbol with the buckets and chains tables.
+ * The hash computed from the symbol name gives an index in the buckets
+ * table. The corresponding value in the bucket table is an index in the
+ * symbols table and in the chains table.
+ * If the corresponding symbol in the symbols table matches, we're done.
+ * Otherwise, the corresponding value in the chains table is a new index
+ * in both tables, which corresponding symbol is tested and so on and so
+ * forth */
+ size_t bucket = hash % buckets.numElements();
+ for (size_t y = buckets[bucket]; y != STN_UNDEF; y = chains[y]) {
+ if (strcmp(symbol, strtab.GetStringAt(symtab[y].st_name)))
+ continue;
+ return &symtab[y];
+ }
+ return nullptr;
+}
+
+bool
+BaseElf::Contains(void *addr) const
+{
+ return base.Contains(addr);
+}
+
+#ifdef __ARM_EABI__
+const void *
+BaseElf::FindExidx(int *pcount) const
+{
+ if (arm_exidx) {
+ *pcount = arm_exidx.numElements();
+ return arm_exidx;
+ }
+ *pcount = 0;
+ return nullptr;
+}
+#endif
+
+already_AddRefed<LibHandle>
+LoadedElf::Create(const char *path, void *base_addr)
+{
+ DEBUG_LOG("LoadedElf::Create(\"%s\", %p) = ...", path, base_addr);
+
+ uint8_t mapped;
+ /* If the page is not mapped, mincore returns an error. If base_addr is
+ * nullptr, as would happen if the corresponding binary is prelinked with
+ * the prelink look (but not with the android apriori tool), no page being
+ * mapped there (right?), mincore returns an error, too, which makes
+ * prelinked libraries on glibc unsupported. This is not an interesting
+ * use case for now, so don't try supporting that case.
+ */
+ if (mincore(const_cast<void*>(base_addr), PageSize(), &mapped))
+ return nullptr;
+
+ RefPtr<LoadedElf> elf = new LoadedElf(path);
+
+ const Ehdr *ehdr = Ehdr::validate(base_addr);
+ if (!ehdr)
+ return nullptr;
+
+ Addr min_vaddr = (Addr) -1; // We want to find the lowest and biggest
+ Addr max_vaddr = 0; // virtual address used by this Elf.
+ const Phdr *dyn = nullptr;
+#ifdef __ARM_EABI__
+ const Phdr *arm_exidx_phdr = nullptr;
+#endif
+
+ Array<Phdr> phdrs(reinterpret_cast<const char *>(ehdr) + ehdr->e_phoff,
+ ehdr->e_phnum);
+ for (auto phdr = phdrs.begin(); phdr < phdrs.end(); ++phdr) {
+ switch (phdr->p_type) {
+ case PT_LOAD:
+ if (phdr->p_vaddr < min_vaddr)
+ min_vaddr = phdr->p_vaddr;
+ if (max_vaddr < phdr->p_vaddr + phdr->p_memsz)
+ max_vaddr = phdr->p_vaddr + phdr->p_memsz;
+ break;
+ case PT_DYNAMIC:
+ dyn = &*phdr;
+ break;
+#ifdef __ARM_EABI__
+ case PT_ARM_EXIDX:
+ /* We cannot initialize arm_exidx here
+ because we don't have a base yet */
+ arm_exidx_phdr = &*phdr;
+ break;
+#endif
+ }
+ }
+
+ /* If the lowest PT_LOAD virtual address in headers is not 0, then the ELF
+ * is either prelinked or a non-PIE executable. The former case is not
+ * possible, because base_addr would be nullptr and the mincore test above
+ * would already have made us return.
+ * For a non-PIE executable, PT_LOADs contain absolute addresses, so in
+ * practice, this means min_vaddr should be equal to base_addr. max_vaddr
+ * can thus be adjusted accordingly.
+ */
+ if (min_vaddr != 0) {
+ void *min_vaddr_ptr = reinterpret_cast<void *>(
+ static_cast<uintptr_t>(min_vaddr));
+ if (min_vaddr_ptr != base_addr) {
+ LOG("%s: %p != %p", elf->GetPath(), min_vaddr_ptr, base_addr);
+ return nullptr;
+ }
+ max_vaddr -= min_vaddr;
+ }
+ if (!dyn) {
+ LOG("%s: No PT_DYNAMIC segment found", elf->GetPath());
+ return nullptr;
+ }
+
+ elf->base.Assign(base_addr, max_vaddr);
+
+ if (!elf->InitDyn(dyn))
+ return nullptr;
+
+#ifdef __ARM_EABI__
+ if (arm_exidx_phdr)
+ elf->arm_exidx.InitSize(elf->GetPtr(arm_exidx_phdr->p_vaddr),
+ arm_exidx_phdr->p_memsz);
+#endif
+
+ DEBUG_LOG("LoadedElf::Create(\"%s\", %p) = %p", path, base_addr,
+ static_cast<void *>(elf));
+
+ ElfLoader::Singleton.Register(elf);
+ return elf.forget();
+}
+
+bool
+LoadedElf::InitDyn(const Phdr *pt_dyn)
+{
+ Array<Dyn> dyns;
+ dyns.InitSize(GetPtr<Dyn>(pt_dyn->p_vaddr), pt_dyn->p_filesz);
+
+ size_t symnum = 0;
+ for (auto dyn = dyns.begin(); dyn < dyns.end() && dyn->d_tag; ++dyn) {
+ switch (dyn->d_tag) {
+ case DT_HASH:
+ {
+ DEBUG_LOG("%s 0x%08" PRIxAddr, "DT_HASH", dyn->d_un.d_val);
+ const Elf::Word *hash_table_header = \
+ GetPtr<Elf::Word>(dyn->d_un.d_ptr);
+ symnum = hash_table_header[1];
+ buckets.Init(&hash_table_header[2], hash_table_header[0]);
+ chains.Init(&*buckets.end());
+ }
+ break;
+ case DT_STRTAB:
+ DEBUG_LOG("%s 0x%08" PRIxAddr, "DT_STRTAB", dyn->d_un.d_val);
+ strtab.Init(GetPtr(dyn->d_un.d_ptr));
+ break;
+ case DT_SYMTAB:
+ DEBUG_LOG("%s 0x%08" PRIxAddr, "DT_SYMTAB", dyn->d_un.d_val);
+ symtab.Init(GetPtr(dyn->d_un.d_ptr));
+ break;
+ }
+ }
+ if (!buckets || !symnum) {
+ ERROR("%s: Missing or broken DT_HASH", GetPath());
+ } else if (!strtab) {
+ ERROR("%s: Missing DT_STRTAB", GetPath());
+ } else if (!symtab) {
+ ERROR("%s: Missing DT_SYMTAB", GetPath());
+ } else {
+ return true;
+ }
+ return false;
+}