Skip to content

Commit 2451d19

Browse files
[lldb][swift] Fix unwinding of Q funclets by comparing PC to continuation ptr
Unwinding these funclets is tricky because they change, halfway through the function, the meaning of the async register. In particular, the meaning changes from: a) "the async context to be freed" [of the async function that just finished executing] To: b) "the async context of the current async function" [which has just been resumed]. LLDB has no way of identifying the instruction in which this transition happened. This patch improves the situation slightly by employing a heuristic: if the async register has a continuation pointer that is equal to the currently executing funclet, then the stop is _before_ the transition point. This heuristic fails on some recursive async functions. An alternative approach involves assuming we will never stop between the end of the prologue and the transition point, so that LLDB may always unwind assuming it is past the transition point if the stop is outside the prologue. This has the benefit that it should "always work", including in recursive funclets. However, it is difficult to reason about where a debugger may stop; in fact, very often the transition point is the second instruction after the prologue, which will trigger the fail point in a common scenario. Breakpoints are often placed in the _first_ instruction after the prologue, and any kind of step operation will first "instruction step" over the breakpoint location, placing the debugger exactly in the "incorrect unwinding" zone. A correct implementation will likely require more guarantees from the compiler. See rdar://139676623
1 parent 41872ac commit 2451d19

File tree

2 files changed

+39
-22
lines changed

2 files changed

+39
-22
lines changed

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,6 +2716,37 @@ static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind(
27162716
"SwiftLanguageRuntime: Unsupported register location type = %d", loctype);
27172717
}
27182718

2719+
/// Returns true if the async register should be dereferenced once to obtain the
2720+
/// CFA of the currently executing function. This is the case at the start of
2721+
/// "Q" funclets, before the low level code changes the meaning of the async
2722+
/// register to not require the indirection.
2723+
/// This implementation detects the transition point by comparing the
2724+
/// continuation pointer in the async context with the currently executing
2725+
/// funclet given by the SymbolContext sc. If they are the same, the PC is
2726+
/// before the transition point.
2727+
/// FIXME: this fails in some recursive async functions. See: rdar://139676623
2728+
static llvm::Expected<bool> IsIndirectContext(Process &process,
2729+
StringRef mangled_name,
2730+
addr_t async_reg,
2731+
SymbolContext &sc) {
2732+
if (!SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol(
2733+
mangled_name))
2734+
return false;
2735+
2736+
llvm::Expected<addr_t> continuation_ptr = ReadPtrFromAddr(
2737+
process, async_reg, /*offset*/ process.GetAddressByteSize());
2738+
if (!continuation_ptr)
2739+
return continuation_ptr.takeError();
2740+
2741+
if (sc.function)
2742+
return sc.function->GetAddressRange().ContainsLoadAddress(
2743+
*continuation_ptr, &process.GetTarget());
2744+
assert(sc.symbol);
2745+
Address continuation_addr;
2746+
continuation_addr.SetLoadAddress(*continuation_ptr, &process.GetTarget());
2747+
return sc.symbol->ContainsFileAddress(continuation_addr.GetFileAddress());
2748+
}
2749+
27192750
// Examine the register state and detect the transition from a real
27202751
// stack frame to an AsyncContext frame, or a frame in the middle of
27212752
// the AsyncContext chain, and return an UnwindPlan for these situations.
@@ -2776,15 +2807,20 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
27762807
// for await resume ("Q") funclets ("indirect context").
27772808
// 2. The async context for the currently executing async function, for all
27782809
// other funclets ("Y" and "Yx" funclets, where "x" is a number).
2779-
bool indirect_context =
2780-
IsSwiftAsyncAwaitResumePartialFunctionSymbol(mangled_name.GetStringRef());
27812810

27822811
llvm::Expected<addr_t> async_reg = ReadAsyncContextRegisterFromUnwind(
27832812
sc, *process_sp, pc, func_start_addr, *regctx, *regnums);
27842813
if (!async_reg)
27852814
return log_expected(async_reg.takeError());
2815+
2816+
llvm::Expected<bool> maybe_indirect_context =
2817+
IsIndirectContext(*process_sp, mangled_name, *async_reg, sc);
2818+
if (!maybe_indirect_context)
2819+
return log_expected(maybe_indirect_context.takeError());
2820+
27862821
llvm::Expected<addr_t> async_ctx =
2787-
indirect_context ? ReadPtrFromAddr(*m_process, *async_reg) : *async_reg;
2822+
*maybe_indirect_context ? ReadPtrFromAddr(*m_process, *async_reg)
2823+
: *async_reg;
27882824
if (!async_ctx)
27892825
return log_expected(async_ctx.takeError());
27902826

lldb/test/API/lang/swift/async/unwind/unwind_in_all_instructions/TestSwiftAsyncUnwindAllInstructions.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,26 +62,7 @@ def set_breakpoints_all_funclets(self, target):
6262
breakpoints.add(bp.GetID())
6363
return breakpoints
6464

65-
# FIXME: there are challenges when unwinding Q funclets ("await resume"),
66-
# see rdar://137048317. For now, we only know how to unwind during and
67-
# shortly after the prologue. This function returns "should skip" if we're
68-
# at a PC that is too far from the prologue (~16 bytes). This is a
69-
# rough approximation that seems to work for both x86 and arm.
70-
def should_skip_Q_funclet(self, thread):
71-
current_frame = thread.frames[0]
72-
function = current_frame.GetFunction()
73-
if "await resume" not in function.GetName():
74-
return False
75-
76-
max_prologue_offset = 16
77-
prologue_end = function.GetStartAddress()
78-
prologue_end.OffsetAddress(function.GetPrologueByteSize() + max_prologue_offset)
79-
current_pc = current_frame.GetPCAddress()
80-
return current_pc.GetFileAddress() >= prologue_end.GetFileAddress()
81-
8265
def check_unwind_ok(self, thread, bpid):
83-
if self.should_skip_Q_funclet(thread):
84-
return
8566
# Check that we see the virtual backtrace:
8667
expected_funcnames = [
8768
"ASYNC___1___",

0 commit comments

Comments
 (0)