From 007965d341349679607699d005c4af811b2c419a Mon Sep 17 00:00:00 2001
From: Andrew Jeffery <andrew@aj.id.au>
Date: Fri, 4 May 2018 11:23:53 +0930
Subject: [PATCH] symbol: Account for prelinked shared objects

Some projects, such as those derived from Yocto, tend to prelink their
binaries and libraries to reduce runtime overhead. Currently this trips
up pyflame in its symbol address calculations, and leads to ptrace
failures due to spurious addresses:

   $ pyflame -t python -c "print 'foo'"
   Unexpected ptrace(2) exception: Failed to PTRACE_PEEKDATA (pid 1482, addr 0x9f9b1d70): Input/output error
   Unexpected ptrace(2) exception: Failed to PTRACE_PEEKDATA (pid 1482, addr 0x9f9b1d70): Input/output error
   Unexpected ptrace(2) exception: Failed to PTRACE_PEEKDATA (pid 1482, addr 0x9f9b1d70): Input/output error
   Unexpected ptrace(2) exception: Failed to PTRACE_PEEKDATA (pid 1482, addr 0x9f9b1d70): Input/output error
   Unexpected ptrace(2) exception: Failed to PTRACE_PEEKDATA (pid 1482, addr 0x9f9b1d70): Input/output error
   ...

Add support for reading a prelinked base p_vaddr out of the ELF and
adjust the PyAddresses values accordingly.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
---
 src/symbol.cc | 15 +++++++++++++++
 src/symbol.h  | 29 ++++++++++++++++++++++++++++-
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/src/symbol.cc b/src/symbol.cc
index 125174efeeb5..39c3e8132dd1 100644
--- a/src/symbol.cc
+++ b/src/symbol.cc
@@ -166,6 +166,17 @@ PyABI ELF::WalkTable(int sym, int str, PyAddresses *addrs) {
   return abi;
 }
 
+addr_t ELF::GetBaseAddress() {
+  int32_t phnum = hdr()->e_phnum;
+  int32_t i;
+  for (i = 0; i < phnum && phdr(i)->p_type != PT_LOAD; i++) {
+  }
+  if (i == phnum) {
+    throw FatalException("Failed to find PT_LOAD entry in program headers");
+  }
+  return phdr(i)->p_vaddr;
+}
+
 PyAddresses ELF::GetAddresses(PyABI *abi) {
   PyAddresses addrs;
   PyABI detected_abi = WalkTable(dynsym_, dynstr_, &addrs);
@@ -176,6 +187,10 @@ PyAddresses ELF::GetAddresses(PyABI *abi) {
   if (abi != nullptr) {
     *abi = detected_abi;
   }
+  // Handle prelinked shared objects
+  if (hdr()->e_type == ET_DYN) {
+    return addrs - GetBaseAddress();
+  }
   return addrs;
 }
 }  // namespace pyflame
diff --git a/src/symbol.h b/src/symbol.h
index 124853bcc1c1..bb92b9a2604b 100644
--- a/src/symbol.h
+++ b/src/symbol.h
@@ -28,15 +28,19 @@
 
 #if USE_ELF64
 #define ehdr_t Elf64_Ehdr
+#define phdr_t Elf64_Phdr
 #define shdr_t Elf64_Shdr
 #define dyn_t Elf64_Dyn
 #define sym_t Elf64_Sym
+#define addr_t Elf64_Addr
 #define ARCH_ELFCLASS ELFCLASS64
 #else
 #define ehdr_t Elf32_Ehdr
+#define phdr_t Elf32_Phdr
 #define shdr_t Elf32_Shdr
 #define dyn_t Elf32_Dyn
 #define sym_t Elf32_Sym
+#define addr_t Elf32_Addr
 #define ARCH_ELFCLASS ELFCLASS32
 #endif
 
@@ -67,8 +71,18 @@ struct PyAddresses {
         interp_head_hint(0),
         pie(false) {}
 
+  PyAddresses operator-(const unsigned long base) const {
+    PyAddresses res(*this);
+    res.tstate_addr = this->tstate_addr == 0 ? 0 : this->tstate_addr - base;
+    res.interp_head_addr =
+        this->interp_head_addr == 0 ? 0 : this->interp_head_addr - base;
+    res.interp_head_fn_addr =
+        this->interp_head_fn_addr == 0 ? 0 : this->interp_head_fn_addr - base;
+    return res;
+  }
+
   PyAddresses operator+(const unsigned long base) const {
-    PyAddresses res;
+    PyAddresses res(*this);
     res.tstate_addr = this->tstate_addr == 0 ? 0 : this->tstate_addr + base;
     res.interp_head_addr =
         this->interp_head_addr == 0 ? 0 : this->interp_head_addr + base;
@@ -113,6 +127,9 @@ class ELF {
   // ABI.
   PyAddresses GetAddresses(PyABI *abi);
 
+  // Extract the base load address from the Program Header table
+  addr_t GetBaseAddress();
+
  private:
   void *addr_;
   size_t length_;
@@ -122,6 +139,16 @@ class ELF {
     return reinterpret_cast<const ehdr_t *>(addr_);
   }
 
+  inline const phdr_t *phdr(int idx) const {
+    if (idx < 0) {
+      std::ostringstream ss;
+      ss << "Illegal phdr index: " << idx;
+      throw FatalException(ss.str());
+    }
+    return reinterpret_cast<const phdr_t *>(p() + hdr()->e_phoff +
+                                            idx * hdr()->e_phentsize);
+  }
+
   inline const shdr_t *shdr(int idx) const {
     if (idx < 0) {
       std::ostringstream ss;
-- 
2.14.1

