This diff introduces a new symbol on-demand which skips loading a module's debug info unless explicitly asked on demand. This provides significant performance improvement for application with dynamic linking mode which has large number of modules. The feature can be turned on with: "settings set symbols.load-on-demand true" The feature works by creating a new SymbolFileOnDemand class for each module which wraps the actual SymbolFIle subclass as member variable. By default, most virtual methods on SymbolFileOnDemand are skipped so that it looks like there is no debug info for that module. But once the module's debug info is explicitly requested to be enabled (in the conditions mentioned below) SymbolFileOnDemand will allow all methods to pass through and forward to the actual SymbolFile which would hydrate module's debug info on-demand. In an internal benchmark, we are seeing more than 95% improvement for a 3000 modules application. Currently we are providing several ways to on demand hydrate a module's debug info: * Source line breakpoint: matching in supported files * Stack trace: resolving symbol context for an address * Symbolic breakpoint: symbol table match guided promotion * Global variable: symbol table match guided promotion In all above situations the module's debug info will be on-demand parsed and indexed. Some follow-ups for this feature: * Add a command that allows users to load debug info explicitly while using a new or existing command when this feature is enabled * Add settings for "never load any of these executables in Symbols On Demand" that takes a list of globs * Add settings for "always load the the debug info for executables in Symbols On Demand" that takes a list of globs * Add a new column in "image list" that shows up by default when Symbols On Demand is enable to show the status for each shlib like "not enabled for this", "debug info off" and "debug info on" (with a single character to short string, not the ones I just typed) Differential Revision: https://reviews.llvm.org/D121631
301 lines
10 KiB
C++
301 lines
10 KiB
C++
//===-- DebugNamesDWARFIndex.cpp ------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.h"
|
|
#include "Plugins/SymbolFile/DWARF/DWARFDebugInfo.h"
|
|
#include "Plugins/SymbolFile/DWARF/DWARFDeclContext.h"
|
|
#include "Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Utility/RegularExpression.h"
|
|
#include "lldb/Utility/Stream.h"
|
|
|
|
using namespace lldb_private;
|
|
using namespace lldb;
|
|
using namespace lldb_private::dwarf;
|
|
|
|
llvm::Expected<std::unique_ptr<DebugNamesDWARFIndex>>
|
|
DebugNamesDWARFIndex::Create(Module &module, DWARFDataExtractor debug_names,
|
|
DWARFDataExtractor debug_str,
|
|
SymbolFileDWARF &dwarf) {
|
|
auto index_up = std::make_unique<DebugNames>(debug_names.GetAsLLVM(),
|
|
debug_str.GetAsLLVM());
|
|
if (llvm::Error E = index_up->extract())
|
|
return std::move(E);
|
|
|
|
return std::unique_ptr<DebugNamesDWARFIndex>(new DebugNamesDWARFIndex(
|
|
module, std::move(index_up), debug_names, debug_str, dwarf));
|
|
}
|
|
|
|
llvm::DenseSet<dw_offset_t>
|
|
DebugNamesDWARFIndex::GetUnits(const DebugNames &debug_names) {
|
|
llvm::DenseSet<dw_offset_t> result;
|
|
for (const DebugNames::NameIndex &ni : debug_names) {
|
|
for (uint32_t cu = 0; cu < ni.getCUCount(); ++cu)
|
|
result.insert(ni.getCUOffset(cu));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
llvm::Optional<DIERef>
|
|
DebugNamesDWARFIndex::ToDIERef(const DebugNames::Entry &entry) {
|
|
llvm::Optional<uint64_t> cu_offset = entry.getCUOffset();
|
|
if (!cu_offset)
|
|
return llvm::None;
|
|
|
|
DWARFUnit *cu = m_debug_info.GetUnitAtOffset(DIERef::Section::DebugInfo, *cu_offset);
|
|
if (!cu)
|
|
return llvm::None;
|
|
|
|
cu = &cu->GetNonSkeletonUnit();
|
|
if (llvm::Optional<uint64_t> die_offset = entry.getDIEUnitOffset())
|
|
return DIERef(cu->GetSymbolFileDWARF().GetDwoNum(),
|
|
DIERef::Section::DebugInfo, cu->GetOffset() + *die_offset);
|
|
|
|
return llvm::None;
|
|
}
|
|
|
|
bool DebugNamesDWARFIndex::ProcessEntry(
|
|
const DebugNames::Entry &entry,
|
|
llvm::function_ref<bool(DWARFDIE die)> callback, llvm::StringRef name) {
|
|
llvm::Optional<DIERef> ref = ToDIERef(entry);
|
|
if (!ref)
|
|
return true;
|
|
SymbolFileDWARF &dwarf = *llvm::cast<SymbolFileDWARF>(
|
|
m_module.GetSymbolFile()->GetBackingSymbolFile());
|
|
DWARFDIE die = dwarf.GetDIE(*ref);
|
|
if (!die)
|
|
return true;
|
|
return callback(die);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::MaybeLogLookupError(llvm::Error error,
|
|
const DebugNames::NameIndex &ni,
|
|
llvm::StringRef name) {
|
|
// Ignore SentinelErrors, log everything else.
|
|
LLDB_LOG_ERROR(
|
|
GetLog(DWARFLog::Lookups),
|
|
handleErrors(std::move(error), [](const DebugNames::SentinelError &) {}),
|
|
"Failed to parse index entries for index at {1:x}, name {2}: {0}",
|
|
ni.getUnitOffset(), name);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetGlobalVariables(
|
|
ConstString basename, llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
for (const DebugNames::Entry &entry :
|
|
m_debug_names_up->equal_range(basename.GetStringRef())) {
|
|
if (entry.tag() != DW_TAG_variable)
|
|
continue;
|
|
|
|
if (!ProcessEntry(entry, callback, basename.GetStringRef()))
|
|
return;
|
|
}
|
|
|
|
m_fallback.GetGlobalVariables(basename, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetGlobalVariables(
|
|
const RegularExpression ®ex,
|
|
llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
for (const DebugNames::NameIndex &ni: *m_debug_names_up) {
|
|
for (DebugNames::NameTableEntry nte: ni) {
|
|
if (!regex.Execute(nte.getString()))
|
|
continue;
|
|
|
|
uint64_t entry_offset = nte.getEntryOffset();
|
|
llvm::Expected<DebugNames::Entry> entry_or = ni.getEntry(&entry_offset);
|
|
for (; entry_or; entry_or = ni.getEntry(&entry_offset)) {
|
|
if (entry_or->tag() != DW_TAG_variable)
|
|
continue;
|
|
|
|
if (!ProcessEntry(*entry_or, callback,
|
|
llvm::StringRef(nte.getString())))
|
|
return;
|
|
}
|
|
MaybeLogLookupError(entry_or.takeError(), ni, nte.getString());
|
|
}
|
|
}
|
|
|
|
m_fallback.GetGlobalVariables(regex, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetGlobalVariables(
|
|
DWARFUnit &cu, llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
lldbassert(!cu.GetSymbolFileDWARF().GetDwoNum());
|
|
uint64_t cu_offset = cu.GetOffset();
|
|
bool found_entry_for_cu = false;
|
|
for (const DebugNames::NameIndex &ni: *m_debug_names_up) {
|
|
for (DebugNames::NameTableEntry nte: ni) {
|
|
uint64_t entry_offset = nte.getEntryOffset();
|
|
llvm::Expected<DebugNames::Entry> entry_or = ni.getEntry(&entry_offset);
|
|
for (; entry_or; entry_or = ni.getEntry(&entry_offset)) {
|
|
if (entry_or->tag() != DW_TAG_variable)
|
|
continue;
|
|
if (entry_or->getCUOffset() != cu_offset)
|
|
continue;
|
|
|
|
found_entry_for_cu = true;
|
|
if (!ProcessEntry(*entry_or, callback,
|
|
llvm::StringRef(nte.getString())))
|
|
return;
|
|
}
|
|
MaybeLogLookupError(entry_or.takeError(), ni, nte.getString());
|
|
}
|
|
}
|
|
// If no name index for that particular CU was found, fallback to
|
|
// creating the manual index.
|
|
if (!found_entry_for_cu)
|
|
m_fallback.GetGlobalVariables(cu, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetCompleteObjCClass(
|
|
ConstString class_name, bool must_be_implementation,
|
|
llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
// Keep a list of incomplete types as fallback for when we don't find the
|
|
// complete type.
|
|
DIEArray incomplete_types;
|
|
|
|
for (const DebugNames::Entry &entry :
|
|
m_debug_names_up->equal_range(class_name.GetStringRef())) {
|
|
if (entry.tag() != DW_TAG_structure_type &&
|
|
entry.tag() != DW_TAG_class_type)
|
|
continue;
|
|
|
|
llvm::Optional<DIERef> ref = ToDIERef(entry);
|
|
if (!ref)
|
|
continue;
|
|
|
|
DWARFUnit *cu = m_debug_info.GetUnit(*ref);
|
|
if (!cu || !cu->Supports_DW_AT_APPLE_objc_complete_type()) {
|
|
incomplete_types.push_back(*ref);
|
|
continue;
|
|
}
|
|
|
|
DWARFDIE die = m_debug_info.GetDIE(*ref);
|
|
if (!die) {
|
|
ReportInvalidDIERef(*ref, class_name.GetStringRef());
|
|
continue;
|
|
}
|
|
|
|
if (die.GetAttributeValueAsUnsigned(DW_AT_APPLE_objc_complete_type, 0)) {
|
|
// If we find the complete version we're done.
|
|
callback(die);
|
|
return;
|
|
}
|
|
incomplete_types.push_back(*ref);
|
|
}
|
|
|
|
auto dierefcallback = DIERefCallback(callback, class_name.GetStringRef());
|
|
for (DIERef ref : incomplete_types)
|
|
if (!dierefcallback(ref))
|
|
return;
|
|
|
|
m_fallback.GetCompleteObjCClass(class_name, must_be_implementation, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetTypes(
|
|
ConstString name, llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
for (const DebugNames::Entry &entry :
|
|
m_debug_names_up->equal_range(name.GetStringRef())) {
|
|
if (isType(entry.tag())) {
|
|
if (!ProcessEntry(entry, callback, name.GetStringRef()))
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_fallback.GetTypes(name, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetTypes(
|
|
const DWARFDeclContext &context,
|
|
llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
auto name = context[0].name;
|
|
for (const DebugNames::Entry &entry : m_debug_names_up->equal_range(name)) {
|
|
if (entry.tag() == context[0].tag) {
|
|
if (!ProcessEntry(entry, callback, name))
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_fallback.GetTypes(context, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetNamespaces(
|
|
ConstString name, llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
for (const DebugNames::Entry &entry :
|
|
m_debug_names_up->equal_range(name.GetStringRef())) {
|
|
if (entry.tag() == DW_TAG_namespace) {
|
|
if (!ProcessEntry(entry, callback, name.GetStringRef()))
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_fallback.GetNamespaces(name, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetFunctions(
|
|
ConstString name, SymbolFileDWARF &dwarf,
|
|
const CompilerDeclContext &parent_decl_ctx, uint32_t name_type_mask,
|
|
llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
|
|
std::set<DWARFDebugInfoEntry *> seen;
|
|
for (const DebugNames::Entry &entry :
|
|
m_debug_names_up->equal_range(name.GetStringRef())) {
|
|
Tag tag = entry.tag();
|
|
if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
|
|
continue;
|
|
|
|
if (llvm::Optional<DIERef> ref = ToDIERef(entry)) {
|
|
if (!ProcessFunctionDIE(name.GetStringRef(), *ref, dwarf, parent_decl_ctx,
|
|
name_type_mask, [&](DWARFDIE die) {
|
|
if (!seen.insert(die.GetDIE()).second)
|
|
return true;
|
|
return callback(die);
|
|
}))
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_fallback.GetFunctions(name, dwarf, parent_decl_ctx, name_type_mask,
|
|
callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::GetFunctions(
|
|
const RegularExpression ®ex,
|
|
llvm::function_ref<bool(DWARFDIE die)> callback) {
|
|
for (const DebugNames::NameIndex &ni: *m_debug_names_up) {
|
|
for (DebugNames::NameTableEntry nte: ni) {
|
|
if (!regex.Execute(nte.getString()))
|
|
continue;
|
|
|
|
uint64_t entry_offset = nte.getEntryOffset();
|
|
llvm::Expected<DebugNames::Entry> entry_or = ni.getEntry(&entry_offset);
|
|
for (; entry_or; entry_or = ni.getEntry(&entry_offset)) {
|
|
Tag tag = entry_or->tag();
|
|
if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine)
|
|
continue;
|
|
|
|
if (!ProcessEntry(*entry_or, callback,
|
|
llvm::StringRef(nte.getString())))
|
|
return;
|
|
}
|
|
MaybeLogLookupError(entry_or.takeError(), ni, nte.getString());
|
|
}
|
|
}
|
|
|
|
m_fallback.GetFunctions(regex, callback);
|
|
}
|
|
|
|
void DebugNamesDWARFIndex::Dump(Stream &s) {
|
|
m_fallback.Dump(s);
|
|
|
|
std::string data;
|
|
llvm::raw_string_ostream os(data);
|
|
m_debug_names_up->dump(os);
|
|
s.PutCString(os.str());
|
|
}
|