Skip to content

Commit bbc1484

Browse files
[lldb][swift] Evaluate entry_value(async_reg) in terms of CFA
Prior to this commit, evaluating the dwarf expression `entry_value(async_reg)` is done by finding the value of the asynchronous register in the parent frame. To enable the above, the unwinder must pretend there is a real function call between the parent frame and the current frame, and that the async register is set by the parent frame prior to making the 'call'. None of this is actually true, and it creates a lot of work for the unwinder (see the amount of code deleted there). Here is further evidence of how awkward this is. Suppose you have this call stack: ``` A <--- younger frame, top of the stack B C <--- older frame, bottom of the stack ``` When the unwinder is creating the frame of C from the register state of B, it must know whether A was an indirect (Q) funclet or not, because that determined how the frame of B was produced from the register state of A. This is very unusual, in fact, the unwinder doesn't even have access to such information (we had to use a "dummy" register for this). This patch changes how `entry_value(async_reg)` (or `entry_value(async_reg),deref` for Q_funclets) is evaluated: this expression is equivalent to the CFA (the async context) of the current frame. Since we no longer need to peek at the parent frame, the unwinder no longer needs to perform the work described previously. The unwinder can instead provide the continuation funclet with the register contents they will _actually_ have when the funclet runs. This patch also addresses a more subtle issue. In Q funclets, after a certain instruction, `entry_value(async_reg)` produces a pointer to memory that has been freed, as Q funclets free the async context of funclet that just finished executing. If the debugger attempts to evaluate `entry_value(async_reg), deref` as two separate operations, it will be accessing freed heap memory. By converting that operation sequence into `DW_OP_call_frame_cfa`, we bypass the issue.
1 parent 3cd7dc2 commit bbc1484

File tree

3 files changed

+111
-103
lines changed

3 files changed

+111
-103
lines changed

lldb/source/Expression/DWARFExpression.cpp

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,74 @@ bool DWARFExpression::LinkThreadLocalStorage(
532532
return true;
533533
}
534534

535+
/// Returns true if \c opcodes contains the opcode for the register used for the
536+
/// Swift Async Context (x22 for aarch64, r14 for x86-64). It must also not
537+
/// contain any other opcodes.
538+
static bool IsAsyncRegCtxExpr(DataExtractor &opcodes,
539+
llvm::Triple::ArchType triple) {
540+
offset_t offset = 0;
541+
const uint8_t op_async_ctx_reg = opcodes.GetU8(&offset);
542+
if (opcodes.BytesLeft(offset) != 0)
543+
return false;
544+
545+
// FIXME: could this be exposed through the ABI/Language plugins?
546+
return (triple == llvm::Triple::x86_64 && op_async_ctx_reg == DW_OP_reg14) ||
547+
(triple == llvm::Triple::aarch64 && op_async_ctx_reg == DW_OP_reg22);
548+
}
549+
550+
/// If \c opcodes contain the location of asynchronous contexts in Swift,
551+
/// evaluates DW_OP_call_frame_cfa, returns its result, and updates
552+
/// \c current_offset. Otherwise, does nothing. This is possible because, for
553+
/// async frames, the language unwinder treats the asynchronous context as the
554+
/// CFA of the frame.
555+
static llvm::Expected<Value> SwiftAsyncEvaluate_DW_OP_entry_value(
556+
ExecutionContext &exe_ctx, StackFrame &current_frame,
557+
const DWARFUnit *dwarf_cu, Function &func, const DataExtractor &opcodes,
558+
offset_t &current_offset) {
559+
auto func_name = func.GetMangled().GetMangledName();
560+
if (!SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(func_name))
561+
return llvm::createStringError(
562+
"SwiftAsyncEvaluate_DW_OP_entry_value: not an async function");
563+
564+
offset_t new_offset = current_offset;
565+
const uint32_t subexpr_len = opcodes.GetULEB128(&new_offset);
566+
const void *subexpr_data = opcodes.GetData(&new_offset, subexpr_len);
567+
if (!subexpr_data)
568+
return llvm::createStringError(
569+
"SwiftAsyncEvaluate_DW_OP_entry_value: failed to extract subexpr");
570+
571+
DataExtractor subexpr_extractor(
572+
subexpr_data, subexpr_len, opcodes.GetByteOrder(),
573+
opcodes.GetAddressByteSize(), opcodes.getTargetByteSize());
574+
if (!IsAsyncRegCtxExpr(subexpr_extractor,
575+
exe_ctx.GetTargetRef().GetArchitecture().GetMachine()))
576+
return llvm::createStringError("SwiftAsyncEvaluate_DW_OP_entry_value: "
577+
"missing async context register opcode");
578+
579+
// Q funclets require an extra level of indirection.
580+
if (SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol(
581+
func_name)) {
582+
const uint8_t maybe_op_deref = opcodes.GetU8(&new_offset);
583+
if (maybe_op_deref != DW_OP_deref)
584+
return llvm::createStringError("SwiftAsyncEvaluate_DW_OP_entry_value: "
585+
"missing DW_OP_deref in Q funclet");
586+
}
587+
588+
static const uint8_t cfa_opcode = DW_OP_call_frame_cfa;
589+
DataExtractor cfa_expr_data(&cfa_opcode, 1, opcodes.GetByteOrder(),
590+
opcodes.GetAddressByteSize(),
591+
opcodes.getTargetByteSize());
592+
DWARFExpressionList cfa_expr(func.CalculateSymbolContextModule(),
593+
cfa_expr_data, dwarf_cu);
594+
llvm::Expected<Value> maybe_result = cfa_expr.Evaluate(
595+
&exe_ctx, current_frame.GetRegisterContext().get(), LLDB_INVALID_ADDRESS,
596+
/*initial_value_ptr=*/nullptr,
597+
/*object_address_ptr=*/nullptr);
598+
if (maybe_result)
599+
current_offset = new_offset;
600+
return maybe_result;
601+
}
602+
535603
static llvm::Error
536604
Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
537605
ExecutionContext *exe_ctx, RegisterContext *reg_ctx,
@@ -629,20 +697,19 @@ Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
629697
if (!current_func)
630698
return llvm::createStringError("no current function");
631699

700+
if (llvm::Expected<Value> result = SwiftAsyncEvaluate_DW_OP_entry_value(
701+
*exe_ctx, *current_frame, dwarf_cu, *current_func, opcodes,
702+
opcode_offset)) {
703+
stack.push_back(*result);
704+
return llvm::Error::success();
705+
} else
706+
LLDB_LOG_ERROR(log, result.takeError(), "{0}");
707+
632708
CallEdge *call_edge = nullptr;
633709
ModuleList &modlist = target.GetImages();
634710
ExecutionContext parent_exe_ctx = *exe_ctx;
635711
parent_exe_ctx.SetFrameSP(parent_frame);
636712
Function *parent_func = nullptr;
637-
#ifdef LLDB_ENABLE_SWIFT
638-
// Swift async function arguments are represented relative to a
639-
// DW_OP_entry_value that fetches the async context register. This
640-
// register is known to the unwinder and can always be restored
641-
// therefore it is not necessary to match up a call site parameter
642-
// with it.
643-
auto fn_name = current_func->GetMangled().GetMangledName().GetStringRef();
644-
if (!SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(fn_name)) {
645-
#endif
646713

647714
parent_func =
648715
parent_frame->GetSymbolContext(eSymbolContextFunction).function;
@@ -677,25 +744,16 @@ Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
677744
if (!call_edge)
678745
return llvm::createStringError("no unambiguous edge from parent "
679746
"to current function");
680-
#ifdef LLDB_ENABLE_SWIFT
681-
}
682-
#endif
683747

684748
// 3. Attempt to locate the DW_OP_entry_value expression in the set of
685749
// available call site parameters. If found, evaluate the corresponding
686750
// parameter in the context of the parent frame.
687751
const uint32_t subexpr_len = opcodes.GetULEB128(&opcode_offset);
688-
#ifdef LLDB_ENABLE_SWIFT
689-
lldb::offset_t subexpr_offset = opcode_offset;
690-
#endif
691752
const void *subexpr_data = opcodes.GetData(&opcode_offset, subexpr_len);
692753
if (!subexpr_data)
693754
return llvm::createStringError("subexpr could not be read");
694755

695756
const CallSiteParameter *matched_param = nullptr;
696-
#ifdef LLDB_ENABLE_SWIFT
697-
if (call_edge) {
698-
#endif
699757
for (const CallSiteParameter &param : call_edge->GetCallSiteParameters()) {
700758
DataExtractor param_subexpr_extractor;
701759
if (!param.LocationInCallee.GetExpressionData(param_subexpr_extractor))
@@ -721,25 +779,10 @@ Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
721779
}
722780
if (!matched_param)
723781
return llvm::createStringError("no matching call site param found");
724-
#ifdef LLDB_ENABLE_SWIFT
725-
}
726-
std::optional<DWARFExpressionList> subexpr;
727-
if (!matched_param) {
728-
auto *ctx_func = parent_func ? parent_func : current_func;
729-
subexpr.emplace(ctx_func->CalculateSymbolContextModule(),
730-
DataExtractor(opcodes, subexpr_offset, subexpr_len),
731-
dwarf_cu);
732-
}
733-
#endif
734782

735783
// TODO: Add support for DW_OP_push_object_address within a DW_OP_entry_value
736784
// subexpresion whenever llvm does.
737-
#ifdef LLDB_ENABLE_SWIFT
738-
const DWARFExpressionList &param_expr =
739-
matched_param ? matched_param->LocationInCaller : *subexpr;
740-
#else
741785
const DWARFExpressionList &param_expr = matched_param->LocationInCaller;
742-
#endif
743786

744787
llvm::Expected<Value> maybe_result = param_expr.Evaluate(
745788
&parent_exe_ctx, parent_frame->GetRegisterContext().get(),

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

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2539,11 +2539,6 @@ struct AsyncUnwindRegisterNumbers {
25392539
uint32_t async_ctx_regnum;
25402540
uint32_t fp_regnum;
25412541
uint32_t pc_regnum;
2542-
/// A register to use as a marker to indicate how the async context is passed
2543-
/// to the function (indirectly, or not). This needs to be communicated to the
2544-
/// frames below us as they need to react differently. There is no good way to
2545-
/// expose this, so we set another dummy register to communicate this state.
2546-
uint32_t dummy_regnum;
25472542

25482543
RegisterKind GetRegisterKind() const { return lldb::eRegisterKindDWARF; }
25492544
};
@@ -2557,15 +2552,13 @@ GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple) {
25572552
regnums.async_ctx_regnum = dwarf_r14_x86_64;
25582553
regnums.fp_regnum = dwarf_rbp_x86_64;
25592554
regnums.pc_regnum = dwarf_rip_x86_64;
2560-
regnums.dummy_regnum = dwarf_r15_x86_64;
25612555
return regnums;
25622556
}
25632557
case llvm::Triple::aarch64: {
25642558
AsyncUnwindRegisterNumbers regnums;
25652559
regnums.async_ctx_regnum = arm64_dwarf::x22;
25662560
regnums.fp_regnum = arm64_dwarf::fp;
25672561
regnums.pc_regnum = arm64_dwarf::pc;
2568-
regnums.dummy_regnum = arm64_dwarf::x23;
25692562
return regnums;
25702563
}
25712564
default:
@@ -2811,18 +2804,11 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
28112804
// The CFA of a funclet is its own async context.
28122805
row->GetCFAValue().SetIsConstant(*async_ctx);
28132806

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,
2807+
// The value of the async register in the parent frame (which is the
2808+
// continuation funclet) is the async context of this frame.
2809+
row->SetRegisterLocationToIsConstant(regnums->async_ctx_regnum, *async_ctx,
28182810
/*can_replace=*/false);
28192811

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);
2825-
28262812
if (std::optional<addr_t> pc_after_prologue =
28272813
TrySkipVirtualParentProlog(*async_ctx, *process_sp))
28282814
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,
@@ -2855,59 +2841,13 @@ UnwindPlanSP SwiftLanguageRuntime::GetFollowAsyncContextUnwindPlan(
28552841
if (!regnums)
28562842
return UnwindPlanSP();
28572843

2858-
const bool is_indirect =
2859-
regctx->ReadRegisterAsUnsigned(regnums->dummy_regnum, (uint64_t)-1ll) ==
2860-
(uint64_t)-1ll;
2861-
// In the general case, the async register setup by the frame above us
2862-
// should be dereferenced twice to get our context, except when the frame
2863-
// above us is an async frame on the OS stack that takes its context directly
2864-
// (see discussion in GetRuntimeUnwindPlan()). The availability of
2865-
// dummy_regnum is used as a marker for this situation.
2866-
if (!is_indirect) {
2867-
row->GetCFAValue().SetIsRegisterDereferenced(regnums->async_ctx_regnum);
2868-
row->SetRegisterLocationToSame(regnums->async_ctx_regnum, false);
2869-
} else {
2870-
static const uint8_t async_dwarf_expression_x86_64[] = {
2871-
llvm::dwarf::DW_OP_regx, dwarf_r14_x86_64, // DW_OP_regx, reg
2872-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2873-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2874-
};
2875-
static const uint8_t async_dwarf_expression_arm64[] = {
2876-
llvm::dwarf::DW_OP_regx, arm64_dwarf::x22, // DW_OP_regx, reg
2877-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2878-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2879-
};
2880-
2881-
const unsigned expr_size = sizeof(async_dwarf_expression_x86_64);
2882-
static_assert(sizeof(async_dwarf_expression_x86_64) ==
2883-
sizeof(async_dwarf_expression_arm64),
2884-
"Expressions of different sizes");
2885-
2886-
const uint8_t *expression = nullptr;
2887-
if (arch.GetMachine() == llvm::Triple::x86_64)
2888-
expression = async_dwarf_expression_x86_64;
2889-
else if (arch.GetMachine() == llvm::Triple::aarch64)
2890-
expression = async_dwarf_expression_arm64;
2891-
else
2892-
llvm_unreachable("Unsupported architecture");
2893-
2894-
// Note how the register location gets the same expression pointer with a
2895-
// different size. We just skip the trailing deref for it.
2896-
assert(expression[expr_size - 1] == llvm::dwarf::DW_OP_deref &&
2897-
"Should skip a deref");
2898-
row->GetCFAValue().SetIsDWARFExpression(expression, expr_size);
2899-
row->SetRegisterLocationToIsDWARFExpression(
2900-
regnums->async_ctx_regnum, expression, expr_size - 1, false);
2901-
}
2902-
2903-
// Suppose this is unwinding frame #2 of a call stack. The value given for
2904-
// the async register has two possible values, depending on what frame #1
2905-
// expects:
2906-
// 1. The CFA of frame #1, direct ABI, dereferencing it once produces CFA of
2907-
// Frame #2.
2908-
// 2. The CFA of frame #0, indirect ABI, dereferencing it twice produces CFA
2909-
// of Frame #2.
2910-
const unsigned num_indirections = 1 + is_indirect;
2844+
row->GetCFAValue().SetIsRegisterDereferenced(regnums->async_ctx_regnum);
2845+
// The value of the async register in the parent frame (which is the
2846+
// continuation funclet) is the async context of this frame.
2847+
row->SetRegisterLocationToIsCFAPlusOffset(regnums->async_ctx_regnum,
2848+
/*offset*/ 0, false);
2849+
2850+
const unsigned num_indirections = 1;
29112851
if (std::optional<addr_t> pc_after_prologue = TrySkipVirtualParentProlog(
29122852
GetAsyncContext(regctx), *process_sp, num_indirections))
29132853
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,

lldb/test/API/lang/swift/async/frame/variables_multiple_frames/TestSwiftAsyncFrameVarMultipleFrames.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import lldbsuite.test.lldbutil as lldbutil
55

66

7+
@skipIf(archs=no_match(["arm64", "arm64e", "x86_64"]))
78
class TestCase(lldbtest.TestBase):
89

910
mydir = lldbtest.TestBase.compute_mydir(__file__)
@@ -37,6 +38,26 @@ def check_pcs(self, async_frames, process, target):
3738
prologue_to_skip = parent_frame.GetFunction().GetPrologueByteSize()
3839
self.assertEqual(continuation_ptr + prologue_to_skip, parent_frame.GetPC())
3940

41+
def check_async_regs_one_frame(self, frame, process):
42+
async_reg_name = "r14" if self.getArchitecture() == "x86_64" else "x22"
43+
44+
cfa = frame.GetCFA()
45+
is_indirect = "await resume" in frame.GetFunctionName()
46+
async_register = frame.FindRegister(async_reg_name).GetValueAsUnsigned()
47+
48+
if is_indirect:
49+
deref_async_reg = self.read_ptr_from_memory(process, async_register)
50+
self.assertEqual(deref_async_reg, cfa)
51+
else:
52+
self.assertEqual(async_register, cfa)
53+
54+
def check_async_regs(self, async_frames, process):
55+
for frame in async_frames:
56+
# The frames from the implicit main function don't have a demangled
57+
# name, so we can't test whether they are a Q funclet or not.
58+
if "Main" in frame.GetFunctionName():
59+
break
60+
self.check_async_regs_one_frame(frame, process)
4061

4162
def check_variables(self, async_frames, expected_values):
4263
for frame, expected_value in zip(async_frames, expected_values):
@@ -58,6 +79,7 @@ def test(self):
5879
self.check_cfas(async_frames, process)
5980
self.check_pcs(async_frames, process, target)
6081
self.check_variables(async_frames, ["222", "333", "444", "555"])
82+
self.check_async_regs(async_frames, process)
6183

6284
target.DeleteAllBreakpoints()
6385
target.BreakpointCreateBySourceRegex("breakpoint2", source_file)
@@ -68,6 +90,7 @@ def test(self):
6890
self.check_cfas(async_frames, process)
6991
self.check_pcs(async_frames, process, target)
7092
self.check_variables(async_frames, ["111", "222", "333", "444", "555"])
93+
self.check_async_regs(async_frames, process)
7194

7295
# Now stop at the Q funclet right after the await to ASYNC___1
7396
target.DeleteAllBreakpoints()
@@ -77,6 +100,7 @@ def test(self):
77100
self.check_cfas(async_frames, process)
78101
self.check_pcs(async_frames, process, target)
79102
self.check_variables(async_frames, ["222", "333", "444", "555"])
103+
self.check_async_regs(async_frames, process)
80104

81105
target.DeleteAllBreakpoints()
82106
target.BreakpointCreateBySourceRegex("breakpoint3", source_file)
@@ -85,3 +109,4 @@ def test(self):
85109
self.check_cfas(async_frames, process)
86110
self.check_pcs(async_frames, process, target)
87111
self.check_variables(async_frames, ["222", "333", "444", "555"])
112+
self.check_async_regs(async_frames, process)

0 commit comments

Comments
 (0)