summaryrefslogtreecommitdiff
path: root/runtime/jit/debugger_interface.cc
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/jit/debugger_interface.cc')
-rw-r--r--runtime/jit/debugger_interface.cc278
1 files changed, 160 insertions, 118 deletions
diff --git a/runtime/jit/debugger_interface.cc b/runtime/jit/debugger_interface.cc
index d60f70a54f..505e626f67 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 @@ extern "C" {
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 };
-
- // 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;
-}
+ // The root data structure describing of all JITed methods.
+ JITDescriptor __jit_debug_descriptor {};
-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 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);
+ }
}
-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 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);
}
- 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;
+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 GetJITCodeEntryMemUsage() {
- return g_jit_debug_mem_usage + g_jit_code_entries.size() * 2 * sizeof(void*);
+size_t GetJitNativeDebugInfoMemUsage() {
+ return __jit_debug_mem_usage + __jit_debug_entries.size() * 2 * sizeof(void*);
}
} // namespace art