Skip to content

Commit 1aa6a85

Browse files
[lldb][swift] Fix unwinding of Q funclets
Consider the following async call sequence: AsyncFunc0_Y called by AsyncFunc1_Q called by AsyncFunc2_Q. That is, when AsyncFunc0_Y returns, it will return to AsyncFunc1_Q. Imagine this backtrace: Frame 0: AsyncFunc1_Q Frame 1: AsyncFunc2_Q That is, AsyncFunc0_Y already finished and we are stopped at AsyncFunc1_Q. One job of the unwinder for a frame is to produce the registers of the **parent** frame. For the top frame (AsyncFunc1_Q), we need to produce the x22 register of AsyncFunc2_Q. This is the value of x22 at the start of AsyncFunc1_Q (we pretend this register was setup as part of some imaginary function call AsyncFunc2_Q -> AsyncFunc1_Q). We use the following async ABI guarantee: A) Register x22 is saved at (fp - 8) during the prologue of funclets. As such, the following dwarf expression is used: *(fp - 8) Prior to this patch, that dereference was missing. The other job of the unwinder for a frame is to produce the CFA of that same frame. For the top frame (AsyncFunc1_Q) we use the following ABI guarantees: B) Its register x22 initially points to the async context of AsyncFunc0 ("Q" funclets are "indirect") C) Dereferencing the first field of an async context produces the async context of the continuation funclet. Combining A), B), and C), the CFA of the top frame (i.e. the async context of AsyncFunc1) can be obtained with the expression: **(fp - 8) Before this patch, the unwinder was missing one dereference.
1 parent 0bc7407 commit 1aa6a85

File tree

2 files changed

+21
-8
lines changed

2 files changed

+21
-8
lines changed

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,7 +2586,7 @@ lldb::addr_t SwiftLanguageRuntime::GetAsyncContext(RegisterContext *regctx) {
25862586
/// operation. This is only valid for x86_64 or aarch64.
25872587
llvm::ArrayRef<uint8_t>
25882588
GetAsyncRegFromFramePointerDWARFExpr(llvm::Triple::ArchType triple,
2589-
bool with_deref) {
2589+
bool with_double_deref) {
25902590
assert(triple == llvm::Triple::x86_64 || triple == llvm::Triple::aarch64);
25912591

25922592
// These expressions must have static storage, due to how UnwindPlan::Row
@@ -2595,11 +2595,13 @@ GetAsyncRegFromFramePointerDWARFExpr(llvm::Triple::ArchType triple,
25952595
llvm::dwarf::DW_OP_breg6, // DW_OP_breg6, register 6 == rbp
25962596
0x78, // sleb128 -8 (ptrsize)
25972597
llvm::dwarf::DW_OP_deref,
2598+
llvm::dwarf::DW_OP_deref,
25982599
};
25992600
static const uint8_t g_cfa_dwarf_expression_arm64[] = {
26002601
llvm::dwarf::DW_OP_breg29, // DW_OP_breg29, register 29 == fp
26012602
0x78, // sleb128 -8 (ptrsize)
26022603
llvm::dwarf::DW_OP_deref,
2604+
llvm::dwarf::DW_OP_deref,
26032605
};
26042606

26052607
const uint8_t *expr = triple == llvm::Triple::x86_64
@@ -2608,7 +2610,7 @@ GetAsyncRegFromFramePointerDWARFExpr(llvm::Triple::ArchType triple,
26082610
auto size = triple == llvm::Triple::x86_64
26092611
? sizeof(g_cfa_dwarf_expression_x86_64)
26102612
: sizeof(g_cfa_dwarf_expression_arm64);
2611-
if (with_deref)
2613+
if (with_double_deref)
26122614
return llvm::ArrayRef<uint8_t>(expr, size);
26132615
return llvm::ArrayRef<uint8_t>(expr, size - 1);
26142616
}
@@ -2708,21 +2710,23 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
27082710
else
27092711
row->GetCFAValue().SetIsRegisterPlusOffset(regnums->async_ctx_regnum, 0);
27102712
} else {
2713+
// In indirect funclets, dereferencing (fp-8) once produces the CFA of the
2714+
// frame above. Dereferencing twice will produce the current frame's CFA.
2715+
bool with_double_deref = indirect_context;
27112716
llvm::ArrayRef<uint8_t> expr = GetAsyncRegFromFramePointerDWARFExpr(
2712-
arch.GetMachine(), true /*with_deref*/);
2717+
arch.GetMachine(), with_double_deref);
27132718
row->GetCFAValue().SetIsDWARFExpression(expr.data(), expr.size());
27142719
}
27152720

27162721
if (indirect_context) {
27172722
if (in_prologue) {
27182723
row->SetRegisterLocationToSame(regnums->async_ctx_regnum, false);
27192724
} else {
2720-
// In a "resume" coroutine, the passed context argument needs to be
2721-
// dereferenced once to get the context. This is reflected in the debug
2722-
// info so we need to account for it and report am async register value
2723-
// that needs to be dereferenced to get to the context.
2725+
// In indirect contexts, the frame above needs to think their x22 is what
2726+
// _our_ x22 is at the start of this function. This can be done by
2727+
// dereferencing ($fp-8) once, a location guaranteed by the ABI.
27242728
llvm::ArrayRef<uint8_t> expr = GetAsyncRegFromFramePointerDWARFExpr(
2725-
arch.GetMachine(), false /*with_deref*/);
2729+
arch.GetMachine(), false /*with_double_deref*/);
27262730
row->SetRegisterLocationToIsDWARFExpression(
27272731
regnums->async_ctx_regnum, expr.data(), expr.size(), false);
27282732
}

lldb/test/API/lang/swift/async/frame/variables_multiple_frames/TestSwiftAsyncFrameVarMultipleFrames.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ def test(self):
6969
self.check_pcs(async_frames, process, target)
7070
self.check_variables(async_frames, ["111", "222", "333", "444", "555"])
7171

72+
# Now stop at the Q funclet right after the await to ASYNC___1
73+
target.DeleteAllBreakpoints()
74+
target.BreakpointCreateByName("$s1a12ASYNC___2___SiyYaFTQ0_")
75+
process.Continue()
76+
async_frames = process.GetSelectedThread().frames
77+
self.check_cfas(async_frames, process)
78+
self.check_pcs(async_frames, process, target)
79+
self.check_variables(async_frames, ["222", "333", "444", "555"])
80+
7281
target.DeleteAllBreakpoints()
7382
target.BreakpointCreateBySourceRegex("breakpoint3", source_file)
7483
process.Continue()

0 commit comments

Comments
 (0)