symbolize: Calculate a module's zero VA using program headers.
authorPeter Collingbourne <pcc@google.com>
Thu, 23 Jun 2016 01:18:06 +0000 (18:18 -0700)
committerPeter Collingbourne <pcc@google.com>
Thu, 23 Jun 2016 01:41:43 +0000 (18:41 -0700)
Previously we were using a module's "start address", i.e. the
address at which the module's executable region was mapped, as the
zero virtual address, i.e. the address from which the DSO's virtual
addresses are calculated. This works fine for DSOs created by the
bfd and gold linkers, which will emit a PT_LOAD directive into the
program header which loads the executable region at virtual address
(p_vaddr) and file offset (p_offset) 0.

However, the lld linker may place a read-only region before the
executable region, meaning that both p_vaddr and p_offset for the
executable region are non-zero. This means that any symbols resolved
by the symbolizer are resolved to an incorrect virtual address. To
correctly calculate the address corresponding to virtual address zero,
we need to take into account p_vaddr and p_offset.

Specifically, the calculation starts with the "base address", i.e. the
start address minus the file offset. To get from the base address to
virtual address zero, we first add p_offset. This gives us the mapped
address of the start of the segment, or in other words the mapped
address corresponding to the virtual address of the segment. (Note
that this is distinct from the start address, as p_offset is not
guaranteed to be page aligned.) We then subtract p_vaddr, which takes
us to virtual address zero.

src/symbolize.cc

index b18796e..f83c309 100644 (file)
@@ -327,7 +327,7 @@ FindSymbol(uint64_t pc, const int fd, char *out, int out_size,
 // false.
 static bool GetSymbolFromObjectFile(const int fd, uint64_t pc,
                                     char *out, int out_size,
-                                    uint64_t map_start_address) {
+                                    uint64_t map_base_address) {
   // Read the ELF header.
   ElfW(Ehdr) elf_header;
   if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) {
@@ -336,7 +336,28 @@ static bool GetSymbolFromObjectFile(const int fd, uint64_t pc,
 
   uint64_t symbol_offset = 0;
   if (elf_header.e_type == ET_DYN) {  // DSO needs offset adjustment.
-    symbol_offset = map_start_address;
+    ElfW(Phdr) phdr;
+    // We need to find the PT_LOAD segment corresponding to the read-execute
+    // file mapping in order to correctly perform the offset adjustment.
+    for (unsigned i = 0; i != elf_header.e_phnum; ++i) {
+      if (!ReadFromOffsetExact(fd, &phdr, sizeof(phdr),
+                               elf_header.e_phoff + i * sizeof(phdr)))
+        return false;
+      if (phdr.p_type == PT_LOAD &&
+          (phdr.p_flags & (PF_R | PF_X)) == (PF_R | PF_X)) {
+        // Find the mapped address corresponding to virtual address zero. We do
+        // this by first adding p_offset. This gives us the mapped address of
+        // the start of the segment, or in other words the mapped address
+        // corresponding to the virtual address of the segment. (Note that this
+        // is distinct from the start address, as p_offset is not guaranteed to
+        // be page aligned.) We then subtract p_vaddr, which takes us to virtual
+        // address zero.
+        symbol_offset = map_base_address + phdr.p_offset - phdr.p_vaddr;
+        break;
+      }
+    }
+    if (symbol_offset == 0)
+      return false;
   }
 
   ElfW(Shdr) symtab, strtab;
@@ -782,7 +803,7 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
     }
   }
   if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0,
-                               out, out_size, start_address)) {
+                               out, out_size, base_address)) {
     return false;
   }