diff --git a/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp b/lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp index 4d00de7aed8924..eac52753028a8a 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" diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt b/lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt index 0318e5c3fbfd06..a6c0c08f263e65 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 fe8c4b75535b3b..53aa8d51de4762 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp @@ -401,6 +401,8 @@ 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 8dc0e107161697..0e449c13d961d4 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,8 @@ 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. virtual llvm::Expected diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index f4e66ed8057176..81bf2d1be77da9 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" @@ -2083,6 +2084,89 @@ 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.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()); + 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; + } + + llvm::Expected task_info = + reflection_ctx->asyncTaskInfo(task_ptr); + if (auto error = task_info.takeError()) { + result.AppendError(toString(std::move(error))); + 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; + } + + // GetStatus prints the backtrace. + thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0, + false, true); + 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 +2178,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 new file mode 100644 index 00000000000000..7b2bc41bc2ed6f --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp @@ -0,0 +1,74 @@ +#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, 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), 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 + // }; + // 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; + 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"); + + return std::make_shared(tid, async_ctx, resume_fn, exe_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_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 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_resume_fn; + return true; + } + if (reg_info->kinds[eRegisterKindLLDB] == m_async_ctx_regnum) { + reg_value = m_async_ctx; + return true; + } + return false; +} + +} // namespace lldb_private diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h new file mode 100644 index 00000000000000..5f57b3d5cde52c --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h @@ -0,0 +1,97 @@ + +#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 final : public Thread { +public: + 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; + + ~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_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_resume_fn = LLDB_INVALID_ADDRESS; +}; + +/// A Swift Task specific register context. Supporting class for `ThreadTask`, +/// see its documentation for details. +class RegisterContextTask final : public RegisterContext { +public: + 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. + 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_resume_fn; +}; + +} // namespace lldb_private 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 00000000000000..cca30b939e652f --- /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 00000000000000..694c800ed55b6b --- /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 00000000000000..11dd1a1a308606 --- /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") + } +}