Index: include/lldb/Core/Debugger.h =================================================================== --- include/lldb/Core/Debugger.h +++ include/lldb/Core/Debugger.h @@ -249,6 +249,8 @@ const FormatEntity::Entry *GetFrameFormat() const; + const FormatEntity::Entry *GetFrameFormatUnique() const; + const FormatEntity::Entry *GetThreadFormat() const; const FormatEntity::Entry *GetThreadStopFormat() const; Index: include/lldb/Target/StackFrame.h =================================================================== --- include/lldb/Target/StackFrame.h +++ include/lldb/Target/StackFrame.h @@ -342,10 +342,13 @@ /// @param [in] strm /// The Stream to print the description to. /// + /// @param [in] show_unique + /// Whether to print the function arguments or not for backtrace unique. + /// /// @param [in] frame_marker /// Optional string that will be prepended to the frame output description. //------------------------------------------------------------------ - void DumpUsingSettingsFormat(Stream *strm, + void DumpUsingSettingsFormat(Stream *strm, bool show_unique = false, const char *frame_marker = nullptr); //------------------------------------------------------------------ @@ -375,6 +378,10 @@ /// @param[in] show_source /// If true, print source or disassembly as per the user's settings. /// + /// @param[in] show_unique + /// If true, print using backtrace unique style, without function + /// arguments as per the user's settings. + /// /// @param[in] frame_marker /// Passed to DumpUsingSettingsFormat() for the frame info printing. /// @@ -382,7 +389,7 @@ /// Returns true if successful. //------------------------------------------------------------------ bool GetStatus(Stream &strm, bool show_frame_info, bool show_source, - const char *frame_marker = nullptr); + bool show_unique = false, const char *frame_marker = nullptr); //------------------------------------------------------------------ /// Query whether this frame is a concrete frame on the call stack, Index: include/lldb/Target/StackFrameList.h =================================================================== --- include/lldb/Target/StackFrameList.h +++ include/lldb/Target/StackFrameList.h @@ -70,7 +70,7 @@ size_t GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, uint32_t num_frames_with_source, - const char *frame_marker = nullptr); + bool show_unique = false, const char *frame_marker = nullptr); protected: friend class Thread; 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: packages/Python/lldbsuite/test/functionalities/thread/num_threads/TestNumThreads.py =================================================================== --- packages/Python/lldbsuite/test/functionalities/thread/num_threads/TestNumThreads.py +++ packages/Python/lldbsuite/test/functionalities/thread/num_threads/TestNumThreads.py @@ -19,10 +19,11 @@ def setUp(self): # Call super's setUp(). TestBase.setUp(self) - # Find the line number to break inside main(). - self.line = line_number('main.cpp', '// Set break point at this line.') + # Find the line numbers for our break points. + self.thread3_notify_all_line = line_number('main.cpp', '// Set thread3 break point on notify_all at this line.') + self.thread3_before_lock_line = line_number('main.cpp', '// Set thread3 break point on lock at this line.') - def test(self): + def test_number_of_threads(self): """Test number of threads.""" self.build() exe = os.path.join(os.getcwd(), "a.out") @@ -30,15 +31,15 @@ # This should create a breakpoint with 1 location. lldbutil.run_break_set_by_file_and_line( - self, "main.cpp", self.line, num_expected_locations=1) + self, "main.cpp", self.thread3_notify_all_line, num_expected_locations=1) - # The breakpoint list should show 3 locations. + # The breakpoint list should show 1 location. self.expect( "breakpoint list -f", "Breakpoint location shown correctly", substrs=[ "1: file = 'main.cpp', line = %d, exact_match = 0, locations = 1" % - self.line]) + self.thread3_notify_all_line]) # Run the program. self.runCmd("run", RUN_SUCCEEDED) @@ -57,5 +58,62 @@ # Using std::thread may involve extra threads, so we assert that there are # at least 4 rather than exactly 4. self.assertTrue( - num_threads >= 4, + num_threads >= 13, 'Number of expected threads and actual threads do not match.') + + def test_unique_stacks(self): + """Test backtrace unique with multiple threads executing the same stack.""" + self.build() + exe = os.path.join(os.getcwd(), "a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + # Set a break point on the thread3 notify all (should get hit on threads 4-13). + lldbutil.run_break_set_by_file_and_line( + self, "main.cpp", self.thread3_before_lock_line, num_expected_locations=1) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # Stopped once. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=["stop reason = breakpoint 1."]) + + process = self.process() + + # Get the number of threads + num_threads = process.GetNumThreads() + + # Using std::thread may involve extra threads, so we assert that there are + # at least 10 thread3's rather than exactly 10. + self.assertTrue( + num_threads >= 10, + 'Number of expected threads and actual threads do not match.') + + expect_threads = "" + threads_found = 0 + # Attempt to walk each of the thread's executing the thread3 function to + # the same breakpoint. + for i in range(num_threads): + thread = process.GetThreadAtIndex(i) + self.assertTrue(thread.IsValid()) + if not "thread3" in thread.GetFrameAtIndex(0).GetFunctionName(): + continue + + # If we aren't stopped out the thread breakpoint try to resume. + if thread.GetStopReason() != lldb.eStopReasonBreakpoint: + thread.Resume() + + # Resume can fail so only include this in the expected result if we failed + if thread.GetStopReason() == lldb.eStopReasonBreakpoint: + threads_found += 1 + expect_threads += " #%d"%(i+1) + + # Construct our expected back trace string + expect_string = "%d thread(s)%s" % (threads_found, expect_threads) + + # Now that we are stopped, we should have 10 threads waiting in the + # thread3 function. All of these threads should show as one stack. + self.expect("thread backtrace unique", + "Backtrace with unique stack shown correctly", + substrs=[expect_string, + "main.cpp:%d"%self.thread3_before_lock_line]) Index: packages/Python/lldbsuite/test/functionalities/thread/num_threads/main.cpp =================================================================== --- packages/Python/lldbsuite/test/functionalities/thread/num_threads/main.cpp +++ packages/Python/lldbsuite/test/functionalities/thread/num_threads/main.cpp @@ -1,15 +1,19 @@ +#include "pseudo_barrier.h" #include #include #include +#include std::mutex mutex; std::condition_variable cond; +pseudo_barrier_t thread3_barrier; void * thread3(void *input) { - std::unique_lock lock(mutex); - cond.notify_all(); // Set break point at this line. + pseudo_barrier_wait(thread3_barrier); + std::unique_lock lock(mutex); // Set thread3 break point on lock at this line. + cond.notify_all(); // Set thread3 break point on notify_all at this line. return NULL; } @@ -17,7 +21,7 @@ thread2(void *input) { std::unique_lock lock(mutex); - cond.notify_all(); + cond.notify_all(); // release main thread cond.wait(lock); return NULL; } @@ -36,15 +40,23 @@ std::unique_lock lock(mutex); std::thread thread_1(thread1, nullptr); - cond.wait(lock); + cond.wait(lock); // wait for thread2 - std::thread thread_3(thread3, nullptr); - cond.wait(lock); + pseudo_barrier_init(thread3_barrier, 10); + + std::vector thread_3s; + for (int i = 0; i < 10; i++) { + thread_3s.push_back(std::thread(thread3, nullptr)); + } + + cond.wait(lock); // wait for thread_3s lock.unlock(); thread_1.join(); - thread_3.join(); + for (auto &t : thread_3s){ + t.join(); + } return 0; } Index: source/Commands/CommandObjectThread.cpp =================================================================== --- source/Commands/CommandObjectThread.cpp +++ source/Commands/CommandObjectThread.cpp @@ -42,10 +42,42 @@ 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) const { + 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(); + } + + 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 m_thread_index_ids; + std::stack m_stack_frames; + }; + public: CommandObjectIterateOverThreads(CommandInterpreter &interpreter, const char *name, const char *help, @@ -57,11 +89,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 +105,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 +143,51 @@ } } - 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::set 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(); + for (const UniqueStack& stack: unique_stacks) { + // List the common thread ID's + const std::vector& 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(); + + // 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(); } 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 +200,42 @@ virtual bool HandleOneThread(lldb::tid_t, CommandReturnObject &result) = 0; + bool BucketThread(lldb::tid_t tid, + std::set& 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 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::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; }; @@ -218,9 +319,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 grouped by unique call stacks.", nullptr, eCommandRequiresProcess | eCommandRequiresThread | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched | @@ -270,11 +371,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 @@ -112,6 +112,12 @@ "{ " \ "${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]}" @@ -141,6 +147,11 @@ "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 @@ -236,7 +247,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."}, @@ -257,6 +268,10 @@ {"escape-non-printables", OptionValue::eTypeBoolean, true, true, nullptr, 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 { @@ -282,7 +297,8 @@ ePropertyAutoIndent, ePropertyPrintDecls, ePropertyTabSize, - ePropertyEscapeNonPrintables + ePropertyEscapeNonPrintables, + ePropertyFrameFormatUnique, }; LoadPluginCallbackType Debugger::g_load_plugin_callback = nullptr; @@ -358,6 +374,11 @@ 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( Index: source/Target/StackFrame.cpp =================================================================== --- source/Target/StackFrame.cpp +++ source/Target/StackFrame.cpp @@ -1744,6 +1744,7 @@ } void StackFrame::DumpUsingSettingsFormat(Stream *strm, + bool show_unique, const char *frame_marker) { if (strm == nullptr) return; @@ -1757,8 +1758,13 @@ 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()); @@ -1840,11 +1846,10 @@ } 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) { Index: source/Target/StackFrameList.cpp =================================================================== --- source/Target/StackFrameList.cpp +++ source/Target/StackFrameList.cpp @@ -801,7 +801,7 @@ size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, uint32_t num_frames, bool show_frame_info, - uint32_t num_frames_with_source, + uint32_t num_frames_with_source, bool show_unique, const char *selected_frame_marker) { size_t num_frames_displayed = 0; @@ -842,7 +842,7 @@ if (!frame_sp->GetStatus(strm, show_frame_info, num_frames_with_source > (first_frame - frame_idx), - marker)) + show_unique, marker)) break; ++num_frames_displayed; } Index: source/Target/Thread.cpp =================================================================== --- source/Target/Thread.cpp +++ source/Target/Thread.cpp @@ -1913,39 +1913,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 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 @@ -1953,7 +1956,7 @@ 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();