Skip to content

Commit cb50c86

Browse files
Merge pull request #9386 from felipepiovezan/felipe/unwind_through_unwind
[lldb][swift] Use assembly unwinders to recover async registers
2 parents e9ce48f + 341dad6 commit cb50c86

File tree

6 files changed

+371
-124
lines changed

6 files changed

+371
-124
lines changed

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

Lines changed: 182 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
#include "lldb/Interpreter/CommandObject.h"
3838
#include "lldb/Interpreter/CommandObjectMultiword.h"
3939
#include "lldb/Interpreter/CommandReturnObject.h"
40+
#include "lldb/Symbol/FuncUnwinders.h"
4041
#include "lldb/Symbol/Function.h"
4142
#include "lldb/Symbol/VariableList.h"
4243
#include "lldb/Target/RegisterContext.h"
44+
#include "lldb/Target/UnwindLLDB.h"
4345
#include "lldb/Utility/LLDBLog.h"
4446
#include "lldb/Utility/Log.h"
4547
#include "lldb/Utility/OptionParsing.h"
@@ -2542,6 +2544,8 @@ struct AsyncUnwindRegisterNumbers {
25422544
/// frames below us as they need to react differently. There is no good way to
25432545
/// expose this, so we set another dummy register to communicate this state.
25442546
uint32_t dummy_regnum;
2547+
2548+
RegisterKind GetRegisterKind() const { return lldb::eRegisterKindDWARF; }
25452549
};
25462550
} // namespace
25472551

@@ -2584,37 +2588,148 @@ lldb::addr_t SwiftLanguageRuntime::GetAsyncContext(RegisterContext *regctx) {
25842588
return LLDB_INVALID_ADDRESS;
25852589
}
25862590

2587-
/// Creates an expression accessing *(fp - 8) or **(fp - 8) if
2588-
/// `with_double_deref` is true. This is only valid for x86_64 or aarch64.
2589-
llvm::ArrayRef<uint8_t>
2590-
GetAsyncRegFromFramePointerDWARFExpr(llvm::Triple::ArchType triple,
2591-
bool with_double_deref) {
2592-
assert(triple == llvm::Triple::x86_64 || triple == llvm::Triple::aarch64);
2593-
2594-
// These expressions must have static storage, due to how UnwindPlan::Row
2595-
// works.
2596-
static const uint8_t g_cfa_dwarf_expression_x86_64[] = {
2597-
llvm::dwarf::DW_OP_breg6, // DW_OP_breg6, register 6 == rbp
2598-
0x78, // sleb128 -8 (ptrsize)
2599-
llvm::dwarf::DW_OP_deref,
2600-
llvm::dwarf::DW_OP_deref,
2601-
};
2602-
static const uint8_t g_cfa_dwarf_expression_arm64[] = {
2603-
llvm::dwarf::DW_OP_breg29, // DW_OP_breg29, register 29 == fp
2604-
0x78, // sleb128 -8 (ptrsize)
2605-
llvm::dwarf::DW_OP_deref,
2606-
llvm::dwarf::DW_OP_deref,
2607-
};
2591+
/// Functional wrapper to read a register as an address.
2592+
static llvm::Expected<addr_t> ReadRegisterAsAddress(RegisterContext &regctx,
2593+
RegisterKind regkind,
2594+
unsigned regnum) {
2595+
unsigned lldb_regnum =
2596+
regctx.ConvertRegisterKindToRegisterNumber(regkind, regnum);
2597+
auto reg = regctx.ReadRegisterAsUnsigned(lldb_regnum, LLDB_INVALID_ADDRESS);
2598+
if (reg != LLDB_INVALID_ADDRESS)
2599+
return reg;
2600+
return llvm::createStringError(
2601+
"SwiftLanguageRuntime: failed to read register from regctx");
2602+
}
2603+
2604+
/// Functional wrapper to read a pointer from process memory at `addr +
2605+
/// offset`.
2606+
static llvm::Expected<addr_t> ReadPtrFromAddr(Process &process, addr_t addr,
2607+
int offset = 0) {
2608+
Status error;
2609+
addr_t ptr = process.ReadPointerFromMemory(addr + offset, error);
2610+
if (ptr != LLDB_INVALID_ADDRESS)
2611+
return ptr;
2612+
return llvm::createStringError("SwiftLanguageRuntime: Failed to read ptr "
2613+
"from memory address 0x%8.8" PRIx64
2614+
" Error was %s",
2615+
addr + offset, error.AsCString());
2616+
}
2617+
2618+
/// Computes the Canonical Frame Address (CFA) by converting the abstract
2619+
/// location of UnwindPlan::Row::FAValue into a concrete address. This is a
2620+
/// simplified version of the methods in RegisterContextUnwind, since plumbing
2621+
/// access to those here would be challenging.
2622+
static llvm::Expected<addr_t> GetCFA(Process &process, RegisterContext &regctx,
2623+
RegisterKind regkind,
2624+
UnwindPlan::Row::FAValue cfa_loc) {
2625+
using ValueType = UnwindPlan::Row::FAValue::ValueType;
2626+
switch (cfa_loc.GetValueType()) {
2627+
case ValueType::isRegisterPlusOffset: {
2628+
unsigned regnum = cfa_loc.GetRegisterNumber();
2629+
if (llvm::Expected<addr_t> regvalue =
2630+
ReadRegisterAsAddress(regctx, regkind, regnum))
2631+
return *regvalue + cfa_loc.GetOffset();
2632+
else
2633+
return regvalue;
2634+
}
2635+
case ValueType::isConstant:
2636+
case ValueType::isDWARFExpression:
2637+
case ValueType::isRaSearch:
2638+
case ValueType::isRegisterDereferenced:
2639+
case ValueType::unspecified:
2640+
break;
2641+
}
2642+
return llvm::createStringError(
2643+
"SwiftLanguageRuntime: Unsupported FA location type = %d",
2644+
cfa_loc.GetValueType());
2645+
}
2646+
2647+
/// Attempts to use UnwindPlans that inspect assembly to recover the entry value
2648+
/// of the async context register. This is a simplified version of the methods
2649+
/// in RegisterContextUnwind, since plumbing access to those here would be
2650+
/// challenging.
2651+
static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind(
2652+
SymbolContext &sc, Process &process, Address pc, Address func_start_addr,
2653+
RegisterContext &regctx, AsyncUnwindRegisterNumbers regnums) {
2654+
FuncUnwindersSP unwinders =
2655+
pc.GetModule()->GetUnwindTable().GetFuncUnwindersContainingAddress(pc,
2656+
sc);
2657+
if (!unwinders)
2658+
return llvm::createStringError("SwiftLanguageRuntime: Failed to find "
2659+
"function unwinder at address 0x%8.8" PRIx64,
2660+
pc.GetFileAddress());
26082661

2609-
const uint8_t *expr = triple == llvm::Triple::x86_64
2610-
? g_cfa_dwarf_expression_x86_64
2611-
: g_cfa_dwarf_expression_arm64;
2612-
auto size = triple == llvm::Triple::x86_64
2613-
? sizeof(g_cfa_dwarf_expression_x86_64)
2614-
: sizeof(g_cfa_dwarf_expression_arm64);
2615-
if (with_double_deref)
2616-
return llvm::ArrayRef<uint8_t>(expr, size);
2617-
return llvm::ArrayRef<uint8_t>(expr, size - 1);
2662+
Target &target = process.GetTarget();
2663+
UnwindPlanSP unwind_plan =
2664+
unwinders->GetUnwindPlanAtNonCallSite(target, regctx.GetThread());
2665+
if (!unwind_plan)
2666+
return llvm::createStringError(
2667+
"SwiftLanguageRuntime: Failed to find non call site unwind plan at "
2668+
"address 0x%8.8" PRIx64,
2669+
pc.GetFileAddress());
2670+
2671+
const RegisterKind unwind_regkind = unwind_plan->GetRegisterKind();
2672+
UnwindPlan::RowSP row = unwind_plan->GetRowForFunctionOffset(
2673+
pc.GetFileAddress() - func_start_addr.GetFileAddress());
2674+
2675+
// To request info about a register from the unwind plan, the register must
2676+
// be in the same domain as the unwind plan's registers.
2677+
uint32_t async_reg_unwind_regdomain;
2678+
if (!regctx.ConvertBetweenRegisterKinds(
2679+
regnums.GetRegisterKind(), regnums.async_ctx_regnum, unwind_regkind,
2680+
async_reg_unwind_regdomain)) {
2681+
// This should never happen.
2682+
// If asserts are disabled, return an error to avoid creating an invalid
2683+
// unwind plan.
2684+
auto error_msg = "SwiftLanguageRuntime: Failed to convert register domains";
2685+
llvm_unreachable(error_msg);
2686+
return llvm::createStringError(error_msg);
2687+
}
2688+
2689+
// If the plan doesn't have information about the async register, we can use
2690+
// its current value, as this is a callee saved register.
2691+
UnwindPlan::Row::AbstractRegisterLocation regloc;
2692+
if (!row->GetRegisterInfo(async_reg_unwind_regdomain, regloc))
2693+
return ReadRegisterAsAddress(regctx, regnums.GetRegisterKind(),
2694+
regnums.async_ctx_regnum);
2695+
2696+
// Handle the few abstract locations we are likely to encounter.
2697+
using RestoreType = UnwindPlan::Row::AbstractRegisterLocation::RestoreType;
2698+
RestoreType loctype = regloc.GetLocationType();
2699+
switch (loctype) {
2700+
case RestoreType::same:
2701+
return ReadRegisterAsAddress(regctx, regnums.GetRegisterKind(),
2702+
regnums.async_ctx_regnum);
2703+
case RestoreType::inOtherRegister: {
2704+
unsigned regnum = regloc.GetRegisterNumber();
2705+
return ReadRegisterAsAddress(regctx, unwind_regkind, regnum);
2706+
}
2707+
case RestoreType::atCFAPlusOffset: {
2708+
llvm::Expected<addr_t> cfa =
2709+
GetCFA(process, regctx, unwind_regkind, row->GetCFAValue());
2710+
if (!cfa)
2711+
return cfa.takeError();
2712+
return ReadPtrFromAddr(process, *cfa, regloc.GetOffset());
2713+
}
2714+
case RestoreType::isCFAPlusOffset: {
2715+
if (llvm::Expected<addr_t> cfa =
2716+
GetCFA(process, regctx, unwind_regkind, row->GetCFAValue()))
2717+
return *cfa + regloc.GetOffset();
2718+
else
2719+
return cfa;
2720+
}
2721+
case RestoreType::isConstant:
2722+
return regloc.GetConstant();
2723+
case RestoreType::unspecified:
2724+
case RestoreType::undefined:
2725+
case RestoreType::atAFAPlusOffset:
2726+
case RestoreType::isAFAPlusOffset:
2727+
case RestoreType::isDWARFExpression:
2728+
case RestoreType::atDWARFExpression:
2729+
break;
2730+
}
2731+
return llvm::createStringError(
2732+
"SwiftLanguageRuntime: Unsupported register location type = %d", loctype);
26182733
}
26192734

26202735
// Examine the register state and detect the transition from a real
@@ -2625,6 +2740,11 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
26252740
RegisterContext *regctx,
26262741
bool &behaves_like_zeroth_frame) {
26272742
LLDB_SCOPED_TIMER();
2743+
auto log_expected = [](llvm::Error error) {
2744+
Log *log = GetLog(LLDBLog::Unwind);
2745+
LLDB_LOG_ERROR(log, std::move(error), "{0}");
2746+
return UnwindPlanSP();
2747+
};
26282748

26292749
Target &target(process_sp->GetTarget());
26302750
auto arch = target.GetArchitecture();
@@ -2644,12 +2764,6 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
26442764
return UnwindPlanSP();
26452765
}
26462766

2647-
// If we're in the prologue of a function, don't provide a Swift async
2648-
// unwind plan. We can be tricked by unmodified caller-registers that
2649-
// make this look like an async frame when this is a standard ABI function
2650-
// call, and the parent is the async frame.
2651-
// This assumes that the frame pointer register will be modified in the
2652-
// prologue.
26532767
Address pc;
26542768
pc.SetLoadAddress(regctx->GetPC(), &target);
26552769
SymbolContext sc;
@@ -2659,111 +2773,58 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
26592773
return UnwindPlanSP();
26602774

26612775
Address func_start_addr;
2662-
uint32_t prologue_size;
26632776
ConstString mangled_name;
26642777
if (sc.function) {
26652778
func_start_addr = sc.function->GetAddressRange().GetBaseAddress();
2666-
prologue_size = sc.function->GetPrologueByteSize();
26672779
mangled_name = sc.function->GetMangled().GetMangledName();
26682780
} else if (sc.symbol) {
26692781
func_start_addr = sc.symbol->GetAddress();
2670-
prologue_size = sc.symbol->GetPrologueByteSize();
26712782
mangled_name = sc.symbol->GetMangled().GetMangledName();
26722783
} else {
26732784
return UnwindPlanSP();
26742785
}
26752786

2676-
AddressRange prologue_range(func_start_addr, prologue_size);
2677-
bool in_prologue = (func_start_addr == pc ||
2678-
prologue_range.ContainsLoadAddress(pc, &target));
2679-
2680-
if (in_prologue) {
2681-
if (!IsAnySwiftAsyncFunctionSymbol(mangled_name.GetStringRef()))
2682-
return UnwindPlanSP();
2683-
} else {
2684-
addr_t saved_fp = LLDB_INVALID_ADDRESS;
2685-
Status error;
2686-
if (!process_sp->ReadMemory(fp, &saved_fp, 8, error))
2687-
return UnwindPlanSP();
2688-
2689-
// Get the high nibble of the dreferenced fp; if the 60th bit is set,
2690-
// this is the transition to a swift async AsyncContext chain.
2691-
if ((saved_fp & (0xfULL << 60)) >> 60 != 1)
2692-
return UnwindPlanSP();
2693-
}
2787+
if (!IsAnySwiftAsyncFunctionSymbol(mangled_name.GetStringRef()))
2788+
return UnwindPlanSP();
26942789

2695-
// The coroutine funclets split from an async function have 2 different ABIs:
2696-
// - Async suspend partial functions and the first funclet get their async
2697-
// context directly in the async register.
2698-
// - Async await resume partial functions take their context indirectly, it
2699-
// needs to be dereferenced to get the actual function's context.
2700-
// The debug info for locals reflects this difference, so our unwinding of the
2701-
// context register needs to reflect it too.
2790+
// The async register contains, at the start of the funclet:
2791+
// 1. The async context of the async function that just finished executing,
2792+
// for await resume ("Q") funclets ("indirect context").
2793+
// 2. The async context for the currently executing async function, for all
2794+
// other funclets ("Y" and "Yx" funclets, where "x" is a number).
27022795
bool indirect_context =
27032796
IsSwiftAsyncAwaitResumePartialFunctionSymbol(mangled_name.GetStringRef());
27042797

2798+
llvm::Expected<addr_t> async_reg = ReadAsyncContextRegisterFromUnwind(
2799+
sc, *process_sp, pc, func_start_addr, *regctx, *regnums);
2800+
if (!async_reg)
2801+
return log_expected(async_reg.takeError());
2802+
llvm::Expected<addr_t> async_ctx =
2803+
indirect_context ? ReadPtrFromAddr(*m_process, *async_reg) : *async_reg;
2804+
if (!async_ctx)
2805+
return log_expected(async_ctx.takeError());
2806+
27052807
UnwindPlan::RowSP row(new UnwindPlan::Row);
27062808
const int32_t ptr_size = 8;
27072809
row->SetOffset(0);
27082810

2709-
if (in_prologue) {
2710-
if (indirect_context)
2711-
row->GetCFAValue().SetIsRegisterDereferenced(regnums->async_ctx_regnum);
2712-
else
2713-
row->GetCFAValue().SetIsRegisterPlusOffset(regnums->async_ctx_regnum, 0);
2714-
} else {
2715-
// In indirect funclets, dereferencing (fp-8) once produces the CFA of the
2716-
// frame above. Dereferencing twice will produce the current frame's CFA.
2717-
bool with_double_deref = indirect_context;
2718-
llvm::ArrayRef<uint8_t> expr = GetAsyncRegFromFramePointerDWARFExpr(
2719-
arch.GetMachine(), with_double_deref);
2720-
row->GetCFAValue().SetIsDWARFExpression(expr.data(), expr.size());
2721-
}
2722-
2723-
if (indirect_context) {
2724-
if (in_prologue) {
2725-
row->SetRegisterLocationToSame(regnums->async_ctx_regnum, false);
2726-
} else {
2727-
llvm::ArrayRef<uint8_t> expr = GetAsyncRegFromFramePointerDWARFExpr(
2728-
arch.GetMachine(), false /*with_double_deref*/);
2729-
row->SetRegisterLocationToIsDWARFExpression(
2730-
regnums->async_ctx_regnum, expr.data(), expr.size(), false);
2731-
}
2732-
} else {
2733-
// In the first part of a split async function, the context is passed
2734-
// directly, so we can use the CFA value directly.
2735-
row->SetRegisterLocationToIsCFAPlusOffset(regnums->async_ctx_regnum, 0,
2736-
false);
2737-
// The fact that we are in this case needs to be communicated to the frames
2738-
// below us as they need to react differently. There is no good way to
2739-
// expose this, so we set another dummy register to communicate this state.
2740-
static const uint8_t g_dummy_dwarf_expression[] = {
2741-
llvm::dwarf::DW_OP_const1u, 0
2742-
};
2743-
row->SetRegisterLocationToIsDWARFExpression(
2744-
regnums->dummy_regnum, g_dummy_dwarf_expression,
2745-
sizeof(g_dummy_dwarf_expression), false);
2746-
}
2747-
2748-
std::optional<addr_t> pc_after_prologue = [&]() -> std::optional<addr_t> {
2749-
// In the prologue, use the async_reg as is, it has not been clobbered.
2750-
if (in_prologue)
2751-
return TrySkipVirtualParentProlog(GetAsyncContext(regctx), *process_sp,
2752-
indirect_context);
2753-
2754-
// Both ABIs (x86_64 and aarch64) guarantee the async reg is saved at:
2755-
// *(fp - 8).
2756-
Status error;
2757-
addr_t async_reg_entry_value = LLDB_INVALID_ADDRESS;
2758-
process_sp->ReadMemory(fp - ptr_size, &async_reg_entry_value, ptr_size,
2759-
error);
2760-
if (error.Fail())
2761-
return {};
2762-
return TrySkipVirtualParentProlog(async_reg_entry_value, *process_sp,
2763-
indirect_context);
2764-
}();
2811+
// The CFA of a funclet is its own async context.
2812+
row->GetCFAValue().SetIsConstant(*async_ctx);
2813+
2814+
// The value of the async register in the parent frame is the entry value of
2815+
// the async register in the current frame. This mimics a function call, as
2816+
// if the parent funclet had called the current funclet.
2817+
row->SetRegisterLocationToIsConstant(regnums->async_ctx_regnum, *async_reg,
2818+
/*can_replace=*/false);
2819+
2820+
// The parent frame needs to know how to interpret the value it is given for
2821+
// its own async register. A dummy register is used to communicate that.
2822+
if (!indirect_context)
2823+
row->SetRegisterLocationToIsConstant(regnums->dummy_regnum, 0,
2824+
/*can_replace=*/false);
27652825

2766-
if (pc_after_prologue)
2826+
if (std::optional<addr_t> pc_after_prologue =
2827+
TrySkipVirtualParentProlog(*async_ctx, *process_sp))
27672828
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,
27682829
false);
27692830
else

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ class SwiftLanguageRuntime : public LanguageRuntime {
496496
/// address of the first non-prologue instruction.
497497
std::optional<lldb::addr_t>
498498
TrySkipVirtualParentProlog(lldb::addr_t async_reg_val, Process &process,
499-
unsigned num_indirections);
499+
unsigned num_indirections = 0);
500500
};
501501

502502
} // namespace lldb_private

lldb/test/API/lang/swift/async/stepping/step-in/task-switch/TestSwiftTaskSwitch.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
class TestCase(lldbtest.TestBase):
88
@swiftTest
99
@skipIf(oslist=["windows", "linux"])
10-
@skipIf(bugnumber="rdar://136083188")
1110
def test(self):
1211
"""Test conditions for async step-in."""
1312
self.build()
@@ -28,7 +27,7 @@ def test(self):
2827
# Using the line table, build a set of the non-zero line numbers for
2928
# this this function - and verify that there is exactly one line.
3029
lines = {inst.addr.line_entry.line for inst in instructions}
31-
lines.remove(0)
30+
lines.discard(0)
3231
self.assertEqual(lines, {3})
3332

3433
# Required for builds that have debug info.
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

0 commit comments

Comments
 (0)