Skip to content

Commit 5372a6f

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. (cherry picked from commit d1ac271)
1 parent b432b54 commit 5372a6f

File tree

1 file changed

+126
-65
lines changed

1 file changed

+126
-65
lines changed

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

Lines changed: 126 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,14 +2480,21 @@ static llvm::Expected<addr_t> ReadPtrFromAddr(Process &process, addr_t addr,
24802480
/// simplified version of the methods in RegisterContextUnwind, since plumbing
24812481
/// access to those here would be challenging.
24822482
static llvm::Expected<addr_t> GetCFA(Process &process, RegisterContext &regctx,
2483-
RegisterKind regkind,
2484-
UnwindPlan::Row::FAValue cfa_loc) {
2483+
addr_t pc_offset,
2484+
UnwindPlan &unwind_plan) {
2485+
auto *row = unwind_plan.GetRowForFunctionOffset(pc_offset);
2486+
if (!row)
2487+
return llvm::createStringError(
2488+
"SwiftLanguageRuntime: Invalid Unwind Row when computing CFA");
2489+
2490+
UnwindPlan::Row::FAValue cfa_loc = row->GetCFAValue();
2491+
24852492
using ValueType = UnwindPlan::Row::FAValue::ValueType;
24862493
switch (cfa_loc.GetValueType()) {
24872494
case ValueType::isRegisterPlusOffset: {
24882495
unsigned regnum = cfa_loc.GetRegisterNumber();
2489-
if (llvm::Expected<addr_t> regvalue =
2490-
ReadRegisterAsAddress(regctx, regkind, regnum))
2496+
if (llvm::Expected<addr_t> regvalue = ReadRegisterAsAddress(
2497+
regctx, unwind_plan.GetRegisterKind(), regnum))
24912498
return *regvalue + cfa_loc.GetOffset();
24922499
else
24932500
return regvalue;
@@ -2519,13 +2526,8 @@ static UnwindPlanSP GetUnwindPlanForAsyncRegister(FuncUnwinders &unwinders,
25192526
return unwinders.GetUnwindPlanAtNonCallSite(target, thread);
25202527
}
25212528

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

2537-
Target &target = process.GetTarget();
2538-
UnwindPlanSP unwind_plan =
2539-
GetUnwindPlanForAsyncRegister(*unwinders, target, regctx.GetThread());
2539+
UnwindPlanSP unwind_plan = GetUnwindPlanForAsyncRegister(
2540+
*unwinders, thread.GetProcess()->GetTarget(), thread);
25402541
if (!unwind_plan)
25412542
return llvm::createStringError(
25422543
"SwiftLanguageRuntime: Failed to find non call site unwind plan at "
25432544
"address 0x%8.8" PRIx64,
25442545
pc.GetFileAddress());
2546+
return unwind_plan;
2547+
}
25452548

2546-
const RegisterKind unwind_regkind = unwind_plan->GetRegisterKind();
2547-
auto *row = unwind_plan->GetRowForFunctionOffset(
2548-
pc.GetFileAddress() - func_start_addr.GetFileAddress());
2549-
2550-
// To request info about a register from the unwind plan, the register must
2551-
// be in the same domain as the unwind plan's registers.
2552-
uint32_t async_reg_unwind_regdomain;
2549+
static llvm::Expected<uint32_t> GetFpRegisterNumber(UnwindPlan &unwind_plan,
2550+
RegisterContext &regctx) {
2551+
uint32_t fp_unwind_regdomain;
25532552
if (!regctx.ConvertBetweenRegisterKinds(
2554-
regnums.GetRegisterKind(), regnums.async_ctx_regnum, unwind_regkind,
2555-
async_reg_unwind_regdomain)) {
2553+
lldb::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FP,
2554+
unwind_plan.GetRegisterKind(), fp_unwind_regdomain)) {
25562555
// This should never happen.
25572556
// If asserts are disabled, return an error to avoid creating an invalid
25582557
// unwind plan.
2559-
auto error_msg = "SwiftLanguageRuntime: Failed to convert register domains";
2558+
const auto *error_msg =
2559+
"SwiftLanguageRuntime: Failed to convert register domains";
25602560
llvm_unreachable(error_msg);
25612561
return llvm::createStringError(error_msg);
25622562
}
2563+
return fp_unwind_regdomain;
2564+
}
25632565

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

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

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

0 commit comments

Comments
 (0)