Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb] Introduce backtracing of Swift Tasks #9787

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
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
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 @@ -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 <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 @@ -2094,6 +2178,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;

kastiglione marked this conversation as resolved.
Show resolved Hide resolved
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(); }
felipepiovezan marked this conversation as resolved.
Show resolved Hide resolved

// 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",
],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does (lldb) help language swift task (backtrace) work?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the help is here in the definitions of these commands.

)
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")
}
}