Skip to content

Commit

Permalink
[lldb] Introduce backtracing of Swift Tasks (#9845) (#9870)
Browse files Browse the repository at this point in the history
Introduces the first of a new group of commands for working with Swift Task instances.

The new command group is `language swift task`, and the first command is `backtrace`. This `backtrace` command takes the name of a task variable and prints the task's backtrace. The variable can be either `Task<Success, Failure>` or `UnsafeCurrentTask`. The output is similar to the builtin `thread backtrace` (`bt`) command.

See the original PR: #9787

(cherry picked from commit 2c3335d)
  • Loading branch information
kastiglione authored Jan 22, 2025
1 parent 240e1da commit 1e87eae
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 0 deletions.
1 change: 1 addition & 0 deletions lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_lldb_library(lldbPluginSwiftLanguageRuntime PLUGIN
SwiftLanguageRuntimeNames.cpp
SwiftLanguageRuntimeRemoteAST.cpp
SwiftMetadataCache.cpp
SwiftTask.cpp

LINK_LIBS
swiftAST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include <mutex>

#include "lldb/lldb-defines.h"
#include "lldb/lldb-types.h"
#include "swift/ABI/ObjectFile.h"
#include "swift/Remote/RemoteAddress.h"
Expand Down Expand Up @@ -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<AsyncTaskInfo>
Expand Down
86 changes: 86 additions & 0 deletions lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2085,6 +2086,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 <variable-name>") {
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<ReflectionContextInterface::AsyncTaskInfo> 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 <subcommand> [<subcommand-options>]") {
LoadSubCommand("backtrace",
CommandObjectSP(new CommandObjectLanguageSwiftTaskBacktrace(
interpreter)));
}
};

class CommandObjectMultiwordSwift : public CommandObjectMultiword {
public:
CommandObjectMultiwordSwift(CommandInterpreter &interpreter)
Expand All @@ -2096,6 +2180,8 @@ class CommandObjectMultiwordSwift : public CommandObjectMultiword {
interpreter)));
LoadSubCommand("refcount", CommandObjectSP(new CommandObjectSwift_RefCount(
interpreter)));
LoadSubCommand("task", CommandObjectSP(new CommandObjectLanguageSwiftTask(
interpreter)));
}

virtual ~CommandObjectMultiwordSwift() {}
Expand Down
74 changes: 74 additions & 0 deletions lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.cpp
Original file line number Diff line number Diff line change
@@ -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<RegisterContextTask>(
*this, m_reg_info_sp, m_resume_fn, m_async_ctx);
return m_async_reg_ctx_sp;
}

llvm::Expected<std::shared_ptr<ThreadTask>>
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<ThreadTask>(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 &reg_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
97 changes: 97 additions & 0 deletions lldb/source/Plugins/LanguageRuntime/Swift/SwiftTask.h
Original file line number Diff line number Diff line change
@@ -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<Success, Failure>` 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<std::shared_ptr<ThreadTask>>
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 &reg_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 &reg_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
3 changes: 3 additions & 0 deletions lldb/test/API/lang/swift/async/tasks/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SWIFT_SOURCES := main.swift
SWIFTFLAGS_EXTRAS := -parse-as-library
include Makefile.rules
21 changes: 21 additions & 0 deletions lldb/test/API/lang/swift/async/tasks/TestSwiftTaskBacktrace.py
Original file line number Diff line number Diff line change
@@ -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",
],
)
17 changes: 17 additions & 0 deletions lldb/test/API/lang/swift/async/tasks/main.swift
Original file line number Diff line number Diff line change
@@ -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")
}
}

0 comments on commit 1e87eae

Please sign in to comment.