Skip to content

Commit 1d2f744

Browse files
[lldb][swift] Add swift_task_switch StepThroughPlan in Language plugin
The symbol for swift_task_switch is marked as a trampoline, which triggers a generic handling of it through DynamicLoaderDarwin. This makes sense, as it is usually just a symbol stub that we jump through to get to the real task_switch: ``` ... 0x1000031bc <+112>: b 0x100003d20 ; symbol stub for: swift_task_switch ... testt.out`swift_task_switch: swift_task_switch -> 0x100003d20 <+0>: adrp x16, 1 0x100003d24 <+4>: ldr x16, [x16, #0x10] 0x100003d28 <+8>: br x16 ; <<< real task_switch_addr ``` DynamicLoaderDarwin will take the thread all the way to the target of the `br x16` instruction, which is the real task switch. However, once that's done, there are no thread plans taking the thread to the target funclet we're switching to. Recall that a task_switch is a request to run a given funclet inside _some_ executor/thread. It's like a function call, but with scheduling in the middle. This commit adds a new step through task_switch thread plan inside SwiftLanguageRuntime; it is a simplified version of the (now removed) ThreadPlanStepInAsync: a breakpoint is created to the target funclet, and we compare async contexts to make sure the breakpoint was hit on the right task. Note: we could remove the handling inside DynamicLoaderDarwin with some kind of "if (symbol == swift_task_switch) don't do anything". The new thread plan would work even on the stub.
1 parent 1774208 commit 1d2f744

File tree

4 files changed

+166
-1
lines changed

4 files changed

+166
-1
lines changed

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

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,113 @@ 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+
362+
/// Given a thread that is stopped at the start of swift_task_switch, create a
363+
/// thread plan that runs to the address of the resume function.
364+
static ThreadPlanSP CreateRunThroughTaskSwitchThreadPlan(Thread &thread) {
365+
// The signature for `swift_task_switch` is as follows:
366+
// SWIFT_CC(swiftasync)
367+
// void swift_task_switch(
368+
// SWIFT_ASYNC_CONTEXT AsyncContext *resumeContext,
369+
// TaskContinuationFunction *resumeFunction,
370+
// ExecutorRef newExecutor);
371+
//
372+
// The async context given as the first argument is not passed using the
373+
// calling convention's first register, it's passed in the platform's async
374+
// context register. This means the `resumeFunction` parameter uses the
375+
// first ABI register (ex: x86-64: rdi, arm64: x0).
376+
RegisterContextSP reg_ctx =
377+
thread.GetStackFrameAtIndex(0)->GetRegisterContext();
378+
constexpr unsigned resume_fn_regnum = LLDB_REGNUM_GENERIC_ARG1;
379+
unsigned resume_fn_reg = reg_ctx->ConvertRegisterKindToRegisterNumber(
380+
RegisterKind::eRegisterKindGeneric, resume_fn_regnum);
381+
uint64_t resume_fn_ptr = reg_ctx->ReadRegisterAsUnsigned(resume_fn_reg, 0);
382+
if (!resume_fn_ptr)
383+
return {};
384+
385+
auto arch = reg_ctx->CalculateTarget()->GetArchitecture();
386+
std::optional<AsyncUnwindRegisterNumbers> async_regs =
387+
GetAsyncUnwindRegisterNumbers(arch.GetMachine());
388+
if (!async_regs)
389+
return {};
390+
unsigned async_reg_number = reg_ctx->ConvertRegisterKindToRegisterNumber(
391+
async_regs->GetRegisterKind(), async_regs->async_ctx_regnum);
392+
uint64_t async_ctx = reg_ctx->ReadRegisterAsUnsigned(async_reg_number, 0);
393+
if (!async_ctx)
394+
return {};
395+
396+
return std::make_shared<ThreadPlanRunToAddressOnAsyncCtx>(
397+
thread, resume_fn_ptr, async_ctx);
398+
}
399+
293400
static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
294401
bool stop_others) {
295402
// Here are the trampolines we have at present.
@@ -319,7 +426,11 @@ static lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread,
319426
if (symbol_addr != cur_addr)
320427
return nullptr;
321428

322-
const char *symbol_name = symbol->GetMangled().GetMangledName().AsCString();
429+
Mangled &mangled_symbol_name = symbol->GetMangled();
430+
const char *symbol_name = mangled_symbol_name.GetMangledName().AsCString();
431+
432+
if (mangled_symbol_name.GetDemangledName() == "swift_task_switch")
433+
return CreateRunThroughTaskSwitchThreadPlan(thread);
323434

324435
ThunkKind thunk_kind = GetThunkKind(symbol);
325436
ThunkAction thunk_action = GetThunkAction(thunk_kind);
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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
import lldbsuite.test.lldbtest as lldbtest
4+
import lldbsuite.test.lldbutil as lldbutil
5+
6+
7+
@skipIfAsan # rdar://138777205
8+
class TestCase(lldbtest.TestBase):
9+
10+
def check_is_in_line(self, thread, linenum):
11+
frame = thread.frames[0]
12+
line_entry = frame.GetLineEntry()
13+
self.assertEqual(linenum, line_entry.GetLine())
14+
15+
@swiftTest
16+
@skipIf(oslist=["windows", "linux"])
17+
def test(self):
18+
"""Test conditions for async step-over."""
19+
self.build()
20+
21+
source_file = lldb.SBFileSpec("main.swift")
22+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
23+
self, "BREAK HERE", source_file
24+
)
25+
26+
expected_line_nums = [4] # print(x)
27+
expected_line_nums += [5, 6, 7, 5, 6, 7, 5] # two runs over the loop
28+
expected_line_nums += [8, 9] # if line + if block
29+
# FIXME: IRGen is producing incorrected line numbers. rdar://139826231
30+
expected_line_nums[-1] = 11
31+
for expected_line_num in expected_line_nums:
32+
thread.StepOver()
33+
stop_reason = thread.GetStopReason()
34+
self.assertStopReason(stop_reason, lldb.eStopReasonPlanComplete)
35+
self.check_is_in_line(thread, expected_line_num)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@main enum entry {
2+
static func main() async {
3+
let x = await f() // BREAK HERE
4+
print(x)
5+
for i in 1...2 {
6+
await f()
7+
}
8+
if (await f() == 30) {
9+
print("here!")
10+
}
11+
}
12+
}
13+
14+
func f() async -> Int {
15+
return 30
16+
}

0 commit comments

Comments
 (0)