From 7b81192d462bbd8031d5c665e29cd6b4c0c6887a Mon Sep 17 00:00:00 2001 From: Jeffrey Tan Date: Wed, 20 Apr 2022 07:30:53 -0700 Subject: [PATCH] Introduce new symbol on-demand for debug info 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 --- lldb/docs/use/ondemand.rst | 158 ++++++ lldb/include/lldb/Core/ModuleList.h | 2 + lldb/include/lldb/Symbol/SymbolFile.h | 14 + lldb/include/lldb/Symbol/SymbolFileOnDemand.h | 235 ++++++++ lldb/include/lldb/Target/Statistics.h | 2 + lldb/include/lldb/Utility/LLDBLog.h | 1 + lldb/source/Core/CoreProperties.td | 4 + lldb/source/Core/Module.cpp | 7 +- lldb/source/Core/ModuleList.cpp | 6 + .../source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp | 3 +- .../SymbolFile/DWARF/DebugNamesDWARFIndex.cpp | 4 +- .../Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp | 4 +- .../source/Plugins/SymbolFile/PDB/PDBASTParser.cpp | 15 +- lldb/source/Symbol/CMakeLists.txt | 1 + lldb/source/Symbol/CompileUnit.cpp | 5 +- lldb/source/Symbol/SymbolFile.cpp | 19 + lldb/source/Symbol/SymbolFileOnDemand.cpp | 590 +++++++++++++++++++++ lldb/source/Target/Statistics.cpp | 20 +- lldb/source/Utility/LLDBLog.cpp | 3 + .../symbol_ondemand/breakpoint_language/Makefile | 4 + .../TestBreakpointLanguageOnDemand.py | 134 +++++ .../symbol_ondemand/breakpoint_language/c_lang.c | 1 + .../breakpoint_language/cpp_lang.cpp | 1 + .../symbol_ondemand/breakpoint_language/main.cpp | 9 + .../breakpoint_source_regex/Makefile | 3 + .../TestSourceTextRegexBreakpoint.py | 35 ++ .../breakpoint_source_regex/main.cpp | 10 + .../API/symbol_ondemand/shared_library/Makefile | 5 + .../shared_library/TestSharedLib.py | 157 ++++++ lldb/test/API/symbol_ondemand/shared_library/foo.c | 4 + lldb/test/API/symbol_ondemand/shared_library/foo.h | 6 + .../API/symbol_ondemand/shared_library/shared.c | 9 + .../Shell/SymbolFile/OnDemand/Inputs/basic.cpp | 5 + .../SymbolFile/OnDemand/source-breakpoint.test | 23 + .../SymbolFile/OnDemand/symbolic-breakpoint.test | 23 + 35 files changed, 1508 insertions(+), 14 deletions(-) create mode 100644 lldb/docs/use/ondemand.rst create mode 100644 lldb/include/lldb/Symbol/SymbolFileOnDemand.h create mode 100644 lldb/source/Symbol/SymbolFileOnDemand.cpp create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_language/Makefile create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_language/TestBreakpointLanguageOnDemand.py create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_language/c_lang.c create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_language/cpp_lang.cpp create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_language/main.cpp create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_source_regex/Makefile create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py create mode 100644 lldb/test/API/symbol_ondemand/breakpoint_source_regex/main.cpp create mode 100644 lldb/test/API/symbol_ondemand/shared_library/Makefile create mode 100644 lldb/test/API/symbol_ondemand/shared_library/TestSharedLib.py create mode 100644 lldb/test/API/symbol_ondemand/shared_library/foo.c create mode 100644 lldb/test/API/symbol_ondemand/shared_library/foo.h create mode 100644 lldb/test/API/symbol_ondemand/shared_library/shared.c create mode 100644 lldb/test/Shell/SymbolFile/OnDemand/Inputs/basic.cpp create mode 100644 lldb/test/Shell/SymbolFile/OnDemand/source-breakpoint.test create mode 100644 lldb/test/Shell/SymbolFile/OnDemand/symbolic-breakpoint.test diff --git a/lldb/docs/use/ondemand.rst b/lldb/docs/use/ondemand.rst new file mode 100644 index 0000000..e5aaee8 --- /dev/null +++ b/lldb/docs/use/ondemand.rst @@ -0,0 +1,158 @@ +On Demand Symbols +================= + +On demand symbols can be enabled in LLDB for projects that generate debug info +for more than what is required by a normal debug session. Some build systems +enable debug information for all binaries and can end up producing many +gigabytes of debug information. This amount of debug information can greatly +increase debug session load times and can slow developer productivity when the +debug information isn't indexed. It can also cause expression evaluation to +be slow when types from all of the binaries have full debug info as each module +is queried for very common types, or global name lookups fail due to a mistyped +expression. + +.. contents:: + :local: + +When should I consider enabling this feature? +--------------------------------------------- + +Anyone that has a build system that produces debug information for many +binaries that are not all required when you want to focus on debugging a few of +the produced binaries. Some build systems enable debug info as a project wide +switch and the build system files that control how things are built are not +easy to modify to produce debug info for only a small subset of the files being +linked. If your debug session startup times are slow because of too much debug +info, this feature might help you be more productive during daily use. + +How do I enable on demand symbols? +---------------------------------- + +This feature is enabled using a LLDB setting: + + +:: + + (lldb) settings set symbols.load-on-demand true + +Users can also put this command into their ~/.lldbinit file so it is always +enabled. + +How does this feature work? +--------------------------- + +This feature works by selectively enabling debug information for modules that +the user focuses on. It is designed to be enabled and work without the user +having to set any other settings and will try and determine when to enable +debug info access from the modules automatically. All modules with debug info +start off with their debug information turned off for expensive name and type +lookups. The debug information for line tables are always left enabled to allow +users to reliably set breakpoints by file and line. As the user debugs their +target, some simple things can cause module to get its debug information +enabled (called hydration): +- Setting a file and line breakpoint +- Any PC from any stack frame that maps to a module +- Setting a breakpoint by function name +- Finding a global variable by name + +Since most users set breakpoints by file and line, this is an easy way for +people to inform the debugger that they want focus on this module. Breakpoints +by file and line are always enabled when on demand symbols is being used. Line +tables in debug information are cheap to parse and breakpoints will be able to +be set in any module that has debug info. Setting breakpoints by file and line +acts as one of the primary ways to enable debug info for a module as it is +the most common way to stop your program at interesting areas of your code. + +Once the user hits a breakpoint, or stops the program for any other reason, +like a crash, assertion or signal, the debugger will calculate the stack frames +for one or more threads. Any stack frames whose PC value is contained within +one of a module's sections will have its debug information enabled. This allows +us to enable debug information for the areas of code that the user is stopped +in and will allow only the important subset of modules to have their debug +information enabled. + +On demand symbol loading tries to avoid indexing the names within the debug +information and makes a few tradeoffs to allow name matching of functions and +globals without having to always index all of the debug information. +Setting breakpoints by function name can work, but we try to avoid using +debug information by relying on the symbol tables from a module. Debug +information name indexing is one of the most expensive operations that we are +trying to avoid with the on demand symbol loading so this is one of the main +tradeoffs of this feature. When setting a breakpoint by function name, if the +symbol table contains a match, the debug inforamation will be enabled for that +module and the query will be completed using the debug information. This does +mean that setting breakpoints on inline function names can fail for modules +that have debug info, but no matches in the symbol table since inlined +functions don't exist in symbol tables. When using on demand symbol loading it +is encouraged to not strip the symbol tables of local symbols as this will +allow users to set breakpoints on all concrete functions reliably. Stripped +symbol tables have their local symbols removed from the symbol table which +means that static functions and non exported function will not appear in the +symbol tables. This can cause breakpoint setting by function name to fail when +previously it wouldn't fail. + +Global variable lookups rely on the same methodology as breakpoint setting by +function name: we use the symbol tables to find a match first if debug +information isn't enabled for a module. If we find a match in the symbol table +for a global variable lookup, we will enable debug inforamation and complete +the query using the debug information. It is encouraged to not strip your +symbol tables with this features as static variables and other non exported +globals will not appear in the symbol table and can lead to matches not being +found. + +What other things might fail? +----------------------------- + +The on demand symbol loading feature tries to limit expensive name lookups +within debug inforamtion. As such, some lookups by name might fail when they +wouldn't when this feature is not eabled: +- Setting breakpoints by function name for inlined functions +- Type lookups when the expression parser requests types by name +- Global variable lookups by name when the name of the variable is stripped + +Setting breakpoints by function name can fail for inline function because this +information is only contained in the debug information. No symbols are created +for inlined functions unless there is a concrete copy of the inlined function +in that same module. As a result, we might not end up stopping at all inlined +functions when requested with this feature enabled. Setting file and line +breakpoints are a good way still use on demand symbol loading effectively +and still being able to stop at inline function invocations. + +The expression parser often tries to lookup types by name when the user types +an expression. These are some of the most costly parts of expression evaluation +as the user can type something like "iterator" as part of their expression and +this can result in matches from all STL types in all modules. These kinds of +global type lookup queries can cause thousands of results to be found if debug +information is enabled. The way that most debug information is created these +days has the type information inlined into each module. Typically each module +will contain full type definitions in the debug info for any types that are +used in code. This means that when you type an expression when stopped, you +have debug information for all of the variables, arguments and global variables +in your current stack frame and we should be able to find type that are +important by using only the modules that have their debug information enabled. + +The expression parser can also be asked to display global variables and they +can be looked up by name. For this feature to work reliably with on demand +symbol loading enabled, just don't strip your symbol tables and the expression +parser should have no problem finding your variables. Most global variables +that are exported will still be in your symbol table if it is stripped, but +static variables and non exported globals will not be. + +Can I troubleshoot issues when I believe this feature is impeding my debugging? +------------------------------------------------------------------------------- + +Logging has been added that can be enabled to help notify our engineers when +something is not producing results when this feature is enabled. This logging +can be enabled during a debug session and can be sent to the LLDB engineers +to help troubleshoot these situation. To enable logging, type the following +command: + +:: + + (lldb) log enable -f /tmp/ondemand.txt lldb on-demand + +When the logging is enabled, we get full visibility into each query that would +have produced results if this feature were not enabled and will allow us to +troublshoot issues. Enabling this logging before an expression, setting a +breakpoint by name, or doing a type lookup can help us see the patterns that +cause failures and will help us improve this feature. diff --git a/lldb/include/lldb/Core/ModuleList.h b/lldb/include/lldb/Core/ModuleList.h index 01b9991..67205ca 100644 --- a/lldb/include/lldb/Core/ModuleList.h +++ b/lldb/include/lldb/Core/ModuleList.h @@ -68,6 +68,8 @@ public: FileSpec GetLLDBIndexCachePath() const; bool SetLLDBIndexCachePath(const FileSpec &path); + bool GetLoadSymbolOnDemand(); + PathMappingList GetSymlinkMappings() const; }; diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h index c9be5db..1470b96 100644 --- a/lldb/include/lldb/Symbol/SymbolFile.h +++ b/lldb/include/lldb/Symbol/SymbolFile.h @@ -77,6 +77,10 @@ public: ~SymbolFile() override = default; + /// SymbolFileOnDemand class overrides this to return the underlying + /// backing SymbolFile implementation that loads on-demand. + virtual SymbolFile *GetBackingSymbolFile() { return this; } + /// Get a mask of what this symbol file supports for the object file /// that it was constructed with. /// @@ -120,6 +124,16 @@ public: /// prior to any other functions in the SymbolFile protocol. virtual void InitializeObject() {} + /// Whether debug info will be loaded or not. + /// + /// It will be true for most implementations except SymbolFileOnDemand. + virtual bool GetLoadDebugInfoEnabled() { return true; } + + /// Specify debug info should be loaded. + /// + /// It will be no-op for most implementations except SymbolFileOnDemand. + virtual void SetLoadDebugInfoEnabled() { return; } + // Compile Unit function calls // Approach 1 - iterator virtual uint32_t GetNumCompileUnits() = 0; diff --git a/lldb/include/lldb/Symbol/SymbolFileOnDemand.h b/lldb/include/lldb/Symbol/SymbolFileOnDemand.h new file mode 100644 index 0000000..ad2a999 --- /dev/null +++ b/lldb/include/lldb/Symbol/SymbolFileOnDemand.h @@ -0,0 +1,235 @@ +//===-- SymbolFileOnDemand.h ------------------------------------*- C++-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SYMBOL_SYMBOLFILEONDEMAND_H +#define LLDB_SYMBOL_SYMBOLFILEONDEMAND_H + +#include +#include + +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/Statistics.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Flags.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +/// SymbolFileOnDemand wraps an actual SymbolFile by providing +/// on demand symbol parsing/indexing to improve performance. +/// By default SymbolFileOnDemand will skip load the underlying +/// symbols. Any client can on demand hydrate the underlying +/// SymbolFile via SymbolFile::SetLoadDebugInfoEnabled(). +class SymbolFileOnDemand : public lldb_private::SymbolFile { + /// LLVM RTTI support. + static char ID; + +public: + /// LLVM RTTI support. + /// \{ + bool isA(const void *ClassID) const override { + return ClassID == &ID || SymbolFile::isA(ClassID); + } + static bool classof(const SymbolFile *obj) { return obj->isA(&ID); } + /// \} + + SymbolFileOnDemand(std::unique_ptr &&symbol_file); + virtual ~SymbolFileOnDemand() override; + + // PluginInterface protocol + llvm::StringRef GetPluginName() override { return "ondemand"; } + + bool GetLoadDebugInfoEnabled() override { return m_debug_info_enabled; } + + void SetLoadDebugInfoEnabled() override; + + uint32_t GetNumCompileUnits() override; + lldb::CompUnitSP GetCompileUnitAtIndex(uint32_t idx) override; + + SymbolFile *GetBackingSymbolFile() override { return m_sym_file_impl.get(); } + + uint32_t CalculateAbilities() override; + + std::recursive_mutex &GetModuleMutex() const override; + + lldb::LanguageType + ParseLanguage(lldb_private::CompileUnit &comp_unit) override; + + lldb_private::XcodeSDK + ParseXcodeSDK(lldb_private::CompileUnit &comp_unit) override; + + void InitializeObject() override; + + size_t ParseFunctions(lldb_private::CompileUnit &comp_unit) override; + + bool ParseLineTable(lldb_private::CompileUnit &comp_unit) override; + + bool ParseDebugMacros(lldb_private::CompileUnit &comp_unit) override; + + bool ForEachExternalModule( + lldb_private::CompileUnit &, llvm::DenseSet &, + llvm::function_ref) override; + + bool ParseSupportFiles(lldb_private::CompileUnit &comp_unit, + lldb_private::FileSpecList &support_files) override; + + bool ParseIsOptimized(lldb_private::CompileUnit &comp_unit) override; + + size_t ParseTypes(lldb_private::CompileUnit &comp_unit) override; + + bool ParseImportedModules( + const lldb_private::SymbolContext &sc, + std::vector &imported_modules) override; + + size_t ParseBlocksRecursive(lldb_private::Function &func) override; + + size_t + ParseVariablesForContext(const lldb_private::SymbolContext &sc) override; + + lldb_private::Type *ResolveTypeUID(lldb::user_id_t type_uid) override; + llvm::Optional GetDynamicArrayInfoForUID( + lldb::user_id_t type_uid, + const lldb_private::ExecutionContext *exe_ctx) override; + + bool CompleteType(lldb_private::CompilerType &compiler_type) override; + + lldb_private::CompilerDecl GetDeclForUID(lldb::user_id_t uid) override; + + lldb_private::CompilerDeclContext + GetDeclContextForUID(lldb::user_id_t uid) override; + + lldb_private::CompilerDeclContext + GetDeclContextContainingUID(lldb::user_id_t uid) override; + + void + ParseDeclsForContext(lldb_private::CompilerDeclContext decl_ctx) override; + + uint32_t ResolveSymbolContext(const lldb_private::Address &so_addr, + lldb::SymbolContextItem resolve_scope, + lldb_private::SymbolContext &sc) override; + + uint32_t ResolveSymbolContext( + const lldb_private::SourceLocationSpec &src_location_spec, + lldb::SymbolContextItem resolve_scope, + lldb_private::SymbolContextList &sc_list) override; + + void Dump(lldb_private::Stream &s) override; + void DumpClangAST(lldb_private::Stream &s) override; + + void + FindGlobalVariables(lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, + lldb_private::VariableList &variables) override; + + void FindGlobalVariables(const lldb_private::RegularExpression ®ex, + uint32_t max_matches, + lldb_private::VariableList &variables) override; + + void FindFunctions(lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + lldb::FunctionNameType name_type_mask, + bool include_inlines, + lldb_private::SymbolContextList &sc_list) override; + + void FindFunctions(const lldb_private::RegularExpression ®ex, + bool include_inlines, + lldb_private::SymbolContextList &sc_list) override; + + void GetMangledNamesForFunction( + const std::string &scope_qualified_name, + std::vector &mangled_names) override; + + void + FindTypes(lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, + llvm::DenseSet &searched_symbol_files, + lldb_private::TypeMap &types) override; + + void FindTypes(llvm::ArrayRef pattern, + lldb_private::LanguageSet languages, + llvm::DenseSet &searched_symbol_files, + lldb_private::TypeMap &types) override; + + void GetTypes(lldb_private::SymbolContextScope *sc_scope, + lldb::TypeClass type_mask, + lldb_private::TypeList &type_list) override; + + llvm::Expected + GetTypeSystemForLanguage(lldb::LanguageType language) override; + + lldb_private::CompilerDeclContext FindNamespace( + lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx) override; + + std::vector> + ParseCallEdgesInFunction(UserID func_id) override; + + lldb::UnwindPlanSP + GetUnwindPlan(const Address &address, + const RegisterInfoResolver &resolver) override; + + llvm::Expected GetParameterStackSize(Symbol &symbol) override; + + void PreloadSymbols() override; + + uint64_t GetDebugInfoSize() override; + lldb_private::StatsDuration::Duration GetDebugInfoParseTime() override; + lldb_private::StatsDuration::Duration GetDebugInfoIndexTime() override; + + uint32_t GetAbilities() override; + + Symtab *GetSymtab() override { return m_sym_file_impl->GetSymtab(); } + + ObjectFile *GetObjectFile() override { + return m_sym_file_impl->GetObjectFile(); + } + const ObjectFile *GetObjectFile() const override { + return m_sym_file_impl->GetObjectFile(); + } + ObjectFile *GetMainObjectFile() override { + return m_sym_file_impl->GetMainObjectFile(); + } + + void SectionFileAddressesChanged() override { + return m_sym_file_impl->SectionFileAddressesChanged(); + } + + bool GetDebugInfoIndexWasLoadedFromCache() const override { + return m_sym_file_impl->GetDebugInfoIndexWasLoadedFromCache(); + } + void SetDebugInfoIndexWasLoadedFromCache() override { + m_sym_file_impl->SetDebugInfoIndexWasLoadedFromCache(); + } + bool GetDebugInfoIndexWasSavedToCache() const override { + return m_sym_file_impl->GetDebugInfoIndexWasSavedToCache(); + } + void SetDebugInfoIndexWasSavedToCache() override { + m_sym_file_impl->SetDebugInfoIndexWasSavedToCache(); + } + +private: + Log *GetLog() const { return ::lldb_private::GetLog(LLDBLog::OnDemand); } + + ConstString GetSymbolFileName() { + return GetObjectFile()->GetFileSpec().GetFilename(); + } + +private: + bool m_debug_info_enabled = false; + bool m_preload_symbols = false; + std::unique_ptr m_sym_file_impl; +}; +} // namespace lldb_private + +#endif // LLDB_SYMBOL_SYMBOLFILEONDEMAND_H diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h index d5c15bc..9c0b3ea 100644 --- a/lldb/include/lldb/Target/Statistics.h +++ b/lldb/include/lldb/Target/Statistics.h @@ -116,6 +116,8 @@ struct ModuleStats { bool symtab_saved_to_cache = false; bool debug_info_index_loaded_from_cache = false; bool debug_info_index_saved_to_cache = false; + bool debug_info_enabled = true; + bool symtab_stripped = false; }; struct ConstStringStats { diff --git a/lldb/include/lldb/Utility/LLDBLog.h b/lldb/include/lldb/Utility/LLDBLog.h index e91c68d..63dbb63 100644 --- a/lldb/include/lldb/Utility/LLDBLog.h +++ b/lldb/include/lldb/Utility/LLDBLog.h @@ -47,6 +47,7 @@ enum class LLDBLog : Log::MaskType { Types = Log::ChannelFlag<28>, Unwind = Log::ChannelFlag<29>, Watchpoints = Log::ChannelFlag<30>, + OnDemand = Log::ChannelFlag<31>, LLVM_MARK_AS_BITMASK_ENUM(Watchpoints), }; diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index 494cc47..2ca432a 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -33,6 +33,10 @@ let Definition = "modulelist" in { Global, DefaultUnsignedValue<7>, Desc<"The expiration time in days for a file. When a file hasn't been accessed for the specified amount of days, it is removed from the cache. A value of 0 disables the expiration-based pruning.">; + def LoadSymbolOnDemand: Property<"load-on-demand", "Boolean">, + Global, + DefaultFalse, + Desc<"Enable on demand symbol loading in LLDB. LLDB will load debug info on demand for each module based on various conditions (e.g. matched breakpoint, resolved stack frame addresses and matched global variables/function symbols in symbol table) to improve performance. Please refer to docs/use/ondemand.rst for details.">; } let Definition = "debugger" in { diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp index f68d82c..e89d5ec 100644 --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -475,6 +475,7 @@ uint32_t Module::ResolveSymbolContextForAddress( resolve_scope & eSymbolContextBlock || resolve_scope & eSymbolContextLineEntry || resolve_scope & eSymbolContextVariable) { + symfile->SetLoadDebugInfoEnabled(); resolved_flags |= symfile->ResolveSymbolContext(so_addr, resolve_scope, sc); } @@ -1405,7 +1406,6 @@ void Module::PreloadSymbols() { // Now let the symbol file preload its data and the symbol table will be // available without needing to take the module lock. sym_file->PreloadSymbols(); - } void Module::SetSymbolFileFileSpec(const FileSpec &file) { @@ -1711,6 +1711,9 @@ DataFileCache *Module::GetIndexCache() { return nullptr; // NOTE: intentional leak so we don't crash if global destructor chain gets // called as other threads still use the result of this function - static DataFileCache *g_data_file_cache = new DataFileCache(ModuleList::GetGlobalModuleListProperties().GetLLDBIndexCachePath().GetPath()); + static DataFileCache *g_data_file_cache = + new DataFileCache(ModuleList::GetGlobalModuleListProperties() + .GetLLDBIndexCachePath() + .GetPath()); return g_data_file_cache; } diff --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp index e30764f..1692a37 100644 --- a/lldb/source/Core/ModuleList.cpp +++ b/lldb/source/Core/ModuleList.cpp @@ -180,6 +180,12 @@ PathMappingList ModuleListProperties::GetSymlinkMappings() const { return m_symlink_paths; } +bool ModuleListProperties::GetLoadSymbolOnDemand() { + const uint32_t idx = ePropertyLoadSymbolOnDemand; + return m_collection_sp->GetPropertyAtIndexAsBoolean( + nullptr, idx, g_modulelist_properties[idx].default_uint_value != 0); +} + ModuleList::ModuleList() : m_modules(), m_modules_mutex() {} ModuleList::ModuleList(const ModuleList &rhs) : m_modules(), m_modules_mutex() { diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp index 6707d47..5131584 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp @@ -68,7 +68,8 @@ DWARFIndex::DIERefCallbackImpl::DIERefCallbackImpl( const DWARFIndex &index, llvm::function_ref callback, llvm::StringRef name) : m_index(index), - m_dwarf(*llvm::cast(index.m_module.GetSymbolFile())), + m_dwarf(*llvm::cast( + index.m_module.GetSymbolFile()->GetBackingSymbolFile())), m_callback(callback), m_name(name) {} bool DWARFIndex::DIERefCallbackImpl::operator()(DIERef ref) const { diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp index 5d99649..ad22078 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp @@ -65,8 +65,8 @@ bool DebugNamesDWARFIndex::ProcessEntry( llvm::Optional ref = ToDIERef(entry); if (!ref) return true; - SymbolFileDWARF &dwarf = - *llvm::cast(m_module.GetSymbolFile()); + SymbolFileDWARF &dwarf = *llvm::cast( + m_module.GetSymbolFile()->GetBackingSymbolFile()); DWARFDIE die = dwarf.GetDIE(*ref); if (!die) return true; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp index 5252c5f..2c6ab62 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -3987,8 +3987,8 @@ SymbolFileDWARFDebugMap *SymbolFileDWARF::GetDebugMapSymfile() { if (m_debug_map_symfile == nullptr) { lldb::ModuleSP module_sp(m_debug_map_module_wp.lock()); if (module_sp) { - m_debug_map_symfile = - static_cast(module_sp->GetSymbolFile()); + m_debug_map_symfile = llvm::cast( + module_sp->GetSymbolFile()->GetBackingSymbolFile()); } } return m_debug_map_symfile; diff --git a/lldb/source/Plugins/SymbolFile/PDB/PDBASTParser.cpp b/lldb/source/Plugins/SymbolFile/PDB/PDBASTParser.cpp index b466f33..e323635 100644 --- a/lldb/source/Plugins/SymbolFile/PDB/PDBASTParser.cpp +++ b/lldb/source/Plugins/SymbolFile/PDB/PDBASTParser.cpp @@ -801,7 +801,8 @@ bool PDBASTParser::CompleteTypeFromPDB( if (uid_it == m_forward_decl_to_uid.end()) return true; - auto symbol_file = static_cast(m_ast.GetSymbolFile()); + auto symbol_file = static_cast( + m_ast.GetSymbolFile()->GetBackingSymbolFile()); if (!symbol_file) return false; @@ -835,7 +836,8 @@ PDBASTParser::GetDeclForSymbol(const llvm::pdb::PDBSymbol &symbol) { if (it != m_uid_to_decl.end()) return it->second; - auto symbol_file = static_cast(m_ast.GetSymbolFile()); + auto symbol_file = static_cast( + m_ast.GetSymbolFile()->GetBackingSymbolFile()); if (!symbol_file) return nullptr; @@ -1001,7 +1003,8 @@ PDBASTParser::GetDeclContextForSymbol(const llvm::pdb::PDBSymbol &symbol) { return result; } - auto symbol_file = static_cast(m_ast.GetSymbolFile()); + auto symbol_file = static_cast( + m_ast.GetSymbolFile()->GetBackingSymbolFile()); if (!symbol_file) return nullptr; @@ -1041,7 +1044,8 @@ clang::DeclContext *PDBASTParser::GetDeclContextContainingSymbol( if (specs.empty()) return m_ast.GetTranslationUnitDecl(); - auto symbol_file = static_cast(m_ast.GetSymbolFile()); + auto symbol_file = static_cast( + m_ast.GetSymbolFile()->GetBackingSymbolFile()); if (!symbol_file) return m_ast.GetTranslationUnitDecl(); @@ -1094,7 +1098,8 @@ clang::DeclContext *PDBASTParser::GetDeclContextContainingSymbol( void PDBASTParser::ParseDeclsForDeclContext( const clang::DeclContext *decl_context) { - auto symbol_file = static_cast(m_ast.GetSymbolFile()); + auto symbol_file = static_cast( + m_ast.GetSymbolFile()->GetBackingSymbolFile()); if (!symbol_file) return; diff --git a/lldb/source/Symbol/CMakeLists.txt b/lldb/source/Symbol/CMakeLists.txt index d40fe41..3646f78 100644 --- a/lldb/source/Symbol/CMakeLists.txt +++ b/lldb/source/Symbol/CMakeLists.txt @@ -27,6 +27,7 @@ add_lldb_library(lldbSymbol Symbol.cpp SymbolContext.cpp SymbolFile.cpp + SymbolFileOnDemand.cpp SymbolVendor.cpp Symtab.cpp Type.cpp diff --git a/lldb/source/Symbol/CompileUnit.cpp b/lldb/source/Symbol/CompileUnit.cpp index 7c840d8..3ce908b 100644 --- a/lldb/source/Symbol/CompileUnit.cpp +++ b/lldb/source/Symbol/CompileUnit.cpp @@ -287,6 +287,9 @@ void CompileUnit::ResolveSymbolContext( if (num_file_indexes == 0) return; + // Found a matching source file in this compile unit load its debug info. + GetModule()->GetSymbolFile()->SetLoadDebugInfoEnabled(); + LineTable *line_table = sc.comp_unit->GetLineTable(); if (line_table == nullptr) { @@ -312,7 +315,7 @@ void CompileUnit::ResolveSymbolContext( line_idx = line_table->FindLineEntryIndexByFileIndex( 0, file_indexes, src_location_spec, &line_entry); } - + // If "exact == true", then "found_line" will be the same as "line". If // "exact == false", the "found_line" will be the closest line entry // with a line number greater than "line" and we will use this for our diff --git a/lldb/source/Symbol/SymbolFile.cpp b/lldb/source/Symbol/SymbolFile.cpp index 440c228..e69150e 100644 --- a/lldb/source/Symbol/SymbolFile.cpp +++ b/lldb/source/Symbol/SymbolFile.cpp @@ -12,6 +12,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolFileOnDemand.h" #include "lldb/Symbol/TypeMap.h" #include "lldb/Symbol/TypeSystem.h" #include "lldb/Symbol/VariableList.h" @@ -77,6 +78,24 @@ SymbolFile *SymbolFile::FindPlugin(ObjectFileSP objfile_sp) { } } if (best_symfile_up) { + // If symbol on-demand is enabled the winning symbol file parser is + // wrapped with SymbolFileOnDemand so that hydration of the debug info + // can be controlled to improve performance. + // + // Currently the supported on-demand symbol files include: + // executables, shared libraries and debug info files. + // + // To reduce unnecessary wrapping files with zero debug abilities are + // skipped. + ObjectFile::Type obj_file_type = objfile_sp->CalculateType(); + if (ModuleList::GetGlobalModuleListProperties().GetLoadSymbolOnDemand() && + best_symfile_abilities > 0 && + (obj_file_type == ObjectFile::eTypeExecutable || + obj_file_type == ObjectFile::eTypeSharedLibrary || + obj_file_type == ObjectFile::eTypeDebugInfo)) { + best_symfile_up = + std::make_unique(std::move(best_symfile_up)); + } // Let the winning symbol file parser initialize itself more completely // now that it has been chosen best_symfile_up->InitializeObject(); diff --git a/lldb/source/Symbol/SymbolFileOnDemand.cpp b/lldb/source/Symbol/SymbolFileOnDemand.cpp new file mode 100644 index 0000000..5c2cbc7 --- /dev/null +++ b/lldb/source/Symbol/SymbolFileOnDemand.cpp @@ -0,0 +1,590 @@ +//===-- SymbolFileDWARFDebugMap.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 "lldb/Symbol/SymbolFileOnDemand.h" + +#include "lldb/Core/Module.h" +#include "lldb/Symbol/SymbolFile.h" + +#include + +using namespace lldb; +using namespace lldb_private; + +char SymbolFileOnDemand::ID; + +SymbolFileOnDemand::SymbolFileOnDemand( + std::unique_ptr &&symbol_file) + : m_sym_file_impl(std::move(symbol_file)) {} + +SymbolFileOnDemand::~SymbolFileOnDemand() {} + +uint32_t SymbolFileOnDemand::CalculateAbilities() { + // Explicitly allow ability checking to pass though. + // This should be a cheap operation. + return m_sym_file_impl->CalculateAbilities(); +} + +std::recursive_mutex &SymbolFileOnDemand::GetModuleMutex() const { + return m_sym_file_impl->GetModuleMutex(); +} + +void SymbolFileOnDemand::InitializeObject() { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->InitializeObject(); +} + +lldb::LanguageType SymbolFileOnDemand::ParseLanguage(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + lldb::LanguageType langType = m_sym_file_impl->ParseLanguage(comp_unit); + if (langType != eLanguageTypeUnknown) + LLDB_LOG(log, "Language {0} would return if hydrated.", langType); + } + return eLanguageTypeUnknown; + } + return m_sym_file_impl->ParseLanguage(comp_unit); +} + +XcodeSDK SymbolFileOnDemand::ParseXcodeSDK(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + XcodeSDK defaultValue{}; + if (log) { + XcodeSDK sdk = m_sym_file_impl->ParseXcodeSDK(comp_unit); + if (!(sdk == defaultValue)) + LLDB_LOG(log, "SDK {0} would return if hydrated.", sdk.GetString()); + } + return defaultValue; + } + return m_sym_file_impl->ParseXcodeSDK(comp_unit); +} + +size_t SymbolFileOnDemand::ParseFunctions(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->ParseFunctions(comp_unit); +} + +bool SymbolFileOnDemand::ParseLineTable(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return false; + } + return m_sym_file_impl->ParseLineTable(comp_unit); +} + +bool SymbolFileOnDemand::ParseDebugMacros(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return false; + } + return m_sym_file_impl->ParseDebugMacros(comp_unit); +} + +bool SymbolFileOnDemand::ForEachExternalModule( + CompileUnit &comp_unit, + llvm::DenseSet &visited_symbol_files, + llvm::function_ref lambda) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + // Return false to not early exit. + return false; + } + return m_sym_file_impl->ForEachExternalModule(comp_unit, visited_symbol_files, + lambda); +} + +bool SymbolFileOnDemand::ParseSupportFiles(CompileUnit &comp_unit, + FileSpecList &support_files) { + LLDB_LOG(GetLog(), + "[{0}] {1} is not skipped: explicitly allowed to support breakpoint", + GetSymbolFileName(), __FUNCTION__); + // Explicitly allow this API through to support source line breakpoint. + return m_sym_file_impl->ParseSupportFiles(comp_unit, support_files); +} + +bool SymbolFileOnDemand::ParseIsOptimized(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + bool optimized = m_sym_file_impl->ParseIsOptimized(comp_unit); + if (optimized) { + LLDB_LOG(log, "Would return optimized if hydrated."); + } + } + return false; + } + return m_sym_file_impl->ParseIsOptimized(comp_unit); +} + +size_t SymbolFileOnDemand::ParseTypes(CompileUnit &comp_unit) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->ParseTypes(comp_unit); +} + +bool SymbolFileOnDemand::ParseImportedModules( + const lldb_private::SymbolContext &sc, + std::vector &imported_modules) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + std::vector tmp_imported_modules; + bool succeed = + m_sym_file_impl->ParseImportedModules(sc, tmp_imported_modules); + if (succeed) + LLDB_LOG(log, "{0} imported modules would be parsed if hydrated.", + tmp_imported_modules.size()); + } + return false; + } + return m_sym_file_impl->ParseImportedModules(sc, imported_modules); +} + +size_t SymbolFileOnDemand::ParseBlocksRecursive(Function &func) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->ParseBlocksRecursive(func); +} + +size_t SymbolFileOnDemand::ParseVariablesForContext(const SymbolContext &sc) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->ParseVariablesForContext(sc); +} + +Type *SymbolFileOnDemand::ResolveTypeUID(lldb::user_id_t type_uid) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + Type *resolved_type = m_sym_file_impl->ResolveTypeUID(type_uid); + if (resolved_type) + LLDB_LOG(log, "Type would be parsed for {0} if hydrated.", type_uid); + } + return nullptr; + } + return m_sym_file_impl->ResolveTypeUID(type_uid); +} + +llvm::Optional +SymbolFileOnDemand::GetDynamicArrayInfoForUID( + lldb::user_id_t type_uid, const lldb_private::ExecutionContext *exe_ctx) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return llvm::None; + } + return m_sym_file_impl->GetDynamicArrayInfoForUID(type_uid, exe_ctx); +} + +bool SymbolFileOnDemand::CompleteType(CompilerType &compiler_type) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return false; + } + return m_sym_file_impl->CompleteType(compiler_type); +} + +CompilerDecl SymbolFileOnDemand::GetDeclForUID(lldb::user_id_t type_uid) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + CompilerDecl parsed_decl = m_sym_file_impl->GetDeclForUID(type_uid); + if (parsed_decl != CompilerDecl()) { + LLDB_LOG(log, "CompilerDecl {0} would be parsed for {1} if hydrated.", + parsed_decl.GetName(), type_uid); + } + } + return CompilerDecl(); + } + return m_sym_file_impl->GetDeclForUID(type_uid); +} + +CompilerDeclContext +SymbolFileOnDemand::GetDeclContextForUID(lldb::user_id_t type_uid) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return CompilerDeclContext(); + } + return m_sym_file_impl->GetDeclContextForUID(type_uid); +} + +CompilerDeclContext +SymbolFileOnDemand::GetDeclContextContainingUID(lldb::user_id_t type_uid) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return CompilerDeclContext(); + } + return m_sym_file_impl->GetDeclContextContainingUID(type_uid); +} + +void SymbolFileOnDemand::ParseDeclsForContext(CompilerDeclContext decl_ctx) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->ParseDeclsForContext(decl_ctx); +} + +uint32_t +SymbolFileOnDemand::ResolveSymbolContext(const Address &so_addr, + SymbolContextItem resolve_scope, + SymbolContext &sc) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->ResolveSymbolContext(so_addr, resolve_scope, sc); +} + +uint32_t SymbolFileOnDemand::ResolveSymbolContext( + const SourceLocationSpec &src_location_spec, + SymbolContextItem resolve_scope, SymbolContextList &sc_list) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->ResolveSymbolContext(src_location_spec, resolve_scope, + sc_list); +} + +void SymbolFileOnDemand::Dump(lldb_private::Stream &s) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->Dump(s); +} + +void SymbolFileOnDemand::DumpClangAST(lldb_private::Stream &s) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->DumpClangAST(s); +} + +void SymbolFileOnDemand::FindGlobalVariables(const RegularExpression ®ex, + uint32_t max_matches, + VariableList &variables) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->FindGlobalVariables(regex, max_matches, variables); +} + +void SymbolFileOnDemand::FindGlobalVariables( + ConstString name, const CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, VariableList &variables) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + Symtab *symtab = GetSymtab(); + if (!symtab) { + LLDB_LOG(log, "[{0}] {1} is skipped - fail to get symtab", + GetSymbolFileName(), __FUNCTION__); + return; + } + Symbol *sym = symtab->FindFirstSymbolWithNameAndType( + name, eSymbolTypeData, Symtab::eDebugAny, Symtab::eVisibilityAny); + if (!sym) { + LLDB_LOG(log, "[{0}] {1} is skipped - fail to find match in symtab", + GetSymbolFileName(), __FUNCTION__); + return; + } + LLDB_LOG(log, "[{0}] {1} is NOT skipped - found match in symtab", + GetSymbolFileName(), __FUNCTION__); + + // Found match in symbol table hydrate debug info and + // allow the FindGlobalVariables to go through. + SetLoadDebugInfoEnabled(); + } + return m_sym_file_impl->FindGlobalVariables(name, parent_decl_ctx, + max_matches, variables); +} + +void SymbolFileOnDemand::FindFunctions(const RegularExpression ®ex, + bool include_inlines, + SymbolContextList &sc_list) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + Symtab *symtab = GetSymtab(); + if (!symtab) { + LLDB_LOG(log, "[{0}] {1} is skipped - fail to get symtab", + GetSymbolFileName(), __FUNCTION__); + return; + } + std::vector symbol_indexes; + symtab->AppendSymbolIndexesMatchingRegExAndType( + regex, eSymbolTypeAny, Symtab::eDebugAny, Symtab::eVisibilityAny, + symbol_indexes); + if (symbol_indexes.empty()) { + LLDB_LOG(log, "[{0}] {1} is skipped - fail to find match in symtab", + GetSymbolFileName(), __FUNCTION__); + return; + } + LLDB_LOG(log, "[{0}] {1} is NOT skipped - found match in symtab", + GetSymbolFileName(), __FUNCTION__); + + // Found match in symbol table hydrate debug info and + // allow the FindFucntions to go through. + SetLoadDebugInfoEnabled(); + } + return m_sym_file_impl->FindFunctions(regex, include_inlines, sc_list); +} + +void SymbolFileOnDemand::FindFunctions( + ConstString name, const CompilerDeclContext &parent_decl_ctx, + FunctionNameType name_type_mask, bool include_inlines, + SymbolContextList &sc_list) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + + Symtab *symtab = GetSymtab(); + if (!symtab) { + LLDB_LOG(log, "[{0}] {1}({2}) is skipped - fail to get symtab", + GetSymbolFileName(), __FUNCTION__, name); + return; + } + + SymbolContextList sc_list_helper; + symtab->FindFunctionSymbols(name, name_type_mask, sc_list_helper); + if (sc_list_helper.GetSize() == 0) { + LLDB_LOG(log, "[{0}] {1}({2}) is skipped - fail to find match in symtab", + GetSymbolFileName(), __FUNCTION__, name); + return; + } + LLDB_LOG(log, "[{0}] {1}({2}) is NOT skipped - found match in symtab", + GetSymbolFileName(), __FUNCTION__, name); + + // Found match in symbol table hydrate debug info and + // allow the FindFucntions to go through. + SetLoadDebugInfoEnabled(); + } + return m_sym_file_impl->FindFunctions(name, parent_decl_ctx, name_type_mask, + include_inlines, sc_list); +} + +void SymbolFileOnDemand::GetMangledNamesForFunction( + const std::string &scope_qualified_name, + std::vector &mangled_names) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1}({2}) is skipped", GetSymbolFileName(), + __FUNCTION__, scope_qualified_name); + return; + } + return m_sym_file_impl->GetMangledNamesForFunction(scope_qualified_name, + mangled_names); +} + +void SymbolFileOnDemand::FindTypes( + ConstString name, const CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, + llvm::DenseSet &searched_symbol_files, + TypeMap &types) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1}({2}) is skipped", GetSymbolFileName(), + __FUNCTION__, name); + return; + } + return m_sym_file_impl->FindTypes(name, parent_decl_ctx, max_matches, + searched_symbol_files, types); +} + +void SymbolFileOnDemand::FindTypes( + llvm::ArrayRef pattern, LanguageSet languages, + llvm::DenseSet &searched_symbol_files, TypeMap &types) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->FindTypes(pattern, languages, searched_symbol_files, + types); +} + +void SymbolFileOnDemand::GetTypes(SymbolContextScope *sc_scope, + TypeClass type_mask, TypeList &type_list) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->GetTypes(sc_scope, type_mask, type_list); +} + +llvm::Expected +SymbolFileOnDemand::GetTypeSystemForLanguage(LanguageType language) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped for language type {2}", + GetSymbolFileName(), __FUNCTION__, language); + return llvm::make_error( + "GetTypeSystemForLanguage is skipped by SymbolFileOnDemand", + llvm::inconvertibleErrorCode()); + } + return m_sym_file_impl->GetTypeSystemForLanguage(language); +} + +CompilerDeclContext +SymbolFileOnDemand::FindNamespace(ConstString name, + const CompilerDeclContext &parent_decl_ctx) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1}({2}) is skipped", GetSymbolFileName(), + __FUNCTION__, name); + return SymbolFile::FindNamespace(name, parent_decl_ctx); + } + return m_sym_file_impl->FindNamespace(name, parent_decl_ctx); +} + +std::vector> +SymbolFileOnDemand::ParseCallEdgesInFunction(UserID func_id) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + std::vector> call_edges = + m_sym_file_impl->ParseCallEdgesInFunction(func_id); + if (call_edges.size() > 0) { + LLDB_LOG(log, "{0} call edges would be parsed for {1} if hydrated.", + call_edges.size(), func_id.GetID()); + } + } + return {}; + } + return m_sym_file_impl->ParseCallEdgesInFunction(func_id); +} + +lldb::UnwindPlanSP +SymbolFileOnDemand::GetUnwindPlan(const Address &address, + const RegisterInfoResolver &resolver) { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return nullptr; + } + return m_sym_file_impl->GetUnwindPlan(address, resolver); +} + +llvm::Expected +SymbolFileOnDemand::GetParameterStackSize(Symbol &symbol) { + if (!m_debug_info_enabled) { + Log *log = GetLog(); + LLDB_LOG(log, "[{0}] {1} is skipped", GetSymbolFileName(), __FUNCTION__); + if (log) { + llvm::Expected stack_size = + m_sym_file_impl->GetParameterStackSize(symbol); + if (stack_size) { + LLDB_LOG(log, "{0} stack size would return for symbol {1} if hydrated.", + *stack_size, symbol.GetName()); + } + } + return SymbolFile::GetParameterStackSize(symbol); + } + return m_sym_file_impl->GetParameterStackSize(symbol); +} + +void SymbolFileOnDemand::PreloadSymbols() { + m_preload_symbols = true; + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return; + } + return m_sym_file_impl->PreloadSymbols(); +} + +uint64_t SymbolFileOnDemand::GetDebugInfoSize() { + // Always return the real debug info size. + LLDB_LOG(GetLog(), "[{0}] {1} is not skipped", GetSymbolFileName(), + __FUNCTION__); + return m_sym_file_impl->GetDebugInfoSize(); +} + +StatsDuration::Duration SymbolFileOnDemand::GetDebugInfoParseTime() { + // Always return the real parse time. + LLDB_LOG(GetLog(), "[{0}] {1} is not skipped", GetSymbolFileName(), + __FUNCTION__); + return m_sym_file_impl->GetDebugInfoParseTime(); +} + +StatsDuration::Duration SymbolFileOnDemand::GetDebugInfoIndexTime() { + // Always return the real index time. + LLDB_LOG(GetLog(), "[{0}] {1} is not skipped", GetSymbolFileName(), + __FUNCTION__); + return m_sym_file_impl->GetDebugInfoIndexTime(); +} + +void SymbolFileOnDemand::SetLoadDebugInfoEnabled() { + if (m_debug_info_enabled) + return; + LLDB_LOG(GetLog(), "[{0}] Hydrate debug info", GetSymbolFileName()); + m_debug_info_enabled = true; + InitializeObject(); + if (m_preload_symbols) + PreloadSymbols(); +} + +uint32_t SymbolFileOnDemand::GetNumCompileUnits() { + LLDB_LOG(GetLog(), "[{0}] {1} is not skipped to support breakpoint hydration", + GetSymbolFileName(), __FUNCTION__); + return m_sym_file_impl->GetNumCompileUnits(); +} + +CompUnitSP SymbolFileOnDemand::GetCompileUnitAtIndex(uint32_t idx) { + LLDB_LOG(GetLog(), "[{0}] {1} is not skipped to support breakpoint hydration", + GetSymbolFileName(), __FUNCTION__); + return m_sym_file_impl->GetCompileUnitAtIndex(idx); +} + +uint32_t SymbolFileOnDemand::GetAbilities() { + if (!m_debug_info_enabled) { + LLDB_LOG(GetLog(), "[{0}] {1} is skipped", GetSymbolFileName(), + __FUNCTION__); + return 0; + } + return m_sym_file_impl->GetAbilities(); +} diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp index 28800f9..b8ad25e 100644 --- a/lldb/source/Target/Statistics.cpp +++ b/lldb/source/Target/Statistics.cpp @@ -62,6 +62,8 @@ json::Value ModuleStats::ToJSON() const { debug_info_index_loaded_from_cache); module.try_emplace("debugInfoIndexSavedToCache", debug_info_index_saved_to_cache); + module.try_emplace("debugInfoEnabled", debug_info_enabled); + module.try_emplace("symbolTableStripped", symtab_stripped); if (!symfile_path.empty()) module.try_emplace("symbolFilePath", symfile_path); @@ -183,7 +185,10 @@ llvm::json::Value DebuggerStats::ReportStatistics(Debugger &debugger, std::vector modules; std::lock_guard guard( Module::GetAllocationModuleCollectionMutex()); - const size_t num_modules = Module::GetNumberAllocatedModules(); + const uint64_t num_modules = Module::GetNumberAllocatedModules(); + uint32_t num_debug_info_enabled_modules = 0; + uint32_t num_modules_has_debug_info = 0; + uint32_t num_stripped_modules = 0; for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { Module *module = Module::GetAllocatedModuleAtIndex(image_idx); ModuleStats module_stat; @@ -227,6 +232,15 @@ llvm::json::Value DebuggerStats::ReportStatistics(Debugger &debugger, ModuleList symbol_modules = sym_file->GetDebugInfoModules(); for (const auto &symbol_module: symbol_modules.Modules()) module_stat.symfile_modules.push_back((intptr_t)symbol_module.get()); + module_stat.symtab_stripped = module->GetObjectFile()->IsStripped(); + if (module_stat.symtab_stripped) + ++num_stripped_modules; + module_stat.debug_info_enabled = sym_file->GetLoadDebugInfoEnabled() && + module_stat.debug_info_size > 0; + if (module_stat.debug_info_enabled) + ++num_debug_info_enabled_modules; + if (module_stat.debug_info_size > 0) + ++num_modules_has_debug_info; } symtab_parse_time += module_stat.symtab_parse_time; symtab_index_time += module_stat.symtab_index_time; @@ -254,6 +268,10 @@ llvm::json::Value DebuggerStats::ReportStatistics(Debugger &debugger, {"totalDebugInfoIndexLoadedFromCache", debug_index_loaded}, {"totalDebugInfoIndexSavedToCache", debug_index_saved}, {"totalDebugInfoByteSize", debug_info_size}, + {"totalModuleCount", num_modules}, + {"totalModuleCountHasDebugInfo", num_modules_has_debug_info}, + {"totalDebugInfoEnabled", num_debug_info_enabled_modules}, + {"totalSymbolTableStripped", num_stripped_modules}, }; return std::move(global_stats); } diff --git a/lldb/source/Utility/LLDBLog.cpp b/lldb/source/Utility/LLDBLog.cpp index 4b535c1..71c534a 100644 --- a/lldb/source/Utility/LLDBLog.cpp +++ b/lldb/source/Utility/LLDBLog.cpp @@ -60,6 +60,9 @@ static constexpr Log::Category g_categories[] = { {{"types"}, {"log type system related activities"}, LLDBLog::Types}, {{"unwind"}, {"log stack unwind activities"}, LLDBLog::Unwind}, {{"watch"}, {"log watchpoint related activities"}, LLDBLog::Watchpoints}, + {{"on-demand"}, + {"log symbol on-demand related activities"}, + LLDBLog::OnDemand}, }; static Log::Channel g_log_channel(g_categories, diff --git a/lldb/test/API/symbol_ondemand/breakpoint_language/Makefile b/lldb/test/API/symbol_ondemand/breakpoint_language/Makefile new file mode 100644 index 0000000..f90fcb2 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_language/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := c_lang.c +CXX_SOURCES := main.cpp cpp_lang.cpp + +include Makefile.rules diff --git a/lldb/test/API/symbol_ondemand/breakpoint_language/TestBreakpointLanguageOnDemand.py b/lldb/test/API/symbol_ondemand/breakpoint_language/TestBreakpointLanguageOnDemand.py new file mode 100644 index 0000000..263dccd --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_language/TestBreakpointLanguageOnDemand.py @@ -0,0 +1,134 @@ +""" +Test that the language option for breakpoints works correctly +with symbol load on-demand. +""" + + +import lldb +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil + + +class TestBreakpointLanguage(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def check_location_file(self, bp, loc, test_name): + bp_loc = bp.GetLocationAtIndex(loc) + addr = bp_loc.GetAddress() + comp_unit = addr.GetCompileUnit() + comp_name = comp_unit.GetFileSpec().GetFilename() + return comp_name == test_name + + def test_regex_breakpoint_language(self): + """Test that the name regex breakpoint commands obey the language filter.""" + + self.build() + + # Load symbols on-demand + self.runCmd("settings set symbols.load-on-demand true") + + # Create a target by the debugger. + exe = self.getBuildArtifact("a.out") + error = lldb.SBError() + # Don't read in dependencies so we don't come across false matches that + # add unwanted breakpoint hits. + self.target = self.dbg.CreateTarget(exe, None, None, False, error) + self.assertTrue(self.target, VALID_TARGET) + + cpp_bp = self.target.BreakpointCreateByRegex( + "func_from", + lldb.eLanguageTypeC_plus_plus, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + cpp_bp.GetNumLocations(), 1, + "Only one C++ symbol matches") + self.assertTrue(self.check_location_file(cpp_bp, 0, "cpp_lang.cpp")) + + c_bp = self.target.BreakpointCreateByRegex( + "func_from", + lldb.eLanguageTypeC, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + c_bp.GetNumLocations(), 1, + "Only one C symbol matches") + self.assertTrue(self.check_location_file(c_bp, 0, "c_lang.c")) + + objc_bp = self.target.BreakpointCreateByRegex( + "func_from", + lldb.eLanguageTypeObjC, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + objc_bp.GetNumLocations(), 0, + "No ObjC symbol matches") + + def test_by_name_breakpoint_language(self): + """Test that the name regex breakpoint commands obey the language filter.""" + + self.build() + + # Load symbols on-demand + self.runCmd("settings set symbols.load-on-demand true") + + # Create a target by the debugger. + exe = self.getBuildArtifact("a.out") + error = lldb.SBError() + # Don't read in dependencies so we don't come across false matches that + # add unwanted breakpoint hits. + self.target = self.dbg.CreateTarget(exe, None, None, False, error) + self.assertTrue(self.target, VALID_TARGET) + + cpp_bp = self.target.BreakpointCreateByName( + "func_from_cpp", + lldb.eFunctionNameTypeAuto, + lldb.eLanguageTypeC_plus_plus, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + cpp_bp.GetNumLocations(), 1, + "Only one C++ symbol matches") + self.assertTrue(self.check_location_file(cpp_bp, 0, "cpp_lang.cpp")) + + no_cpp_bp = self.target.BreakpointCreateByName( + "func_from_c", + lldb.eFunctionNameTypeAuto, + lldb.eLanguageTypeC_plus_plus, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + no_cpp_bp.GetNumLocations(), 0, + "And the C one doesn't match") + + c_bp = self.target.BreakpointCreateByName( + "func_from_c", + lldb.eFunctionNameTypeAuto, + lldb.eLanguageTypeC, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + c_bp.GetNumLocations(), 1, + "Only one C symbol matches") + self.assertTrue(self.check_location_file(c_bp, 0, "c_lang.c")) + + no_c_bp = self.target.BreakpointCreateByName( + "func_from_cpp", + lldb.eFunctionNameTypeAuto, + lldb.eLanguageTypeC, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + no_c_bp.GetNumLocations(), 0, + "And the C++ one doesn't match") + + objc_bp = self.target.BreakpointCreateByName( + "func_from_cpp", + lldb.eFunctionNameTypeAuto, + lldb.eLanguageTypeObjC, + lldb.SBFileSpecList(), + lldb.SBFileSpecList()) + self.assertEqual( + objc_bp.GetNumLocations(), 0, + "No ObjC symbol matches") diff --git a/lldb/test/API/symbol_ondemand/breakpoint_language/c_lang.c b/lldb/test/API/symbol_ondemand/breakpoint_language/c_lang.c new file mode 100644 index 0000000..789bdc2 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_language/c_lang.c @@ -0,0 +1 @@ +int func_from_c() { return 5; } diff --git a/lldb/test/API/symbol_ondemand/breakpoint_language/cpp_lang.cpp b/lldb/test/API/symbol_ondemand/breakpoint_language/cpp_lang.cpp new file mode 100644 index 0000000..b8b21d24 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_language/cpp_lang.cpp @@ -0,0 +1 @@ +int func_from_cpp() { return 10; } diff --git a/lldb/test/API/symbol_ondemand/breakpoint_language/main.cpp b/lldb/test/API/symbol_ondemand/breakpoint_language/main.cpp new file mode 100644 index 0000000..f7add3c --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_language/main.cpp @@ -0,0 +1,9 @@ +#include +extern "C" int func_from_c(); +extern int func_from_cpp(); + +int main() { + func_from_c(); + func_from_cpp(); + return 0; +} diff --git a/lldb/test/API/symbol_ondemand/breakpoint_source_regex/Makefile b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py new file mode 100644 index 0000000..13490cc --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py @@ -0,0 +1,35 @@ +""" +Test source text regex breakpoint hydrates module debug info +in symbol on-demand mode. +""" + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestSourceTextRegexBreakpoint(TestBase): + mydir = TestBase.compute_mydir(__file__) + + def test_with_run_command(self): + self.build() + + # Load symbols on-demand + self.runCmd("settings set symbols.load-on-demand true") + + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_source_regexp( + self, "Set break point at this line.") + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', 'stop reason = breakpoint']) + + frame = self.frame() + self.assertTrue(frame.IsValid()) + self.assertEqual(frame.GetLineEntry().GetFileSpec().GetFilename(), "main.cpp") + self.assertEqual(frame.GetLineEntry().GetLine(), 4) diff --git a/lldb/test/API/symbol_ondemand/breakpoint_source_regex/main.cpp b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/main.cpp new file mode 100644 index 0000000..1c300cc --- /dev/null +++ b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/main.cpp @@ -0,0 +1,10 @@ +#include + +void foo() { + printf("hello world from foo"); // Set break point at this line. +} + +int main() { + foo(); + return 0; +} diff --git a/lldb/test/API/symbol_ondemand/shared_library/Makefile b/lldb/test/API/symbol_ondemand/shared_library/Makefile new file mode 100644 index 0000000..8e85f42 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/shared_library/Makefile @@ -0,0 +1,5 @@ +DYLIB_NAME := foo +DYLIB_C_SOURCES := foo.c +C_SOURCES := shared.c + +include Makefile.rules diff --git a/lldb/test/API/symbol_ondemand/shared_library/TestSharedLib.py b/lldb/test/API/symbol_ondemand/shared_library/TestSharedLib.py new file mode 100644 index 0000000..51a8e3a --- /dev/null +++ b/lldb/test/API/symbol_ondemand/shared_library/TestSharedLib.py @@ -0,0 +1,157 @@ +"""Test that types defined in shared libraries work correctly.""" + + +import lldb +import unittest2 +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil + + +class SharedLibTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number to break inside main(). + self.source = "shared.c" + self.line = line_number(self.source, "// Set breakpoint 0 here.") + self.shlib_names = ["foo"] + + def common_setup(self): + # Run in synchronous mode + self.dbg.SetAsync(False) + self.runCmd("settings set symbols.load-on-demand true") + + # Create a target by the debugger. + self.target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(self.target, VALID_TARGET) + + # Register our shared libraries for remote targets so they get + # automatically uploaded + self.environment = self.registerSharedLibrariesWithTarget( + self.target, self.shlib_names + ) + + ctx = self.platformContext + self.shared_lib_name = ctx.shlib_prefix + "foo." + ctx.shlib_extension + + def test_source_line_breakpoint(self): + self.build() + self.common_setup() + + lldbutil.run_break_set_by_file_and_line( + self, "foo.c", 4, num_expected_locations=1, loc_exact=True + ) + + # Now launch the process, and do not stop at entry point. + process = self.target.LaunchSimple( + None, self.environment, self.get_process_working_directory() + ) + self.assertTrue(process, PROCESS_IS_VALID) + + # The stop reason of the thread should be breakpoint. + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + # The breakpoint should have a hit count of 1. + lldbutil.check_breakpoint(self, bpno=1, expected_hit_count=1) + + thread = process.GetSelectedThread() + stack_frames = lldbutil.get_stack_frames(thread) + self.assertGreater(len(stack_frames), 2) + + leaf_frame = stack_frames[0] + self.assertEqual("foo.c", leaf_frame.GetLineEntry().GetFileSpec().GetFilename()) + self.assertEqual(4, leaf_frame.GetLineEntry().GetLine()) + + parent_frame = stack_frames[1] + self.assertEqual( + "shared.c", parent_frame.GetLineEntry().GetFileSpec().GetFilename() + ) + self.assertEqual(7, parent_frame.GetLineEntry().GetLine()) + + def test_symbolic_breakpoint(self): + self.build() + self.common_setup() + + lldbutil.run_break_set_by_symbol( + self, "foo", sym_exact=True, num_expected_locations=1 + ) + + # Now launch the process, and do not stop at entry point. + process = self.target.LaunchSimple( + None, self.environment, self.get_process_working_directory() + ) + self.assertTrue(process, PROCESS_IS_VALID) + + # The stop reason of the thread should be breakpoint. + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + # The breakpoint should have a hit count of 1. + lldbutil.check_breakpoint(self, bpno=1, expected_hit_count=1) + + thread = process.GetSelectedThread() + stack_frames = lldbutil.get_stack_frames(thread) + self.assertGreater(len(stack_frames), 2) + + leaf_frame = stack_frames[0] + self.assertEqual("foo.c", leaf_frame.GetLineEntry().GetFileSpec().GetFilename()) + self.assertEqual(4, leaf_frame.GetLineEntry().GetLine()) + + parent_frame = stack_frames[1] + self.assertEqual( + "shared.c", parent_frame.GetLineEntry().GetFileSpec().GetFilename() + ) + self.assertEqual(7, parent_frame.GetLineEntry().GetLine()) + + def test_global_variable_hydration(self): + self.build() + self.common_setup() + + lldbutil.run_break_set_by_file_and_line( + self, self.source, self.line, num_expected_locations=1, loc_exact=True + ) + + # Now launch the process, and do not stop at entry point. + process = self.target.LaunchSimple( + None, self.environment, self.get_process_working_directory() + ) + self.assertTrue(process, PROCESS_IS_VALID) + + # The stop reason of the thread should be breakpoint. + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + # The breakpoint should have a hit count of 1. + lldbutil.check_breakpoint(self, bpno=1, expected_hit_count=1) + + self.expect( + "target variable --shlib a.out", + "Breakpoint in a.out should have hydrated the debug info", + substrs=["global_shared = 897"], + ) + + self.expect( + "target variable --shlib " + self.shared_lib_name, + "shared library should not have debug info by default", + matching=False, + substrs=["global_foo"], + ) + + self.expect( + "target variable global_foo --shlib " + self.shared_lib_name, + "Match global_foo in symbol table should hydrate debug info", + matching=True, + substrs=["global_foo = 321"], + ) diff --git a/lldb/test/API/symbol_ondemand/shared_library/foo.c b/lldb/test/API/symbol_ondemand/shared_library/foo.c new file mode 100644 index 0000000..6d92c6a --- /dev/null +++ b/lldb/test/API/symbol_ondemand/shared_library/foo.c @@ -0,0 +1,4 @@ +#include + +int global_foo = 321; +void foo(void) { puts("Hello, I am a shared library"); } diff --git a/lldb/test/API/symbol_ondemand/shared_library/foo.h b/lldb/test/API/symbol_ondemand/shared_library/foo.h new file mode 100644 index 0000000..847d7a1 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/shared_library/foo.h @@ -0,0 +1,6 @@ +#ifndef foo_h__ +#define foo_h__ + +extern void foo(void); + +#endif // foo_h__ diff --git a/lldb/test/API/symbol_ondemand/shared_library/shared.c b/lldb/test/API/symbol_ondemand/shared_library/shared.c new file mode 100644 index 0000000..4241ee3 --- /dev/null +++ b/lldb/test/API/symbol_ondemand/shared_library/shared.c @@ -0,0 +1,9 @@ +#include "foo.h" +#include + +int global_shared = 897; +int main(void) { + puts("This is a shared library test..."); + foo(); // Set breakpoint 0 here. + return 0; +} diff --git a/lldb/test/Shell/SymbolFile/OnDemand/Inputs/basic.cpp b/lldb/test/Shell/SymbolFile/OnDemand/Inputs/basic.cpp new file mode 100644 index 0000000..ec45938 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/OnDemand/Inputs/basic.cpp @@ -0,0 +1,5 @@ +int bar(int x, int y) { return x + y + 5; } + +int foo(int x, int y) { return bar(x, y) + 12; } + +int main(int argc, char **argv) { return foo(33, 78); } diff --git a/lldb/test/Shell/SymbolFile/OnDemand/source-breakpoint.test b/lldb/test/Shell/SymbolFile/OnDemand/source-breakpoint.test new file mode 100644 index 0000000..465a6e1 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/OnDemand/source-breakpoint.test @@ -0,0 +1,23 @@ +# Test shows that source line breakpoint works with LLDB on demand symbol loading. + +# RUN: mkdir -p %t +# RUN: cd %t +# RUN: %build %p/Inputs/basic.cpp -o basic.out +# RUN: %lldb -b -O "settings set symbols.load-on-demand true" -s %s basic.out | FileCheck %s + +breakpoint list +# CHECK: No breakpoints currently set + +breakpoint set -f basic.cpp -l 1 +# CHECK: where = {{.*}}`bar(int, int) + {{.*}} at basic.cpp:1 + +breakpoint list +# CHECK: file = 'basic.cpp' + +run +# CHECK: stop reason = breakpoint + +bt +# CHECK: {{.*}}`bar(x=33, y=78) at basic.cpp:1 +# CHECK: {{.*}}`foo(x=33, y=78) at basic.cpp:3 +# CHECK: {{.*}}`main(argc=1, argv={{.*}}) at basic.cpp:5 diff --git a/lldb/test/Shell/SymbolFile/OnDemand/symbolic-breakpoint.test b/lldb/test/Shell/SymbolFile/OnDemand/symbolic-breakpoint.test new file mode 100644 index 0000000..d8f61aa --- /dev/null +++ b/lldb/test/Shell/SymbolFile/OnDemand/symbolic-breakpoint.test @@ -0,0 +1,23 @@ +# Test shows that symbolic function breakpoint works with LLDB on demand symbol loading. + +# RUN: mkdir -p %t +# RUN: cd %t +# RUN: %build %p/Inputs/basic.cpp -o basic.out +# RUN: %lldb -b -O "settings set symbols.load-on-demand true" -s %s basic.out | FileCheck %s + +breakpoint list +# CHECK: No breakpoints currently set + +b bar +# CHECK: where = {{.*}}`bar(int, int) + {{.*}} at basic.cpp:1 + +breakpoint list +# CHECK: where = {{.*}}`bar(int, int) + {{.*}} at basic.cpp:1 + +run +# CHECK: stop reason = breakpoint + +bt +# CHECK: {{.*}}`bar(x=33, y=78) at basic.cpp:1 +# CHECK: {{.*}}`foo(x=33, y=78) at basic.cpp:3 +# CHECK: {{.*}}`main(argc=1, argv={{.*}}) at basic.cpp:5 -- 2.7.4