Skip to content

Commit d85bebe

Browse files
Merge pull request #9025 from felipepiovezan/felipe/unwinding_pc_patches_to_release
Fix swift async frames unwinding unwinding
2 parents d83ae22 + 85069c8 commit d85bebe

File tree

9 files changed

+283
-18
lines changed

9 files changed

+283
-18
lines changed

lldb/include/lldb/Symbol/UnwindPlan.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ class UnwindPlan {
6868
isAFAPlusOffset, // reg = AFA + offset
6969
inOtherRegister, // reg = other reg
7070
atDWARFExpression, // reg = deref(eval(dwarf_expr))
71-
isDWARFExpression // reg = eval(dwarf_expr)
71+
isDWARFExpression, // reg = eval(dwarf_expr)
72+
isConstant // reg = constant
7273
};
7374

7475
RegisterLocation() : m_location() {}
@@ -105,6 +106,15 @@ class UnwindPlan {
105106

106107
bool IsDWARFExpression() const { return m_type == isDWARFExpression; }
107108

109+
bool IsConstant() const { return m_type == isConstant; }
110+
111+
void SetIsConstant(uint64_t value) {
112+
m_type = isConstant;
113+
m_location.constant_value = value;
114+
}
115+
116+
uint64_t GetConstant() const { return m_location.constant_value; }
117+
108118
void SetAtCFAPlusOffset(int32_t offset) {
109119
m_type = atCFAPlusOffset;
110120
m_location.offset = offset;
@@ -192,6 +202,8 @@ class UnwindPlan {
192202
const uint8_t *opcodes;
193203
uint16_t length;
194204
} expr;
205+
// For m_type == isConstant
206+
uint64_t constant_value;
195207
} m_location;
196208
};
197209

@@ -361,6 +373,10 @@ class UnwindPlan {
361373
bool SetRegisterLocationToIsDWARFExpression(uint32_t reg_num,
362374
const uint8_t *opcodes,
363375
uint32_t len, bool can_replace);
376+
377+
bool SetRegisterLocationToIsConstant(uint32_t reg_num, uint64_t constant,
378+
bool can_replace);
379+
364380
// When this UnspecifiedRegistersAreUndefined mode is
365381
// set, any register that is not specified by this Row will
366382
// be described as Undefined.

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

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,6 @@ static bool IsModuleSwiftRuntime(lldb_private::Process &process,
9999
return module.GetFileSpec().GetFilename() == GetStandardLibraryName(process);
100100
}
101101

102-
static UnwindPlanSP
103-
GetFollowAsyncContextUnwindPlan(RegisterContext *regctx, ArchSpec &arch,
104-
bool &behaves_like_zeroth_frame);
105-
106102
AppleObjCRuntimeV2 *
107103
SwiftLanguageRuntime::GetObjCRuntime(lldb_private::Process &process) {
108104
if (auto objc_runtime = ObjCLanguageRuntime::Get(process)) {
@@ -2608,7 +2604,7 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
26082604
addr_t fp = regctx->GetFP(LLDB_INVALID_ADDRESS);
26092605
if (fp == LLDB_INVALID_ADDRESS) {
26102606
if (GetAsyncContext(regctx) != LLDB_INVALID_ADDRESS)
2611-
return GetFollowAsyncContextUnwindPlan(regctx, arch,
2607+
return GetFollowAsyncContextUnwindPlan(process_sp, regctx, arch,
26122608
behaves_like_zeroth_frame);
26132609
return UnwindPlanSP();
26142610
}
@@ -2749,9 +2745,31 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
27492745
regnums->dummy_regnum, g_dummy_dwarf_expression,
27502746
sizeof(g_dummy_dwarf_expression), false);
27512747
}
2752-
row->SetRegisterLocationToAtCFAPlusOffset(regnums->pc_regnum, ptr_size,
2753-
false);
27542748

2749+
std::optional<addr_t> pc_after_prologue = [&]() -> std::optional<addr_t> {
2750+
// In the prologue, use the async_reg as is, it has not been clobbered.
2751+
if (in_prologue)
2752+
return TrySkipVirtualParentProlog(GetAsyncContext(regctx), *process_sp,
2753+
indirect_context);
2754+
2755+
// Both ABIs (x86_64 and aarch64) guarantee the async reg is saved at:
2756+
// *(fp - 8).
2757+
Status error;
2758+
addr_t async_reg_entry_value = LLDB_INVALID_ADDRESS;
2759+
process_sp->ReadMemory(fp - ptr_size, &async_reg_entry_value, ptr_size,
2760+
error);
2761+
if (error.Fail())
2762+
return {};
2763+
return TrySkipVirtualParentProlog(async_reg_entry_value, *process_sp,
2764+
indirect_context);
2765+
}();
2766+
2767+
if (pc_after_prologue)
2768+
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,
2769+
false);
2770+
else
2771+
row->SetRegisterLocationToAtCFAPlusOffset(regnums->pc_regnum, ptr_size,
2772+
false);
27552773
row->SetUnspecifiedRegistersAreUndefined(true);
27562774

27572775
UnwindPlanSP plan = std::make_shared<UnwindPlan>(lldb::eRegisterKindDWARF);
@@ -2763,11 +2781,9 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
27632781
return plan;
27642782
}
27652783

2766-
// Creates an UnwindPlan for following the AsyncContext chain
2767-
// up the stack, from a current AsyncContext frame.
2768-
static UnwindPlanSP
2769-
GetFollowAsyncContextUnwindPlan(RegisterContext *regctx, ArchSpec &arch,
2770-
bool &behaves_like_zeroth_frame) {
2784+
UnwindPlanSP SwiftLanguageRuntime::GetFollowAsyncContextUnwindPlan(
2785+
ProcessSP process_sp, RegisterContext *regctx, ArchSpec &arch,
2786+
bool &behaves_like_zeroth_frame) {
27712787
LLDB_SCOPED_TIMER();
27722788

27732789
UnwindPlan::RowSP row(new UnwindPlan::Row);
@@ -2779,13 +2795,15 @@ GetFollowAsyncContextUnwindPlan(RegisterContext *regctx, ArchSpec &arch,
27792795
if (!regnums)
27802796
return UnwindPlanSP();
27812797

2798+
const bool is_indirect =
2799+
regctx->ReadRegisterAsUnsigned(regnums->dummy_regnum, (uint64_t)-1ll) ==
2800+
(uint64_t)-1ll;
27822801
// In the general case, the async register setup by the frame above us
27832802
// should be dereferenced twice to get our context, except when the frame
27842803
// above us is an async frame on the OS stack that takes its context directly
27852804
// (see discussion in GetRuntimeUnwindPlan()). The availability of
27862805
// dummy_regnum is used as a marker for this situation.
2787-
if (regctx->ReadRegisterAsUnsigned(regnums->dummy_regnum, (uint64_t)-1ll) !=
2788-
(uint64_t)-1ll) {
2806+
if (!is_indirect) {
27892807
row->GetCFAValue().SetIsRegisterDereferenced(regnums->async_ctx_regnum);
27902808
row->SetRegisterLocationToSame(regnums->async_ctx_regnum, false);
27912809
} else {
@@ -2822,8 +2840,21 @@ GetFollowAsyncContextUnwindPlan(RegisterContext *regctx, ArchSpec &arch,
28222840
regnums->async_ctx_regnum, expression, expr_size - 1, false);
28232841
}
28242842

2825-
row->SetRegisterLocationToAtCFAPlusOffset(regnums->pc_regnum, ptr_size,
2826-
false);
2843+
// Suppose this is unwinding frame #2 of a call stack. The value given for
2844+
// the async register has two possible values, depending on what frame #1
2845+
// expects:
2846+
// 1. The CFA of frame #1, direct ABI, dereferencing it once produces CFA of
2847+
// Frame #2.
2848+
// 2. The CFA of frame #0, indirect ABI, dereferencing it twice produces CFA
2849+
// of Frame #2.
2850+
const unsigned num_indirections = 1 + is_indirect;
2851+
if (std::optional<addr_t> pc_after_prologue = TrySkipVirtualParentProlog(
2852+
GetAsyncContext(regctx), *process_sp, num_indirections))
2853+
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,
2854+
false);
2855+
else
2856+
row->SetRegisterLocationToAtCFAPlusOffset(regnums->pc_regnum, ptr_size,
2857+
false);
28272858

28282859
row->SetUnspecifiedRegistersAreUndefined(true);
28292860

@@ -2837,4 +2868,51 @@ GetFollowAsyncContextUnwindPlan(RegisterContext *regctx, ArchSpec &arch,
28372868
return plan;
28382869
}
28392870

2871+
std::optional<lldb::addr_t> SwiftLanguageRuntime::TrySkipVirtualParentProlog(
2872+
lldb::addr_t async_reg_val, Process &process, unsigned num_indirections) {
2873+
assert(num_indirections <= 2 &&
2874+
"more than two dereferences should not be needed");
2875+
if (async_reg_val == LLDB_INVALID_ADDRESS || async_reg_val == 0)
2876+
return {};
2877+
2878+
const auto ptr_size = process.GetAddressByteSize();
2879+
Status error;
2880+
2881+
// Compute the CFA of this frame.
2882+
addr_t cfa = async_reg_val;
2883+
for (; num_indirections != 0; --num_indirections) {
2884+
process.ReadMemory(cfa, &cfa, ptr_size, error);
2885+
if (error.Fail())
2886+
return {};
2887+
}
2888+
2889+
// The last funclet will have a zero CFA, we don't want to read that.
2890+
if (cfa == 0)
2891+
return {};
2892+
2893+
// Get the PC of the parent frame, i.e. the continuation pointer, which is
2894+
// the second field of the CFA.
2895+
addr_t pc_location = cfa + ptr_size;
2896+
addr_t pc_value = LLDB_INVALID_ADDRESS;
2897+
process.ReadMemory(pc_location, &pc_value, ptr_size, error);
2898+
if (error.Fail())
2899+
return {};
2900+
2901+
Address pc;
2902+
Target &target = process.GetTarget();
2903+
pc.SetLoadAddress(pc_value, &target);
2904+
if (!pc.IsValid())
2905+
return {};
2906+
2907+
SymbolContext sc;
2908+
if (!pc.CalculateSymbolContext(&sc,
2909+
eSymbolContextFunction | eSymbolContextSymbol))
2910+
return {};
2911+
if (!sc.symbol && !sc.function)
2912+
return {};
2913+
2914+
auto prologue_size = sc.symbol ? sc.symbol->GetPrologueByteSize()
2915+
: sc.function->GetPrologueByteSize();
2916+
return pc_value + prologue_size;
2917+
}
28402918
} // namespace lldb_private

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,21 @@ class SwiftLanguageRuntime : public LanguageRuntime {
482482
bool GetTargetOfPartialApply(SymbolContext &curr_sc, ConstString &apply_name,
483483
SymbolContext &sc);
484484
AppleObjCRuntimeV2 *GetObjCRuntime();
485+
486+
private:
487+
/// Creates an UnwindPlan for following the AsyncContext chain up the stack,
488+
/// from a current AsyncContext frame.
489+
lldb::UnwindPlanSP
490+
GetFollowAsyncContextUnwindPlan(lldb::ProcessSP process_sp,
491+
RegisterContext *regctx, ArchSpec &arch,
492+
bool &behaves_like_zeroth_frame);
493+
494+
/// Given the async register of a funclet, extract its continuation pointer,
495+
/// compute the prologue size of the continuation function, and return the
496+
/// address of the first non-prologue instruction.
497+
std::optional<lldb::addr_t>
498+
TrySkipVirtualParentProlog(lldb::addr_t async_reg_val, Process &process,
499+
unsigned num_indirections);
485500
};
486501

487502
} // namespace lldb_private

lldb/source/Symbol/UnwindPlan.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ operator==(const UnwindPlan::Row::RegisterLocation &rhs) const {
4646
return !memcmp(m_location.expr.opcodes, rhs.m_location.expr.opcodes,
4747
m_location.expr.length);
4848
break;
49+
case isConstant:
50+
return m_location.constant_value == rhs.m_location.constant_value;
4951
}
5052
}
5153
return false;
@@ -153,6 +155,9 @@ void UnwindPlan::Row::RegisterLocation::Dump(Stream &s,
153155
if (m_type == atDWARFExpression)
154156
s.PutChar(']');
155157
} break;
158+
case isConstant:
159+
s.Printf("=0x%" PRIx64, m_location.constant_value);
160+
break;
156161
}
157162
}
158163

@@ -362,6 +367,17 @@ bool UnwindPlan::Row::SetRegisterLocationToIsDWARFExpression(
362367
return true;
363368
}
364369

370+
bool UnwindPlan::Row::SetRegisterLocationToIsConstant(uint32_t reg_num,
371+
uint64_t constant,
372+
bool can_replace) {
373+
if (!can_replace &&
374+
m_register_locations.find(reg_num) != m_register_locations.end())
375+
return false;
376+
RegisterLocation reg_loc;
377+
reg_loc.SetIsConstant(constant);
378+
m_register_locations[reg_num] = reg_loc;
379+
return true;
380+
}
365381

366382
bool UnwindPlan::Row::operator==(const UnwindPlan::Row &rhs) const {
367383
return m_offset == rhs.m_offset && m_cfa_value == rhs.m_cfa_value &&

lldb/source/Target/RegisterContextUnwind.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,15 @@ RegisterContextUnwind::SavedLocationForRegister(
16921692
return UnwindLLDB::RegisterSearchResult::eRegisterNotFound;
16931693
}
16941694

1695+
if (unwindplan_regloc.IsConstant()) {
1696+
regloc.type = UnwindLLDB::RegisterLocation::eRegisterValueInferred;
1697+
regloc.location.inferred_value = unwindplan_regloc.GetConstant();
1698+
m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc;
1699+
UnwindLogMsg("supplying caller's register %s (%d) via constant value",
1700+
regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB));
1701+
return UnwindLLDB::RegisterSearchResult::eRegisterFound;
1702+
}
1703+
16951704
UnwindLogMsg("no save location for %s (%d) in this stack frame",
16961705
regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB));
16971706

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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
class TestCase(lldbtest.TestBase):
8+
9+
mydir = lldbtest.TestBase.compute_mydir(__file__)
10+
11+
# Check that the CFA chain is correctly built
12+
def check_cfas(self, async_frames, process):
13+
async_cfas = list(map(lambda frame: frame.GetCFA(), async_frames))
14+
expected_cfas = [async_cfas[0]]
15+
# The CFA chain ends in nullptr.
16+
while expected_cfas[-1] != 0:
17+
error = lldb.SBError()
18+
expected_cfas.append(
19+
process.ReadPointerFromMemory(expected_cfas[-1], error)
20+
)
21+
self.assertSuccess(error, "Managed to read cfa memory")
22+
23+
self.assertEqual(async_cfas, expected_cfas[:-1])
24+
25+
def check_pcs(self, async_frames, process, target):
26+
for idx, frame in enumerate(async_frames[:-1]):
27+
# Read the continuation pointer from the second field of the CFA.
28+
error = lldb.SBError()
29+
continuation_ptr = process.ReadPointerFromMemory(
30+
frame.GetCFA() + target.addr_size, error
31+
)
32+
self.assertSuccess(error, "Managed to read context memory")
33+
34+
# The PC of the previous frame should be the continuation pointer
35+
# with the funclet's prologue skipped.
36+
parent_frame = async_frames[idx + 1]
37+
prologue_to_skip = parent_frame.GetFunction().GetPrologueByteSize()
38+
self.assertEqual(continuation_ptr + prologue_to_skip, parent_frame.GetPC())
39+
40+
41+
def check_variables(self, async_frames, expected_values):
42+
for (frame, expected_value) in zip(async_frames, expected_values):
43+
myvar = frame.FindVariable("myvar")
44+
lldbutil.check_variable(self, myvar, False, value=expected_value)
45+
46+
@swiftTest
47+
@skipIf(oslist=["windows", "linux"])
48+
def test(self):
49+
"""Test `frame variable` in async functions"""
50+
self.build()
51+
52+
source_file = lldb.SBFileSpec("main.swift")
53+
target, process, _, _ = lldbutil.run_to_source_breakpoint(
54+
self, "breakpoint1", source_file
55+
)
56+
57+
async_frames = process.GetSelectedThread().frames
58+
self.check_cfas(async_frames, process)
59+
self.check_pcs(async_frames, process, target)
60+
self.check_variables(async_frames, ["222", "333", "444", "555"])
61+
62+
target.DeleteAllBreakpoints()
63+
target.BreakpointCreateBySourceRegex("breakpoint2", source_file)
64+
process.Continue()
65+
# First frame is from a synchronous function
66+
frames = process.GetSelectedThread().frames
67+
async_frames = frames[1:]
68+
self.check_cfas(async_frames, process)
69+
self.check_pcs(async_frames, process, target)
70+
self.check_variables(async_frames, ["111", "222", "333", "444", "555"])
71+
72+
target.DeleteAllBreakpoints()
73+
target.BreakpointCreateBySourceRegex("breakpoint3", source_file)
74+
process.Continue()
75+
async_frames = process.GetSelectedThread().frames
76+
self.check_cfas(async_frames, process)
77+
self.check_pcs(async_frames, process, target)
78+
self.check_variables(async_frames, ["222", "333", "444", "555"])

0 commit comments

Comments
 (0)