Skip to content

Commit dd46e67

Browse files
[lldb][swift] Swift OS Plugin
This commit introduces an OS plugin converting Process threads into OperatingSystem threads that represent a Task. This is done by inspecting ThreadLocalStorage for a specific location where the swift runtime writes information about the current Task. With this plugin, ThreadPlans now work properly in async code: a step action on a Thread is now effectively an action on a Task, and LLDB will correctly track that. ThreadPlans are effectively "Task Plans".
1 parent 62b2f52 commit dd46e67

File tree

9 files changed

+342
-72
lines changed

9 files changed

+342
-72
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2793,7 +2793,11 @@ llvm::Expected<lldb::addr_t> GetTaskAddrFromThreadLocalStorage(Thread &thread) {
27932793
#else
27942794
// Compute the thread local storage address for this thread.
27952795
addr_t tsd_addr = LLDB_INVALID_ADDRESS;
2796-
if (auto info_sp = thread.GetExtendedInfo())
2796+
2797+
// Look through backing threads when inspecting TLS.
2798+
Thread &real_thread =
2799+
thread.GetBackingThread() ? *thread.GetBackingThread() : thread;
2800+
if (auto info_sp = real_thread.GetExtendedInfo())
27972801
if (auto *info_dict = info_sp->GetAsDictionary())
27982802
info_dict->GetValueForKeyAsInteger("tsd_address", tsd_addr);
27992803

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

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -290,75 +290,6 @@ static ThunkAction GetThunkAction(ThunkKind kind) {
290290
}
291291
}
292292

293-
/// A thread plan to run to a specific address on a specific async context.
294-
class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan {
295-
public:
296-
/// Creates a thread plan to run to destination_addr of an async function
297-
/// whose context is async_ctx.
298-
ThreadPlanRunToAddressOnAsyncCtx(Thread &thread, addr_t destination_addr,
299-
addr_t async_ctx)
300-
: ThreadPlan(eKindGeneric, "run-to-funclet", thread, eVoteNoOpinion,
301-
eVoteNoOpinion),
302-
m_destination_addr(destination_addr), m_expected_async_ctx(async_ctx) {
303-
auto &target = thread.GetProcess()->GetTarget();
304-
m_funclet_bp = target.CreateBreakpoint(destination_addr, true, false);
305-
m_funclet_bp->SetBreakpointKind("async-run-to-funclet");
306-
}
307-
308-
bool ValidatePlan(Stream *error) override {
309-
if (m_funclet_bp->HasResolvedLocations())
310-
return true;
311-
312-
// If we failed to resolve any locations, this plan is invalid.
313-
m_funclet_bp->GetTarget().RemoveBreakpointByID(m_funclet_bp->GetID());
314-
return false;
315-
}
316-
317-
void GetDescription(Stream *s, lldb::DescriptionLevel level) override {
318-
s->PutCString("ThreadPlanRunToAddressOnAsyncCtx to address = ");
319-
s->PutHex64(m_destination_addr);
320-
s->PutCString(" with async ctx = ");
321-
s->PutHex64(m_expected_async_ctx);
322-
}
323-
324-
/// This plan explains the stop if the current async context is the async
325-
/// context this plan was created with.
326-
bool DoPlanExplainsStop(Event *event) override {
327-
if (!HasTID())
328-
return false;
329-
return GetCurrentAsyncContext() == m_expected_async_ctx;
330-
}
331-
332-
/// If this plan explained the stop, it always stops: its sole purpose is to
333-
/// run to the breakpoint it set on the right async function invocation.
334-
bool ShouldStop(Event *event) override {
335-
SetPlanComplete();
336-
return true;
337-
}
338-
339-
/// If this plan said ShouldStop, then its job is complete.
340-
bool MischiefManaged() override {
341-
return IsPlanComplete();
342-
}
343-
344-
bool WillStop() override { return false; }
345-
lldb::StateType GetPlanRunState() override { return eStateRunning; }
346-
bool StopOthers() override { return false; }
347-
void DidPop() override {
348-
m_funclet_bp->GetTarget().RemoveBreakpointByID(m_funclet_bp->GetID());
349-
}
350-
351-
private:
352-
addr_t GetCurrentAsyncContext() {
353-
auto frame_sp = GetThread().GetStackFrameAtIndex(0);
354-
return frame_sp->GetStackID().GetCallFrameAddress();
355-
}
356-
357-
addr_t m_destination_addr;
358-
addr_t m_expected_async_ctx;
359-
BreakpointSP m_funclet_bp;
360-
};
361-
362293
/// Given a thread that is stopped at the start of swift_task_switch, create a
363294
/// thread plan that runs to the address of the resume function.
364295
static ThreadPlanSP
@@ -383,8 +314,8 @@ CreateRunThroughTaskSwitchThreadPlan(Thread &thread,
383314
if (!async_ctx)
384315
return {};
385316

386-
return std::make_shared<ThreadPlanRunToAddressOnAsyncCtx>(
387-
thread, resume_fn_ptr, async_ctx);
317+
return std::make_shared<ThreadPlanRunToAddress>(thread, resume_fn_ptr,
318+
/*stop_others*/ false);
388319
}
389320

390321
/// Creates a thread plan to step over swift runtime functions that can trigger
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
if (LLDB_ENABLE_PYTHON)
22
add_subdirectory(Python)
3+
add_subdirectory(SwiftTasks)
34
endif()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_lldb_library(lldbPluginOperatingSystemSwiftTasks PLUGIN
2+
OperatingSystemSwiftTasks.cpp
3+
4+
LINK_LIBS
5+
lldbCore
6+
lldbInterpreter
7+
lldbSymbol
8+
lldbTarget
9+
lldbValueObject
10+
lldbPluginProcessUtility
11+
lldbPluginSwiftLanguageRuntime
12+
)
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//===-- OperatingSystemSwiftTasks.cpp -------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#if LLDB_ENABLE_SWIFT
10+
11+
#include "OperatingSystemSwiftTasks.h"
12+
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h"
13+
#include "Plugins/Process/Utility/ThreadMemory.h"
14+
#include "lldb/Core/Debugger.h"
15+
#include "lldb/Core/Module.h"
16+
#include "lldb/Core/PluginManager.h"
17+
#include "lldb/Target/Process.h"
18+
#include "lldb/Target/Thread.h"
19+
#include "lldb/Target/ThreadList.h"
20+
#include "lldb/Utility/LLDBLog.h"
21+
22+
#include <memory>
23+
24+
using namespace lldb;
25+
using namespace lldb_private;
26+
27+
LLDB_PLUGIN_DEFINE(OperatingSystemSwiftTasks)
28+
29+
void OperatingSystemSwiftTasks::Initialize() {
30+
PluginManager::RegisterPlugin(GetPluginNameStatic(),
31+
GetPluginDescriptionStatic(), CreateInstance,
32+
nullptr);
33+
}
34+
35+
void OperatingSystemSwiftTasks::Terminate() {
36+
PluginManager::UnregisterPlugin(CreateInstance);
37+
}
38+
39+
OperatingSystem *OperatingSystemSwiftTasks::CreateInstance(Process *process,
40+
bool force) {
41+
if (!process || !process->GetTarget().GetSwiftUseTasksPlugin())
42+
return nullptr;
43+
44+
Log *log = GetLog(LLDBLog::OS);
45+
std::optional<uint32_t> concurrency_version =
46+
SwiftLanguageRuntime::FindConcurrencyDebugVersion(*process);
47+
if (!concurrency_version) {
48+
LLDB_LOG(log,
49+
"OperatingSystemSwiftTasks: did not find concurrency module.");
50+
return nullptr;
51+
}
52+
53+
LLDB_LOGF(log,
54+
"OperatingSystemSwiftTasks: got a concurrency version symbol of %u",
55+
*concurrency_version);
56+
if (*concurrency_version > 1) {
57+
auto warning =
58+
llvm::formatv("Unexpected Swift concurrency version {0}. Stepping on "
59+
"concurrent code may behave incorrectly.",
60+
*concurrency_version);
61+
lldb::user_id_t debugger_id = process->GetTarget().GetDebugger().GetID();
62+
static std::once_flag concurrency_warning_flag;
63+
Debugger::ReportWarning(warning, debugger_id, &concurrency_warning_flag);
64+
return nullptr;
65+
}
66+
return new OperatingSystemSwiftTasks(*process);
67+
}
68+
69+
llvm::StringRef OperatingSystemSwiftTasks::GetPluginDescriptionStatic() {
70+
return "Operating system plug-in converting Swift Tasks into Threads.";
71+
}
72+
73+
OperatingSystemSwiftTasks::~OperatingSystemSwiftTasks() = default;
74+
75+
OperatingSystemSwiftTasks::OperatingSystemSwiftTasks(
76+
lldb_private::Process &process)
77+
: OperatingSystem(&process) {
78+
size_t ptr_size = process.GetAddressByteSize();
79+
// Offset of a Task ID inside a Task data structure, guaranteed by the ABI.
80+
// See Job in swift/RemoteInspection/RuntimeInternals.h.
81+
m_job_id_offset = 4 * ptr_size + 4;
82+
}
83+
84+
ThreadSP
85+
OperatingSystemSwiftTasks::FindOrCreateSwiftThread(ThreadList &old_thread_list,
86+
uint64_t task_id) {
87+
// Mask higher bits to avoid conflicts with core thread IDs.
88+
uint64_t masked_task_id = 0x0000000f00000000 | task_id;
89+
90+
// If we already had a thread for this Task in the last stop, re-use it.
91+
if (ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id);
92+
IsOperatingSystemPluginThread(old_thread))
93+
return old_thread;
94+
95+
std::string name = llvm::formatv("Swift Task {0:x}", task_id);
96+
llvm::StringRef queue_name = "";
97+
return std::make_shared<ThreadMemory>(*m_process, masked_task_id, name,
98+
queue_name,
99+
/*register_data_addr*/ 0);
100+
}
101+
102+
bool OperatingSystemSwiftTasks::UpdateThreadList(ThreadList &old_thread_list,
103+
ThreadList &core_thread_list,
104+
ThreadList &new_thread_list) {
105+
Log *log = GetLog(LLDBLog::OS);
106+
LLDB_LOG(log, "OperatingSystemSwiftTasks: Updating thread list");
107+
108+
for (const ThreadSP &real_thread : core_thread_list.Threads()) {
109+
std::optional<uint64_t> task_id = FindTaskId(*real_thread);
110+
111+
// If this is not a thread running a Task, add it to the list as is.
112+
if (!task_id) {
113+
new_thread_list.AddThread(real_thread);
114+
LLDB_LOGF(log,
115+
"OperatingSystemSwiftTasks: thread %" PRIx64
116+
" is not executing a Task",
117+
real_thread->GetID());
118+
continue;
119+
}
120+
121+
ThreadSP swift_thread = FindOrCreateSwiftThread(old_thread_list, *task_id);
122+
swift_thread->SetBackingThread(real_thread);
123+
new_thread_list.AddThread(swift_thread);
124+
LLDB_LOGF(log,
125+
"OperatingSystemSwiftTasks: mapping thread IDs: %" PRIx64
126+
" -> %" PRIx64,
127+
real_thread->GetID(), swift_thread->GetID());
128+
}
129+
return true;
130+
}
131+
132+
void OperatingSystemSwiftTasks::ThreadWasSelected(Thread *thread) {}
133+
134+
RegisterContextSP OperatingSystemSwiftTasks::CreateRegisterContextForThread(
135+
Thread *thread, addr_t reg_data_addr) {
136+
if (!thread || !IsOperatingSystemPluginThread(thread->shared_from_this()))
137+
return nullptr;
138+
return thread->GetRegisterContext();
139+
}
140+
141+
StopInfoSP OperatingSystemSwiftTasks::CreateThreadStopReason(
142+
lldb_private::Thread *thread) {
143+
return thread->GetStopInfo();
144+
}
145+
146+
std::optional<uint64_t> OperatingSystemSwiftTasks::FindTaskId(Thread &thread) {
147+
llvm::Expected<addr_t> task_addr = GetTaskAddrFromThreadLocalStorage(thread);
148+
if (!task_addr) {
149+
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), task_addr.takeError(),
150+
"OperatingSystemSwiftTasks: failed to find task address in "
151+
"thread local storage: {0}");
152+
return {};
153+
}
154+
155+
Status error;
156+
// The Task ID is at offset m_job_id_offset from the Task pointer.
157+
constexpr uint32_t num_bytes_task_id = 4;
158+
auto task_id = m_process->ReadUnsignedIntegerFromMemory(
159+
*task_addr + m_job_id_offset, num_bytes_task_id, LLDB_INVALID_ADDRESS,
160+
error);
161+
if (error.Fail())
162+
return {};
163+
return task_id;
164+
}
165+
166+
#endif // #if LLDB_ENABLE_SWIFT
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===-- OperatingSystemSwiftTasks.h -----------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef liblldb_OperatingSystemSwiftTasks_h_
10+
#define liblldb_OperatingSystemSwiftTasks_h_
11+
12+
#if LLDB_ENABLE_SWIFT
13+
14+
#include "lldb/Target/OperatingSystem.h"
15+
16+
namespace lldb_private {
17+
class OperatingSystemSwiftTasks : public OperatingSystem {
18+
public:
19+
OperatingSystemSwiftTasks(Process &process);
20+
~OperatingSystemSwiftTasks() override;
21+
22+
static OperatingSystem *CreateInstance(Process *process, bool force);
23+
static void Initialize();
24+
static void Terminate();
25+
static llvm::StringRef GetPluginNameStatic() { return "swift"; }
26+
static llvm::StringRef GetPluginDescriptionStatic();
27+
28+
/// PluginInterface Methods
29+
30+
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
31+
32+
/// OperatingSystem Methods
33+
34+
bool UpdateThreadList(ThreadList &old_thread_list,
35+
ThreadList &real_thread_list,
36+
ThreadList &new_thread_list) override;
37+
38+
void ThreadWasSelected(Thread *thread) override;
39+
40+
lldb::RegisterContextSP
41+
CreateRegisterContextForThread(Thread *thread,
42+
lldb::addr_t reg_data_addr) override;
43+
44+
lldb::StopInfoSP CreateThreadStopReason(Thread *thread) override;
45+
46+
bool DoesPluginReportAllThreads() override { return false; }
47+
48+
private:
49+
/// If a thread for task_id had been created in the last stop, return it.
50+
/// Otherwise, create a new MemoryThread for it.
51+
lldb::ThreadSP FindOrCreateSwiftThread(ThreadList &old_thread_list,
52+
uint64_t task_id);
53+
54+
/// Find the Task ID of the task being executed by `thread`, if any.
55+
std::optional<uint64_t> FindTaskId(Thread &thread);
56+
57+
/// The offset of the Job ID inside a Task data structure.
58+
size_t m_job_id_offset;
59+
};
60+
} // namespace lldb_private
61+
62+
#endif // LLDB_ENABLE_SWIFT
63+
64+
#endif // liblldb_OperatingSystemSwiftTasks_h_
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

0 commit comments

Comments
 (0)