Refine the reporting mechanism for interruption.

Also, make it possible for new Targets which haven't been added to
the TargetList yet to check for interruption, and add a few more
places in building modules where we can check for interruption.

Differential Revision: https://reviews.llvm.org/D154542
This commit is contained in:
Jim Ingham 2023-05-23 11:13:36 -07:00
parent a4a26374aa
commit 2b0c886542
13 changed files with 256 additions and 46 deletions

View File

@ -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 <cassert>
@ -85,6 +86,8 @@ public:
eBroadcastSymbolChange = (1 << 3),
};
using DebuggerList = std::vector<lldb::DebuggerSP>;
static ConstString GetStaticBroadcasterClass();
/// Get the public broadcaster for this debugger.
@ -411,12 +414,75 @@ public:
/// 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.
bool InterruptRequested();
template <typename... Args>
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 = "<UNKNOWN>";
ReportInterruption(InterruptionReport(cur_func,
llvm::formatv(formatv,
std::forward<Args>(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 <typename... Args>
InterruptionReport(std::string function_name,
const char *format, Args &&... args) :
InterruptionReport(function_name, llvm::formatv(format, std::forward<Args>(args)...)) {}
std::string m_function_name;
std::string m_description;
const std::chrono::time_point<std::chrono::system_clock> 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.

View File

@ -184,6 +184,12 @@ public:
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 @@ public:
private:
collection m_target_list;
std::unordered_set<lldb::TargetSP> m_in_process_target_list;
mutable std::recursive_mutex m_target_list_mutex;
uint32_t m_selected_target_idx;
@ -206,6 +213,12 @@ private:
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);

View File

@ -813,12 +813,13 @@ SBValueList SBFrame::GetVariables(const lldb::SBVariablesOptions &options) {
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 @@ SBValueList SBFrame::GetVariables(const lldb::SBVariablesOptions &options) {
}
}
}
num_produced++;
}
}
if (recognized_arguments) {

View File

@ -735,7 +735,7 @@ void CommandCompletions::FrameIndexes(CommandInterpreter &interpreter,
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());

View File

@ -326,21 +326,29 @@ protected:
}
} 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<int32_t>(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<int32_t>(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 {

View File

@ -2005,8 +2005,11 @@ protected:
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 @@ protected:
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 @@ protected:
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 @@ protected:
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 @@ protected:
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 @@ protected:
}
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 @@ protected:
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 @@ protected:
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 @@ protected:
const ModuleList &target_modules = target->GetImages();
std::lock_guard<std::recursive_mutex> 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,

View File

@ -228,8 +228,11 @@ protected:
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;

View File

@ -99,10 +99,9 @@ static size_t g_debugger_event_thread_stack_bytes = 8 * 1024 * 1024;
#pragma mark Static Functions
typedef std::vector<DebuggerSP> 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 @@ bool Debugger::InterruptRequested() {
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<std::recursive_mutex> 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<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr);

View File

@ -1047,10 +1047,38 @@ void Module::FindTypes(
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<std::recursive_mutex> 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();

View File

@ -1894,7 +1894,7 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
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 @@ void CommandInterpreter::PrintCommandOutput(IOHandler &io_handler,
}
std::lock_guard<std::recursive_mutex> 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();
}

View File

@ -509,11 +509,11 @@ bool StackFrameList::GetFramesUpTo(uint32_t end_idx,
} 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 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
// 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),

View File

@ -2236,7 +2236,6 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &module_spec, bool notify,
// each library in parallel.
if (GetPreloadSymbols())
module_sp->PreloadSymbols();
llvm::SmallVector<ModuleSP, 1> replaced_modules;
for (ModuleSP &old_module_sp : old_modules) {
if (m_images.GetIndexForModule(old_module_sp.get()) !=
@ -4205,6 +4204,10 @@ bool TargetProperties::SetPreferDynamicValue(lldb::DynamicValueType d) {
}
bool TargetProperties::GetPreloadSymbols() const {
if (INTERRUPT_REQUESTED(m_target->GetDebugger(),
"Interrupted checking preload symbols")) {
return false;
}
const uint32_t idx = ePropertyPreloadSymbols;
return GetPropertyAtIndexAs<bool>(
idx, g_target_properties[idx].default_uint_value != 0);

View File

@ -325,6 +325,7 @@ Status TargetList::CreateTargetInternal(Debugger &debugger,
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 @@ Status TargetList::CreateTargetInternal(Debugger &debugger,
// 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 @@ uint32_t TargetList::GetIndexOfTarget(lldb::TargetSP target_sp) const {
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 @@ lldb::TargetSP TargetList::GetSelectedTarget() {
m_selected_target_idx = 0;
return GetTargetAtIndex(m_selected_target_idx);
}
bool TargetList::AnyTargetContainsModule(Module &module) {
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> guard(m_target_list_mutex);
std::unordered_set<TargetSP>::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<std::recursive_mutex> 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<std::recursive_mutex> guard(m_target_list_mutex);
return m_in_process_target_list.count(target_sp) == 1;
}