-
Notifications
You must be signed in to change notification settings - Fork 339
[lldb][swift] Use frame formation as a guide for async unwinding #10637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: swift/release/6.2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2474,14 +2474,21 @@ static llvm::Expected<addr_t> ReadPtrFromAddr(Process &process, addr_t addr, | |
/// simplified version of the methods in RegisterContextUnwind, since plumbing | ||
/// access to those here would be challenging. | ||
static llvm::Expected<addr_t> GetCFA(Process &process, RegisterContext ®ctx, | ||
RegisterKind regkind, | ||
UnwindPlan::Row::FAValue cfa_loc) { | ||
addr_t pc_offset, | ||
UnwindPlan &unwind_plan) { | ||
UnwindPlan::RowSP row = unwind_plan.GetRowForFunctionOffset(pc_offset); | ||
if (!row) | ||
return llvm::createStringError( | ||
"SwiftLanguageRuntime: Invalid Unwind Row when computing CFA"); | ||
|
||
UnwindPlan::Row::FAValue cfa_loc = row->GetCFAValue(); | ||
|
||
using ValueType = UnwindPlan::Row::FAValue::ValueType; | ||
switch (cfa_loc.GetValueType()) { | ||
case ValueType::isRegisterPlusOffset: { | ||
unsigned regnum = cfa_loc.GetRegisterNumber(); | ||
if (llvm::Expected<addr_t> regvalue = | ||
ReadRegisterAsAddress(regctx, regkind, regnum)) | ||
if (llvm::Expected<addr_t> regvalue = ReadRegisterAsAddress( | ||
regctx, unwind_plan.GetRegisterKind(), regnum)) | ||
return *regvalue + cfa_loc.GetOffset(); | ||
else | ||
return regvalue; | ||
|
@@ -2513,13 +2520,8 @@ static UnwindPlanSP GetUnwindPlanForAsyncRegister(FuncUnwinders &unwinders, | |
return unwinders.GetUnwindPlanAtNonCallSite(target, thread); | ||
} | ||
|
||
/// Attempts to use UnwindPlans that inspect assembly to recover the entry value | ||
/// of the async context register. This is a simplified version of the methods | ||
/// in RegisterContextUnwind, since plumbing access to those here would be | ||
/// challenging. | ||
static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind( | ||
SymbolContext &sc, Process &process, Address pc, Address func_start_addr, | ||
RegisterContext ®ctx, AsyncUnwindRegisterNumbers regnums) { | ||
static llvm::Expected<UnwindPlanSP> | ||
GetAsmUnwindPlan(Address pc, SymbolContext &sc, Thread &thread) { | ||
FuncUnwindersSP unwinders = | ||
pc.GetModule()->GetUnwindTable().GetFuncUnwindersContainingAddress(pc, | ||
sc); | ||
|
@@ -2528,77 +2530,137 @@ static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind( | |
"function unwinder at address 0x%8.8" PRIx64, | ||
pc.GetFileAddress()); | ||
|
||
Target &target = process.GetTarget(); | ||
UnwindPlanSP unwind_plan = | ||
GetUnwindPlanForAsyncRegister(*unwinders, target, regctx.GetThread()); | ||
UnwindPlanSP unwind_plan = GetUnwindPlanForAsyncRegister( | ||
*unwinders, thread.GetProcess()->GetTarget(), thread); | ||
if (!unwind_plan) | ||
return llvm::createStringError( | ||
"SwiftLanguageRuntime: Failed to find non call site unwind plan at " | ||
"address 0x%8.8" PRIx64, | ||
pc.GetFileAddress()); | ||
return unwind_plan; | ||
} | ||
|
||
const RegisterKind unwind_regkind = unwind_plan->GetRegisterKind(); | ||
UnwindPlan::RowSP row = unwind_plan->GetRowForFunctionOffset( | ||
pc.GetFileAddress() - func_start_addr.GetFileAddress()); | ||
|
||
// To request info about a register from the unwind plan, the register must | ||
// be in the same domain as the unwind plan's registers. | ||
uint32_t async_reg_unwind_regdomain; | ||
static llvm::Expected<uint32_t> GetFpRegisterNumber(UnwindPlan &unwind_plan, | ||
RegisterContext ®ctx) { | ||
uint32_t fp_unwind_regdomain; | ||
if (!regctx.ConvertBetweenRegisterKinds( | ||
regnums.GetRegisterKind(), regnums.async_ctx_regnum, unwind_regkind, | ||
async_reg_unwind_regdomain)) { | ||
lldb::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FP, | ||
unwind_plan.GetRegisterKind(), fp_unwind_regdomain)) { | ||
// This should never happen. | ||
// If asserts are disabled, return an error to avoid creating an invalid | ||
// unwind plan. | ||
auto error_msg = "SwiftLanguageRuntime: Failed to convert register domains"; | ||
const auto *error_msg = | ||
"SwiftLanguageRuntime: Failed to convert register domains"; | ||
llvm_unreachable(error_msg); | ||
return llvm::createStringError(error_msg); | ||
} | ||
return fp_unwind_regdomain; | ||
} | ||
|
||
// If the plan doesn't have information about the async register, we can use | ||
// its current value, as this is a callee saved register. | ||
UnwindPlan::Row::AbstractRegisterLocation regloc; | ||
if (!row->GetRegisterInfo(async_reg_unwind_regdomain, regloc)) | ||
return ReadRegisterAsAddress(regctx, regnums.GetRegisterKind(), | ||
regnums.async_ctx_regnum); | ||
struct FrameSetupInfo { | ||
addr_t frame_setup_func_offset; | ||
int fp_cfa_offset; | ||
}; | ||
|
||
// Handle the few abstract locations we are likely to encounter. | ||
using RestoreType = UnwindPlan::Row::AbstractRegisterLocation::RestoreType; | ||
RestoreType loctype = regloc.GetLocationType(); | ||
switch (loctype) { | ||
case RestoreType::same: | ||
/// Detect the point in the function where the prologue created a frame, | ||
/// returning: | ||
/// 1. The offset of the first instruction after that point. For a frameless | ||
/// function, this offset is large positive number, so that PC can still be | ||
/// compared against it. | ||
/// 2. The CFA offset at which FP is stored, meaningless in the frameless case. | ||
static llvm::Expected<FrameSetupInfo> | ||
GetFrameSetupInfo(UnwindPlan &unwind_plan, RegisterContext ®ctx) { | ||
using RowSP = UnwindPlan::RowSP; | ||
using AbstractRegisterLocation = UnwindPlan::Row::AbstractRegisterLocation; | ||
|
||
llvm::Expected<uint32_t> fp_unwind_regdomain = | ||
GetFpRegisterNumber(unwind_plan, regctx); | ||
if (!fp_unwind_regdomain) | ||
return fp_unwind_regdomain.takeError(); | ||
|
||
// Look at the first few (4) rows of the plan and store FP's location. | ||
const int upper_bound = std::min(4, unwind_plan.GetRowCount()); | ||
llvm::SmallVector<AbstractRegisterLocation, 4> fp_locs; | ||
for (int row_idx = 0; row_idx < upper_bound; row_idx++) { | ||
RowSP row = unwind_plan.GetRowAtIndex(row_idx); | ||
AbstractRegisterLocation regloc; | ||
if (!row->GetRegisterInfo(*fp_unwind_regdomain, regloc)) | ||
regloc.SetSame(); | ||
fp_locs.push_back(regloc); | ||
} | ||
|
||
// Find first location where FP is stored *at* some CFA offset. | ||
auto *it = llvm::find_if( | ||
fp_locs, [](auto fp_loc) { return fp_loc.IsAtCFAPlusOffset(); }); | ||
|
||
// This is a frameless function, use large positive offset so that a PC can | ||
// still be compared against it. | ||
if (it == fp_locs.end()) | ||
return FrameSetupInfo{std::numeric_limits<addr_t>::max(), 0}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be better to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes me a bit anxious, as this type could very well be int64_t (even though it isn't). So I wanted to be very explicit that this is a large positive number. |
||
|
||
// This is an async function with a frame. The prologue roughly follows this | ||
// sequence of instructions: | ||
// adjust sp | ||
// save lr @ CFA-8 | ||
// save fp @ CFA-16 << `it` points to this row. | ||
// save async_reg @ CFA-24 << subsequent row. | ||
// Use subsequent row, if available. | ||
// Pointer auth may introduce more instructions, but they don't affect the | ||
// unwinder rows / store to the stack. | ||
int row_idx = fp_locs.end() - it; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if you're showing the instruction sequence or the rows here. A typical AArch64 prologue can be
If this is arm64e (ARMv8.3 ptrauth) codegen, there will be a Then from an unwind rule perspective we'll see
So we'll identify row 2 (the third row) as the point where fp has been saved to stack at CFA+offset, in nearly all prologues. I'm mostly not clear what the numbering in this comment is representing - instructions or unwind rows. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Neither! It was just meant to be: "this is roughly the order in which things happen". I'll update the comment to make this clear and remove numbers altogether |
||
int next_row_idx = row_idx + 1; | ||
|
||
// If subsequent row is invalid, approximate through current row. | ||
if (next_row_idx == unwind_plan.GetRowCount() || | ||
next_row_idx == upper_bound || | ||
!fp_locs[next_row_idx].IsAtCFAPlusOffset()) { | ||
LLDB_LOG(GetLog(LLDBLog::Unwind), "SwiftLanguageRuntime:: UnwindPlan did " | ||
"not contain a valid row after FP setup"); | ||
UnwindPlan::RowSP row = unwind_plan.GetRowAtIndex(row_idx); | ||
return FrameSetupInfo{row->GetOffset(), fp_locs[row_idx].GetOffset()}; | ||
} | ||
|
||
UnwindPlan::RowSP subsequent_row = unwind_plan.GetRowAtIndex(next_row_idx); | ||
return FrameSetupInfo{subsequent_row->GetOffset(), | ||
fp_locs[next_row_idx].GetOffset()}; | ||
} | ||
|
||
/// Reads the async register from its ABI-guaranteed stack-slot, or directly | ||
/// from the register depending on where pc is relative to the start of the | ||
/// function. | ||
static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind( | ||
SymbolContext &sc, Process &process, Address pc, Address func_start_addr, | ||
RegisterContext ®ctx, AsyncUnwindRegisterNumbers regnums) { | ||
llvm::Expected<UnwindPlanSP> unwind_plan = | ||
GetAsmUnwindPlan(pc, sc, regctx.GetThread()); | ||
if (!unwind_plan) | ||
return unwind_plan.takeError(); | ||
llvm::Expected<FrameSetupInfo> frame_setup = | ||
GetFrameSetupInfo(**unwind_plan, regctx); | ||
if (!frame_setup) | ||
return frame_setup.takeError(); | ||
|
||
// Is PC before the frame formation? If so, use async register directly. | ||
// This handles frameless functions, as frame_setup_func_offset is INT_MAX. | ||
addr_t pc_offset = pc.GetFileAddress() - func_start_addr.GetFileAddress(); | ||
if (pc_offset < frame_setup->frame_setup_func_offset) | ||
return ReadRegisterAsAddress(regctx, regnums.GetRegisterKind(), | ||
regnums.async_ctx_regnum); | ||
case RestoreType::inOtherRegister: { | ||
unsigned regnum = regloc.GetRegisterNumber(); | ||
return ReadRegisterAsAddress(regctx, unwind_regkind, regnum); | ||
} | ||
case RestoreType::atCFAPlusOffset: { | ||
llvm::Expected<addr_t> cfa = | ||
GetCFA(process, regctx, unwind_regkind, row->GetCFAValue()); | ||
if (!cfa) | ||
return cfa.takeError(); | ||
return ReadPtrFromAddr(process, *cfa, regloc.GetOffset()); | ||
} | ||
case RestoreType::isCFAPlusOffset: { | ||
if (llvm::Expected<addr_t> cfa = | ||
GetCFA(process, regctx, unwind_regkind, row->GetCFAValue())) | ||
return *cfa + regloc.GetOffset(); | ||
else | ||
return cfa; | ||
} | ||
case RestoreType::isConstant: | ||
return regloc.GetConstant(); | ||
case RestoreType::unspecified: | ||
case RestoreType::undefined: | ||
case RestoreType::atAFAPlusOffset: | ||
case RestoreType::isAFAPlusOffset: | ||
case RestoreType::isDWARFExpression: | ||
case RestoreType::atDWARFExpression: | ||
break; | ||
} | ||
return llvm::createStringError( | ||
"SwiftLanguageRuntime: Unsupported register location type = %d", loctype); | ||
|
||
// A frame was formed, and FP was saved at a CFA offset. Compute CFA and read | ||
// the location beneath where FP was saved. | ||
llvm::Expected<addr_t> cfa = | ||
GetCFA(process, regctx, pc_offset, **unwind_plan); | ||
if (!cfa) | ||
return cfa.takeError(); | ||
|
||
addr_t async_reg_addr = process.FixDataAddress( | ||
*cfa + frame_setup->fp_cfa_offset - process.GetAddressByteSize()); | ||
Status error; | ||
addr_t async_reg = process.ReadPointerFromMemory(async_reg_addr, error); | ||
if (error.Fail()) | ||
return error.ToError(); | ||
return async_reg; | ||
} | ||
|
||
/// Returns true if the async register should be dereferenced once to obtain the | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasonmolenda this is the most important function of this patch.