summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author David Srbecky <dsrbecky@google.com> 2018-12-05 13:24:06 +0000
committer David Srbecky <dsrbecky@google.com> 2018-12-14 16:47:38 +0000
commit0b21e41260bea6c078c18eba745d43e549230fd0 (patch)
tree25e41920b8a38d01ec904a2d1a1498c1364c07fb
parent889e29aae6a7cbd3c6f8dae44f67e054e083a8b6 (diff)
Pack JIT mini-debug-infos together.
We currently produce one in-memory ELF file per JITed method, which has significant overheads due to the ELF file headers. Pack several of those files together regularly to save space. Similarly, prune information about methods which were GCed. This reduces the size of JIT mini-debug-info by factor of 10. The final overhead is 5% to 10% relative to the JIT code size. Test: ./art/test.py -b -r -t 137 Change-Id: Idfaff8ed9a209e871e815e527f24f36c63a57bbf
-rw-r--r--compiler/debug/dwarf/headers.h28
-rw-r--r--compiler/debug/elf_debug_frame_writer.h3
-rw-r--r--compiler/debug/elf_debug_reader.h104
-rw-r--r--compiler/debug/elf_debug_writer.cc140
-rw-r--r--compiler/debug/elf_debug_writer.h7
-rw-r--r--compiler/jit/jit_compiler.cc5
-rw-r--r--compiler/optimizing/optimizing_compiler.cc14
-rw-r--r--runtime/jit/debugger_interface.cc138
-rw-r--r--runtime/jit/debugger_interface.h28
9 files changed, 422 insertions, 45 deletions
diff --git a/compiler/debug/dwarf/headers.h b/compiler/debug/dwarf/headers.h
index 28f108423e..4a27178146 100644
--- a/compiler/debug/dwarf/headers.h
+++ b/compiler/debug/dwarf/headers.h
@@ -107,7 +107,9 @@ void WriteFDE(bool is64bit,
} else {
DCHECK(format == DW_DEBUG_FRAME_FORMAT);
// Relocate code_address if it has absolute value.
- patch_locations->push_back(buffer_address + buffer->size() - section_address);
+ if (patch_locations != nullptr) {
+ patch_locations->push_back(buffer_address + buffer->size() - section_address);
+ }
}
if (is64bit) {
writer.PushUint64(code_address);
@@ -122,6 +124,30 @@ void WriteFDE(bool is64bit,
writer.UpdateUint32(fde_header_start, writer.data()->size() - fde_header_start - 4);
}
+// Read singe FDE entry from 'data' (which is advanced).
+template<typename Addr>
+bool ReadFDE(const uint8_t** data, Addr* addr, Addr* size, ArrayRef<const uint8_t>* opcodes) {
+ struct Header {
+ uint32_t length;
+ int32_t cie_pointer;
+ Addr addr;
+ Addr size;
+ uint8_t augmentaion;
+ uint8_t opcodes[];
+ } PACKED(1);
+ const Header* header = reinterpret_cast<const Header*>(*data);
+ const size_t length = 4 + header->length;
+ *data += length;
+ if (header->cie_pointer == -1) {
+ return false; // Not an FDE entry.
+ }
+ DCHECK_EQ(header->cie_pointer, 0); // Expects single CIE. Assumes DW_DEBUG_FRAME_FORMAT.
+ *addr = header->addr;
+ *size = header->size;
+ *opcodes = ArrayRef<const uint8_t>(header->opcodes, length - offsetof(Header, opcodes));
+ return true;
+}
+
// Write compilation unit (CU) to .debug_info section.
template<typename Vector>
void WriteDebugInfoCU(uint32_t debug_abbrev_offset,
diff --git a/compiler/debug/elf_debug_frame_writer.h b/compiler/debug/elf_debug_frame_writer.h
index 27b70c8caa..e0116c6f41 100644
--- a/compiler/debug/elf_debug_frame_writer.h
+++ b/compiler/debug/elf_debug_frame_writer.h
@@ -182,7 +182,7 @@ void WriteCFISection(linker::ElfBuilder<ElfTypes>* builder,
std::vector<const MethodDebugInfo*> sorted_method_infos;
sorted_method_infos.reserve(method_infos.size());
for (size_t i = 0; i < method_infos.size(); i++) {
- if (!method_infos[i].cfi.empty() && !method_infos[i].deduped) {
+ if (!method_infos[i].deduped) {
sorted_method_infos.push_back(&method_infos[i]);
}
}
@@ -222,7 +222,6 @@ void WriteCFISection(linker::ElfBuilder<ElfTypes>* builder,
buffer.clear();
for (const MethodDebugInfo* mi : sorted_method_infos) {
DCHECK(!mi->deduped);
- DCHECK(!mi->cfi.empty());
const Elf_Addr code_address = mi->code_address +
(mi->is_code_address_text_relative ? builder->GetText()->GetAddress() : 0);
if (format == dwarf::DW_EH_FRAME_FORMAT) {
diff --git a/compiler/debug/elf_debug_reader.h b/compiler/debug/elf_debug_reader.h
new file mode 100644
index 0000000000..91b1b3ea81
--- /dev/null
+++ b/compiler/debug/elf_debug_reader.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_COMPILER_DEBUG_ELF_DEBUG_READER_H_
+#define ART_COMPILER_DEBUG_ELF_DEBUG_READER_H_
+
+#include "base/array_ref.h"
+#include "debug/dwarf/headers.h"
+#include "elf.h"
+#include "xz_utils.h"
+
+namespace art {
+namespace debug {
+
+// Trivial ELF file reader.
+//
+// It is the bare minimum needed to read mini-debug-info symbols for unwinding.
+// We use it to merge JIT mini-debug-infos together or to prune them after GC.
+// The consumed ELF file comes from ART JIT.
+template <typename ElfTypes, typename VisitSym, typename VisitFde>
+static void ReadElfSymbols(const uint8_t* elf, VisitSym visit_sym, VisitFde visit_fde) {
+ // Note that the input buffer might be misaligned.
+ typedef typename ElfTypes::Ehdr ALIGNED(1) Elf_Ehdr;
+ typedef typename ElfTypes::Shdr ALIGNED(1) Elf_Shdr;
+ typedef typename ElfTypes::Sym ALIGNED(1) Elf_Sym;
+ typedef typename ElfTypes::Addr ALIGNED(1) Elf_Addr;
+
+ // Read and check the elf header.
+ const Elf_Ehdr* header = reinterpret_cast<const Elf_Ehdr*>(elf);
+ CHECK(header->checkMagic());
+
+ // Find sections that we are interested in.
+ const Elf_Shdr* sections = reinterpret_cast<const Elf_Shdr*>(elf + header->e_shoff);
+ const Elf_Shdr* strtab = nullptr;
+ const Elf_Shdr* symtab = nullptr;
+ const Elf_Shdr* debug_frame = nullptr;
+ const Elf_Shdr* gnu_debugdata = nullptr;
+ for (size_t i = 1 /* skip null section */; i < header->e_shnum; i++) {
+ const Elf_Shdr* section = sections + i;
+ const char* name = reinterpret_cast<const char*>(
+ elf + sections[header->e_shstrndx].sh_offset + section->sh_name);
+ if (strcmp(name, ".strtab") == 0) {
+ strtab = section;
+ } else if (strcmp(name, ".symtab") == 0) {
+ symtab = section;
+ } else if (strcmp(name, ".debug_frame") == 0) {
+ debug_frame = section;
+ } else if (strcmp(name, ".gnu_debugdata") == 0) {
+ gnu_debugdata = section;
+ }
+ }
+
+ // Visit symbols.
+ if (symtab != nullptr && strtab != nullptr) {
+ const Elf_Sym* symbols = reinterpret_cast<const Elf_Sym*>(elf + symtab->sh_offset);
+ DCHECK_EQ(symtab->sh_entsize, sizeof(Elf_Sym));
+ size_t count = symtab->sh_size / sizeof(Elf_Sym);
+ for (size_t i = 1 /* skip null symbol */; i < count; i++) {
+ Elf_Sym symbol = symbols[i];
+ if (symbol.getBinding() != STB_LOCAL) { // Ignore local symbols (e.g. "$t").
+ const uint8_t* name = elf + strtab->sh_offset + symbol.st_name;
+ visit_sym(symbol, reinterpret_cast<const char*>(name));
+ }
+ }
+ }
+
+ // Visit CFI (unwind) data.
+ if (debug_frame != nullptr) {
+ const uint8_t* data = elf + debug_frame->sh_offset;
+ const uint8_t* end = data + debug_frame->sh_size;
+ while (data < end) {
+ Elf_Addr addr, size;
+ ArrayRef<const uint8_t> opcodes;
+ if (dwarf::ReadFDE<Elf_Addr>(&data, &addr, &size, &opcodes)) {
+ visit_fde(addr, size, opcodes);
+ }
+ }
+ }
+
+ // Process embedded compressed ELF file.
+ if (gnu_debugdata != nullptr) {
+ ArrayRef<const uint8_t> compressed(elf + gnu_debugdata->sh_offset, gnu_debugdata->sh_size);
+ std::vector<uint8_t> decompressed;
+ XzDecompress(compressed, &decompressed);
+ ReadElfSymbols<ElfTypes>(decompressed.data(), visit_sym, visit_fde);
+ }
+}
+
+} // namespace debug
+} // namespace art
+#endif // ART_COMPILER_DEBUG_ELF_DEBUG_READER_H_
diff --git a/compiler/debug/elf_debug_writer.cc b/compiler/debug/elf_debug_writer.cc
index 1ecb1d8ed9..56d773f508 100644
--- a/compiler/debug/elf_debug_writer.cc
+++ b/compiler/debug/elf_debug_writer.cc
@@ -21,12 +21,14 @@
#include <vector>
#include "base/array_ref.h"
+#include "base/stl_util.h"
#include "debug/dwarf/dwarf_constants.h"
#include "debug/elf_compilation_unit.h"
#include "debug/elf_debug_frame_writer.h"
#include "debug/elf_debug_info_writer.h"
#include "debug/elf_debug_line_writer.h"
#include "debug/elf_debug_loc_writer.h"
+#include "debug/elf_debug_reader.h"
#include "debug/elf_symtab_writer.h"
#include "debug/method_debug_info.h"
#include "debug/xz_utils.h"
@@ -203,9 +205,147 @@ std::vector<uint8_t> MakeElfFileForJIT(
}
builder->End();
CHECK(builder->Good());
+ // Verify the ELF file by reading it back using the trivial reader.
+ if (kIsDebugBuild) {
+ using Elf_Sym = typename ElfTypes::Sym;
+ using Elf_Addr = typename ElfTypes::Addr;
+ size_t num_syms = 0;
+ size_t num_cfis = 0;
+ ReadElfSymbols<ElfTypes>(
+ buffer.data(),
+ [&](Elf_Sym sym, const char*) {
+ DCHECK_EQ(sym.st_value, method_info.code_address + CompiledMethod::CodeDelta(isa));
+ DCHECK_EQ(sym.st_size, method_info.code_size);
+ num_syms++;
+ },
+ [&](Elf_Addr addr, Elf_Addr size, ArrayRef<const uint8_t> opcodes) {
+ DCHECK_EQ(addr, method_info.code_address);
+ DCHECK_EQ(size, method_info.code_size);
+ DCHECK_GE(opcodes.size(), method_info.cfi.size());
+ DCHECK_EQ(memcmp(opcodes.data(), method_info.cfi.data(), method_info.cfi.size()), 0);
+ num_cfis++;
+ });
+ DCHECK_EQ(num_syms, 1u);
+ DCHECK_EQ(num_cfis, 1u);
+ }
return buffer;
}
+// Combine several mini-debug-info ELF files into one, while filtering some symbols.
+std::vector<uint8_t> PackElfFileForJIT(
+ InstructionSet isa,
+ const InstructionSetFeatures* features,
+ std::vector<const uint8_t*>& added_elf_files,
+ std::vector<const void*>& removed_symbols,
+ /*out*/ size_t* num_symbols) {
+ using ElfTypes = ElfRuntimeTypes;
+ using Elf_Addr = typename ElfTypes::Addr;
+ using Elf_Sym = typename ElfTypes::Sym;
+ CHECK_EQ(sizeof(Elf_Addr), static_cast<size_t>(GetInstructionSetPointerSize(isa)));
+ const bool is64bit = Is64BitInstructionSet(isa);
+ auto is_removed_symbol = [&removed_symbols](Elf_Addr addr) {
+ const void* code_ptr = reinterpret_cast<const void*>(addr);
+ return std::binary_search(removed_symbols.begin(), removed_symbols.end(), code_ptr);
+ };
+ uint64_t min_address = std::numeric_limits<uint64_t>::max();
+ uint64_t max_address = 0;
+
+ // Produce the inner ELF file.
+ // It will contain the symbols (.symtab) and unwind information (.debug_frame).
+ std::vector<uint8_t> inner_elf_file;
+ {
+ inner_elf_file.reserve(1 * KB); // Approximate size of ELF file with a single symbol.
+ linker::VectorOutputStream out("Mini-debug-info ELF file for JIT", &inner_elf_file);
+ std::unique_ptr<linker::ElfBuilder<ElfTypes>> builder(
+ new linker::ElfBuilder<ElfTypes>(isa, features, &out));
+ builder->Start(/*write_program_headers=*/ false);
+ auto* text = builder->GetText();
+ auto* strtab = builder->GetStrTab();
+ auto* symtab = builder->GetSymTab();
+ auto* debug_frame = builder->GetDebugFrame();
+ std::deque<Elf_Sym> symbols;
+ std::vector<uint8_t> debug_frame_buffer;
+ WriteCIE(isa, dwarf::DW_DEBUG_FRAME_FORMAT, &debug_frame_buffer);
+
+ // Write symbols names. All other data is buffered.
+ strtab->Start();
+ strtab->Write(""); // strtab should start with empty string.
+ for (const uint8_t* added_elf_file : added_elf_files) {
+ ReadElfSymbols<ElfTypes>(
+ added_elf_file,
+ [&](Elf_Sym sym, const char* name) {
+ if (is_removed_symbol(sym.st_value)) {
+ return;
+ }
+ sym.st_name = strtab->Write(name);
+ symbols.push_back(sym);
+ min_address = std::min<uint64_t>(min_address, sym.st_value);
+ max_address = std::max<uint64_t>(max_address, sym.st_value + sym.st_size);
+ },
+ [&](Elf_Addr addr, Elf_Addr size, ArrayRef<const uint8_t> opcodes) {
+ if (is_removed_symbol(addr)) {
+ return;
+ }
+ WriteFDE(is64bit,
+ /*section_address=*/ 0,
+ /*cie_address=*/ 0,
+ addr,
+ size,
+ opcodes,
+ dwarf::DW_DEBUG_FRAME_FORMAT,
+ debug_frame_buffer.size(),
+ &debug_frame_buffer,
+ /*patch_locations=*/ nullptr);
+ });
+ }
+ strtab->End();
+
+ // Create .text covering the code range. Needed for gdb to find the symbols.
+ if (max_address > min_address) {
+ text->AllocateVirtualMemory(min_address, max_address - min_address);
+ }
+
+ // Add the symbols.
+ *num_symbols = symbols.size();
+ for (; !symbols.empty(); symbols.pop_front()) {
+ symtab->Add(symbols.front(), text);
+ }
+ symtab->WriteCachedSection();
+
+ // Add the CFI/unwind section.
+ debug_frame->Start();
+ debug_frame->WriteFully(debug_frame_buffer.data(), debug_frame_buffer.size());
+ debug_frame->End();
+
+ builder->End();
+ CHECK(builder->Good());
+ }
+
+ // Produce the outer ELF file.
+ // It contains only the inner ELF file compressed as .gnu_debugdata section.
+ // This extra wrapping is not necessary but the compression saves space.
+ std::vector<uint8_t> outer_elf_file;
+ {
+ std::vector<uint8_t> gnu_debugdata;
+ gnu_debugdata.reserve(inner_elf_file.size() / 4);
+ XzCompress(ArrayRef<const uint8_t>(inner_elf_file), &gnu_debugdata);
+
+ outer_elf_file.reserve(KB + gnu_debugdata.size());
+ linker::VectorOutputStream out("Mini-debug-info ELF file for JIT", &outer_elf_file);
+ std::unique_ptr<linker::ElfBuilder<ElfTypes>> builder(
+ new linker::ElfBuilder<ElfTypes>(isa, features, &out));
+ builder->Start(/*write_program_headers=*/ false);
+ if (max_address > min_address) {
+ builder->GetText()->AllocateVirtualMemory(min_address, max_address - min_address);
+ }
+ builder->WriteSection(".gnu_debugdata", &gnu_debugdata);
+ builder->End();
+ CHECK(builder->Good());
+ }
+
+ return outer_elf_file;
+}
+
std::vector<uint8_t> WriteDebugElfFileForClasses(
InstructionSet isa,
const InstructionSetFeatures* features,
diff --git a/compiler/debug/elf_debug_writer.h b/compiler/debug/elf_debug_writer.h
index 8ad0c4219a..85ab356b0c 100644
--- a/compiler/debug/elf_debug_writer.h
+++ b/compiler/debug/elf_debug_writer.h
@@ -56,6 +56,13 @@ std::vector<uint8_t> MakeElfFileForJIT(
bool mini_debug_info,
const MethodDebugInfo& method_info);
+std::vector<uint8_t> PackElfFileForJIT(
+ InstructionSet isa,
+ const InstructionSetFeatures* features,
+ std::vector<const uint8_t*>& added_elf_files,
+ std::vector<const void*>& removed_symbols,
+ /*out*/ size_t* num_symbols);
+
std::vector<uint8_t> WriteDebugElfFileForClasses(
InstructionSet isa,
const InstructionSetFeatures* features,
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 9b8bb3e90e..27749a61b1 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -146,7 +146,10 @@ extern "C" void jit_types_loaded(void* handle, mirror::Class** types, size_t cou
// (which would have been otherwise used as identifier to remove it later).
AddNativeDebugInfoForJit(Thread::Current(),
/*code_ptr=*/ nullptr,
- elf_file);
+ elf_file,
+ debug::PackElfFileForJIT,
+ compiler_options.GetInstructionSet(),
+ compiler_options.GetInstructionSetFeatures());
}
}
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index c9b4d36bc4..4936a6d3c0 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1456,8 +1456,8 @@ bool OptimizingCompiler::JitCompile(Thread* self,
return true;
}
-void OptimizingCompiler::GenerateJitDebugInfo(
- ArtMethod* method, const debug::MethodDebugInfo& info) {
+void OptimizingCompiler::GenerateJitDebugInfo(ArtMethod* method ATTRIBUTE_UNUSED,
+ const debug::MethodDebugInfo& info) {
const CompilerOptions& compiler_options = GetCompilerDriver()->GetCompilerOptions();
DCHECK(compiler_options.GenerateAnyDebugInfo());
@@ -1472,12 +1472,10 @@ void OptimizingCompiler::GenerateJitDebugInfo(
info);
AddNativeDebugInfoForJit(Thread::Current(),
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(GetJitMiniDebugInfoMemUsage());
+ elf_file,
+ debug::PackElfFileForJIT,
+ compiler_options.GetInstructionSet(),
+ compiler_options.GetInstructionSetFeatures());
}
} // namespace art
diff --git a/runtime/jit/debugger_interface.cc b/runtime/jit/debugger_interface.cc
index 853c0cab96..7aa6ddfb00 100644
--- a/runtime/jit/debugger_interface.cc
+++ b/runtime/jit/debugger_interface.cc
@@ -19,14 +19,17 @@
#include <android-base/logging.h>
#include "base/array_ref.h"
+#include "base/logging.h"
#include "base/mutex.h"
#include "base/time_utils.h"
+#include "base/utils.h"
#include "dex/dex_file.h"
#include "thread-current-inl.h"
#include "thread.h"
#include <atomic>
#include <cstddef>
+#include <deque>
#include <map>
//
@@ -77,6 +80,10 @@
//
namespace art {
+
+static Mutex g_jit_debug_lock("JIT native debug entries", kNativeDebugInterfaceLock);
+static Mutex g_dex_debug_lock("DEX native debug entries", kNativeDebugInterfaceLock);
+
extern "C" {
enum JITAction {
JIT_NOACTION = 0,
@@ -127,14 +134,14 @@ extern "C" {
void (*__jit_debug_register_code_ptr)() = __jit_debug_register_code;
// The root data structure describing of all JITed methods.
- JITDescriptor __jit_debug_descriptor GUARDED_BY(*Locks::native_debug_interface_lock_) {};
+ JITDescriptor __jit_debug_descriptor GUARDED_BY(g_jit_debug_lock) {};
// 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 GUARDED_BY(*Locks::native_debug_interface_lock_) {};
+ JITDescriptor __dex_debug_descriptor GUARDED_BY(g_dex_debug_lock) {};
}
// Mark the descriptor as "locked", so native tools know the data is being modified.
@@ -157,8 +164,7 @@ static JITCodeEntry* CreateJITCodeEntryInternal(
JITDescriptor& descriptor,
void (*register_code_ptr)(),
ArrayRef<const uint8_t> symfile,
- bool copy_symfile)
- REQUIRES(Locks::native_debug_interface_lock_) {
+ bool copy_symfile) {
// Make a copy of the buffer to shrink it and to pass ownership to JITCodeEntry.
if (copy_symfile) {
uint8_t* copy = new uint8_t[symfile.size()];
@@ -199,8 +205,7 @@ static void DeleteJITCodeEntryInternal(
JITDescriptor& descriptor,
void (*register_code_ptr)(),
JITCodeEntry* entry,
- bool free_symfile)
- REQUIRES(Locks::native_debug_interface_lock_) {
+ bool free_symfile) {
CHECK(entry != nullptr);
const uint8_t* symfile = entry->symfile_addr_;
@@ -238,11 +243,10 @@ static void DeleteJITCodeEntryInternal(
}
}
-static std::map<const DexFile*, JITCodeEntry*> g_dex_debug_entries
- GUARDED_BY(*Locks::native_debug_interface_lock_);
+static std::map<const DexFile*, JITCodeEntry*> g_dex_debug_entries GUARDED_BY(g_dex_debug_lock);
void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) {
- MutexLock mu(self, *Locks::native_debug_interface_lock_);
+ MutexLock mu(self, g_dex_debug_lock);
DCHECK(dexfile != nullptr);
// This is just defensive check. The class linker should not register the dex file twice.
if (g_dex_debug_entries.count(dexfile) == 0) {
@@ -256,7 +260,7 @@ void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) {
}
void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) {
- MutexLock mu(self, *Locks::native_debug_interface_lock_);
+ MutexLock mu(self, g_dex_debug_lock);
auto it = g_dex_debug_entries.find(dexfile);
// 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.
@@ -270,46 +274,134 @@ void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) {
}
// Mapping from handle to entry. Used to manage life-time of the entries.
-static std::map<const void*, JITCodeEntry*> g_jit_debug_entries
- GUARDED_BY(*Locks::native_debug_interface_lock_);
+static std::map<const void*, JITCodeEntry*> g_jit_debug_entries GUARDED_BY(g_jit_debug_lock);
+
+// Number of entries added since last packing. Used to pack entries in bulk.
+static size_t g_jit_num_unpacked_entries GUARDED_BY(g_jit_debug_lock) = 0;
+
+// We postpone removal so that it is done in bulk.
+static std::deque<const void*> g_jit_removed_entries GUARDED_BY(g_jit_debug_lock);
+
+// Split the JIT code cache into groups of fixed size and create singe JITCodeEntry for each group.
+// The start address of method's code determines which group it belongs to. The end is irrelevant.
+// As a consequnce, newly added mini debug infos will be merged and old ones (GCed) will be pruned.
+static void MaybePackJitMiniDebugInfo(PackElfFileForJITFunction pack,
+ InstructionSet isa,
+ const InstructionSetFeatures* features)
+ REQUIRES(g_jit_debug_lock) {
+ // Size of memory range covered by each JITCodeEntry.
+ // The number of methods per entry is variable (depending on how many fit in that range).
+ constexpr uint32_t kGroupSize = 64 * KB;
+ // Even if there are no removed entries, we want to pack new entries on regular basis.
+ constexpr uint32_t kPackFrequency = 64;
+
+ std::deque<const void*>& removed_entries = g_jit_removed_entries;
+ std::sort(removed_entries.begin(), removed_entries.end());
+ if (removed_entries.empty() && g_jit_num_unpacked_entries < kPackFrequency) {
+ return; // Nothing to do.
+ }
+
+ std::vector<const uint8_t*> added_elf_files;
+ std::vector<const void*> removed_symbols;
+ auto added_it = g_jit_debug_entries.begin();
+ auto removed_it = removed_entries.begin();
+ while (added_it != g_jit_debug_entries.end()) {
+ // Collect all entries that have been added or removed within our memory range.
+ const void* group_ptr = AlignDown(added_it->first, kGroupSize);
+ added_elf_files.clear();
+ auto added_begin = added_it;
+ while (added_it != g_jit_debug_entries.end() &&
+ AlignDown(added_it->first, kGroupSize) == group_ptr) {
+ added_elf_files.push_back((added_it++)->second->symfile_addr_);
+ }
+ removed_symbols.clear();
+ while (removed_it != removed_entries.end() &&
+ AlignDown(*removed_it, kGroupSize) == group_ptr) {
+ removed_symbols.push_back(*(removed_it++));
+ }
+
+ // Create new singe JITCodeEntry that covers this memory range.
+ if (added_elf_files.size() == 1 && removed_symbols.size() == 0) {
+ continue; // Nothing changed in this memory range.
+ }
+ uint64_t start_time = MilliTime();
+ size_t symbols;
+ std::vector<uint8_t> packed = pack(isa, features, added_elf_files, removed_symbols, &symbols);
+ VLOG(jit)
+ << "JIT mini-debug-info packed"
+ << " for " << group_ptr
+ << " in " << MilliTime() - start_time << "ms"
+ << " files=" << added_elf_files.size()
+ << " removed=" << removed_symbols.size()
+ << " symbols=" << symbols
+ << " size=" << PrettySize(packed.size());
+
+ // Replace the old entries with the new one (with their lifetime temporally overlapping).
+ JITCodeEntry* packed_entry = CreateJITCodeEntryInternal(
+ __jit_debug_descriptor,
+ __jit_debug_register_code_ptr,
+ ArrayRef<const uint8_t>(packed),
+ /*copy_symfile=*/ true);
+ for (auto it = added_begin; it != added_it; ++it) {
+ DeleteJITCodeEntryInternal(__jit_debug_descriptor,
+ __jit_debug_register_code_ptr,
+ /*entry=*/ it->second,
+ /*free_symfile=*/ true);
+ }
+ g_jit_debug_entries.erase(added_begin, added_it);
+ g_jit_debug_entries.emplace(group_ptr, packed_entry);
+ }
+ CHECK(added_it == g_jit_debug_entries.end());
+ CHECK(removed_it == removed_entries.end());
+ removed_entries.clear();
+ g_jit_num_unpacked_entries = 0;
+}
void AddNativeDebugInfoForJit(Thread* self,
const void* code_ptr,
- const std::vector<uint8_t>& symfile) {
- MutexLock mu(self, *Locks::native_debug_interface_lock_);
+ const std::vector<uint8_t>& symfile,
+ PackElfFileForJITFunction pack,
+ InstructionSet isa,
+ const InstructionSetFeatures* features) {
+ MutexLock mu(self, g_jit_debug_lock);
DCHECK_NE(symfile.size(), 0u);
+ MaybePackJitMiniDebugInfo(pack, isa, features);
+
JITCodeEntry* entry = CreateJITCodeEntryInternal(
__jit_debug_descriptor,
__jit_debug_register_code_ptr,
ArrayRef<const uint8_t>(symfile),
/*copy_symfile=*/ true);
+ VLOG(jit)
+ << "JIT mini-debug-info added"
+ << " for " << code_ptr
+ << " size=" << PrettySize(symfile.size());
+
// We don't provide code_ptr 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).
if (code_ptr != nullptr) {
bool ok = g_jit_debug_entries.emplace(code_ptr, entry).second;
DCHECK(ok) << "Native debug entry already exists for " << std::hex << code_ptr;
+ // Count how many entries we have added since the last mini-debug-info packing.
+ // We avoid g_jit_debug_entries.size() here because it can shrink during packing.
+ g_jit_num_unpacked_entries++;
}
}
void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr) {
- MutexLock mu(self, *Locks::native_debug_interface_lock_);
- auto it = g_jit_debug_entries.find(code_ptr);
+ MutexLock mu(self, g_jit_debug_lock);
// 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 != g_jit_debug_entries.end()) {
- DeleteJITCodeEntryInternal(__jit_debug_descriptor,
- __jit_debug_register_code_ptr,
- it->second,
- /*free_symfile=*/ true);
- g_jit_debug_entries.erase(it);
+ if (!g_jit_debug_entries.empty()) {
+ g_jit_removed_entries.push_back(code_ptr);
}
}
size_t GetJitMiniDebugInfoMemUsage() {
- MutexLock mu(Thread::Current(), *Locks::native_debug_interface_lock_);
+ MutexLock mu(Thread::Current(), g_jit_debug_lock);
size_t size = 0;
for (auto entry : g_jit_debug_entries) {
size += sizeof(JITCodeEntry) + entry.second->symfile_size_ + /*map entry*/ 4 * sizeof(void*);
diff --git a/runtime/jit/debugger_interface.h b/runtime/jit/debugger_interface.h
index 4b0d011295..17beb4baca 100644
--- a/runtime/jit/debugger_interface.h
+++ b/runtime/jit/debugger_interface.h
@@ -20,6 +20,7 @@
#include <inttypes.h>
#include <vector>
+#include "arch/instruction_set_features.h"
#include "base/locks.h"
namespace art {
@@ -27,28 +28,35 @@ namespace art {
class DexFile;
class Thread;
+// This method is declared in the compiler library.
+// We need to pass it by pointer to be able to call it from runtime.
+typedef std::vector<uint8_t> PackElfFileForJITFunction(
+ InstructionSet isa,
+ const InstructionSetFeatures* features,
+ std::vector<const uint8_t*>& added_elf_files,
+ std::vector<const void*>& removed_symbols,
+ /*out*/ size_t* num_symbols);
+
// Notify native tools (e.g. libunwind) that DEX file has been opened.
-void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile)
- REQUIRES(!Locks::native_debug_interface_lock_);
+void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile);
// Notify native tools (e.g. libunwind) that DEX file has been closed.
-void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile)
- REQUIRES(!Locks::native_debug_interface_lock_);
+void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile);
// Notify native tools (e.g. libunwind) that JIT has compiled a new method.
// The method will make copy of the passed ELF file (to shrink it to the minimum size).
void AddNativeDebugInfoForJit(Thread* self,
const void* code_ptr,
- const std::vector<uint8_t>& symfile)
- REQUIRES(!Locks::native_debug_interface_lock_);
+ const std::vector<uint8_t>& symfile,
+ PackElfFileForJITFunction pack,
+ InstructionSet isa,
+ const InstructionSetFeatures* features);
// Notify native tools (e.g. libunwind) that JIT code has been garbage collected.
-void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr)
- REQUIRES(!Locks::native_debug_interface_lock_);
+void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr);
// Returns approximate memory used by debug info for JIT code.
-size_t GetJitMiniDebugInfoMemUsage()
- REQUIRES(!Locks::native_debug_interface_lock_);
+size_t GetJitMiniDebugInfoMemUsage();
} // namespace art