From 01c80c918a3f5138874066b1fb693815e9ee3b07 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Thu, 19 Dec 2024 08:39:38 -0800 Subject: [PATCH 1/4] [lldb] Introduce backtracing of Swift Tasks --- .../Language/Swift/SwiftFormatters.cpp | 12 +++ .../LanguageRuntime/Swift/CMakeLists.txt | 1 + .../Swift/ReflectionContext.cpp | 1 + .../Swift/ReflectionContextInterface.h | 2 + .../LanguageRuntime/Swift/SwiftTask.cpp | 53 +++++++++++ .../Plugins/LanguageRuntime/Swift/SwiftTask.h | 93 +++++++++++++++++++ 6 files changed, 162 insertions(+) create mode 100644 lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp create mode 100644 lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h diff --git a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp index 4d00de7aed892..5b2a3487eb221 100644 --- a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp +++ b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp @@ -14,6 +14,7 @@ #include "Plugins/Language/Swift/SwiftStringIndex.h" #include "Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h" #include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h" +#include "Plugins/LanguageRuntime/Swift/SwiftTask.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "lldb/DataFormatters/FormattersHelpers.h" #include "lldb/DataFormatters/StringPrinter.h" @@ -23,6 +24,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" #include "lldb/Utility/Timer.h" #include "lldb/ValueObject/ValueObject.h" #include "lldb/lldb-enumerations.h" @@ -818,6 +820,16 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { "could not get info for async task {0:x}: {1}", task_ptr, fmt_consume(std::move(err))); } else { + + // Print a backtrace of the Task to stdout. + ExecutionContext exe_ctx{m_backend.GetExecutionContextRef()}; + auto tt = std::make_shared( + 3000, task_info->resumeAsyncContext, exe_ctx); + StreamString ss; + tt->GetStatus(ss, 0, 100, 0, false, true); + auto desc = ss.GetString(); + printf("%.*s\n", (int)desc.size(), desc.data()); + m_task_info = *task_info; for (auto child : {m_is_child_task_sp, m_is_future_sp, m_is_group_child_task_sp, diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt b/lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt index 0318e5c3fbfd0..a6c0c08f263e6 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt +++ b/lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt @@ -6,6 +6,7 @@ add_lldb_library(lldbPluginSwiftLanguageRuntime PLUGIN SwiftLanguageRuntimeNames.cpp SwiftLanguageRuntimeRemoteAST.cpp SwiftMetadataCache.cpp + SwiftTask.cpp LINK_LIBS swiftAST diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp index fe8c4b75535b3..d39faeef04ed8 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp @@ -401,6 +401,7 @@ class TargetReflectionContext : public ReflectionContextInterface { result.hasIsRunning = task_info.HasIsRunning; result.isRunning = task_info.IsRunning; result.isEnqueued = task_info.IsEnqueued; + result.resumeAsyncContext = task_info.ResumeAsyncContext; return result; } diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h index 8dc0e10716169..1ba221984a66f 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h @@ -15,6 +15,7 @@ #include +#include "lldb/lldb-defines.h" #include "lldb/lldb-types.h" #include "swift/ABI/ObjectFile.h" #include "swift/Remote/RemoteAddress.h" @@ -163,6 +164,7 @@ class ReflectionContextInterface { bool hasIsRunning = false; bool isRunning = false; bool isEnqueued = false; + lldb::addr_t resumeAsyncContext = LLDB_INVALID_ADDRESS; }; // The default limits are copied from swift-inspect. virtual llvm::Expected diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp new file mode 100644 index 0000000000000..69281bae9f405 --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp @@ -0,0 +1,53 @@ +#include "SwiftTask.h" +#include "SwiftLanguageRuntime.h" +#include "lldb/Target/Process.h" + +using namespace llvm; +using namespace lldb; + +lldb_private::ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, + ExecutionContext &exe_ctx) + : Thread(exe_ctx.GetProcessRef(), tid, true), + m_concrete_reg_ctx_sp(exe_ctx.GetFrameSP()->GetRegisterContext()) { + m_async_ctx = async_ctx; + auto ptr_size = exe_ctx.GetTargetRef().GetArchitecture().GetAddressByteSize(); + // A simplified description of AsyncContext. See swift/Task/ABI.h + // struct AsyncContext { + // AsyncContext *Parent; // offset 0 + // TaskContinuationFunction *ResumeParent; // offset 8 + // }; + auto resume_offset = ptr_size; // offsetof(AsyncContext, ResumeParent) + auto resume_ptr = async_ctx + resume_offset; + Status status; + m_pc = exe_ctx.GetProcessRef().ReadPointerFromMemory(resume_ptr, status); +} + +RegisterContextSP lldb_private::ThreadTask::GetRegisterContext() { + if (!m_async_reg_ctx_sp) + m_async_reg_ctx_sp = std::make_shared( + *this, m_concrete_reg_ctx_sp, m_pc, m_async_ctx); + return m_async_reg_ctx_sp; +} + +lldb_private::RegisterContextTask::RegisterContextTask( + Thread &thread, RegisterContextSP reg_info_sp, addr_t pc, addr_t async_ctx) + : RegisterContext(thread, 0), m_reg_info_sp(reg_info_sp), + m_async_ctx(async_ctx), m_pc(pc) { + auto &target = thread.GetProcess()->GetTarget(); + auto triple = target.GetArchitecture().GetTriple(); + if (auto regnums = GetAsyncUnwindRegisterNumbers(triple.getArch())) + m_async_ctx_regnum = regnums->async_ctx_regnum; +} + +bool lldb_private::RegisterContextTask::ReadRegister( + const RegisterInfo *reg_info, RegisterValue ®_value) { + if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC) { + reg_value = m_pc; + return true; + } + if (reg_info->kinds[eRegisterKindLLDB] == m_async_ctx_regnum) { + reg_value = m_async_ctx; + return true; + } + return false; +} diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h new file mode 100644 index 0000000000000..63569e22f746a --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h @@ -0,0 +1,93 @@ + +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/lldb-forward.h" + +namespace lldb_private { + +using namespace lldb; + +/// Provides a subset of Thread operations for Swift Tasks. +/// +/// Currently, this supports backtraces of Tasks, and selecting frames in the +/// backtrace. Async frames make available the variables that are stored in the +/// Task's "async context" (instead of the stack). +/// +/// See `Task` and `UnsafeCurrentTask` +class ThreadTask : public Thread { +public: + ThreadTask(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx); + + /// Returns a Task specific register context (RegisterContextTask). + RegisterContextSP GetRegisterContext() override; + + ~ThreadTask() override { DestroyThread(); } + + // No-op overrides. + void RefreshStateAfterStop() override {} + lldb::RegisterContextSP + CreateRegisterContextForFrame(StackFrame *frame) override { + return {}; + } + bool CalculateStopInfo() override { return false; } + +private: + /// A register context that is the source of `RegisterInfo` data. + RegisterContextSP m_concrete_reg_ctx_sp; + /// Lazily initialized `RegisterContextTask`. + RegisterContextSP m_async_reg_ctx_sp; + /// The Task's async context. + addr_t m_async_ctx = LLDB_INVALID_ADDRESS; + /// The address of the async context's resume function. + addr_t m_pc = LLDB_INVALID_ADDRESS; +}; + +/// A Swift Task specific register context. Supporting class for `ThreadTask`, +/// see its documentation for details. +class RegisterContextTask : public RegisterContext { +public: + RegisterContextTask(Thread &thread, RegisterContextSP reg_info_sp, addr_t pc, + addr_t async_ctx); + + /// RegisterContextTask supports readonly from only two (necessary) + /// registers. Namely, the pc and the async context registers. + bool ReadRegister(const RegisterInfo *reg_info, + RegisterValue ®_value) override; + + // Pass through overrides. + size_t GetRegisterCount() override { + return m_reg_info_sp->GetRegisterCount(); + } + const RegisterInfo *GetRegisterInfoAtIndex(size_t idx) override { + return m_reg_info_sp->GetRegisterInfoAtIndex(idx); + } + size_t GetRegisterSetCount() override { + return m_reg_info_sp->GetRegisterSetCount(); + } + const RegisterSet *GetRegisterSet(size_t reg_set) override { + return m_reg_info_sp->GetRegisterSet(reg_set); + } + lldb::ByteOrder GetByteOrder() override { + return m_reg_info_sp->GetByteOrder(); + } + + // No-op overrides. + void InvalidateAllRegisters() override {} + bool WriteRegister(const RegisterInfo *reg_info, + const RegisterValue ®_value) override { + return false; + } + +private: + /// A register context that is the source of `RegisterInfo` data. + RegisterContextSP m_reg_info_sp; + /// The architecture specific regnum (LLDB) which holds the async context. + uint32_t m_async_ctx_regnum = LLDB_INVALID_REGNUM; + /// The Task's async context. + RegisterValue m_async_ctx; + /// The address of the async context's resume function. + RegisterValue m_pc; +}; + +} // namespace lldb_private From ead37d60ec942d7269bbfe6a950dad6ed16d3bc4 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Mon, 6 Jan 2025 15:31:58 -0800 Subject: [PATCH 2/4] Apply code review advice --- .../LanguageRuntime/Swift/SwiftTask.cpp | 37 ++++++++++++------- .../Plugins/LanguageRuntime/Swift/SwiftTask.h | 10 ++--- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp index 69281bae9f405..4cf155717f712 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp @@ -5,44 +5,51 @@ using namespace llvm; using namespace lldb; -lldb_private::ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, - ExecutionContext &exe_ctx) +namespace lldb_private { + +ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx) : Thread(exe_ctx.GetProcessRef(), tid, true), - m_concrete_reg_ctx_sp(exe_ctx.GetFrameSP()->GetRegisterContext()) { + m_reg_info_sp(exe_ctx.GetFrameSP()->GetRegisterContext()) { m_async_ctx = async_ctx; - auto ptr_size = exe_ctx.GetTargetRef().GetArchitecture().GetAddressByteSize(); + uint32_t ptr_size = + exe_ctx.GetTargetRef().GetArchitecture().GetAddressByteSize(); // A simplified description of AsyncContext. See swift/Task/ABI.h // struct AsyncContext { // AsyncContext *Parent; // offset 0 // TaskContinuationFunction *ResumeParent; // offset 8 // }; - auto resume_offset = ptr_size; // offsetof(AsyncContext, ResumeParent) - auto resume_ptr = async_ctx + resume_offset; + uint32_t resume_offset = ptr_size; // offsetof(AsyncContext, ResumeParent) + uint32_t resume_ptr = async_ctx + resume_offset; Status status; - m_pc = exe_ctx.GetProcessRef().ReadPointerFromMemory(resume_ptr, status); + m_resume_fn = + exe_ctx.GetProcessRef().ReadPointerFromMemory(resume_ptr, status); } RegisterContextSP lldb_private::ThreadTask::GetRegisterContext() { if (!m_async_reg_ctx_sp) m_async_reg_ctx_sp = std::make_shared( - *this, m_concrete_reg_ctx_sp, m_pc, m_async_ctx); + *this, m_reg_info_sp, m_resume_fn, m_async_ctx); return m_async_reg_ctx_sp; } -lldb_private::RegisterContextTask::RegisterContextTask( - Thread &thread, RegisterContextSP reg_info_sp, addr_t pc, addr_t async_ctx) +RegisterContextTask::RegisterContextTask(Thread &thread, + RegisterContextSP reg_info_sp, + addr_t resume_fn, addr_t async_ctx) : RegisterContext(thread, 0), m_reg_info_sp(reg_info_sp), - m_async_ctx(async_ctx), m_pc(pc) { + m_async_ctx(async_ctx), m_resume_fn(resume_fn) { auto &target = thread.GetProcess()->GetTarget(); auto triple = target.GetArchitecture().GetTriple(); if (auto regnums = GetAsyncUnwindRegisterNumbers(triple.getArch())) m_async_ctx_regnum = regnums->async_ctx_regnum; } -bool lldb_private::RegisterContextTask::ReadRegister( - const RegisterInfo *reg_info, RegisterValue ®_value) { +bool RegisterContextTask::ReadRegister(const RegisterInfo *reg_info, + RegisterValue ®_value) { + if (!reg_info) + return false; + if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC) { - reg_value = m_pc; + reg_value = m_resume_fn; return true; } if (reg_info->kinds[eRegisterKindLLDB] == m_async_ctx_regnum) { @@ -51,3 +58,5 @@ bool lldb_private::RegisterContextTask::ReadRegister( } return false; } + +} // namespace lldb_private diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h index 63569e22f746a..9df85dad2a7e7 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h @@ -34,21 +34,21 @@ class ThreadTask : public Thread { private: /// A register context that is the source of `RegisterInfo` data. - RegisterContextSP m_concrete_reg_ctx_sp; + RegisterContextSP m_reg_info_sp; /// Lazily initialized `RegisterContextTask`. RegisterContextSP m_async_reg_ctx_sp; /// The Task's async context. addr_t m_async_ctx = LLDB_INVALID_ADDRESS; /// The address of the async context's resume function. - addr_t m_pc = LLDB_INVALID_ADDRESS; + addr_t m_resume_fn = LLDB_INVALID_ADDRESS; }; /// A Swift Task specific register context. Supporting class for `ThreadTask`, /// see its documentation for details. class RegisterContextTask : public RegisterContext { public: - RegisterContextTask(Thread &thread, RegisterContextSP reg_info_sp, addr_t pc, - addr_t async_ctx); + RegisterContextTask(Thread &thread, RegisterContextSP reg_info_sp, + addr_t resume_fn, addr_t async_ctx); /// RegisterContextTask supports readonly from only two (necessary) /// registers. Namely, the pc and the async context registers. @@ -87,7 +87,7 @@ class RegisterContextTask : public RegisterContext { /// The Task's async context. RegisterValue m_async_ctx; /// The address of the async context's resume function. - RegisterValue m_pc; + RegisterValue m_resume_fn; }; } // namespace lldb_private From 6bdbcdccbe0154bbc8c42c6084f418c89049f599 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Tue, 14 Jan 2025 15:40:31 -0800 Subject: [PATCH 3/4] Add backtrace command, and tests --- .../Language/Swift/SwiftFormatters.cpp | 10 --- .../Swift/ReflectionContext.cpp | 1 + .../Swift/ReflectionContextInterface.h | 1 + .../Swift/SwiftLanguageRuntime.cpp | 82 +++++++++++++++++++ .../LanguageRuntime/Swift/SwiftTask.cpp | 42 ++++++---- .../Plugins/LanguageRuntime/Swift/SwiftTask.h | 10 ++- lldb/test/API/lang/swift/async/tasks/Makefile | 3 + .../async/tasks/TestSwiftTaskBacktrace.py | 21 +++++ .../API/lang/swift/async/tasks/main.swift | 17 ++++ 9 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 lldb/test/API/lang/swift/async/tasks/Makefile create mode 100644 lldb/test/API/lang/swift/async/tasks/TestSwiftTaskBacktrace.py create mode 100644 lldb/test/API/lang/swift/async/tasks/main.swift diff --git a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp index 5b2a3487eb221..eac52753028a8 100644 --- a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp +++ b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp @@ -820,16 +820,6 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { "could not get info for async task {0:x}: {1}", task_ptr, fmt_consume(std::move(err))); } else { - - // Print a backtrace of the Task to stdout. - ExecutionContext exe_ctx{m_backend.GetExecutionContextRef()}; - auto tt = std::make_shared( - 3000, task_info->resumeAsyncContext, exe_ctx); - StreamString ss; - tt->GetStatus(ss, 0, 100, 0, false, true); - auto desc = ss.GetString(); - printf("%.*s\n", (int)desc.size(), desc.data()); - m_task_info = *task_info; for (auto child : {m_is_child_task_sp, m_is_future_sp, m_is_group_child_task_sp, diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp index d39faeef04ed8..53aa8d51de476 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp @@ -401,6 +401,7 @@ class TargetReflectionContext : public ReflectionContextInterface { result.hasIsRunning = task_info.HasIsRunning; result.isRunning = task_info.IsRunning; result.isEnqueued = task_info.IsEnqueued; + result.id = task_info.Id; result.resumeAsyncContext = task_info.ResumeAsyncContext; return result; } diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h index 1ba221984a66f..0e449c13d961d 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h @@ -164,6 +164,7 @@ class ReflectionContextInterface { bool hasIsRunning = false; bool isRunning = false; bool isEnqueued = false; + uint64_t id = 0; lldb::addr_t resumeAsyncContext = LLDB_INVALID_ADDRESS; }; // The default limits are copied from swift-inspect. diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index f4e66ed805717..f505f8ca594d5 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp @@ -16,6 +16,7 @@ #include "SwiftMetadataCache.h" #include "Plugins/ExpressionParser/Swift/SwiftPersistentExpressionState.h" +#include "Plugins/LanguageRuntime/Swift/SwiftTask.h" #include "Plugins/Process/Utility/RegisterContext_x86.h" #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "Plugins/TypeSystem/Swift/SwiftDemangle.h" @@ -47,6 +48,7 @@ #include "lldb/ValueObject/ValueObjectCast.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/ValueObject/ValueObjectVariable.h" +#include "llvm/Support/FormatAdapters.h" #include "lldb/lldb-enumerations.h" #include "swift/AST/ASTMangler.h" @@ -2083,6 +2085,84 @@ class CommandObjectSwift_RefCount : public CommandObjectRaw { } }; +class CommandObjectLanguageSwiftTaskBacktrace final + : public CommandObjectParsed { +public: + CommandObjectLanguageSwiftTaskBacktrace(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "backtrace", + "Show the backtrace of Swift tasks. See `thread " + "backtrace` for customizing backtrace output.", + "language swift task backtrace ") { + AddSimpleArgumentList(eArgTypeVarName); + } + +private: + void DoExecute(Args &command, CommandReturnObject &result) override { + if (!m_exe_ctx.GetFramePtr()) { + result.AppendError("no active frame selected"); + return; + } + + if (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) + return; + + ValueObjectSP task_obj_sp = valobj_sp->GetChildMemberWithName("_task"); + if (!task_obj_sp) + return; + uint64_t task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + if (task_ptr == LLDB_INVALID_ADDRESS) + return; + auto *runtime = SwiftLanguageRuntime::Get(m_exe_ctx.GetProcessSP()); + if (!runtime) + return; + ThreadSafeReflectionContext reflection_ctx = + runtime->GetReflectionContext(); + llvm::Expected task_info = + reflection_ctx->asyncTaskInfo(task_ptr); + if (auto err = task_info.takeError()) { + LLDB_LOG(GetLog(LLDBLog::DataFormatters | LLDBLog::Types), + "could not get info for async task {0:x}: {1}", task_ptr, + fmt_consume(std::move(err))); + return; + } + + auto thread_task = ThreadTask::Create( + task_info->id, task_info->resumeAsyncContext, m_exe_ctx); + if (auto error = thread_task.takeError()) { + result.AppendError(toString(std::move(error))); + return; + } + thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0, + false, false); + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectLanguageSwiftTask final : public CommandObjectMultiword { +public: + CommandObjectLanguageSwiftTask(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "task", "Commands for inspecting Swift Tasks.", + "language swift task []") { + LoadSubCommand("backtrace", + CommandObjectSP(new CommandObjectLanguageSwiftTaskBacktrace( + interpreter))); + } +}; + class CommandObjectMultiwordSwift : public CommandObjectMultiword { public: CommandObjectMultiwordSwift(CommandInterpreter &interpreter) @@ -2094,6 +2174,8 @@ class CommandObjectMultiwordSwift : public CommandObjectMultiword { interpreter))); LoadSubCommand("refcount", CommandObjectSP(new CommandObjectSwift_RefCount( interpreter))); + LoadSubCommand("task", CommandObjectSP(new CommandObjectLanguageSwiftTask( + interpreter))); } virtual ~CommandObjectMultiwordSwift() {} diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp index 4cf155717f712..7b2bc41bc2ed6 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp @@ -1,35 +1,47 @@ #include "SwiftTask.h" #include "SwiftLanguageRuntime.h" #include "lldb/Target/Process.h" +#include "lldb/lldb-enumerations.h" +#include "llvm/Support/Error.h" using namespace llvm; using namespace lldb; namespace lldb_private { -ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx) +ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, addr_t resume_fn, + ExecutionContext &exe_ctx) : Thread(exe_ctx.GetProcessRef(), tid, true), - m_reg_info_sp(exe_ctx.GetFrameSP()->GetRegisterContext()) { - m_async_ctx = async_ctx; - uint32_t ptr_size = - exe_ctx.GetTargetRef().GetArchitecture().GetAddressByteSize(); + m_reg_info_sp(exe_ctx.GetFrameSP()->GetRegisterContext()), + m_async_ctx(async_ctx), m_resume_fn(resume_fn) {} + +RegisterContextSP lldb_private::ThreadTask::GetRegisterContext() { + if (!m_async_reg_ctx_sp) + m_async_reg_ctx_sp = std::make_shared( + *this, m_reg_info_sp, m_resume_fn, m_async_ctx); + return m_async_reg_ctx_sp; +} + +llvm::Expected> +ThreadTask::Create(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx) { + auto &process = exe_ctx.GetProcessRef(); + auto &target = exe_ctx.GetTargetRef(); + // A simplified description of AsyncContext. See swift/Task/ABI.h // struct AsyncContext { // AsyncContext *Parent; // offset 0 // TaskContinuationFunction *ResumeParent; // offset 8 // }; - uint32_t resume_offset = ptr_size; // offsetof(AsyncContext, ResumeParent) - uint32_t resume_ptr = async_ctx + resume_offset; + // The resume function is stored at `offsetof(AsyncContext, ResumeParent)`, + // which is the async context's base address plus the size of a pointer. + uint32_t ptr_size = target.GetArchitecture().GetAddressByteSize(); + addr_t resume_ptr = async_ctx + ptr_size; Status status; - m_resume_fn = - exe_ctx.GetProcessRef().ReadPointerFromMemory(resume_ptr, status); -} + addr_t resume_fn = process.ReadPointerFromMemory(resume_ptr, status); + if (status.Fail() || resume_fn == LLDB_INVALID_ADDRESS) + return createStringError("failed to read task's resume function"); -RegisterContextSP lldb_private::ThreadTask::GetRegisterContext() { - if (!m_async_reg_ctx_sp) - m_async_reg_ctx_sp = std::make_shared( - *this, m_reg_info_sp, m_resume_fn, m_async_ctx); - return m_async_reg_ctx_sp; + return std::make_shared(tid, async_ctx, resume_fn, exe_ctx); } RegisterContextTask::RegisterContextTask(Thread &thread, diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h index 9df85dad2a7e7..5f57b3d5cde52 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h @@ -15,9 +15,13 @@ using namespace lldb; /// Task's "async context" (instead of the stack). /// /// See `Task` and `UnsafeCurrentTask` -class ThreadTask : public Thread { +class ThreadTask final : public Thread { public: - ThreadTask(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx); + ThreadTask(tid_t tid, addr_t async_ctx, addr_t resume_fn, + ExecutionContext &exe_ctx); + + static llvm::Expected> + Create(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx); /// Returns a Task specific register context (RegisterContextTask). RegisterContextSP GetRegisterContext() override; @@ -45,7 +49,7 @@ class ThreadTask : public Thread { /// A Swift Task specific register context. Supporting class for `ThreadTask`, /// see its documentation for details. -class RegisterContextTask : public RegisterContext { +class RegisterContextTask final : public RegisterContext { public: RegisterContextTask(Thread &thread, RegisterContextSP reg_info_sp, addr_t resume_fn, addr_t async_ctx); diff --git a/lldb/test/API/lang/swift/async/tasks/Makefile b/lldb/test/API/lang/swift/async/tasks/Makefile new file mode 100644 index 0000000000000..cca30b939e652 --- /dev/null +++ b/lldb/test/API/lang/swift/async/tasks/Makefile @@ -0,0 +1,3 @@ +SWIFT_SOURCES := main.swift +SWIFTFLAGS_EXTRAS := -parse-as-library +include Makefile.rules diff --git a/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskBacktrace.py b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskBacktrace.py new file mode 100644 index 0000000000000..694c800ed55b6 --- /dev/null +++ b/lldb/test/API/lang/swift/async/tasks/TestSwiftTaskBacktrace.py @@ -0,0 +1,21 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase +import lldbsuite.test.lldbutil as lldbutil + + +class TestCase(TestBase): + def test(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.swift") + ) + self.expect( + "language swift task backtrace task", + substrs=[ + ".sleep(", + "`second() at main.swift:6", + "`first() at main.swift:2", + "`closure #1 in static Main.main() at main.swift:12", + ], + ) diff --git a/lldb/test/API/lang/swift/async/tasks/main.swift b/lldb/test/API/lang/swift/async/tasks/main.swift new file mode 100644 index 0000000000000..11dd1a1a30860 --- /dev/null +++ b/lldb/test/API/lang/swift/async/tasks/main.swift @@ -0,0 +1,17 @@ +func first() async { + await second() +} + +func second() async { + try? await Task.sleep(for: .seconds(10)) +} + +@main struct Main { + static func main() async { + let task = Task { + await first() + } + try? await Task.sleep(for: .seconds(0.01)) + print("break here") + } +} From 64253acff50535f2f4c78a1af1f93425ff0e0fb4 Mon Sep 17 00:00:00 2001 From: Dave Lee Date: Wed, 15 Jan 2025 14:37:04 -0800 Subject: [PATCH 4/4] Improve command error messages --- .../Swift/SwiftLanguageRuntime.cpp | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index f505f8ca594d5..81bf2d1be77da 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp @@ -48,7 +48,6 @@ #include "lldb/ValueObject/ValueObjectCast.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/ValueObject/ValueObjectVariable.h" -#include "llvm/Support/FormatAdapters.h" #include "lldb/lldb-enumerations.h" #include "swift/AST/ASTMangler.h" @@ -2103,7 +2102,7 @@ class CommandObjectLanguageSwiftTaskBacktrace final return; } - if (command[0].ref().empty()) { + if (command.empty() || command[0].ref().empty()) { result.AppendError("no task variable"); return; } @@ -2116,26 +2115,29 @@ class CommandObjectLanguageSwiftTaskBacktrace final ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath( command[0].c_str(), eDynamicDontRunTarget, path_options, var_sp, status); - if (!valobj_sp) + if (!valobj_sp) { + result.AppendError(status.AsCString()); return; + } - ValueObjectSP task_obj_sp = valobj_sp->GetChildMemberWithName("_task"); - if (!task_obj_sp) - return; - uint64_t task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); - if (task_ptr == LLDB_INVALID_ADDRESS) - return; - auto *runtime = SwiftLanguageRuntime::Get(m_exe_ctx.GetProcessSP()); - if (!runtime) + 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; - ThreadSafeReflectionContext reflection_ctx = - runtime->GetReflectionContext(); + } + llvm::Expected task_info = reflection_ctx->asyncTaskInfo(task_ptr); - if (auto err = task_info.takeError()) { - LLDB_LOG(GetLog(LLDBLog::DataFormatters | LLDBLog::Types), - "could not get info for async task {0:x}: {1}", task_ptr, - fmt_consume(std::move(err))); + if (auto error = task_info.takeError()) { + result.AppendError(toString(std::move(error))); return; } @@ -2145,8 +2147,10 @@ class CommandObjectLanguageSwiftTaskBacktrace final result.AppendError(toString(std::move(error))); return; } + + // GetStatus prints the backtrace. thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0, - false, false); + false, true); result.SetStatus(lldb::eReturnStatusSuccessFinishResult); } };