Check for data races when reading JIT/DEX entries.

Update the entries only when the list is modified by the runtime.

Check that the list wasn't concurrently modified when being read.

Bug: 124287208
Test: libunwindstack_test
Test: art/test.py -b --host -r -t 137-cfi
Change-Id: I87ba70322053a01b3d5be1fdf6310e1dc21bb084
diff --git a/libunwindstack/JitDebug.cpp b/libunwindstack/JitDebug.cpp
index 20bc4b9..71665a1 100644
--- a/libunwindstack/JitDebug.cpp
+++ b/libunwindstack/JitDebug.cpp
@@ -16,8 +16,13 @@
 
 #include <stdint.h>
 #include <sys/mman.h>
+#include <cstddef>
 
+#include <atomic>
+#include <deque>
+#include <map>
 #include <memory>
+#include <unordered_set>
 #include <vector>
 
 #include <unwindstack/Elf.h>
@@ -25,197 +30,334 @@
 #include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
 
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+#include <DexFile.h>
+#endif
+
 // This implements the JIT Compilation Interface.
 // See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html
 
 namespace unwindstack {
 
-struct JITCodeEntry32Pack {
-  uint32_t next;
-  uint32_t prev;
-  uint32_t symfile_addr;
-  uint64_t symfile_size;
+// 32-bit platforms may differ in alignment of uint64_t.
+struct Uint64_P {
+  uint64_t value;
 } __attribute__((packed));
+struct Uint64_A {
+  uint64_t value;
+} __attribute__((aligned(8)));
 
-struct JITCodeEntry32Pad {
-  uint32_t next;
-  uint32_t prev;
-  uint32_t symfile_addr;
-  uint32_t pad;
-  uint64_t symfile_size;
+// Wrapper around other memory object which protects us against data races.
+// It will check seqlock after every read, and fail if the seqlock changed.
+// This ensues that the read memory has not been partially modified.
+struct JitMemory : public Memory {
+  size_t Read(uint64_t addr, void* dst, size_t size) override;
+
+  Memory* parent_ = nullptr;
+  uint64_t seqlock_addr_ = 0;
+  uint32_t expected_seqlock_ = 0;
+  bool failed_due_to_race_ = false;
 };
 
-struct JITCodeEntry64 {
-  uint64_t next;
-  uint64_t prev;
-  uint64_t symfile_addr;
-  uint64_t symfile_size;
+template <typename Symfile>
+struct JitCacheEntry {
+  // PC memory range described by this entry.
+  uint64_t addr_ = 0;
+  uint64_t size_ = 0;
+  std::unique_ptr<Symfile> symfile_;
+
+  bool Init(Maps* maps, JitMemory* memory, uint64_t addr, uint64_t size);
 };
 
-struct JITDescriptorHeader {
-  uint32_t version;
-  uint32_t action_flag;
+template <typename Symfile, typename PointerT, typename Uint64_T>
+class JitDebugImpl : public JitDebug<Symfile>, public Global {
+ public:
+  static constexpr const char* kDescriptorExtMagic = "Android1";
+  static constexpr int kMaxRaceRetries = 16;
+
+  struct JITCodeEntry {
+    PointerT next;
+    PointerT prev;
+    PointerT symfile_addr;
+    Uint64_T symfile_size;
+  };
+
+  struct JITDescriptor {
+    uint32_t version;
+    uint32_t action_flag;
+    PointerT relevant_entry;
+    PointerT first_entry;
+  };
+
+  // Android-specific extensions.
+  struct JITDescriptorExt {
+    JITDescriptor desc;
+    uint8_t magic[8];
+    uint32_t flags;
+    uint32_t sizeof_descriptor;
+    uint32_t sizeof_entry;
+    uint32_t action_seqlock;
+    uint64_t action_timestamp;
+  };
+
+  JitDebugImpl(ArchEnum arch, std::shared_ptr<Memory>& memory,
+               std::vector<std::string>& search_libs)
+      : Global(memory, search_libs) {
+    SetArch(arch);
+  }
+
+  Symfile* Get(Maps* maps, uint64_t pc) override;
+  virtual bool ReadVariableData(uint64_t offset);
+  virtual void ProcessArch() {}
+  bool Update(Maps* maps);
+  bool Read(Maps* maps, JitMemory* memory);
+
+  bool initialized_ = false;
+  uint64_t descriptor_addr_ = 0;  // Non-zero if we have found (non-empty) descriptor.
+  uint64_t seqlock_addr_ = 0;     // Re-read entries if the value at this address changes.
+  uint32_t last_seqlock_ = ~0u;   // The value of seqlock when we last read the entries.
+
+  std::deque<JitCacheEntry<Symfile>> entries_;
+
+  std::mutex lock_;
 };
 
-struct JITDescriptor32 {
-  JITDescriptorHeader header;
-  uint32_t relevant_entry;
-  uint32_t first_entry;
-};
-
-struct JITDescriptor64 {
-  JITDescriptorHeader header;
-  uint64_t relevant_entry;
-  uint64_t first_entry;
-};
-
-JitDebug::JitDebug(std::shared_ptr<Memory>& memory) : Global(memory) {}
-
-JitDebug::JitDebug(std::shared_ptr<Memory>& memory, std::vector<std::string>& search_libs)
-    : Global(memory, search_libs) {}
-
-JitDebug::~JitDebug() {
-  for (auto* elf : elf_list_) {
-    delete elf;
-  }
-}
-
-uint64_t JitDebug::ReadDescriptor32(uint64_t addr) {
-  JITDescriptor32 desc;
-  if (!memory_->ReadFully(addr, &desc, sizeof(desc))) {
-    return 0;
-  }
-
-  if (desc.header.version != 1 || desc.first_entry == 0) {
-    // Either unknown version, or no jit entries.
-    return 0;
-  }
-
-  return desc.first_entry;
-}
-
-uint64_t JitDebug::ReadDescriptor64(uint64_t addr) {
-  JITDescriptor64 desc;
-  if (!memory_->ReadFully(addr, &desc, sizeof(desc))) {
-    return 0;
-  }
-
-  if (desc.header.version != 1 || desc.first_entry == 0) {
-    // Either unknown version, or no jit entries.
-    return 0;
-  }
-
-  return desc.first_entry;
-}
-
-uint64_t JitDebug::ReadEntry32Pack(uint64_t* start, uint64_t* size) {
-  JITCodeEntry32Pack code;
-  if (!memory_->ReadFully(entry_addr_, &code, sizeof(code))) {
-    return 0;
-  }
-
-  *start = code.symfile_addr;
-  *size = code.symfile_size;
-  return code.next;
-}
-
-uint64_t JitDebug::ReadEntry32Pad(uint64_t* start, uint64_t* size) {
-  JITCodeEntry32Pad code;
-  if (!memory_->ReadFully(entry_addr_, &code, sizeof(code))) {
-    return 0;
-  }
-
-  *start = code.symfile_addr;
-  *size = code.symfile_size;
-  return code.next;
-}
-
-uint64_t JitDebug::ReadEntry64(uint64_t* start, uint64_t* size) {
-  JITCodeEntry64 code;
-  if (!memory_->ReadFully(entry_addr_, &code, sizeof(code))) {
-    return 0;
-  }
-
-  *start = code.symfile_addr;
-  *size = code.symfile_size;
-  return code.next;
-}
-
-void JitDebug::ProcessArch() {
-  switch (arch()) {
+template <typename Symfile>
+std::unique_ptr<JitDebug<Symfile>> JitDebug<Symfile>::Create(ArchEnum arch,
+                                                             std::shared_ptr<Memory>& memory,
+                                                             std::vector<std::string> search_libs) {
+  typedef JitDebugImpl<Symfile, uint32_t, Uint64_P> JitDebugImpl32P;
+  typedef JitDebugImpl<Symfile, uint32_t, Uint64_A> JitDebugImpl32A;
+  typedef JitDebugImpl<Symfile, uint64_t, Uint64_A> JitDebugImpl64A;
+  switch (arch) {
     case ARCH_X86:
-      read_descriptor_func_ = &JitDebug::ReadDescriptor32;
-      read_entry_func_ = &JitDebug::ReadEntry32Pack;
+      static_assert(sizeof(typename JitDebugImpl32P::JITCodeEntry) == 20, "layout");
+      static_assert(sizeof(typename JitDebugImpl32P::JITDescriptor) == 16, "layout");
+      static_assert(sizeof(typename JitDebugImpl32P::JITDescriptorExt) == 48, "layout");
+      return std::unique_ptr<JitDebug>(new JitDebugImpl32P(arch, memory, search_libs));
       break;
-
     case ARCH_ARM:
     case ARCH_MIPS:
-      read_descriptor_func_ = &JitDebug::ReadDescriptor32;
-      read_entry_func_ = &JitDebug::ReadEntry32Pad;
+      static_assert(sizeof(typename JitDebugImpl32A::JITCodeEntry) == 24, "layout");
+      static_assert(sizeof(typename JitDebugImpl32A::JITDescriptor) == 16, "layout");
+      static_assert(sizeof(typename JitDebugImpl32A::JITDescriptorExt) == 48, "layout");
+      return std::unique_ptr<JitDebug>(new JitDebugImpl32A(arch, memory, search_libs));
       break;
-
     case ARCH_ARM64:
     case ARCH_X86_64:
     case ARCH_MIPS64:
-      read_descriptor_func_ = &JitDebug::ReadDescriptor64;
-      read_entry_func_ = &JitDebug::ReadEntry64;
+      static_assert(sizeof(typename JitDebugImpl64A::JITCodeEntry) == 32, "layout");
+      static_assert(sizeof(typename JitDebugImpl64A::JITDescriptor) == 24, "layout");
+      static_assert(sizeof(typename JitDebugImpl64A::JITDescriptorExt) == 56, "layout");
+      return std::unique_ptr<JitDebug>(new JitDebugImpl64A(arch, memory, search_libs));
       break;
-    case ARCH_UNKNOWN:
+    default:
       abort();
   }
 }
 
-bool JitDebug::ReadVariableData(uint64_t ptr) {
-  entry_addr_ = (this->*read_descriptor_func_)(ptr);
-  return entry_addr_ != 0;
-}
-
-void JitDebug::Init(Maps* maps) {
-  if (initialized_) {
-    return;
+size_t JitMemory::Read(uint64_t addr, void* dst, size_t size) {
+  if (!parent_->ReadFully(addr, dst, size)) {
+    return 0;
   }
-  // Regardless of what happens below, consider the init finished.
-  initialized_ = true;
-
-  FindAndReadVariable(maps, "__jit_debug_descriptor");
+  // This is required for memory synchronization if the we are working with local memory.
+  // For other types of memory (e.g. remote) this is no-op and has no significant effect.
+  std::atomic_thread_fence(std::memory_order_acquire);
+  uint32_t seen_seqlock;
+  if (!parent_->Read32(seqlock_addr_, &seen_seqlock)) {
+    return 0;
+  }
+  if (seen_seqlock != expected_seqlock_) {
+    failed_due_to_race_ = true;
+    return 0;
+  }
+  return size;
 }
 
-Elf* JitDebug::GetElf(Maps* maps, uint64_t pc) {
-  // Use a single lock, this object should be used so infrequently that
-  // a fine grain lock is unnecessary.
+template <typename Symfile, typename PointerT, typename Uint64_T>
+bool JitDebugImpl<Symfile, PointerT, Uint64_T>::ReadVariableData(uint64_t addr) {
+  JITDescriptor desc;
+  if (!this->memory_->ReadFully(addr, &desc, sizeof(desc))) {
+    return false;
+  }
+  if (desc.version != 1) {
+    return false;
+  }
+  if (desc.first_entry == 0) {
+    return false;  // There could be multiple descriptors. Ignore empty ones.
+  }
+  descriptor_addr_ = addr;
+  JITDescriptorExt desc_ext;
+  if (this->memory_->ReadFully(addr, &desc_ext, sizeof(desc_ext)) &&
+      memcmp(desc_ext.magic, kDescriptorExtMagic, 8) == 0) {
+    seqlock_addr_ = descriptor_addr_ + offsetof(JITDescriptorExt, action_seqlock);
+  } else {
+    // In the absence of Android-specific fields, use the head pointer instead.
+    seqlock_addr_ = descriptor_addr_ + offsetof(JITDescriptor, first_entry);
+  }
+  return true;
+}
+
+template <typename Symfile>
+static const char* GetDescriptorName();
+
+template <>
+const char* GetDescriptorName<Elf>() {
+  return "__jit_debug_descriptor";
+}
+
+template <typename Symfile, typename PointerT, typename Uint64_T>
+Symfile* JitDebugImpl<Symfile, PointerT, Uint64_T>::Get(Maps* maps, uint64_t pc) {
   std::lock_guard<std::mutex> guard(lock_);
   if (!initialized_) {
-    Init(maps);
+    FindAndReadVariable(maps, GetDescriptorName<Symfile>());
+    initialized_ = true;
   }
 
-  // Search the existing elf object first.
-  for (Elf* elf : elf_list_) {
-    if (elf->IsValidPc(pc)) {
-      return elf;
-    }
+  if (descriptor_addr_ == 0) {
+    return nullptr;
   }
 
-  while (entry_addr_ != 0) {
-    uint64_t start;
-    uint64_t size;
-    entry_addr_ = (this->*read_entry_func_)(&start, &size);
+  if (!Update(maps)) {
+    return nullptr;
+  }
 
-    Elf* elf = new Elf(new MemoryRange(memory_, start, size, 0));
-    elf->Init();
-    if (!elf->valid()) {
-      // The data is not formatted in a way we understand, do not attempt
-      // to process any other entries.
-      entry_addr_ = 0;
-      delete elf;
-      return nullptr;
-    }
-    elf_list_.push_back(elf);
-
-    if (elf->IsValidPc(pc)) {
-      return elf;
+  Symfile* fallback = nullptr;
+  for (auto& entry : entries_) {
+    // Skip entries which are obviously not relevant (if we know the PC range).
+    if (entry.size_ == 0 || (entry.addr_ <= pc && (pc - entry.addr_) < entry.size_)) {
+      // Double check the entry contains the PC in case there are overlapping entries.
+      // This is might happen for native-code due to GC and for DEX due to data sharing.
+      std::string method_name;
+      uint64_t method_offset;
+      if (entry.symfile_->GetFunctionName(pc, &method_name, &method_offset)) {
+        return entry.symfile_.get();
+      }
+      fallback = entry.symfile_.get();  // Tests don't have any symbols.
     }
   }
-  return nullptr;
+  return fallback;  // Not found.
 }
 
+// Update JIT entries if needed.  It will retry if there are data races.
+template <typename Symfile, typename PointerT, typename Uint64_T>
+bool JitDebugImpl<Symfile, PointerT, Uint64_T>::Update(Maps* maps) {
+  // We might need to retry the whole read in the presence of data races.
+  for (int i = 0; i < kMaxRaceRetries; i++) {
+    // Read the seqlock (counter which is incremented before and after any modification).
+    uint32_t seqlock = 0;
+    if (!this->memory_->Read32(seqlock_addr_, &seqlock)) {
+      return false;  // Failed to read seqlock.
+    }
+
+    // Check if anything changed since the last time we checked.
+    if (last_seqlock_ != seqlock) {
+      // Create memory wrapper to allow us to read the entries safely even in a live process.
+      JitMemory safe_memory;
+      safe_memory.parent_ = this->memory_.get();
+      safe_memory.seqlock_addr_ = seqlock_addr_;
+      safe_memory.expected_seqlock_ = seqlock;
+      std::atomic_thread_fence(std::memory_order_acquire);
+
+      // Add all entries to our cache.
+      if (!Read(maps, &safe_memory)) {
+        if (safe_memory.failed_due_to_race_) {
+          sleep(0);
+          continue;  // Try again (there was a data race).
+        } else {
+          return false;  // Proper failure (we could not read the data).
+        }
+      }
+      last_seqlock_ = seqlock;
+    }
+    return true;
+  }
+  return false;  // Too many retries.
+}
+
+// Read all JIT entries.  It might randomly fail due to data races.
+template <typename Symfile, typename PointerT, typename Uint64_T>
+bool JitDebugImpl<Symfile, PointerT, Uint64_T>::Read(Maps* maps, JitMemory* memory) {
+  std::unordered_set<uint64_t> seen_entry_addr;
+
+  // Read and verify the descriptor (must be after we have read the initial seqlock).
+  JITDescriptor desc;
+  if (!(memory->ReadFully(descriptor_addr_, &desc, sizeof(desc)))) {
+    return false;
+  }
+
+  entries_.clear();
+  JITCodeEntry entry;
+  for (uint64_t entry_addr = desc.first_entry; entry_addr != 0; entry_addr = entry.next) {
+    // Check for infinite loops in the lined list.
+    if (!seen_entry_addr.emplace(entry_addr).second) {
+      return true;  // TODO: Fail when seening infinite loop.
+    }
+
+    // Read the entry (while checking for data races).
+    if (!memory->ReadFully(entry_addr, &entry, sizeof(entry))) {
+      return false;
+    }
+
+    // Copy and load the symfile.
+    entries_.emplace_back(JitCacheEntry<Symfile>());
+    if (!entries_.back().Init(maps, memory, entry.symfile_addr, entry.symfile_size.value)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// Copy and load ELF file.
+template <>
+bool JitCacheEntry<Elf>::Init(Maps*, JitMemory* memory, uint64_t addr, uint64_t size) {
+  // Make a copy of the in-memory symbol file (while checking for data races).
+  std::unique_ptr<MemoryBuffer> buffer(new MemoryBuffer());
+  buffer->Resize(size);
+  if (!memory->ReadFully(addr, buffer->GetPtr(0), buffer->Size())) {
+    return false;
+  }
+
+  // Load and validate the ELF file.
+  symfile_.reset(new Elf(buffer.release()));
+  symfile_->Init();
+  if (!symfile_->valid()) {
+    return false;
+  }
+
+  symfile_->GetTextRange(&addr_, &size_);
+  return true;
+}
+
+template std::unique_ptr<JitDebug<Elf>> JitDebug<Elf>::Create(ArchEnum, std::shared_ptr<Memory>&,
+                                                              std::vector<std::string>);
+
+#if !defined(NO_LIBDEXFILE_SUPPORT)
+
+template <>
+const char* GetDescriptorName<DexFile>() {
+  return "__dex_debug_descriptor";
+}
+
+// Copy and load DEX file.
+template <>
+bool JitCacheEntry<DexFile>::Init(Maps* maps, JitMemory* memory, uint64_t addr, uint64_t) {
+  MapInfo* info = maps->Find(addr);
+  if (info == nullptr) {
+    return false;
+  }
+  symfile_ = DexFile::Create(addr, memory, info);
+  if (symfile_ == nullptr) {
+    return false;
+  }
+  return true;
+}
+
+template std::unique_ptr<JitDebug<DexFile>> JitDebug<DexFile>::Create(ArchEnum,
+                                                                      std::shared_ptr<Memory>&,
+                                                                      std::vector<std::string>);
+
+#endif
+
 }  // namespace unwindstack