Skip to content

Commit b45de43

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. (cherry picked from commit bbc1484)
1 parent 3911c0e commit b45de43

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
@@ -533,6 +533,74 @@ bool DWARFExpression::LinkThreadLocalStorage(
533533
return true;
534534
}
535535

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

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

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

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

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

736784
// TODO: Add support for DW_OP_push_object_address within a DW_OP_entry_value
737785
// subexpresion whenever llvm does.
738-
#ifdef LLDB_ENABLE_SWIFT
739-
const DWARFExpressionList &param_expr =
740-
matched_param ? matched_param->LocationInCaller : *subexpr;
741-
#else
742786
const DWARFExpressionList &param_expr = matched_param->LocationInCaller;
743-
#endif
744787

745788
llvm::Expected<Value> maybe_result = param_expr.Evaluate(
746789
&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)