Index: include/lldb/Target/Thread.h =================================================================== --- include/lldb/Target/Thread.h +++ include/lldb/Target/Thread.h @@ -1164,7 +1164,7 @@ size_t GetStatus(Stream &strm, uint32_t start_frame, uint32_t num_frames, uint32_t num_frames_with_source, - bool stop_format); + bool stop_format, bool only_stacks = false); size_t GetStackFrameStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, Index: source/Commands/CommandObjectThread.cpp =================================================================== --- source/Commands/CommandObjectThread.cpp +++ source/Commands/CommandObjectThread.cpp @@ -42,10 +42,40 @@ using namespace lldb_private; //------------------------------------------------------------------------- -// CommandObjectThreadBacktrace +// CommandObjectIterateOverThreads //------------------------------------------------------------------------- class CommandObjectIterateOverThreads : public CommandObjectParsed { + + class UniqueStack { + + public: + UniqueStack(std::stack
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) { + m_thread_index_ids.push_back(thread_index_id); + } + + const std::vector& GetUniqueThreadIndexIDs() const { + return m_thread_index_ids; + } + + lldb::tid_t GetRepresentativeThread() const { + return m_thread_index_ids.front(); + } + + bool IsEqual(std::stack
stack_frames) const { + return stack_frames == m_stack_frames; + } + + protected: + std::vector m_thread_index_ids; + std::stack
m_stack_frames; + }; + public: CommandObjectIterateOverThreads(CommandInterpreter &interpreter, const char *name, const char *help, @@ -57,11 +87,15 @@ 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 @@ -69,8 +103,7 @@ // code while iterating over the (locked) ThreadSP list. std::vector 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()) @@ -108,20 +141,52 @@ } } - 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; + if (m_unique_stacks) { + // Iterate over threads, finding unique stack buckets. + std::vector unique_stacks; + for (const lldb::tid_t &tid : tids) { + if (!BucketThread(tid, unique_stacks, result)) { + return false; + } + } - ++idx; + // Write the thread id's and unique call stacks to the output stream + Stream &strm = result.GetOutputStream(); + Process *process = m_exe_ctx.GetProcessPtr(); + strm.IndentMore(); + for (const UniqueStack& stack: unique_stacks) { + // List the common thread ID's + strm.Indent("thread(s) "); + for (const uint32_t &thread_index_id : stack.GetUniqueThreadIndexIDs()) { + strm.Printf("#%u ", thread_index_id); + } + strm.EOL(); + + // 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; + } + } + strm.IndentLess(); + } 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(); } protected: + // Override this to do whatever you need to do for one thread. // // If you return false, the iteration will stop, otherwise it will proceed. @@ -134,7 +199,47 @@ virtual bool HandleOneThread(lldb::tid_t, CommandReturnObject &result) = 0; + bool BucketThread(lldb::tid_t tid, + std::vector& 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
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 Address frame_address = frame_sp->GetFrameCodeAddress(); + stack_frames.push(frame_address); + } + + // Try to match the threads stack to and existing entry. + bool found_match = false; + for (UniqueStack& unique_stack : unique_stacks) { + if (unique_stack.IsEqual(stack_frames)) { + found_match = true; + unique_stack.AddThread(thread->GetIndexID()); + break; + } + } + + // We failed to find an existing unique stack, so create a new one. + if (!found_match) { + UniqueStack new_unique_stack(stack_frames, thread->GetIndexID()); + unique_stacks.push_back(new_unique_stack); + } + return true; + } + ReturnStatus m_success_return = eReturnStatusSuccessFinishResult; + bool m_unique_stacks = false; bool m_add_return = true; }; @@ -218,9 +323,9 @@ : 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 with unique call stacks.", nullptr, eCommandRequiresProcess | eCommandRequiresThread | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | @@ -270,11 +375,14 @@ 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()); Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -236,7 +236,7 @@ "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 " + "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."}, Index: source/Target/Thread.cpp =================================================================== --- source/Target/Thread.cpp +++ source/Target/Thread.cpp @@ -1912,39 +1912,42 @@ 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 char *selected_frame_marker = nullptr; - if (num_frames == 1 || + if (num_frames == 1 || only_stacks || (GetID() != GetProcess()->GetThreadList().GetSelectedThread()->GetID())) strm.IndentMore(); else