137 changes: 123 additions & 14 deletions lldb/source/Commands/CommandObjectThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,44 @@ using namespace lldb;
using namespace lldb_private;

//-------------------------------------------------------------------------
// CommandObjectThreadBacktrace
// CommandObjectIterateOverThreads
//-------------------------------------------------------------------------

class CommandObjectIterateOverThreads : public CommandObjectParsed {

class UniqueStack {

public:
UniqueStack(std::stack<lldb::addr_t> stack_frames, uint32_t thread_index_id)
: m_stack_frames(stack_frames) {
m_thread_index_ids.push_back(thread_index_id);
}

void AddThread(uint32_t thread_index_id) const {
m_thread_index_ids.push_back(thread_index_id);
}

const std::vector<uint32_t> &GetUniqueThreadIndexIDs() const {
return m_thread_index_ids;
}

lldb::tid_t GetRepresentativeThread() const {
return m_thread_index_ids.front();
}

friend bool inline operator<(const UniqueStack &lhs,
const UniqueStack &rhs) {
return lhs.m_stack_frames < rhs.m_stack_frames;
}

protected:
// Mark the thread index as mutable, as we don't care about it from a const
// perspective, we only care about m_stack_frames so we keep our std::set
// sorted.
mutable std::vector<uint32_t> m_thread_index_ids;
std::stack<lldb::addr_t> m_stack_frames;
};

public:
CommandObjectIterateOverThreads(CommandInterpreter &interpreter,
const char *name, const char *help,
Expand All @@ -57,20 +91,23 @@ class CommandObjectIterateOverThreads : public CommandObjectParsed {
bool DoExecute(Args &command, CommandReturnObject &result) override {
result.SetStatus(m_success_return);

bool all_threads = false;
if (command.GetArgumentCount() == 0) {
Thread *thread = m_exe_ctx.GetThreadPtr();
if (!HandleOneThread(thread->GetID(), result))
return false;
return result.Succeeded();
} else if (command.GetArgumentCount() == 1) {
all_threads = ::strcmp(command.GetArgumentAtIndex(0), "all") == 0;
m_unique_stacks = ::strcmp(command.GetArgumentAtIndex(0), "unique") == 0;
}

// Use tids instead of ThreadSPs to prevent deadlocking problems which
// result from JIT-ing
// code while iterating over the (locked) ThreadSP list.
std::vector<lldb::tid_t> tids;

if (command.GetArgumentCount() == 1 &&
::strcmp(command.GetArgumentAtIndex(0), "all") == 0) {
if (all_threads || m_unique_stacks) {
Process *process = m_exe_ctx.GetProcessPtr();

for (ThreadSP thread_sp : process->Threads())
Expand Down Expand Up @@ -108,15 +145,47 @@ class CommandObjectIterateOverThreads : public CommandObjectParsed {
}
}

uint32_t idx = 0;
for (const lldb::tid_t &tid : tids) {
if (idx != 0 && m_add_return)
result.AppendMessage("");
if (m_unique_stacks) {
// Iterate over threads, finding unique stack buckets.
std::set<UniqueStack> unique_stacks;
for (const lldb::tid_t &tid : tids) {
if (!BucketThread(tid, unique_stacks, result)) {
return false;
}
}

if (!HandleOneThread(tid, result))
return false;
// Write the thread id's and unique call stacks to the output stream
Stream &strm = result.GetOutputStream();
Process *process = m_exe_ctx.GetProcessPtr();
for (const UniqueStack &stack : unique_stacks) {
// List the common thread ID's
const std::vector<uint32_t> &thread_index_ids =
stack.GetUniqueThreadIndexIDs();
strm.Printf("%lu thread(s) ", thread_index_ids.size());
for (const uint32_t &thread_index_id : thread_index_ids) {
strm.Printf("#%u ", thread_index_id);
}
strm.EOL();

++idx;
// List the shared call stack for this set of threads
uint32_t representative_thread_id = stack.GetRepresentativeThread();
ThreadSP thread = process->GetThreadList().FindThreadByIndexID(
representative_thread_id);
if (!HandleOneThread(thread->GetID(), result)) {
return false;
}
}
} else {
uint32_t idx = 0;
for (const lldb::tid_t &tid : tids) {
if (idx != 0 && m_add_return)
result.AppendMessage("");

if (!HandleOneThread(tid, result))
return false;

++idx;
}
}
return result.Succeeded();
}
Expand All @@ -134,7 +203,43 @@ class CommandObjectIterateOverThreads : public CommandObjectParsed {

virtual bool HandleOneThread(lldb::tid_t, CommandReturnObject &result) = 0;

bool BucketThread(lldb::tid_t tid, std::set<UniqueStack> &unique_stacks,
CommandReturnObject &result) {
// Grab the corresponding thread for the given thread id.
Process *process = m_exe_ctx.GetProcessPtr();
Thread *thread = process->GetThreadList().FindThreadByID(tid).get();
if (thread == nullptr) {
result.AppendErrorWithFormat("Failed to process thread# %lu.\n", tid);
result.SetStatus(eReturnStatusFailed);
return false;
}

// Collect the each frame's address for this call-stack
std::stack<lldb::addr_t> stack_frames;
const uint32_t frame_count = thread->GetStackFrameCount();
for (uint32_t frame_index = 0; frame_index < frame_count; frame_index++) {
const lldb::StackFrameSP frame_sp =
thread->GetStackFrameAtIndex(frame_index);
const lldb::addr_t pc = frame_sp->GetStackID().GetPC();
stack_frames.push(pc);
}

uint32_t thread_index_id = thread->GetIndexID();
UniqueStack new_unique_stack(stack_frames, thread_index_id);

// Try to match the threads stack to and existing entry.
std::set<UniqueStack>::iterator matching_stack =
unique_stacks.find(new_unique_stack);
if (matching_stack != unique_stacks.end()) {
matching_stack->AddThread(thread_index_id);
} else {
unique_stacks.insert(new_unique_stack);
}
return true;
}

ReturnStatus m_success_return = eReturnStatusSuccessFinishResult;
bool m_unique_stacks = false;
bool m_add_return = true;
};

Expand Down Expand Up @@ -218,9 +323,10 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
: CommandObjectIterateOverThreads(
interpreter, "thread backtrace",
"Show thread call stacks. Defaults to the current thread, thread "
"indexes can be specified as arguments. Use the thread-index "
"\"all\" "
"to see all threads.",
"indexes can be specified as arguments.\n"
"Use the thread-index \"all\" to see all threads.\n"
"Use the thread-index \"unique\" to see threads grouped by unique "
"call stacks.",
nullptr,
eCommandRequiresProcess | eCommandRequiresThread |
eCommandTryTargetAPILock | eCommandProcessMustBeLaunched |
Expand Down Expand Up @@ -270,11 +376,14 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {

Stream &strm = result.GetOutputStream();

// Only dump stack info if we processing unique stacks.
const bool only_stacks = m_unique_stacks;

// Don't show source context when doing backtraces.
const uint32_t num_frames_with_source = 0;
const bool stop_format = true;
if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count,
num_frames_with_source, stop_format)) {
num_frames_with_source, stop_format, only_stacks)) {
result.AppendErrorWithFormat(
"error displaying backtrace for thread: \"0x%4.4x\"\n",
thread->GetIndexID());
Expand Down
73 changes: 51 additions & 22 deletions lldb/source/Core/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ OptionEnumValueElement g_language_enumerators[] = {
"{ " \
"${module.file.basename}{`${function.name-with-args}" \
"{${frame.no-debug}${function.pc-offset}}}}"

#define MODULE_WITH_FUNC_NO_ARGS \
"{ " \
"${module.file.basename}{`${function.name-without-args}" \
"{${frame.no-debug}${function.pc-offset}}}}"

#define FILE_AND_LINE "{ at ${line.file.basename}:${line.number}}"
#define IS_OPTIMIZED "{${function.is-optimized} [opt]}"

Expand Down Expand Up @@ -141,6 +147,10 @@ OptionEnumValueElement g_language_enumerators[] = {
"frame #${frame.index}: ${frame.pc}" MODULE_WITH_FUNC FILE_AND_LINE \
IS_OPTIMIZED "\\n"

#define DEFAULT_FRAME_FORMAT_NO_ARGS \
"frame #${frame.index}: ${frame.pc}" MODULE_WITH_FUNC_NO_ARGS FILE_AND_LINE \
IS_OPTIMIZED "\\n"

// Three parts to this disassembly format specification:
// 1. If this is a new function/symbol (no previous symbol/function), print
// dylib`funcname:\n
Expand Down Expand Up @@ -186,13 +196,15 @@ static PropertyDefinition g_properties[] = {
{"auto-confirm", OptionValue::eTypeBoolean, true, false, nullptr, nullptr,
"If true all confirmation prompts will receive their default reply."},
{"disassembly-format", OptionValue::eTypeFormatEntity, true, 0,
DEFAULT_DISASSEMBLY_FORMAT, nullptr, "The default disassembly format "
"string to use when disassembling "
"instruction sequences."},
DEFAULT_DISASSEMBLY_FORMAT, nullptr,
"The default disassembly format "
"string to use when disassembling "
"instruction sequences."},
{"frame-format", OptionValue::eTypeFormatEntity, true, 0,
DEFAULT_FRAME_FORMAT, nullptr, "The default frame format string to use "
"when displaying stack frame information "
"for threads."},
DEFAULT_FRAME_FORMAT, nullptr,
"The default frame format string to use "
"when displaying stack frame information "
"for threads."},
{"notify-void", OptionValue::eTypeBoolean, true, false, nullptr, nullptr,
"Notify the user explicitly if an expression returns void (default: "
"false)."},
Expand All @@ -203,18 +215,21 @@ static PropertyDefinition g_properties[] = {
nullptr, g_language_enumerators,
"The script language to be used for evaluating user-written scripts."},
{"stop-disassembly-count", OptionValue::eTypeSInt64, true, 4, nullptr,
nullptr, "The number of disassembly lines to show when displaying a "
"stopped context."},
nullptr,
"The number of disassembly lines to show when displaying a "
"stopped context."},
{"stop-disassembly-display", OptionValue::eTypeEnum, true,
Debugger::eStopDisassemblyTypeNoDebugInfo, nullptr,
g_show_disassembly_enum_values,
"Control when to display disassembly when displaying a stopped context."},
{"stop-line-count-after", OptionValue::eTypeSInt64, true, 3, nullptr,
nullptr, "The number of sources lines to display that come after the "
"current source line when displaying a stopped context."},
nullptr,
"The number of sources lines to display that come after the "
"current source line when displaying a stopped context."},
{"stop-line-count-before", OptionValue::eTypeSInt64, true, 3, nullptr,
nullptr, "The number of sources lines to display that come before the "
"current source line when displaying a stopped context."},
nullptr,
"The number of sources lines to display that come before the "
"current source line when displaying a stopped context."},
{"stop-show-column", OptionValue::eTypeEnum, false,
eStopShowColumnAnsiOrCaret, nullptr, s_stop_show_column_values,
"If true, LLDB will use the column information from the debug info to "
Expand All @@ -232,19 +247,22 @@ static PropertyDefinition g_properties[] = {
{"term-width", OptionValue::eTypeSInt64, true, 80, nullptr, nullptr,
"The maximum number of columns to use for displaying text."},
{"thread-format", OptionValue::eTypeFormatEntity, true, 0,
DEFAULT_THREAD_FORMAT, nullptr, "The default thread format string to use "
"when displaying thread information."},
DEFAULT_THREAD_FORMAT, nullptr,
"The default thread format string to use "
"when displaying thread information."},
{"thread-stop-format", OptionValue::eTypeFormatEntity, true, 0,
DEFAULT_THREAD_STOP_FORMAT, nullptr, "The default thread format "
"string to usewhen displaying thread "
"information as part of the stop display."},
DEFAULT_THREAD_STOP_FORMAT, nullptr,
"The default thread format "
"string to use when displaying thread "
"information as part of the stop display."},
{"use-external-editor", OptionValue::eTypeBoolean, true, false, nullptr,
nullptr, "Whether to use an external editor or not."},
{"use-color", OptionValue::eTypeBoolean, true, true, nullptr, nullptr,
"Whether to use Ansi color codes or not."},
{"auto-one-line-summaries", OptionValue::eTypeBoolean, true, true, nullptr,
nullptr, "If true, LLDB will automatically display small structs in "
"one-liner format (default: true)."},
nullptr,
"If true, LLDB will automatically display small structs in "
"one-liner format (default: true)."},
{"auto-indent", OptionValue::eTypeBoolean, true, true, nullptr, nullptr,
"If true, LLDB will auto indent/outdent code. Currently only supported in "
"the REPL (default: true)."},
Expand All @@ -255,8 +273,13 @@ static PropertyDefinition g_properties[] = {
"The tab size to use when indenting code in multi-line input mode "
"(default: 4)."},
{"escape-non-printables", OptionValue::eTypeBoolean, true, true, nullptr,
nullptr, "If true, LLDB will automatically escape non-printable and "
"escape characters when formatting strings."},
nullptr,
"If true, LLDB will automatically escape non-printable and "
"escape characters when formatting strings."},
{"frame-format-unique", OptionValue::eTypeFormatEntity, true, 0,
DEFAULT_FRAME_FORMAT_NO_ARGS, nullptr,
"The default frame format string to use when displaying stack frame"
"information for threads from thread backtrace unique."},
{nullptr, OptionValue::eTypeInvalid, true, 0, nullptr, nullptr, nullptr}};

enum {
Expand All @@ -282,7 +305,8 @@ enum {
ePropertyAutoIndent,
ePropertyPrintDecls,
ePropertyTabSize,
ePropertyEscapeNonPrintables
ePropertyEscapeNonPrintables,
ePropertyFrameFormatUnique,
};

LoadPluginCallbackType Debugger::g_load_plugin_callback = nullptr;
Expand Down Expand Up @@ -358,6 +382,11 @@ const FormatEntity::Entry *Debugger::GetFrameFormat() const {
return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx);
}

const FormatEntity::Entry *Debugger::GetFrameFormatUnique() const {
const uint32_t idx = ePropertyFrameFormatUnique;
return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx);
}

bool Debugger::GetNotifyVoid() const {
const uint32_t idx = ePropertyNotiftVoid;
return m_collection_sp->GetPropertyAtIndexAsBoolean(
Expand Down
16 changes: 10 additions & 6 deletions lldb/source/Target/StackFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1744,7 +1744,7 @@ void StackFrame::CalculateExecutionContext(ExecutionContext &exe_ctx) {
exe_ctx.SetContext(shared_from_this());
}

void StackFrame::DumpUsingSettingsFormat(Stream *strm,
void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique,
const char *frame_marker) {
if (strm == nullptr)
return;
Expand All @@ -1758,8 +1758,13 @@ void StackFrame::DumpUsingSettingsFormat(Stream *strm,

const FormatEntity::Entry *frame_format = nullptr;
Target *target = exe_ctx.GetTargetPtr();
if (target)
frame_format = target->GetDebugger().GetFrameFormat();
if (target) {
if (show_unique) {
frame_format = target->GetDebugger().GetFrameFormatUnique();
} else {
frame_format = target->GetDebugger().GetFrameFormat();
}
}
if (frame_format && FormatEntity::Format(*frame_format, s, &m_sc, &exe_ctx,
nullptr, nullptr, false, false)) {
strm->PutCString(s.GetString());
Expand Down Expand Up @@ -1841,11 +1846,10 @@ bool StackFrame::HasCachedData() const {
}

bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source,
const char *frame_marker) {

bool show_unique, const char *frame_marker) {
if (show_frame_info) {
strm.Indent();
DumpUsingSettingsFormat(&strm, frame_marker);
DumpUsingSettingsFormat(&strm, show_unique, frame_marker);
}

if (show_source) {
Expand Down
3 changes: 2 additions & 1 deletion lldb/source/Target/StackFrameList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) {
size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,
uint32_t num_frames, bool show_frame_info,
uint32_t num_frames_with_source,
bool show_unique,
const char *selected_frame_marker) {
size_t num_frames_displayed = 0;

Expand Down Expand Up @@ -842,7 +843,7 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame,

if (!frame_sp->GetStatus(strm, show_frame_info,
num_frames_with_source > (first_frame - frame_idx),
marker))
show_unique, marker))
break;
++num_frames_displayed;
}
Expand Down
53 changes: 28 additions & 25 deletions lldb/source/Target/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1913,47 +1913,50 @@ const char *Thread::RunModeAsCString(lldb::RunMode mode) {

size_t Thread::GetStatus(Stream &strm, uint32_t start_frame,
uint32_t num_frames, uint32_t num_frames_with_source,
bool stop_format) {
ExecutionContext exe_ctx(shared_from_this());
Target *target = exe_ctx.GetTargetPtr();
Process *process = exe_ctx.GetProcessPtr();
size_t num_frames_shown = 0;
strm.Indent();
bool is_selected = false;
if (process) {
if (process->GetThreadList().GetSelectedThread().get() == this)
is_selected = true;
}
strm.Printf("%c ", is_selected ? '*' : ' ');
if (target && target->GetDebugger().GetUseExternalEditor()) {
StackFrameSP frame_sp = GetStackFrameAtIndex(start_frame);
if (frame_sp) {
SymbolContext frame_sc(
frame_sp->GetSymbolContext(eSymbolContextLineEntry));
if (frame_sc.line_entry.line != 0 && frame_sc.line_entry.file) {
Host::OpenFileInExternalEditor(frame_sc.line_entry.file,
frame_sc.line_entry.line);
bool stop_format, bool only_stacks) {

if (!only_stacks) {
ExecutionContext exe_ctx(shared_from_this());
Target *target = exe_ctx.GetTargetPtr();
Process *process = exe_ctx.GetProcessPtr();
strm.Indent();
bool is_selected = false;
if (process) {
if (process->GetThreadList().GetSelectedThread().get() == this)
is_selected = true;
}
strm.Printf("%c ", is_selected ? '*' : ' ');
if (target && target->GetDebugger().GetUseExternalEditor()) {
StackFrameSP frame_sp = GetStackFrameAtIndex(start_frame);
if (frame_sp) {
SymbolContext frame_sc(
frame_sp->GetSymbolContext(eSymbolContextLineEntry));
if (frame_sc.line_entry.line != 0 && frame_sc.line_entry.file) {
Host::OpenFileInExternalEditor(frame_sc.line_entry.file,
frame_sc.line_entry.line);
}
}
}
}

DumpUsingSettingsFormat(strm, start_frame, stop_format);
DumpUsingSettingsFormat(strm, start_frame, stop_format);
}

size_t num_frames_shown = 0;
if (num_frames > 0) {
strm.IndentMore();

const bool show_frame_info = true;

const bool show_frame_unique = only_stacks;
const char *selected_frame_marker = nullptr;
if (num_frames == 1 ||
if (num_frames == 1 || only_stacks ||
(GetID() != GetProcess()->GetThreadList().GetSelectedThread()->GetID()))
strm.IndentMore();
else
selected_frame_marker = "* ";

num_frames_shown = GetStackFrameList()->GetStatus(
strm, start_frame, num_frames, show_frame_info, num_frames_with_source,
selected_frame_marker);
show_frame_unique, selected_frame_marker);
if (num_frames == 1)
strm.IndentLess();
strm.IndentLess();
Expand Down