diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -43,6 +43,7 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Threading.h" #include @@ -85,6 +86,8 @@ eBroadcastSymbolChange = (1 << 3), }; + using DebuggerList = std::vector; + static ConstString GetStaticBroadcasterClass(); /// Get the public broadcaster for this debugger. @@ -411,12 +414,75 @@ /// If you are on the RunCommandInterpreter thread, it will check the /// command interpreter state, and if it is on another thread it will /// check the debugger Interrupt Request state. + /// \param[in] cur_func + /// For reporting if the interruption was requested. Don't provide this by + /// hand, use INTERRUPT_REQUESTED so this gets done consistently. /// + /// \param[in] formatv + /// A formatv string for the interrupt message. If the elements of the + /// message are expensive to compute, you can use the no-argument form of + /// InterruptRequested, then make up the report using REPORT_INTERRUPTION. + /// /// \return /// A boolean value, if \b true an interruptible operation should interrupt /// itself. + template + bool InterruptRequested(const char *cur_func, + const char *formatv, Args &&... args) { + bool ret_val = InterruptRequested(); + if (ret_val) { + if (!formatv) + formatv = "Unknown message"; + if (!cur_func) + cur_func = ""; + ReportInterruption(InterruptionReport(cur_func, + llvm::formatv(formatv, + std::forward(args)...))); + } + return ret_val; + } + + + /// This handy define will keep you from having to generate a report for the + /// interruption by hand. Use this except in the case where the arguments to + /// the message description are expensive to compute. +#define INTERRUPT_REQUESTED(debugger, ...) \ + (debugger).InterruptRequested(__func__, __VA_ARGS__) + + // This form just queries for whether to interrupt, and does no reporting: bool InterruptRequested(); + + // FIXME: Do we want to capture a backtrace at the interruption point? + class InterruptionReport { + public: + InterruptionReport(std::string function_name, std::string description) : + m_function_name(std::move(function_name)), + m_description(std::move(description)), + m_interrupt_time(std::chrono::system_clock::now()), + m_thread_id(llvm::get_threadid()) {} + + InterruptionReport(std::string function_name, + const llvm::formatv_object_base &payload); + + template + InterruptionReport(std::string function_name, + const char *format, Args &&... args) : + InterruptionReport(function_name, llvm::formatv(format, std::forward(args)...)) {} + + std::string m_function_name; + std::string m_description; + const std::chrono::time_point m_interrupt_time; + const uint64_t m_thread_id; + }; + void ReportInterruption(const InterruptionReport &report); +#define REPORT_INTERRUPTION(debugger, ...) \ + (debugger).ReportInterruption(Debugger::InterruptionReport(__func__, \ + __VA_ARGS__)) + static DebuggerList DebuggersRequestingInterruption(); + +public: + // This is for use in the command interpreter, when you either want the // selected target, or if no target is present you want to prime the dummy // target with entities that will be copied over to new targets. diff --git a/lldb/include/lldb/Target/TargetList.h b/lldb/include/lldb/Target/TargetList.h --- a/lldb/include/lldb/Target/TargetList.h +++ b/lldb/include/lldb/Target/TargetList.h @@ -184,6 +184,12 @@ void SetSelectedTarget(const lldb::TargetSP &target); lldb::TargetSP GetSelectedTarget(); + + /// Returns whether any module, including ones in the process of being + /// added, contains this module. I don't want to give direct access to + /// these not yet added target, but for interruption purposes, we might + /// need to ask whether this target contains this module. + bool AnyTargetContainsModule(Module &module); TargetIterable Targets() { return TargetIterable(m_target_list, m_target_list_mutex); @@ -191,6 +197,7 @@ private: collection m_target_list; + std::unordered_set m_in_process_target_list; mutable std::recursive_mutex m_target_list_mutex; uint32_t m_selected_target_idx; @@ -206,6 +213,12 @@ lldb::PlatformSP &platform_sp, lldb::TargetSP &target_sp); + void RegisterInProcessTarget(lldb::TargetSP target_sp); + + void UnregisterInProcessTarget(lldb::TargetSP target_sp); + + bool IsTargetInProcess(lldb::TargetSP target_sp); + void AddTargetInternal(lldb::TargetSP target_sp, bool do_select); void SetSelectedTargetInternal(uint32_t index); diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp --- a/lldb/source/API/SBFrame.cpp +++ b/lldb/source/API/SBFrame.cpp @@ -813,12 +813,13 @@ if (variable_list) { const size_t num_variables = variable_list->GetSize(); if (num_variables) { + size_t num_produced = 0; for (const VariableSP &variable_sp : *variable_list) { - if (dbg.InterruptRequested()) { - Log *log = GetLog(LLDBLog::Host); - LLDB_LOG(log, "Interrupted SBFrame::GetVariables"); + if (INTERRUPT_REQUESTED(dbg, + "Interrupted getting frame variables with {0} of {1} " + "produced.", num_produced, num_variables)) return {}; - } + if (variable_sp) { bool add_variable = false; switch (variable_sp->GetScope()) { @@ -862,6 +863,7 @@ } } } + num_produced++; } } if (recognized_arguments) { diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp --- a/lldb/source/Commands/CommandCompletions.cpp +++ b/lldb/source/Commands/CommandCompletions.cpp @@ -735,7 +735,7 @@ lldb::StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(i); StreamString strm; // Dumping frames can be slow, allow interruption. - if (dbg.InterruptRequested()) + if (INTERRUPT_REQUESTED(dbg, "Interrupted in frame completion")) break; frame_sp->Dump(&strm, false, true); request.TryCompleteCurrentArg(std::to_string(i), strm.GetString()); diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -326,21 +326,29 @@ } } else if (*m_options.relative_frame_offset > 0) { // I don't want "up 20" where "20" takes you past the top of the stack - // to produce - // an error, but rather to just go to the top. So I have to count the - // stack here... - const uint32_t num_frames = thread->GetStackFrameCount(); - if (static_cast(num_frames - frame_idx) > - *m_options.relative_frame_offset) - frame_idx += *m_options.relative_frame_offset; + // to produce an error, but rather to just go to the top. OTOH, start + // by seeing if the requested frame exists, in which case we can avoid + // counting the stack here... + const uint32_t frame_requested = frame_idx + + *m_options.relative_frame_offset; + StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_requested); + if (frame_sp) + frame_idx = frame_requested; else { - if (frame_idx == num_frames - 1) { - // If we are already at the top of the stack, just warn and don't - // reset the frame. - result.AppendError("Already at the top of the stack."); - return false; - } else - frame_idx = num_frames - 1; + // The request went past the stack, so handle that case: + const uint32_t num_frames = thread->GetStackFrameCount(); + if (static_cast(num_frames - frame_idx) > + *m_options.relative_frame_offset) + frame_idx += *m_options.relative_frame_offset; + else { + if (frame_idx == num_frames - 1) { + // If we are already at the top of the stack, just warn and don't + // reset the frame. + result.AppendError("Already at the top of the stack."); + return false; + } else + frame_idx = num_frames - 1; + } } } } else { diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -2005,8 +2005,11 @@ result.GetOutputStream().EOL(); result.GetOutputStream().EOL(); } - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted in dump all symtabs with {0} " + "of {1} dumped.", num_dumped, num_modules)) break; + num_dumped++; DumpModuleSymtab(m_interpreter, result.GetOutputStream(), module_sp.get(), m_options.m_sort_order, @@ -2032,8 +2035,11 @@ result.GetOutputStream().EOL(); result.GetOutputStream().EOL(); } - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted in dump symtab list with {0} of {1} dumped.", + num_dumped, num_matches)) break; + num_dumped++; DumpModuleSymtab(m_interpreter, result.GetOutputStream(), module_sp.get(), m_options.m_sort_order, @@ -2093,8 +2099,11 @@ result.GetOutputStream().Format("Dumping sections for {0} modules.\n", num_modules); for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted in dump all sections with {0} of {1} dumped", + image_idx, num_modules)) break; + num_dumped++; DumpModuleSections( m_interpreter, result.GetOutputStream(), @@ -2111,8 +2120,11 @@ FindModulesByName(target, arg_cstr, module_list, true); if (num_matches > 0) { for (size_t i = 0; i < num_matches; ++i) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted in dump section list with {0} of {1} dumped.", + i, num_matches)) break; + Module *module = module_list.GetModulePointerAtIndex(i); if (module) { num_dumped++; @@ -2228,7 +2240,7 @@ result.GetOutputStream().Format("Dumping clang ast for {0} modules.\n", num_modules); for (ModuleSP module_sp : module_list.ModulesNoLocking()) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted dumping clang ast")) break; if (SymbolFile *sf = module_sp->GetSymbolFile()) sf->DumpClangAST(result.GetOutputStream()); @@ -2253,8 +2265,11 @@ } for (size_t i = 0; i < num_matches; ++i) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted in dump clang ast list with {0} of {1} dumped.", + i, num_matches)) break; + Module *m = module_list.GetModulePointerAtIndex(i); if (SymbolFile *sf = m->GetSymbolFile()) sf->DumpClangAST(result.GetOutputStream()); @@ -2302,8 +2317,11 @@ result.GetOutputStream().Format( "Dumping debug symbols for {0} modules.\n", num_modules); for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted in dumping all " + "debug symbols with {0} of {1} modules dumped", + num_dumped, num_modules)) break; + if (DumpModuleSymbolFile(result.GetOutputStream(), module_sp.get())) num_dumped++; } @@ -2318,7 +2336,9 @@ FindModulesByName(target, arg_cstr, module_list, true); if (num_matches > 0) { for (size_t i = 0; i < num_matches; ++i) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted dumping {0} " + "of {1} requested modules", + i, num_matches)) break; Module *module = module_list.GetModulePointerAtIndex(i); if (module) { @@ -2382,11 +2402,16 @@ const ModuleList &target_modules = target->GetImages(); std::lock_guard guard(target_modules.GetMutex()); - if (target_modules.GetSize() > 0) { + size_t num_modules = target_modules.GetSize(); + if (num_modules > 0) { uint32_t num_dumped = 0; for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { - if (GetDebugger().InterruptRequested()) + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted in dump all line tables with " + "{0} of {1} dumped", num_dumped, + num_modules)) break; + if (DumpCompileUnitLineTable( m_interpreter, result.GetOutputStream(), module_sp.get(), file_spec, diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -228,8 +228,11 @@ thread->GetIndexID()); return false; } - if (m_options.m_extended_backtrace && !GetDebugger().InterruptRequested()) { - DoExtendedBacktrace(thread, result); + if (m_options.m_extended_backtrace) { + if (!INTERRUPT_REQUESTED(GetDebugger(), + "Interrupt skipped extended backtrace")) { + DoExtendedBacktrace(thread, result); + } } return true; diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -99,10 +99,9 @@ #pragma mark Static Functions -typedef std::vector DebuggerList; static std::recursive_mutex *g_debugger_list_mutex_ptr = nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain -static DebuggerList *g_debugger_list_ptr = +static Debugger::DebuggerList *g_debugger_list_ptr = nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain static llvm::ThreadPool *g_thread_pool = nullptr; @@ -1276,6 +1275,33 @@ return GetCommandInterpreter().WasInterrupted(); } +Debugger::InterruptionReport::InterruptionReport(std::string function_name, + const llvm::formatv_object_base &payload) : + m_function_name(std::move(function_name)), + m_interrupt_time(std::chrono::system_clock::now()), + m_thread_id(llvm::get_threadid()) { + llvm::raw_string_ostream desc(m_description); + desc << payload << "\n"; +} + +void Debugger::ReportInterruption(const InterruptionReport &report) { + // For now, just log the description: + Log *log = GetLog(LLDBLog::Host); + LLDB_LOG(log, "Interruption: {0}", report.m_description); +} + +Debugger::DebuggerList Debugger::DebuggersRequestingInterruption() { + DebuggerList result; + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard guard(*g_debugger_list_mutex_ptr); + for (auto debugger_sp : *g_debugger_list_ptr) { + if (debugger_sp->InterruptRequested()) + result.push_back(debugger_sp); + } + } + return result; +} + size_t Debugger::GetNumDebuggers() { if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -1047,10 +1047,38 @@ symbols->FindTypes(pattern, languages, searched_symbol_files, types); } +static Debugger::DebuggerList +DebuggersOwningModuleRequestingInterruption(Module &module) { + Debugger::DebuggerList requestors + = Debugger::DebuggersRequestingInterruption(); + Debugger::DebuggerList interruptors; + if (requestors.empty()) + return interruptors; + + for (auto debugger_sp : requestors) { + if (!debugger_sp->InterruptRequested()) + continue; + if (debugger_sp->GetTargetList() + .AnyTargetContainsModule(module)) + interruptors.push_back(debugger_sp); + } + return interruptors; +} + SymbolFile *Module::GetSymbolFile(bool can_create, Stream *feedback_strm) { if (!m_did_load_symfile.load()) { std::lock_guard guard(m_mutex); if (!m_did_load_symfile.load() && can_create) { + Debugger::DebuggerList interruptors + = DebuggersOwningModuleRequestingInterruption(*this); + if (!interruptors.empty()) { + for (auto debugger_sp : interruptors) { + REPORT_INTERRUPTION(*(debugger_sp.get()), + "Interrupted fetching symbols for module {0}", + this->GetFileSpec()); + } + return nullptr; + } ObjectFile *obj_file = GetObjectFile(); if (obj_file != nullptr) { LLDB_SCOPED_TIMER(); diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1894,7 +1894,7 @@ LLDB_LOGF(log, "Processing command: %s", command_line); LLDB_SCOPED_TIMERF("Processing command: %s.", command_line); - if (GetDebugger().InterruptRequested()) { + if (INTERRUPT_REQUESTED(GetDebugger(), "Interrupted initiating command")) { result.AppendError("... Interrupted"); return false; } @@ -3071,7 +3071,8 @@ } std::lock_guard guard(io_handler.GetOutputMutex()); - if (had_output && GetDebugger().InterruptRequested()) + if (had_output && INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted dumping command output")) stream->Printf("\n... Interrupted.\n"); stream->Flush(); } diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -509,11 +509,11 @@ } else { // Check for interruption when building the frames. // Do the check in idx > 0 so that we'll always create a 0th frame. - if (allow_interrupt && dbg.InterruptRequested()) { - Log *log = GetLog(LLDBLog::Host); - LLDB_LOG(log, "Interrupted %s", __FUNCTION__); - was_interrupted = true; - break; + if (allow_interrupt + && INTERRUPT_REQUESTED(dbg, "Interrupted having fetched {0} frames", + m_frames.size())) { + was_interrupted = true; + break; } const bool success = @@ -965,11 +965,11 @@ // Check for interruption here. If we're fetching arguments, this loop // can go slowly: Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); - if (dbg.InterruptRequested()) { - Log *log = GetLog(LLDBLog::Host); - LLDB_LOG(log, "Interrupted %s", __FUNCTION__); + if (INTERRUPT_REQUESTED(dbg, + "Interrupted dumping stack for thread {0:hex} with {1} shown.", + m_thread.GetID(), num_frames_displayed)) break; - } + if (!frame_sp->GetStatus(strm, show_frame_info, num_frames_with_source > (first_frame - frame_idx), diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -2236,7 +2236,6 @@ // each library in parallel. if (GetPreloadSymbols()) module_sp->PreloadSymbols(); - llvm::SmallVector replaced_modules; for (ModuleSP &old_module_sp : old_modules) { if (m_images.GetIndexForModule(old_module_sp.get()) != @@ -4205,6 +4204,10 @@ } bool TargetProperties::GetPreloadSymbols() const { + if (INTERRUPT_REQUESTED(m_target->GetDebugger(), + "Interrupted checking preload symbols")) { + return false; + } const uint32_t idx = ePropertyPreloadSymbols; return GetPropertyAtIndexAs( idx, g_target_properties[idx].default_uint_value != 0); diff --git a/lldb/source/Target/TargetList.cpp b/lldb/source/Target/TargetList.cpp --- a/lldb/source/Target/TargetList.cpp +++ b/lldb/source/Target/TargetList.cpp @@ -325,6 +325,7 @@ return error; } target_sp.reset(new Target(debugger, arch, platform_sp, is_dummy_target)); + debugger.GetTargetList().RegisterInProcessTarget(target_sp); target_sp->SetExecutableModule(exe_module_sp, load_dependent_files); if (user_exe_path_is_bundle) exe_module_sp->GetFileSpec().GetPath(resolved_bundle_exe_path, @@ -336,6 +337,7 @@ // No file was specified, just create an empty target with any arch if a // valid arch was specified target_sp.reset(new Target(debugger, arch, platform_sp, is_dummy_target)); + debugger.GetTargetList().RegisterInProcessTarget(target_sp); } if (!target_sp) @@ -513,6 +515,7 @@ void TargetList::AddTargetInternal(TargetSP target_sp, bool do_select) { lldbassert(!llvm::is_contained(m_target_list, target_sp) && "target already exists it the list"); + UnregisterInProcessTarget(target_sp); m_target_list.push_back(std::move(target_sp)); if (do_select) SetSelectedTargetInternal(m_target_list.size() - 1); @@ -540,3 +543,35 @@ m_selected_target_idx = 0; return GetTargetAtIndex(m_selected_target_idx); } + +bool TargetList::AnyTargetContainsModule(Module &module) { + std::lock_guard guard(m_target_list_mutex); + for (const auto &target_sp : m_target_list) { + if (target_sp->GetImages().FindModule(&module)) + return true; + } + for (const auto &target_sp: m_in_process_target_list) { + if (target_sp->GetImages().FindModule(&module)) + return true; + } + return false; +} + + void TargetList::RegisterInProcessTarget(TargetSP target_sp) { + std::lock_guard guard(m_target_list_mutex); + std::unordered_set::iterator iter; + bool was_added; + std::tie(iter, was_added) = m_in_process_target_list.insert(target_sp); + assert(was_added && "Target pointer was left in the in-process map"); + } + + void TargetList::UnregisterInProcessTarget(TargetSP target_sp) { + std::lock_guard guard(m_target_list_mutex); + bool was_present = m_in_process_target_list.erase(target_sp); + assert(was_present && "Target pointer being removed was not registered"); + } + + bool TargetList::IsTargetInProcess(TargetSP target_sp) { + std::lock_guard guard(m_target_list_mutex); + return m_in_process_target_list.count(target_sp) == 1; + }