Skip to content

Commit d1ac271

Browse files
[lldb][swift] Use frame formation as a guide for async unwinding
This patch changes how the swift language runtime obtains the async context of the currently executing function. The old strategy consists of querying unwind plans for what they believe the location of the async register is. This proved problematic in cases where the register is not saved directly, e.g. in arm64e, where the register is signed and moved to a scratch register prior to being saved. The new strategy relies on ABI guarantees: the async context is _always_ saved at the stack slot immediately beneath where FP is saved. As long as a frame has been formed, we can always access the async context by reading memory @ [CFA-24] (FP is saved in [CFA-16]). Before the frame is formed, the async register can be accessed directly. In other words, the main problem this patch has to solve is detecting the point at which a frame has been fully formed. This is accomplished by scanning the first few Rows of the assembly unwind plan, until a Row is found where FP's location is "AtCFAPlusOffset". The Row immediately after that is used. This is reliable because async prologues follow this pattern in all tested architectures (x86-64, arm64, arm64e): // 0. adjust sp // 1. save lr @ CFA-8 // 2. save fp @ CFA-16 // 3. save async_reg @ CFA-24 << "row immediately after" For frameless functions, we can always use the async register directly. While this patch adds no tests, all of the existing stepping tests and tests inspecting variables exercise the new behavior.
1 parent 0a257a1 commit d1ac271

File tree

1 file changed

+127
-65
lines changed

1 file changed

+127
-65
lines changed

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

Lines changed: 127 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,14 +2474,21 @@ static llvm::Expected<addr_t> ReadPtrFromAddr(Process &process, addr_t addr,
24742474
/// simplified version of the methods in RegisterContextUnwind, since plumbing
24752475
/// access to those here would be challenging.
24762476
static llvm::Expected<addr_t> GetCFA(Process &process, RegisterContext &regctx,
2477-
RegisterKind regkind,
2478-
UnwindPlan::Row::FAValue cfa_loc) {
2477+
addr_t pc_offset,
2478+
UnwindPlan &unwind_plan) {
2479+
UnwindPlan::RowSP row = unwind_plan.GetRowForFunctionOffset(pc_offset);
2480+
if (!row)
2481+
return llvm::createStringError(
2482+
"SwiftLanguageRuntime: Invalid Unwind Row when computing CFA");
2483+
2484+
UnwindPlan::Row::FAValue cfa_loc = row->GetCFAValue();
2485+
24792486
using ValueType = UnwindPlan::Row::FAValue::ValueType;
24802487
switch (cfa_loc.GetValueType()) {
24812488
case ValueType::isRegisterPlusOffset: {
24822489
unsigned regnum = cfa_loc.GetRegisterNumber();
2483-
if (llvm::Expected<addr_t> regvalue =
2484-
ReadRegisterAsAddress(regctx, regkind, regnum))
2490+
if (llvm::Expected<addr_t> regvalue = ReadRegisterAsAddress(
2491+
regctx, unwind_plan.GetRegisterKind(), regnum))
24852492
return *regvalue + cfa_loc.GetOffset();
24862493
else
24872494
return regvalue;
@@ -2513,13 +2520,8 @@ static UnwindPlanSP GetUnwindPlanForAsyncRegister(FuncUnwinders &unwinders,
25132520
return unwinders.GetUnwindPlanAtNonCallSite(target, thread);
25142521
}
25152522

2516-
/// Attempts to use UnwindPlans that inspect assembly to recover the entry value
2517-
/// of the async context register. This is a simplified version of the methods
2518-
/// in RegisterContextUnwind, since plumbing access to those here would be
2519-
/// challenging.
2520-
static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind(
2521-
SymbolContext &sc, Process &process, Address pc, Address func_start_addr,
2522-
RegisterContext &regctx, AsyncUnwindRegisterNumbers regnums) {
2523+
static llvm::Expected<UnwindPlanSP>
2524+
GetAsmUnwindPlan(Address pc, SymbolContext &sc, Thread &thread) {
25232525
FuncUnwindersSP unwinders =
25242526
pc.GetModule()->GetUnwindTable().GetFuncUnwindersContainingAddress(pc,
25252527
sc);
@@ -2528,77 +2530,137 @@ static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind(
25282530
"function unwinder at address 0x%8.8" PRIx64,
25292531
pc.GetFileAddress());
25302532

2531-
Target &target = process.GetTarget();
2532-
UnwindPlanSP unwind_plan =
2533-
GetUnwindPlanForAsyncRegister(*unwinders, target, regctx.GetThread());
2533+
UnwindPlanSP unwind_plan = GetUnwindPlanForAsyncRegister(
2534+
*unwinders, thread.GetProcess()->GetTarget(), thread);
25342535
if (!unwind_plan)
25352536
return llvm::createStringError(
25362537
"SwiftLanguageRuntime: Failed to find non call site unwind plan at "
25372538
"address 0x%8.8" PRIx64,
25382539
pc.GetFileAddress());
2540+
return unwind_plan;
2541+
}
25392542

2540-
const RegisterKind unwind_regkind = unwind_plan->GetRegisterKind();
2541-
UnwindPlan::RowSP row = unwind_plan->GetRowForFunctionOffset(
2542-
pc.GetFileAddress() - func_start_addr.GetFileAddress());
2543-
2544-
// To request info about a register from the unwind plan, the register must
2545-
// be in the same domain as the unwind plan's registers.
2546-
uint32_t async_reg_unwind_regdomain;
2543+
static llvm::Expected<uint32_t> GetFpRegisterNumber(UnwindPlan &unwind_plan,
2544+
RegisterContext &regctx) {
2545+
uint32_t fp_unwind_regdomain;
25472546
if (!regctx.ConvertBetweenRegisterKinds(
2548-
regnums.GetRegisterKind(), regnums.async_ctx_regnum, unwind_regkind,
2549-
async_reg_unwind_regdomain)) {
2547+
lldb::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FP,
2548+
unwind_plan.GetRegisterKind(), fp_unwind_regdomain)) {
25502549
// This should never happen.
25512550
// If asserts are disabled, return an error to avoid creating an invalid
25522551
// unwind plan.
2553-
auto error_msg = "SwiftLanguageRuntime: Failed to convert register domains";
2552+
const auto *error_msg =
2553+
"SwiftLanguageRuntime: Failed to convert register domains";
25542554
llvm_unreachable(error_msg);
25552555
return llvm::createStringError(error_msg);
25562556
}
2557+
return fp_unwind_regdomain;
2558+
}
25572559

2558-
// If the plan doesn't have information about the async register, we can use
2559-
// its current value, as this is a callee saved register.
2560-
UnwindPlan::Row::AbstractRegisterLocation regloc;
2561-
if (!row->GetRegisterInfo(async_reg_unwind_regdomain, regloc))
2562-
return ReadRegisterAsAddress(regctx, regnums.GetRegisterKind(),
2563-
regnums.async_ctx_regnum);
2560+
struct FrameSetupInfo {
2561+
addr_t frame_setup_func_offset;
2562+
int fp_cfa_offset;
2563+
};
25642564

2565-
// Handle the few abstract locations we are likely to encounter.
2566-
using RestoreType = UnwindPlan::Row::AbstractRegisterLocation::RestoreType;
2567-
RestoreType loctype = regloc.GetLocationType();
2568-
switch (loctype) {
2569-
case RestoreType::same:
2565+
/// Detect the point in the function where the prologue created a frame,
2566+
/// returning:
2567+
/// 1. The offset of the first instruction after that point. For a frameless
2568+
/// function, this offset is large positive number, so that PC can still be
2569+
/// compared against it.
2570+
/// 2. The CFA offset at which FP is stored, meaningless in the frameless case.
2571+
static llvm::Expected<FrameSetupInfo>
2572+
GetFrameSetupInfo(UnwindPlan &unwind_plan, RegisterContext &regctx) {
2573+
using RowSP = UnwindPlan::RowSP;
2574+
using AbstractRegisterLocation = UnwindPlan::Row::AbstractRegisterLocation;
2575+
2576+
llvm::Expected<uint32_t> fp_unwind_regdomain =
2577+
GetFpRegisterNumber(unwind_plan, regctx);
2578+
if (!fp_unwind_regdomain)
2579+
return fp_unwind_regdomain.takeError();
2580+
2581+
// Look at the first few (4) rows of the plan and store FP's location.
2582+
const int upper_bound = std::min(4, unwind_plan.GetRowCount());
2583+
llvm::SmallVector<AbstractRegisterLocation, 4> fp_locs;
2584+
for (int row_idx = 0; row_idx < upper_bound; row_idx++) {
2585+
RowSP row = unwind_plan.GetRowAtIndex(row_idx);
2586+
AbstractRegisterLocation regloc;
2587+
if (!row->GetRegisterInfo(*fp_unwind_regdomain, regloc))
2588+
regloc.SetSame();
2589+
fp_locs.push_back(regloc);
2590+
}
2591+
2592+
// Find first location where FP is stored *at* some CFA offset.
2593+
auto *it = llvm::find_if(
2594+
fp_locs, [](auto fp_loc) { return fp_loc.IsAtCFAPlusOffset(); });
2595+
2596+
// This is a frameless function, use large positive offset so that a PC can
2597+
// still be compared against it.
2598+
if (it == fp_locs.end())
2599+
return FrameSetupInfo{std::numeric_limits<addr_t>::max(), 0};
2600+
2601+
// This is an async function with a frame. The prologue roughly follows this
2602+
// sequence of instructions:
2603+
// adjust sp
2604+
// save lr @ CFA-8
2605+
// save fp @ CFA-16 << `it` points to this row.
2606+
// save async_reg @ CFA-24 << subsequent row.
2607+
// Use subsequent row, if available.
2608+
// Pointer auth may introduce more instructions, but they don't affect the
2609+
// unwinder rows / store to the stack.
2610+
int row_idx = fp_locs.end() - it;
2611+
int next_row_idx = row_idx + 1;
2612+
2613+
// If subsequent row is invalid, approximate through current row.
2614+
if (next_row_idx == unwind_plan.GetRowCount() ||
2615+
next_row_idx == upper_bound ||
2616+
!fp_locs[next_row_idx].IsAtCFAPlusOffset()) {
2617+
LLDB_LOG(GetLog(LLDBLog::Unwind), "SwiftLanguageRuntime:: UnwindPlan did "
2618+
"not contain a valid row after FP setup");
2619+
UnwindPlan::RowSP row = unwind_plan.GetRowAtIndex(row_idx);
2620+
return FrameSetupInfo{row->GetOffset(), fp_locs[row_idx].GetOffset()};
2621+
}
2622+
2623+
UnwindPlan::RowSP subsequent_row = unwind_plan.GetRowAtIndex(next_row_idx);
2624+
return FrameSetupInfo{subsequent_row->GetOffset(),
2625+
fp_locs[next_row_idx].GetOffset()};
2626+
}
2627+
2628+
/// Reads the async register from its ABI-guaranteed stack-slot, or directly
2629+
/// from the register depending on where pc is relative to the start of the
2630+
/// function.
2631+
static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind(
2632+
SymbolContext &sc, Process &process, Address pc, Address func_start_addr,
2633+
RegisterContext &regctx, AsyncUnwindRegisterNumbers regnums) {
2634+
llvm::Expected<UnwindPlanSP> unwind_plan =
2635+
GetAsmUnwindPlan(pc, sc, regctx.GetThread());
2636+
if (!unwind_plan)
2637+
return unwind_plan.takeError();
2638+
llvm::Expected<FrameSetupInfo> frame_setup =
2639+
GetFrameSetupInfo(**unwind_plan, regctx);
2640+
if (!frame_setup)
2641+
return frame_setup.takeError();
2642+
2643+
// Is PC before the frame formation? If so, use async register directly.
2644+
// This handles frameless functions, as frame_setup_func_offset is INT_MAX.
2645+
addr_t pc_offset = pc.GetFileAddress() - func_start_addr.GetFileAddress();
2646+
if (pc_offset < frame_setup->frame_setup_func_offset)
25702647
return ReadRegisterAsAddress(regctx, regnums.GetRegisterKind(),
25712648
regnums.async_ctx_regnum);
2572-
case RestoreType::inOtherRegister: {
2573-
unsigned regnum = regloc.GetRegisterNumber();
2574-
return ReadRegisterAsAddress(regctx, unwind_regkind, regnum);
2575-
}
2576-
case RestoreType::atCFAPlusOffset: {
2577-
llvm::Expected<addr_t> cfa =
2578-
GetCFA(process, regctx, unwind_regkind, row->GetCFAValue());
2579-
if (!cfa)
2580-
return cfa.takeError();
2581-
return ReadPtrFromAddr(process, *cfa, regloc.GetOffset());
2582-
}
2583-
case RestoreType::isCFAPlusOffset: {
2584-
if (llvm::Expected<addr_t> cfa =
2585-
GetCFA(process, regctx, unwind_regkind, row->GetCFAValue()))
2586-
return *cfa + regloc.GetOffset();
2587-
else
2588-
return cfa;
2589-
}
2590-
case RestoreType::isConstant:
2591-
return regloc.GetConstant();
2592-
case RestoreType::unspecified:
2593-
case RestoreType::undefined:
2594-
case RestoreType::atAFAPlusOffset:
2595-
case RestoreType::isAFAPlusOffset:
2596-
case RestoreType::isDWARFExpression:
2597-
case RestoreType::atDWARFExpression:
2598-
break;
2599-
}
2600-
return llvm::createStringError(
2601-
"SwiftLanguageRuntime: Unsupported register location type = %d", loctype);
2649+
2650+
// A frame was formed, and FP was saved at a CFA offset. Compute CFA and read
2651+
// the location beneath where FP was saved.
2652+
llvm::Expected<addr_t> cfa =
2653+
GetCFA(process, regctx, pc_offset, **unwind_plan);
2654+
if (!cfa)
2655+
return cfa.takeError();
2656+
2657+
addr_t async_reg_addr = process.FixDataAddress(
2658+
*cfa + frame_setup->fp_cfa_offset - process.GetAddressByteSize());
2659+
Status error;
2660+
addr_t async_reg = process.ReadPointerFromMemory(async_reg_addr, error);
2661+
if (error.Fail())
2662+
return error.ToError();
2663+
return async_reg;
26022664
}
26032665

26042666
/// Returns true if the async register should be dereferenced once to obtain the

0 commit comments

Comments
 (0)