Skip to content

Commit 14f6cd5

Browse files
Merge pull request #9162 from felipepiovezan/felipe/step_out_fixes
[lldb][swift] Fix StackID comparisons to enable stepping out of async functions
2 parents f76d2f2 + 8d7935d commit 14f6cd5

File tree

3 files changed

+76
-14
lines changed

3 files changed

+76
-14
lines changed

lldb/source/Target/StackID.cpp

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@
1313
#include "lldb/Target/MemoryRegionInfo.h"
1414
#include "lldb/Target/Process.h"
1515
#include "lldb/Target/Thread.h"
16+
#include "lldb/Utility/LLDBLog.h"
1617
#include "lldb/Utility/Stream.h"
1718

1819
using namespace lldb_private;
1920

2021
bool StackID::IsCFAOnStack(Process &process) const {
2122
if (m_cfa_on_stack == eLazyBoolCalculate) {
22-
m_cfa_on_stack = eLazyBoolNo;
23+
// Conservatively assume stack memory
24+
m_cfa_on_stack = eLazyBoolYes;
2325
if (m_cfa != LLDB_INVALID_ADDRESS) {
2426
MemoryRegionInfo mem_info;
2527
if (process.GetMemoryRegionInfo(m_cfa, mem_info).Success())
26-
if (mem_info.IsStackMemory() == MemoryRegionInfo::eYes)
27-
m_cfa_on_stack = eLazyBoolYes;
28+
if (mem_info.IsStackMemory() == MemoryRegionInfo::eNo)
29+
m_cfa_on_stack = eLazyBoolNo;
2830
}
2931
}
3032
return m_cfa_on_stack == eLazyBoolYes;
@@ -84,14 +86,43 @@ IsYoungerHeapCFAs(const StackID &lhs, const StackID &rhs, Process &process) {
8486
if (lhs_cfa_on_stack && rhs_cfa_on_stack)
8587
return HeapCFAComparisonResult::NoOpinion;
8688

87-
// FIXME: rdar://76119439
88-
// At the boundary between an async parent frame calling a regular child
89-
// frame, the CFA of the parent async function is a heap addresses, and the
90-
// CFA of concrete child function is a stack address. Therefore, if lhs is
91-
// on stack, and rhs is not, lhs is considered less than rhs, independent of
92-
// address values.
89+
// If one of the frames has a CFA on the stack and the other doesn't, we are
90+
// at the boundary between an asynchronous and a synchronous function.
91+
// Synchronous functions cannot call asynchronous functions, therefore the
92+
// synchronous frame is always younger.
9393
if (lhs_cfa_on_stack && !rhs_cfa_on_stack)
9494
return HeapCFAComparisonResult::Younger;
95+
if (!lhs_cfa_on_stack && rhs_cfa_on_stack)
96+
return HeapCFAComparisonResult::Older;
97+
98+
const lldb::addr_t lhs_cfa = lhs.GetCallFrameAddress();
99+
const lldb::addr_t rhs_cfa = rhs.GetCallFrameAddress();
100+
// If the cfas are the same, fallback to the usual scope comparison.
101+
if (lhs_cfa == rhs_cfa)
102+
return HeapCFAComparisonResult::NoOpinion;
103+
104+
// Both CFAs are on the heap and they are distinct.
105+
// LHS is younger if and only if its continuation async context is (directly
106+
// or indirectly) RHS. Chase continuation pointers to check this case, until
107+
// we hit the end of the chain (parent_ctx == 0) or a safety limit in case of
108+
// an invalid continuation chain.
109+
auto max_num_frames = 512;
110+
for (lldb::addr_t parent_ctx = lhs_cfa; parent_ctx && max_num_frames;
111+
max_num_frames--) {
112+
Status error;
113+
lldb::addr_t old_parent_ctx = parent_ctx;
114+
// The continuation's context is the first field of an async context.
115+
parent_ctx = process.ReadPointerFromMemory(old_parent_ctx, error);
116+
if (error.Fail()) {
117+
Log *log = GetLog(LLDBLog::Unwind);
118+
LLDB_LOGF(log, "Failed to read parent async context of: 0x%8.8" PRIx64,
119+
old_parent_ctx);
120+
break;
121+
}
122+
if (parent_ctx == rhs_cfa)
123+
return HeapCFAComparisonResult::Younger;
124+
}
125+
95126
return HeapCFAComparisonResult::NoOpinion;
96127
}
97128
// END SWIFT

lldb/test/API/lang/swift/async/stepping/step_out/TestSteppingOutAsync.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ def step_out_checks(self, thread, expected_func_names):
3030

3131
@swiftTest
3232
@skipIf(oslist=["windows", "linux"])
33-
@skipIf("rdar://133849022")
3433
def test(self):
3534
"""Test `frame variable` in async functions"""
3635
self.build()

lldb/unittests/StackID/StackIDTest.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,19 @@ class StackIDTest : public ::testing::Test {
3939
};
4040

4141
struct MockProcess : Process {
42+
llvm::DenseMap<addr_t, addr_t> memory_map;
43+
4244
MockProcess(TargetSP target_sp, ListenerSP listener_sp)
4345
: Process(target_sp, listener_sp) {}
46+
MockProcess(TargetSP target_sp, ListenerSP listener_sp,
47+
llvm::DenseMap<addr_t, addr_t> &&memory_map)
48+
: Process(target_sp, listener_sp), memory_map(memory_map) {}
4449
size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size,
4550
Status &error) override {
46-
return 0;
51+
assert(memory_map.contains(vm_addr));
52+
assert(size == sizeof(addr_t));
53+
*reinterpret_cast<addr_t *>(buf) = memory_map[vm_addr];
54+
return sizeof(addr_t);
4755
}
4856
size_t ReadMemory(addr_t addr, void *buf, size_t size,
4957
Status &status) override {
@@ -86,8 +94,32 @@ TEST_F(StackIDTest, StackHeapCFAComparison) {
8694
MockStackID cfa_on_heap(/*cfa*/ 10, OnStack::No);
8795

8896
EXPECT_TRUE(StackID::IsYounger(cfa_on_stack, cfa_on_heap, process));
97+
EXPECT_FALSE(StackID::IsYounger(cfa_on_heap, cfa_on_stack, process));
98+
}
99+
100+
TEST_F(StackIDTest, HeapHeapCFAComparison) {
101+
// Create a mock async continuation chain:
102+
// 100 -> 108 -> 116 -> 0
103+
// This should be read as:
104+
// "Async context whose address is 100 has a continuation context whose
105+
// address is 108", etc.
106+
llvm::DenseMap<addr_t, addr_t> memory_map;
107+
memory_map[100] = 108;
108+
memory_map[108] = 116;
109+
memory_map[116] = 0;
110+
auto process = MockProcess(m_target_sp, Listener::MakeListener("dummy"),
111+
std::move(memory_map));
112+
113+
MockStackID oldest_cfa(/*cfa*/ 116, OnStack::No);
114+
MockStackID middle_cfa(/*cfa*/ 108, OnStack::No);
115+
MockStackID youngest_cfa(/*cfa*/ 100, OnStack::No);
116+
117+
EXPECT_TRUE(StackID::IsYounger(youngest_cfa, oldest_cfa, process));
118+
EXPECT_FALSE(StackID::IsYounger(oldest_cfa, youngest_cfa, process));
119+
120+
EXPECT_TRUE(StackID::IsYounger(youngest_cfa, middle_cfa, process));
121+
EXPECT_FALSE(StackID::IsYounger(middle_cfa, youngest_cfa, process));
89122

90-
// FIXME: if the above returned true, swapping the arguments **must** return
91-
// false. And yet it doesn't.
92-
// EXPECT_FALSE(StackID::IsYounger(cfa_on_heap, cfa_on_stack, process));
123+
EXPECT_TRUE(StackID::IsYounger(middle_cfa, oldest_cfa, process));
124+
EXPECT_FALSE(StackID::IsYounger(oldest_cfa, middle_cfa, process));
93125
}

0 commit comments

Comments
 (0)