Skip to content

Commit 94fd311

Browse files
Merge pull request #9787 from swiftlang/dl/lldb-Introduce-backtracing-of-Swift-Tasks
[lldb] Introduce backtracing of Swift Tasks
2 parents 14c8403 + 64253ac commit 94fd311

File tree

10 files changed

+306
-0
lines changed

10 files changed

+306
-0
lines changed

lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "Plugins/Language/Swift/SwiftStringIndex.h"
1515
#include "Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h"
1616
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
17+
#include "Plugins/LanguageRuntime/Swift/SwiftTask.h"
1718
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
1819
#include "lldb/DataFormatters/FormattersHelpers.h"
1920
#include "lldb/DataFormatters/StringPrinter.h"
@@ -23,6 +24,7 @@
2324
#include "lldb/Utility/LLDBLog.h"
2425
#include "lldb/Utility/Log.h"
2526
#include "lldb/Utility/Status.h"
27+
#include "lldb/Utility/StreamString.h"
2628
#include "lldb/Utility/Timer.h"
2729
#include "lldb/ValueObject/ValueObject.h"
2830
#include "lldb/lldb-enumerations.h"

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"
@@ -2083,6 +2084,89 @@ class CommandObjectSwift_RefCount : public CommandObjectRaw {
20832084
}
20842085
};
20852086

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

20992185
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)