From d32d287e6abf64baaa14a812ec7f43f7fb0d2531 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 15 Jan 2025 16:17:09 -0800 Subject: [PATCH 1/6] [lldb] Introduce command to select task "threads" --- .../Swift/SwiftLanguageRuntime.cpp | 115 +++++++++++------- .../swift/async/tasks/TestSwiftTaskSelect.py | 75 ++++++++++++ 2 files changed, 146 insertions(+), 44 deletions(-) create mode 100644 lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index 096e139bbfcd2..0ca4b03edc2a2 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp @@ -2086,6 +2086,46 @@ class CommandObjectSwift_RefCount : public CommandObjectRaw { } }; +/// Construct a `ThreadTask` instance for a Task variable contained in the first +/// argument. +static llvm::Expected +ThreadForTaskVariable(Args &command, ExecutionContext &exe_ctx) { + if (!exe_ctx.GetFramePtr()) + return llvm::createStringError("no active frame selected"); + + if (command.empty() || command[0].ref().empty()) + return llvm::createStringError("missing task variable argument"); + + StackFrame &frame = exe_ctx.GetFrameRef(); + uint32_t path_options = + StackFrame::eExpressionPathOptionsAllowDirectIVarAccess; + VariableSP var_sp; + Status status; + ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath( + command[0].c_str(), eDynamicDontRunTarget, path_options, var_sp, status); + if (!valobj_sp) + return status.takeError(); + + addr_t task_ptr = LLDB_INVALID_ADDRESS; + ThreadSafeReflectionContext reflection_ctx; + if (ValueObjectSP task_obj_sp = valobj_sp->GetChildMemberWithName("_task")) { + task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + if (task_ptr != LLDB_INVALID_ADDRESS) + if (auto *runtime = SwiftLanguageRuntime::Get(exe_ctx.GetProcessSP())) + reflection_ctx = runtime->GetReflectionContext(); + } + if (task_ptr == LLDB_INVALID_ADDRESS || !reflection_ctx) + return llvm::createStringError("failed to access Task data from runtime"); + + llvm::Expected task_info = + reflection_ctx->asyncTaskInfo(task_ptr); + if (auto error = task_info.takeError()) + return error; + + return ThreadTask::Create(task_info->id, task_info->resumeAsyncContext, + exe_ctx); +} + class CommandObjectLanguageSwiftTaskBacktrace final : public CommandObjectParsed { public: @@ -2099,60 +2139,44 @@ class CommandObjectLanguageSwiftTaskBacktrace final private: void DoExecute(Args &command, CommandReturnObject &result) override { - if (!m_exe_ctx.GetFramePtr()) { - result.AppendError("no active frame selected"); - return; - } - - if (command.empty() || command[0].ref().empty()) { - result.AppendError("no task variable"); - return; - } - - StackFrame &frame = m_exe_ctx.GetFrameRef(); - uint32_t path_options = - StackFrame::eExpressionPathOptionsAllowDirectIVarAccess; - VariableSP var_sp; - Status status; - ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath( - command[0].c_str(), eDynamicDontRunTarget, path_options, var_sp, - status); - if (!valobj_sp) { - result.AppendError(status.AsCString()); + llvm::Expected thread_task = + ThreadForTaskVariable(command, m_exe_ctx); + if (auto error = thread_task.takeError()) { + result.AppendError(toString(std::move(error))); return; } - addr_t task_ptr = LLDB_INVALID_ADDRESS; - ThreadSafeReflectionContext reflection_ctx; - if (ValueObjectSP task_obj_sp = - valobj_sp->GetChildMemberWithName("_task")) { - task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); - if (task_ptr != LLDB_INVALID_ADDRESS) - if (auto *runtime = SwiftLanguageRuntime::Get(m_exe_ctx.GetProcessSP())) - reflection_ctx = runtime->GetReflectionContext(); - } - if (task_ptr == LLDB_INVALID_ADDRESS || !reflection_ctx) { - result.AppendError("failed to access Task data from runtime"); - return; - } + // GetStatus prints the backtrace. + thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0, + false, true); + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + } +}; - llvm::Expected task_info = - reflection_ctx->asyncTaskInfo(task_ptr); - if (auto error = task_info.takeError()) { - result.AppendError(toString(std::move(error))); - return; - } +class CommandObjectLanguageSwiftTaskSelect final : public CommandObjectParsed { +public: + CommandObjectLanguageSwiftTaskSelect(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "select", + "Change the currently selected thread to thread representation of " + "the given Swift Task. See `thread select`.", + "language swift task select ") { + AddSimpleArgumentList(eArgTypeVarName); + } - auto thread_task = ThreadTask::Create( - task_info->id, task_info->resumeAsyncContext, m_exe_ctx); +private: + void DoExecute(Args &command, CommandReturnObject &result) override { + llvm::Expected thread_task = + ThreadForTaskVariable(command, m_exe_ctx); if (auto error = thread_task.takeError()) { result.AppendError(toString(std::move(error))); return; } - // GetStatus prints the backtrace. - thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0, - false, true); + auto &thread_list = m_exe_ctx.GetProcessRef().GetThreadList(); + thread_list.AddThread(thread_task.get()); + thread_list.SetSelectedThreadByID(thread_task.get()->GetID()); + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); } }; @@ -2166,6 +2190,9 @@ class CommandObjectLanguageSwiftTask final : public CommandObjectMultiword { LoadSubCommand("backtrace", CommandObjectSP(new CommandObjectLanguageSwiftTaskBacktrace( interpreter))); + LoadSubCommand( + "select", + CommandObjectSP(new CommandObjectLanguageSwiftTaskSelect(interpreter))); } }; diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py new file mode 100644 index 0000000000000..5d3712c43377d --- /dev/null +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py @@ -0,0 +1,75 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase +import lldbsuite.test.lldbutil as lldbutil + + +class TestCase(TestBase): + + def test_backtrace_selected_task(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.swift") + ) + self.runCmd("language swift task select task") + self.expect( + "thread backtrace", + substrs=[ + ".sleep(", + "`second() at main.swift:6:", + "`first() at main.swift:2:", + "`closure #1 in static Main.main() at main.swift:12:", + ], + ) + + def test_navigate_selected_task_stack(self): + self.build() + # target, process, thread, bkpt + _, process, _, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.swift") + ) + self.runCmd("language swift task select task") + + self.expect( + "thread list", + substrs=[ + "* thread #4294967295: tid = 0x0002,", + "libswift_Concurrency.dylib", + "._sleep(", + ], + ) + + frame_index = 0 + for frame in process.selected_thread: + if "`second()" in str(frame): + frame_index = frame.idx + self.assertNotEqual(frame_index, -1) + + self.expect( + f"frame select {frame_index}", + substrs=[ + f"frame #{frame_index}:", + "`second() at main.swift:6:", + " 5 \tfunc second() async {", + "-> 6 \t try? await Task.sleep(for: .seconds(10))", + ], + ) + + self.expect( + "up", + substrs=[ + f"frame #{frame_index + 1}:", + "`first() at main.swift:2:", + " 1 \tfunc first() async {", + "-> 2 \t await second()", + ], + ) + + self.expect( + "up", + substrs=[ + f"frame #{frame_index + 2}:", + "`closure #1 in static Main.main() at main.swift:12:", + "-> 12 \t await first()", + ], + ) From 9e8c614379aac7ebfbeaa82d45ecfbad033b7a32 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Fri, 17 Jan 2025 16:09:07 -0800 Subject: [PATCH 2/6] Reduce tid expectation in test On darwin it's "0x0002" but on linux it's "2". --- lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py index 5d3712c43377d..70cfafc17da5a 100644 --- a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py @@ -33,7 +33,7 @@ def test_navigate_selected_task_stack(self): self.expect( "thread list", substrs=[ - "* thread #4294967295: tid = 0x0002,", + "* thread #4294967295: tid = ", "libswift_Concurrency.dylib", "._sleep(", ], From d7f10b91d44bcadf2dc74440f027f1ba0678b70a Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Fri, 17 Jan 2025 18:15:26 -0800 Subject: [PATCH 3/6] Change test to use platform agnostic library name --- lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py index 70cfafc17da5a..b646a8fe10db4 100644 --- a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py @@ -34,7 +34,7 @@ def test_navigate_selected_task_stack(self): "thread list", substrs=[ "* thread #4294967295: tid = ", - "libswift_Concurrency.dylib", + "libswift_Concurrency.", "._sleep(", ], ) From 554406f4a96ea081751198ffe5674eedb4ea4869 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Sat, 18 Jan 2025 07:55:38 -0800 Subject: [PATCH 4/6] Fix logic mistake in test --- lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py index b646a8fe10db4..cb714010f3074 100644 --- a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py @@ -39,7 +39,7 @@ def test_navigate_selected_task_stack(self): ], ) - frame_index = 0 + frame_index = -1 for frame in process.selected_thread: if "`second()" in str(frame): frame_index = frame.idx From 1f1c08aeb60f817bd7e2a1c471c1c401bc97fa6c Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 21 Jan 2025 14:41:31 -0800 Subject: [PATCH 5/6] Use SBAPI to test selected thread/frame --- .../swift/async/tasks/TestSwiftTaskSelect.py | 55 ++++++------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py index cb714010f3074..53da678ce1ddc 100644 --- a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py @@ -30,46 +30,27 @@ def test_navigate_selected_task_stack(self): ) self.runCmd("language swift task select task") - self.expect( - "thread list", - substrs=[ - "* thread #4294967295: tid = ", - "libswift_Concurrency.", - "._sleep(", - ], + thread = process.selected_thread + self.assertEqual(thread.id, 2) + self.assertEqual(thread.idx, 0xFFFFFFFF) + self.assertIn( + "libswift_Concurrency.", thread.GetSelectedFrame().module.file.basename ) - frame_index = -1 - for frame in process.selected_thread: + frame_idx = -1 + for frame in thread: if "`second()" in str(frame): - frame_index = frame.idx - self.assertNotEqual(frame_index, -1) + frame_idx = frame.idx + self.assertNotEqual(frame_idx, -1) - self.expect( - f"frame select {frame_index}", - substrs=[ - f"frame #{frame_index}:", - "`second() at main.swift:6:", - " 5 \tfunc second() async {", - "-> 6 \t try? await Task.sleep(for: .seconds(10))", - ], - ) + self.expect(f"frame select {frame_idx}", substrs=[f"frame #{frame_idx}:"]) + frame = thread.GetSelectedFrame() + self.assertIn(".second()", frame.function.name) - self.expect( - "up", - substrs=[ - f"frame #{frame_index + 1}:", - "`first() at main.swift:2:", - " 1 \tfunc first() async {", - "-> 2 \t await second()", - ], - ) + self.expect("up", substrs=[f"frame #{frame_idx + 1}:"]) + frame = thread.GetSelectedFrame() + self.assertIn(".first()", frame.function.name) - self.expect( - "up", - substrs=[ - f"frame #{frame_index + 2}:", - "`closure #1 in static Main.main() at main.swift:12:", - "-> 12 \t await first()", - ], - ) + self.expect("up", substrs=[f"frame #{frame_idx + 2}:"]) + frame = thread.GetSelectedFrame() + self.assertIn(".Main.main()", frame.function.name) From 277bba409c023b2bad0bfc961f953a533b450b40 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 21 Jan 2025 14:50:47 -0800 Subject: [PATCH 6/6] Remove temporary comment --- lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py index 53da678ce1ddc..61d4f38d0b056 100644 --- a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py @@ -24,7 +24,6 @@ def test_backtrace_selected_task(self): def test_navigate_selected_task_stack(self): self.build() - # target, process, thread, bkpt _, process, _, _ = lldbutil.run_to_source_breakpoint( self, "break here", lldb.SBFileSpec("main.swift") )