Add timestamps to JIT/DEX native debug info.

This a forward-looking change intended to allow simpleperf to
reliably correlate samples and native debug information.

I have added the timestamps to both JIT and DEX, and refactored
the code in the process to avoid code duplication.

Test: testrunner.py -t 137
Change-Id: I45fa4310305aff540e036db9af15a86c5b8b7aff
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 17b94d3..ac5c6fb 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -77,7 +77,9 @@
     std::vector<uint8_t> elf_file = debug::WriteDebugElfFileForClasses(
         kRuntimeISA, jit_compiler->GetCompilerDriver()->GetInstructionSetFeatures(), types_array);
     MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
-    CreateJITCodeEntry(std::move(elf_file));
+    // We never free debug info for types, so we don't need to provide a handle
+    // (which would have been otherwise used as identifier to remove it later).
+    AddNativeDebugInfoForJit(nullptr /* handle */, elf_file);
   }
 }
 
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index b3f23a0..e42dfc1 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1411,13 +1411,12 @@
       mini_debug_info,
       ArrayRef<const debug::MethodDebugInfo>(&info, 1));
   MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
-  JITCodeEntry* entry = CreateJITCodeEntry(elf_file);
-  IncrementJITCodeEntryRefcount(entry, info.code_address);
+  AddNativeDebugInfoForJit(reinterpret_cast<const void*>(info.code_address), elf_file);
 
   VLOG(jit)
       << "JIT mini-debug-info added for " << ArtMethod::PrettyMethod(method)
       << " size=" << PrettySize(elf_file.size())
-      << " total_size=" << PrettySize(GetJITCodeEntryMemUsage());
+      << " total_size=" << PrettySize(GetJitNativeDebugInfoMemUsage());
 }
 
 }  // namespace art
diff --git a/runtime/Android.bp b/runtime/Android.bp
index e011c2e..4db9b74 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -18,7 +18,10 @@
 // we use gold as the linker (arm, x86, x86_64). The symbol is used by the debuggers to detect when
 // new jit code is generated. We don't want it to be called when a different function with the same
 // (empty) body is called.
-JIT_DEBUG_REGISTER_CODE_LDFLAGS = ["-Wl,--keep-unique,__jit_debug_register_code"]
+JIT_DEBUG_REGISTER_CODE_LDFLAGS = [
+    "-Wl,--keep-unique,__jit_debug_register_code",
+    "-Wl,--keep-unique,__dex_debug_register_code"
+]
 
 cc_defaults {
     name: "libart_defaults",
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index a4c32dd..8a24daa 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -1218,6 +1218,10 @@
     DCHECK(jni_function_table_lock_ == nullptr);
     jni_function_table_lock_ = new Mutex("JNI function table lock", current_lock_level);
 
+    UPDATE_CURRENT_LOCK_LEVEL(kNativeDebugInterfaceLock);
+    DCHECK(native_debug_interface_lock_ == nullptr);
+    native_debug_interface_lock_ = new Mutex("Native debug interface lock", current_lock_level);
+
     UPDATE_CURRENT_LOCK_LEVEL(kAbortLock);
     DCHECK(abort_lock_ == nullptr);
     abort_lock_ = new Mutex("abort lock", current_lock_level, true);
@@ -1230,10 +1234,6 @@
     DCHECK(unexpected_signal_lock_ == nullptr);
     unexpected_signal_lock_ = new Mutex("unexpected signal lock", current_lock_level, true);
 
-    UPDATE_CURRENT_LOCK_LEVEL(kNativeDebugInterfaceLock);
-    DCHECK(native_debug_interface_lock_ == nullptr);
-    native_debug_interface_lock_ = new Mutex("Native debug interface lock", current_lock_level);
-
     UPDATE_CURRENT_LOCK_LEVEL(kLoggingLock);
     DCHECK(logging_lock_ == nullptr);
     logging_lock_ = new Mutex("logging lock", current_lock_level, true);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index bf27b7f..4f7001a 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -58,11 +58,11 @@
 // [1] http://www.drdobbs.com/parallel/use-lock-hierarchies-to-avoid-deadlock/204801163
 enum LockLevel {
   kLoggingLock = 0,
-  kNativeDebugInterfaceLock,
   kSwapMutexesLock,
   kUnexpectedSignalLock,
   kThreadSuspendCountLock,
   kAbortLock,
+  kNativeDebugInterfaceLock,
   kSignalHandlingLock,
   kJdwpAdbStateLock,
   kJdwpSocketLock,
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index c4b1bf8..f7bd45a 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3433,7 +3433,8 @@
   data.weak_root = dex_cache_jweak;
   data.dex_file = dex_cache->GetDexFile();
   data.class_table = ClassTableForClassLoader(class_loader);
-  RegisterDexFileForNative(self, data.dex_file->Begin());
+  AddNativeDebugInfoForDex(self, ArrayRef<const uint8_t>(data.dex_file->Begin(),
+                                                         data.dex_file->Size()));
   DCHECK(data.class_table != nullptr);
   // Make sure to hold the dex cache live in the class table. This case happens for the boot class
   // path dex caches without an image.
diff --git a/runtime/elf_file_impl.h b/runtime/elf_file_impl.h
index 04c2243..3143df5 100644
--- a/runtime/elf_file_impl.h
+++ b/runtime/elf_file_impl.h
@@ -28,10 +28,6 @@
 
 namespace art {
 
-extern "C" {
-  struct JITCodeEntry;
-}
-
 template <typename ElfTypes>
 class ElfFileImpl {
  public:
diff --git a/runtime/jit/debugger_interface.cc b/runtime/jit/debugger_interface.cc
index d60f70a..505e626 100644
--- a/runtime/jit/debugger_interface.cc
+++ b/runtime/jit/debugger_interface.cc
@@ -18,18 +18,35 @@
 
 #include <android-base/logging.h>
 
+#include "base/array_ref.h"
 #include "base/mutex.h"
+#include "base/time_utils.h"
 #include "thread-current-inl.h"
 #include "thread.h"
 
 #include <unordered_map>
 
-namespace art {
+//
+// Debug interface for native tools (gdb, lldb, libunwind, simpleperf).
+//
+// See http://sourceware.org/gdb/onlinedocs/gdb/Declarations.html
+//
+// There are two ways for native tools to access the debug data safely:
+//
+// 1) Synchronously, by setting a breakpoint in the __*_debug_register_code
+//    method, which is called after every modification of the linked list.
+//    GDB does this, but it is complex to set up and it stops the process.
+//
+// 2) Asynchronously, by monitoring the action_counter_, which is incremented
+//    on every modification of the linked list and kept at -1 during updates.
+//    Therefore, if the tool process reads the counter both before and after
+//    iterating over the linked list, and the counters match and are not -1,
+//    the tool process can be sure the list was not modified during the read.
+//    Obviously, it can also cache the data and use the counter to determine
+//    if the cache is up to date, or to intelligently update it if needed.
+//
 
-// -------------------------------------------------------------------
-// Binary GDB JIT Interface as described in
-//   http://sourceware.org/gdb/onlinedocs/gdb/Declarations.html
-// -------------------------------------------------------------------
+namespace art {
 extern "C" {
   typedef enum {
     JIT_NOACTION = 0,
@@ -40,168 +57,193 @@
   struct JITCodeEntry {
     JITCodeEntry* next_;
     JITCodeEntry* prev_;
-    const uint8_t *symfile_addr_;
-    uint64_t symfile_size_;
-    uint32_t ref_count;  // ART internal field.
+    const uint8_t* symfile_addr_;
+    uint64_t symfile_size_;  // Beware of the offset (12 on x86; but 16 on ARM32).
+
+    // Android-specific fields:
+    uint64_t register_timestamp_;  // CLOCK_MONOTONIC time of entry registration.
   };
 
   struct JITDescriptor {
-    uint32_t version_;
-    uint32_t action_flag_;
-    JITCodeEntry* relevant_entry_;
-    JITCodeEntry* first_entry_;
+    uint32_t version_ = 1;                    // NB: GDB supports only version 1.
+    uint32_t action_flag_ = JIT_NOACTION;     // One of the JITAction enum values.
+    JITCodeEntry* relevant_entry_ = nullptr;  // The entry affected by the action.
+    JITCodeEntry* first_entry_ = nullptr;     // Head of link list of all entries.
+
+    // Android-specific fields:
+    uint8_t magic_[8] = {'A', 'n', 'd', 'r', 'o', 'i', 'd', '1'};
+    uint32_t flags_ = 0;                   // Reserved for future use. Must be 0.
+    uint32_t sizeof_descriptor = sizeof(JITDescriptor);
+    uint32_t sizeof_entry = sizeof(JITCodeEntry);
+    std::atomic_int32_t action_counter_;   // Number of actions, or -1 if locked.
+                                           // It can overflow from INT32_MAX to 0.
+    uint64_t action_timestamp_ = 1;        // CLOCK_MONOTONIC time of last action.
   };
 
-  // GDB will place breakpoint into this function.
-  // To prevent GCC from inlining or removing it we place noinline attribute
-  // and inline assembler statement inside.
-  void __attribute__((noinline)) __jit_debug_register_code();
+  // Check that std::atomic_int32_t has the same layout as int32_t.
+  static_assert(alignof(std::atomic_int32_t) == alignof(int32_t), "Weird alignment");
+  static_assert(sizeof(std::atomic_int32_t) == sizeof(int32_t), "Weird size");
+
+  // GDB may set breakpoint here. We must ensure it is not removed or deduplicated.
   void __attribute__((noinline)) __jit_debug_register_code() {
     __asm__("");
   }
 
-  // Call __jit_debug_register_code indirectly via global variable.
-  // This gives the debugger an easy way to inject custom code to handle the events.
+  // Alternatively, native tools may overwrite this field to execute custom handler.
   void (*__jit_debug_register_code_ptr)() = __jit_debug_register_code;
 
-  // GDB will inspect contents of this descriptor.
-  // Static initialization is necessary to prevent GDB from seeing
-  // uninitialized descriptor.
-  JITDescriptor __jit_debug_descriptor = { 1, JIT_NOACTION, nullptr, nullptr };
+  // The root data structure describing of all JITed methods.
+  JITDescriptor __jit_debug_descriptor {};
 
-  // Incremented whenever __jit_debug_descriptor is modified.
-  uint32_t __jit_debug_descriptor_timestamp = 0;
-
-  struct DEXFileEntry {
-    DEXFileEntry* next_;
-    DEXFileEntry* prev_;
-    const void* dexfile_;
-  };
-
-  DEXFileEntry* __art_debug_dexfiles = nullptr;
-
-  // Incremented whenever __art_debug_dexfiles is modified.
-  uint32_t __art_debug_dexfiles_timestamp = 0;
-}
-
-static size_t g_jit_debug_mem_usage
-    GUARDED_BY(Locks::native_debug_interface_lock_) = 0;
-
-static std::unordered_map<const void*, DEXFileEntry*> g_dexfile_entries
-    GUARDED_BY(Locks::native_debug_interface_lock_);
-
-void RegisterDexFileForNative(Thread* current_thread, const void* dexfile_header) {
-  MutexLock mu(current_thread, *Locks::native_debug_interface_lock_);
-  if (g_dexfile_entries.count(dexfile_header) == 0) {
-    DEXFileEntry* entry = new DEXFileEntry();
-    CHECK(entry != nullptr);
-    entry->dexfile_ = dexfile_header;
-    entry->prev_ = nullptr;
-    entry->next_ = __art_debug_dexfiles;
-    if (entry->next_ != nullptr) {
-      entry->next_->prev_ = entry;
-    }
-    __art_debug_dexfiles = entry;
-    __art_debug_dexfiles_timestamp++;
-    g_dexfile_entries.emplace(dexfile_header, entry);
+  // The following globals mirror the ones above, but are used to register dex files.
+  void __attribute__((noinline)) __dex_debug_register_code() {
+    __asm__("");
   }
+  void (*__dex_debug_register_code_ptr)() = __dex_debug_register_code;
+  JITDescriptor __dex_debug_descriptor {};
 }
 
-void DeregisterDexFileForNative(Thread* current_thread, const void* dexfile_header) {
-  MutexLock mu(current_thread, *Locks::native_debug_interface_lock_);
-  auto it = g_dexfile_entries.find(dexfile_header);
-  // We register dex files in the class linker and free them in DexFile_closeDexFile,
-  // but might be cases where we load the dex file without using it in the class linker.
-  if (it != g_dexfile_entries.end()) {
-    DEXFileEntry* entry = it->second;
-    if (entry->prev_ != nullptr) {
-      entry->prev_->next_ = entry->next_;
-    } else {
-      __art_debug_dexfiles = entry->next_;
-    }
-    if (entry->next_ != nullptr) {
-      entry->next_->prev_ = entry->prev_;
-    }
-    __art_debug_dexfiles_timestamp++;
-    delete entry;
-    g_dexfile_entries.erase(it);
-  }
+// Mark the descriptor as "locked", so native tools know the data is unstable.
+// Returns the old value of the counter.
+static int32_t LockActionCounter(JITDescriptor& descriptor) {
+  return descriptor.action_counter_.exchange(-1);
 }
 
-JITCodeEntry* CreateJITCodeEntry(const std::vector<uint8_t>& symfile) {
-  DCHECK_NE(symfile.size(), 0u);
+// Mark the descriptor as "unlocked", so native tools know the data is safe to read.
+// It will also increment the value so that the tools know the data has changed.
+static void UnlockActionCounter(JITDescriptor& descriptor, int32_t old_value) {
+  int32_t new_value = (old_value + 1) & 0x7FFFFFFF;  // Handle overflow to avoid -1.
+  descriptor.action_counter_.store(new_value);
+}
 
-  // Make a copy of the buffer. We want to shrink it anyway.
-  uint8_t* symfile_copy = new uint8_t[symfile.size()];
-  CHECK(symfile_copy != nullptr);
-  memcpy(symfile_copy, symfile.data(), symfile.size());
+static JITCodeEntry* CreateJITCodeEntryInternal(
+    JITDescriptor& descriptor,
+    void (*register_code_ptr)(),
+    const ArrayRef<const uint8_t>& symfile)
+    REQUIRES(Locks::native_debug_interface_lock_) {
+  int32_t old_action_counter = LockActionCounter(descriptor);
 
   JITCodeEntry* entry = new JITCodeEntry;
   CHECK(entry != nullptr);
-  entry->symfile_addr_ = symfile_copy;
+  entry->symfile_addr_ = symfile.data();
   entry->symfile_size_ = symfile.size();
   entry->prev_ = nullptr;
-  entry->ref_count = 0;
-  entry->next_ = __jit_debug_descriptor.first_entry_;
+  entry->next_ = descriptor.first_entry_;
+  entry->register_timestamp_ = NanoTime();
   if (entry->next_ != nullptr) {
     entry->next_->prev_ = entry;
   }
-  g_jit_debug_mem_usage += sizeof(JITCodeEntry) + entry->symfile_size_;
-  __jit_debug_descriptor.first_entry_ = entry;
-  __jit_debug_descriptor.relevant_entry_ = entry;
-  __jit_debug_descriptor.action_flag_ = JIT_REGISTER_FN;
-  __jit_debug_descriptor_timestamp++;
-  (*__jit_debug_register_code_ptr)();
+  descriptor.first_entry_ = entry;
+  descriptor.relevant_entry_ = entry;
+  descriptor.action_flag_ = JIT_REGISTER_FN;
+  descriptor.action_timestamp_ = entry->register_timestamp_;
+  UnlockActionCounter(descriptor, old_action_counter);
+
+  (*register_code_ptr)();
   return entry;
 }
 
-void DeleteJITCodeEntry(JITCodeEntry* entry) {
+static void DeleteJITCodeEntryInternal(
+    JITDescriptor& descriptor,
+    void (*register_code_ptr)(),
+    JITCodeEntry* entry)
+    REQUIRES(Locks::native_debug_interface_lock_) {
+  CHECK(entry != nullptr);
+  int32_t old_action_counter = LockActionCounter(descriptor);
+
   if (entry->prev_ != nullptr) {
     entry->prev_->next_ = entry->next_;
   } else {
-    __jit_debug_descriptor.first_entry_ = entry->next_;
+    descriptor.first_entry_ = entry->next_;
   }
-
   if (entry->next_ != nullptr) {
     entry->next_->prev_ = entry->prev_;
   }
 
-  g_jit_debug_mem_usage -= sizeof(JITCodeEntry) + entry->symfile_size_;
-  __jit_debug_descriptor.relevant_entry_ = entry;
-  __jit_debug_descriptor.action_flag_ = JIT_UNREGISTER_FN;
-  __jit_debug_descriptor_timestamp++;
-  (*__jit_debug_register_code_ptr)();
-  delete[] entry->symfile_addr_;
+  descriptor.relevant_entry_ = entry;
+  descriptor.action_flag_ = JIT_UNREGISTER_FN;
+  descriptor.action_timestamp_ = NanoTime();
+  UnlockActionCounter(descriptor, old_action_counter);
+
+  (*register_code_ptr)();
   delete entry;
 }
 
-// Mapping from code address to entry. Used to manage life-time of the entries.
-static std::unordered_map<uintptr_t, JITCodeEntry*> g_jit_code_entries
+static std::unordered_map<const void*, JITCodeEntry*> __dex_debug_entries
     GUARDED_BY(Locks::native_debug_interface_lock_);
 
-void IncrementJITCodeEntryRefcount(JITCodeEntry* entry, uintptr_t code_address) {
-  DCHECK(entry != nullptr);
-  DCHECK_EQ(g_jit_code_entries.count(code_address), 0u);
-  entry->ref_count++;
-  g_jit_code_entries.emplace(code_address, entry);
-}
-
-void DecrementJITCodeEntryRefcount(JITCodeEntry* entry, uintptr_t code_address) {
-  DCHECK(entry != nullptr);
-  DCHECK(g_jit_code_entries[code_address] == entry);
-  if (--entry->ref_count == 0) {
-    DeleteJITCodeEntry(entry);
+void AddNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile) {
+  MutexLock mu(current_thread, *Locks::native_debug_interface_lock_);
+  DCHECK(dexfile.data() != nullptr);
+  // This is just defensive check. The class linker should not register the dex file twice.
+  if (__dex_debug_entries.count(dexfile.data()) == 0) {
+    JITCodeEntry* entry = CreateJITCodeEntryInternal(__dex_debug_descriptor,
+                                                     __dex_debug_register_code_ptr,
+                                                     dexfile);
+    __dex_debug_entries.emplace(dexfile.data(), entry);
   }
-  g_jit_code_entries.erase(code_address);
 }
 
-JITCodeEntry* GetJITCodeEntry(uintptr_t code_address) {
-  auto it = g_jit_code_entries.find(code_address);
-  return it == g_jit_code_entries.end() ? nullptr : it->second;
+void RemoveNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile) {
+  MutexLock mu(current_thread, *Locks::native_debug_interface_lock_);
+  auto it = __dex_debug_entries.find(dexfile.data());
+  // We register dex files in the class linker and free them in DexFile_closeDexFile, but
+  // there might be cases where we load the dex file without using it in the class linker.
+  if (it != __dex_debug_entries.end()) {
+    DeleteJITCodeEntryInternal(__dex_debug_descriptor,
+                               __dex_debug_register_code_ptr,
+                               it->second);
+    __dex_debug_entries.erase(it);
+  }
 }
 
-size_t GetJITCodeEntryMemUsage() {
-  return g_jit_debug_mem_usage + g_jit_code_entries.size() * 2 * sizeof(void*);
+static size_t __jit_debug_mem_usage
+    GUARDED_BY(Locks::native_debug_interface_lock_) = 0;
+
+// Mapping from handle to entry. Used to manage life-time of the entries.
+static std::unordered_map<const void*, JITCodeEntry*> __jit_debug_entries
+    GUARDED_BY(Locks::native_debug_interface_lock_);
+
+void AddNativeDebugInfoForJit(const void* handle, const std::vector<uint8_t>& symfile) {
+  DCHECK_NE(symfile.size(), 0u);
+
+  // Make a copy of the buffer to shrink it and to pass ownership to JITCodeEntry.
+  uint8_t* copy = new uint8_t[symfile.size()];
+  CHECK(copy != nullptr);
+  memcpy(copy, symfile.data(), symfile.size());
+
+  JITCodeEntry* entry = CreateJITCodeEntryInternal(
+      __jit_debug_descriptor,
+      __jit_debug_register_code_ptr,
+      ArrayRef<const uint8_t>(copy, symfile.size()));
+  __jit_debug_mem_usage += sizeof(JITCodeEntry) + entry->symfile_size_;
+
+  // We don't provide handle for type debug info, which means we cannot free it later.
+  // (this only happens when --generate-debug-info flag is enabled for the purpose
+  // of being debugged with gdb; it does not happen for debuggable apps by default).
+  bool ok = handle == nullptr || __jit_debug_entries.emplace(handle, entry).second;
+  DCHECK(ok) << "Native debug entry already exists for " << std::hex << handle;
+}
+
+void RemoveNativeDebugInfoForJit(const void* handle) {
+  auto it = __jit_debug_entries.find(handle);
+  // We generate JIT native debug info only if the right runtime flags are enabled,
+  // but we try to remove it unconditionally whenever code is freed from JIT cache.
+  if (it != __jit_debug_entries.end()) {
+    JITCodeEntry* entry = it->second;
+    const uint8_t* symfile_addr = entry->symfile_addr_;
+    uint64_t symfile_size = entry->symfile_size_;
+    DeleteJITCodeEntryInternal(__jit_debug_descriptor,
+                               __jit_debug_register_code_ptr,
+                               entry);
+    __jit_debug_entries.erase(it);
+    __jit_debug_mem_usage -= sizeof(JITCodeEntry) + symfile_size;
+    delete[] symfile_addr;
+  }
+}
+
+size_t GetJitNativeDebugInfoMemUsage() {
+  return __jit_debug_mem_usage + __jit_debug_entries.size() * 2 * sizeof(void*);
 }
 
 }  // namespace art
diff --git a/runtime/jit/debugger_interface.h b/runtime/jit/debugger_interface.h
index 8c4bb3f..3d25910 100644
--- a/runtime/jit/debugger_interface.h
+++ b/runtime/jit/debugger_interface.h
@@ -26,45 +26,26 @@
 
 namespace art {
 
-extern "C" {
-  struct JITCodeEntry;
-}
-
 // Notify native tools (e.g. libunwind) that DEX file has been opened.
-// The pointer needs to point the start of the dex data (not the DexFile* object).
-void RegisterDexFileForNative(Thread* current_thread, const void* dexfile_header);
+// It takes the lock itself. The parameter must point to dex data (not the DexFile* object).
+void AddNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile);
 
 // Notify native tools (e.g. libunwind) that DEX file has been closed.
-// The pointer needs to point the start of the dex data (not the DexFile* object).
-void DeregisterDexFileForNative(Thread* current_thread, const void* dexfile_header);
+// It takes the lock itself. The parameter must point to dex data (not the DexFile* object).
+void RemoveNativeDebugInfoForDex(Thread* current_thread, ArrayRef<const uint8_t> dexfile);
 
-// Notify native debugger about new JITed code by passing in-memory ELF.
-// It takes ownership of the in-memory ELF file.
-JITCodeEntry* CreateJITCodeEntry(const std::vector<uint8_t>& symfile)
+// Notify native tools about new JITed code by passing in-memory ELF.
+// The handle is the object that is being described (needed to be able to remove the entry).
+// The method will make copy of the passed ELF file (to shrink it to the minimum size).
+void AddNativeDebugInfoForJit(const void* handle, const std::vector<uint8_t>& symfile)
     REQUIRES(Locks::native_debug_interface_lock_);
 
-// Notify native debugger that JITed code has been removed.
-// It also releases the associated in-memory ELF file.
-void DeleteJITCodeEntry(JITCodeEntry* entry)
-    REQUIRES(Locks::native_debug_interface_lock_);
-
-// Helper method to track life-time of JITCodeEntry.
-// It registers given code address as being described by the given entry.
-void IncrementJITCodeEntryRefcount(JITCodeEntry* entry, uintptr_t code_address)
-    REQUIRES(Locks::native_debug_interface_lock_);
-
-// Helper method to track life-time of JITCodeEntry.
-// It de-registers given code address as being described by the given entry.
-void DecrementJITCodeEntryRefcount(JITCodeEntry* entry, uintptr_t code_address)
-    REQUIRES(Locks::native_debug_interface_lock_);
-
-// Find the registered JITCodeEntry for given code address.
-// There can be only one entry per address at any given time.
-JITCodeEntry* GetJITCodeEntry(uintptr_t code_address)
+// Notify native debugger that JITed code has been removed and free the debug info.
+void RemoveNativeDebugInfoForJit(const void* handle)
     REQUIRES(Locks::native_debug_interface_lock_);
 
 // Returns approximate memory used by all JITCodeEntries.
-size_t GetJITCodeEntryMemUsage()
+size_t GetJitNativeDebugInfoMemUsage()
     REQUIRES(Locks::native_debug_interface_lock_);
 
 }  // namespace art
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index c8c13cb..68a3647 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -550,10 +550,7 @@
   // Notify native debugger that we are about to remove the code.
   // It does nothing if we are not using native debugger.
   MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
-  JITCodeEntry* entry = GetJITCodeEntry(reinterpret_cast<uintptr_t>(code_ptr));
-  if (entry != nullptr) {
-    DecrementJITCodeEntryRefcount(entry, reinterpret_cast<uintptr_t>(code_ptr));
-  }
+  RemoveNativeDebugInfoForJit(code_ptr);
   if (OatQuickMethodHeader::FromCodePointer(code_ptr)->IsOptimized()) {
     FreeData(GetRootTable(code_ptr));
   }  // else this is a JNI stub without any data.
@@ -1828,7 +1825,7 @@
   MutexLock mu2(Thread::Current(), *Locks::native_debug_interface_lock_);
   os << "Current JIT code cache size: " << PrettySize(used_memory_for_code_) << "\n"
      << "Current JIT data cache size: " << PrettySize(used_memory_for_data_) << "\n"
-     << "Current JIT mini-debug-info size: " << PrettySize(GetJITCodeEntryMemUsage()) << "\n"
+     << "Current JIT mini-debug-info size: " << PrettySize(GetJitNativeDebugInfoMemUsage()) << "\n"
      << "Current JIT capacity: " << PrettySize(current_capacity_) << "\n"
      << "Current number of JIT JNI stub entries: " << jni_stubs_map_.size() << "\n"
      << "Current number of JIT code cache entries: " << method_code_map_.size() << "\n"
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index b49209c..b602602 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -332,7 +332,8 @@
     int32_t i = kDexFileIndexStart;  // Oat file is at index 0.
     for (const DexFile* dex_file : dex_files) {
       if (dex_file != nullptr) {
-        DeregisterDexFileForNative(soa.Self(), dex_file->Begin());
+        RemoveNativeDebugInfoForDex(soa.Self(), ArrayRef<const uint8_t>(dex_file->Begin(),
+                                                                        dex_file->Size()));
         // Only delete the dex file if the dex cache is not found to prevent runtime crashes if there
         // are calls to DexFile.close while the ART DexFile is still in use.
         if (!class_linker->IsDexFileRegistered(soa.Self(), *dex_file)) {