Skip to content

Commit 47287d7

Browse files
committed
[lldb] Introduce backtracing of Swift Tasks (#9845)
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
1 parent 240e1da commit 47287d7

File tree

9 files changed

+304
-0
lines changed

9 files changed

+304
-0
lines changed

lldb/source/Plugins/LanguageRuntime/Swift/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ add_lldb_library(lldbPluginSwiftLanguageRuntime PLUGIN
66
SwiftLanguageRuntimeNames.cpp
77
SwiftLanguageRuntimeRemoteAST.cpp
88
SwiftMetadataCache.cpp
9+
SwiftTask.cpp
910

1011
LINK_LIBS
1112
swiftAST

lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContext.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,8 @@ class TargetReflectionContext : public ReflectionContextInterface {
401401
result.hasIsRunning = task_info.HasIsRunning;
402402
result.isRunning = task_info.IsRunning;
403403
result.isEnqueued = task_info.IsEnqueued;
404+
result.id = task_info.Id;
405+
result.resumeAsyncContext = task_info.ResumeAsyncContext;
404406
return result;
405407
}
406408

lldb/source/Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include <mutex>
1717

18+
#include "lldb/lldb-defines.h"
1819
#include "lldb/lldb-types.h"
1920
#include "swift/ABI/ObjectFile.h"
2021
#include "swift/Remote/RemoteAddress.h"
@@ -163,6 +164,8 @@ class ReflectionContextInterface {
163164
bool hasIsRunning = false;
164165
bool isRunning = false;
165166
bool isEnqueued = false;
167+
uint64_t id = 0;
168+
lldb::addr_t resumeAsyncContext = LLDB_INVALID_ADDRESS;
166169
};
167170
// The default limits are copied from swift-inspect.
168171
virtual llvm::Expected<AsyncTaskInfo>

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "SwiftMetadataCache.h"
1717

1818
#include "Plugins/ExpressionParser/Swift/SwiftPersistentExpressionState.h"
19+
#include "Plugins/LanguageRuntime/Swift/SwiftTask.h"
1920
#include "Plugins/Process/Utility/RegisterContext_x86.h"
2021
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
2122
#include "Plugins/TypeSystem/Swift/SwiftDemangle.h"
@@ -2085,6 +2086,89 @@ class CommandObjectSwift_RefCount : public CommandObjectRaw {
20852086
}
20862087
};
20872088

2089+
class CommandObjectLanguageSwiftTaskBacktrace final
2090+
: public CommandObjectParsed {
2091+
public:
2092+
CommandObjectLanguageSwiftTaskBacktrace(CommandInterpreter &interpreter)
2093+
: CommandObjectParsed(interpreter, "backtrace",
2094+
"Show the backtrace of Swift tasks. See `thread "
2095+
"backtrace` for customizing backtrace output.",
2096+
"language swift task backtrace <variable-name>") {
2097+
AddSimpleArgumentList(eArgTypeVarName);
2098+
}
2099+
2100+
private:
2101+
void DoExecute(Args &command, CommandReturnObject &result) override {
2102+
if (!m_exe_ctx.GetFramePtr()) {
2103+
result.AppendError("no active frame selected");
2104+
return;
2105+
}
2106+
2107+
if (command.empty() || command[0].ref().empty()) {
2108+
result.AppendError("no task variable");
2109+
return;
2110+
}
2111+
2112+
StackFrame &frame = m_exe_ctx.GetFrameRef();
2113+
uint32_t path_options =
2114+
StackFrame::eExpressionPathOptionsAllowDirectIVarAccess;
2115+
VariableSP var_sp;
2116+
Status status;
2117+
ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath(
2118+
command[0].c_str(), eDynamicDontRunTarget, path_options, var_sp,
2119+
status);
2120+
if (!valobj_sp) {
2121+
result.AppendError(status.AsCString());
2122+
return;
2123+
}
2124+
2125+
addr_t task_ptr = LLDB_INVALID_ADDRESS;
2126+
ThreadSafeReflectionContext reflection_ctx;
2127+
if (ValueObjectSP task_obj_sp =
2128+
valobj_sp->GetChildMemberWithName("_task")) {
2129+
task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
2130+
if (task_ptr != LLDB_INVALID_ADDRESS)
2131+
if (auto *runtime = SwiftLanguageRuntime::Get(m_exe_ctx.GetProcessSP()))
2132+
reflection_ctx = runtime->GetReflectionContext();
2133+
}
2134+
if (task_ptr == LLDB_INVALID_ADDRESS || !reflection_ctx) {
2135+
result.AppendError("failed to access Task data from runtime");
2136+
return;
2137+
}
2138+
2139+
llvm::Expected<ReflectionContextInterface::AsyncTaskInfo> task_info =
2140+
reflection_ctx->asyncTaskInfo(task_ptr);
2141+
if (auto error = task_info.takeError()) {
2142+
result.AppendError(toString(std::move(error)));
2143+
return;
2144+
}
2145+
2146+
auto thread_task = ThreadTask::Create(
2147+
task_info->id, task_info->resumeAsyncContext, m_exe_ctx);
2148+
if (auto error = thread_task.takeError()) {
2149+
result.AppendError(toString(std::move(error)));
2150+
return;
2151+
}
2152+
2153+
// GetStatus prints the backtrace.
2154+
thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0,
2155+
false, true);
2156+
result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
2157+
}
2158+
};
2159+
2160+
class CommandObjectLanguageSwiftTask final : public CommandObjectMultiword {
2161+
public:
2162+
CommandObjectLanguageSwiftTask(CommandInterpreter &interpreter)
2163+
: CommandObjectMultiword(
2164+
interpreter, "task", "Commands for inspecting Swift Tasks.",
2165+
"language swift task <subcommand> [<subcommand-options>]") {
2166+
LoadSubCommand("backtrace",
2167+
CommandObjectSP(new CommandObjectLanguageSwiftTaskBacktrace(
2168+
interpreter)));
2169+
}
2170+
};
2171+
20882172
class CommandObjectMultiwordSwift : public CommandObjectMultiword {
20892173
public:
20902174
CommandObjectMultiwordSwift(CommandInterpreter &interpreter)
@@ -2096,6 +2180,8 @@ class CommandObjectMultiwordSwift : public CommandObjectMultiword {
20962180
interpreter)));
20972181
LoadSubCommand("refcount", CommandObjectSP(new CommandObjectSwift_RefCount(
20982182
interpreter)));
2183+
LoadSubCommand("task", CommandObjectSP(new CommandObjectLanguageSwiftTask(
2184+
interpreter)));
20992185
}
21002186

21012187
virtual ~CommandObjectMultiwordSwift() {}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include "SwiftTask.h"
2+
#include "SwiftLanguageRuntime.h"
3+
#include "lldb/Target/Process.h"
4+
#include "lldb/lldb-enumerations.h"
5+
#include "llvm/Support/Error.h"
6+
7+
using namespace llvm;
8+
using namespace lldb;
9+
10+
namespace lldb_private {
11+
12+
ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, addr_t resume_fn,
13+
ExecutionContext &exe_ctx)
14+
: Thread(exe_ctx.GetProcessRef(), tid, true),
15+
m_reg_info_sp(exe_ctx.GetFrameSP()->GetRegisterContext()),
16+
m_async_ctx(async_ctx), m_resume_fn(resume_fn) {}
17+
18+
RegisterContextSP lldb_private::ThreadTask::GetRegisterContext() {
19+
if (!m_async_reg_ctx_sp)
20+
m_async_reg_ctx_sp = std::make_shared<RegisterContextTask>(
21+
*this, m_reg_info_sp, m_resume_fn, m_async_ctx);
22+
return m_async_reg_ctx_sp;
23+
}
24+
25+
llvm::Expected<std::shared_ptr<ThreadTask>>
26+
ThreadTask::Create(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx) {
27+
auto &process = exe_ctx.GetProcessRef();
28+
auto &target = exe_ctx.GetTargetRef();
29+
30+
// A simplified description of AsyncContext. See swift/Task/ABI.h
31+
// struct AsyncContext {
32+
// AsyncContext *Parent; // offset 0
33+
// TaskContinuationFunction *ResumeParent; // offset 8
34+
// };
35+
// The resume function is stored at `offsetof(AsyncContext, ResumeParent)`,
36+
// which is the async context's base address plus the size of a pointer.
37+
uint32_t ptr_size = target.GetArchitecture().GetAddressByteSize();
38+
addr_t resume_ptr = async_ctx + ptr_size;
39+
Status status;
40+
addr_t resume_fn = process.ReadPointerFromMemory(resume_ptr, status);
41+
if (status.Fail() || resume_fn == LLDB_INVALID_ADDRESS)
42+
return createStringError("failed to read task's resume function");
43+
44+
return std::make_shared<ThreadTask>(tid, async_ctx, resume_fn, exe_ctx);
45+
}
46+
47+
RegisterContextTask::RegisterContextTask(Thread &thread,
48+
RegisterContextSP reg_info_sp,
49+
addr_t resume_fn, addr_t async_ctx)
50+
: RegisterContext(thread, 0), m_reg_info_sp(reg_info_sp),
51+
m_async_ctx(async_ctx), m_resume_fn(resume_fn) {
52+
auto &target = thread.GetProcess()->GetTarget();
53+
auto triple = target.GetArchitecture().GetTriple();
54+
if (auto regnums = GetAsyncUnwindRegisterNumbers(triple.getArch()))
55+
m_async_ctx_regnum = regnums->async_ctx_regnum;
56+
}
57+
58+
bool RegisterContextTask::ReadRegister(const RegisterInfo *reg_info,
59+
RegisterValue &reg_value) {
60+
if (!reg_info)
61+
return false;
62+
63+
if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC) {
64+
reg_value = m_resume_fn;
65+
return true;
66+
}
67+
if (reg_info->kinds[eRegisterKindLLDB] == m_async_ctx_regnum) {
68+
reg_value = m_async_ctx;
69+
return true;
70+
}
71+
return false;
72+
}
73+
74+
} // namespace lldb_private
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
2+
#include "lldb/Target/RegisterContext.h"
3+
#include "lldb/Target/Thread.h"
4+
#include "lldb/Utility/RegisterValue.h"
5+
#include "lldb/lldb-forward.h"
6+
7+
namespace lldb_private {
8+
9+
using namespace lldb;
10+
11+
/// Provides a subset of Thread operations for Swift Tasks.
12+
///
13+
/// Currently, this supports backtraces of Tasks, and selecting frames in the
14+
/// backtrace. Async frames make available the variables that are stored in the
15+
/// Task's "async context" (instead of the stack).
16+
///
17+
/// See `Task<Success, Failure>` and `UnsafeCurrentTask`
18+
class ThreadTask final : public Thread {
19+
public:
20+
ThreadTask(tid_t tid, addr_t async_ctx, addr_t resume_fn,
21+
ExecutionContext &exe_ctx);
22+
23+
static llvm::Expected<std::shared_ptr<ThreadTask>>
24+
Create(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx);
25+
26+
/// Returns a Task specific register context (RegisterContextTask).
27+
RegisterContextSP GetRegisterContext() override;
28+
29+
~ThreadTask() override { DestroyThread(); }
30+
31+
// No-op overrides.
32+
void RefreshStateAfterStop() override {}
33+
lldb::RegisterContextSP
34+
CreateRegisterContextForFrame(StackFrame *frame) override {
35+
return {};
36+
}
37+
bool CalculateStopInfo() override { return false; }
38+
39+
private:
40+
/// A register context that is the source of `RegisterInfo` data.
41+
RegisterContextSP m_reg_info_sp;
42+
/// Lazily initialized `RegisterContextTask`.
43+
RegisterContextSP m_async_reg_ctx_sp;
44+
/// The Task's async context.
45+
addr_t m_async_ctx = LLDB_INVALID_ADDRESS;
46+
/// The address of the async context's resume function.
47+
addr_t m_resume_fn = LLDB_INVALID_ADDRESS;
48+
};
49+
50+
/// A Swift Task specific register context. Supporting class for `ThreadTask`,
51+
/// see its documentation for details.
52+
class RegisterContextTask final : public RegisterContext {
53+
public:
54+
RegisterContextTask(Thread &thread, RegisterContextSP reg_info_sp,
55+
addr_t resume_fn, addr_t async_ctx);
56+
57+
/// RegisterContextTask supports readonly from only two (necessary)
58+
/// registers. Namely, the pc and the async context registers.
59+
bool ReadRegister(const RegisterInfo *reg_info,
60+
RegisterValue &reg_value) override;
61+
62+
// Pass through overrides.
63+
size_t GetRegisterCount() override {
64+
return m_reg_info_sp->GetRegisterCount();
65+
}
66+
const RegisterInfo *GetRegisterInfoAtIndex(size_t idx) override {
67+
return m_reg_info_sp->GetRegisterInfoAtIndex(idx);
68+
}
69+
size_t GetRegisterSetCount() override {
70+
return m_reg_info_sp->GetRegisterSetCount();
71+
}
72+
const RegisterSet *GetRegisterSet(size_t reg_set) override {
73+
return m_reg_info_sp->GetRegisterSet(reg_set);
74+
}
75+
lldb::ByteOrder GetByteOrder() override {
76+
return m_reg_info_sp->GetByteOrder();
77+
}
78+
79+
// No-op overrides.
80+
void InvalidateAllRegisters() override {}
81+
bool WriteRegister(const RegisterInfo *reg_info,
82+
const RegisterValue &reg_value) override {
83+
return false;
84+
}
85+
86+
private:
87+
/// A register context that is the source of `RegisterInfo` data.
88+
RegisterContextSP m_reg_info_sp;
89+
/// The architecture specific regnum (LLDB) which holds the async context.
90+
uint32_t m_async_ctx_regnum = LLDB_INVALID_REGNUM;
91+
/// The Task's async context.
92+
RegisterValue m_async_ctx;
93+
/// The address of the async context's resume function.
94+
RegisterValue m_resume_fn;
95+
};
96+
97+
} // namespace lldb_private
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
SWIFTFLAGS_EXTRAS := -parse-as-library
3+
include Makefile.rules
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import TestBase
4+
import lldbsuite.test.lldbutil as lldbutil
5+
6+
7+
class TestCase(TestBase):
8+
def test(self):
9+
self.build()
10+
lldbutil.run_to_source_breakpoint(
11+
self, "break here", lldb.SBFileSpec("main.swift")
12+
)
13+
self.expect(
14+
"language swift task backtrace task",
15+
substrs=[
16+
".sleep(",
17+
"`second() at main.swift:6",
18+
"`first() at main.swift:2",
19+
"`closure #1 in static Main.main() at main.swift:12",
20+
],
21+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
func first() async {
2+
await second()
3+
}
4+
5+
func second() async {
6+
try? await Task.sleep(for: .seconds(10))
7+
}
8+
9+
@main struct Main {
10+
static func main() async {
11+
let task = Task {
12+
await first()
13+
}
14+
try? await Task.sleep(for: .seconds(0.01))
15+
print("break here")
16+
}
17+
}

0 commit comments

Comments
 (0)