Skip to content

Commit d87f634

Browse files
committed
[lldb] Fix source line annotations for libsanitizers
When providing allocation and deallocation traces, the ASan compiler-rt runtime already provides call addresses (`HistoryPCType::Calls`). On Darwin, system sanitizers (libsanitizers) provides return address. It also discards a few non-user frames at the top of the stack, because these internal libmalloc/libsanitizers stack frames do not provide any value when diagnosing memory errors. Introduce and add handling for `HistoryPCType::ReturnsNoZerothFrame` to cover this case and enable libsanitizers traces line-level testing. rdar://157596927
1 parent 51480ef commit d87f634

File tree

4 files changed

+54
-26
lines changed

4 files changed

+54
-26
lines changed

lldb/include/lldb/lldb-private-enumerations.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,21 @@ enum class IterationAction {
248248
Stop,
249249
};
250250

251-
enum class HistoryPCType { Returns, ReturnsNoZerothFrame, Calls };
251+
/// Specifies the type of PCs when creating a `HistoryThread`.
252+
/// - `Returns` - Usually, when LLDB unwinds the stack or we retrieve a stack
253+
/// trace via `backtrace()` we are collecting return addresses (except for the
254+
/// topmost frame which is the actual PC). LLDB then maps these return
255+
/// addresses back to call addresses to give accurate source line annotations.
256+
/// - `ReturnsNoZerothFrame` - Some trace providers (e.g., libsanitizers traces)
257+
/// collect return addresses but prune the topmost frames, so we should skip
258+
/// the special treatment of frame 0.
259+
/// - `Calls` - Other trace providers (e.g., ASan compiler-rt runtime) already
260+
/// perform this mapping, so we need to prevent LLDB from doing it again.
261+
enum class HistoryPCType {
262+
Returns, ///< PCs are return addresses, except for topmost frame.
263+
ReturnsNoZerothFrame, ///< All PCs are return addresses.
264+
Calls ///< PCs are call addresses.
265+
};
252266

253267
inline std::string GetStatDescription(lldb_private::StatisticKind K) {
254268
switch (K) {

lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,9 @@ const char *memory_history_asan_command_format =
9191
t;
9292
)";
9393

94-
static void CreateHistoryThreadFromValueObject(ProcessSP process_sp,
95-
ValueObjectSP return_value_sp,
96-
const char *type,
97-
const char *thread_name,
98-
HistoryThreads &result) {
94+
static void CreateHistoryThreadFromValueObject(
95+
ProcessSP process_sp, ValueObjectSP return_value_sp, HistoryPCType pc_type,
96+
const char *type, const char *thread_name, HistoryThreads &result) {
9997
std::string count_path = "." + std::string(type) + "_count";
10098
std::string tid_path = "." + std::string(type) + "_tid";
10199
std::string trace_path = "." + std::string(type) + "_trace";
@@ -128,10 +126,6 @@ static void CreateHistoryThreadFromValueObject(ProcessSP process_sp,
128126
pcs.push_back(pc);
129127
}
130128

131-
// The ASAN runtime already massages the return addresses into call
132-
// addresses, we don't want LLDB's unwinder to try to locate the previous
133-
// instruction again as this might lead to us reporting a different line.
134-
auto pc_type = HistoryPCType::Calls;
135129
HistoryThread *history_thread =
136130
new HistoryThread(*process_sp, tid, pcs, pc_type);
137131
ThreadSP new_thread_sp(history_thread);
@@ -176,10 +170,24 @@ HistoryThreads MemoryHistoryASan::GetHistoryThreads(lldb::addr_t address) {
176170
options.SetAutoApplyFixIts(false);
177171
options.SetLanguage(eLanguageTypeObjC_plus_plus);
178172

173+
// The ASan compiler-rt runtime already massages the return addresses into
174+
// call addresses, so we don't want LLDB's unwinder to try to locate the
175+
// previous instruction again as this might lead to us reporting a different
176+
// line.
177+
auto pc_type = HistoryPCType::Calls;
178+
179179
if (auto m = GetPreferredAsanModule(process_sp->GetTarget())) {
180180
SymbolContextList sc_list;
181181
sc_list.Append(SymbolContext(std::move(m)));
182182
options.SetPreferredSymbolContexts(std::move(sc_list));
183+
} else if (process_sp->GetTarget()
184+
.GetArchitecture()
185+
.GetTriple()
186+
.isOSDarwin()) {
187+
// Darwin, but not ASan compiler-rt implies libsanitizers which collects
188+
// return addresses. It also discards a few non-user frames at the top of
189+
// the stack.
190+
pc_type = HistoryPCType::ReturnsNoZerothFrame;
183191
}
184192

185193
ExpressionResults expr_result = UserExpression::Evaluate(
@@ -197,10 +205,10 @@ HistoryThreads MemoryHistoryASan::GetHistoryThreads(lldb::addr_t address) {
197205
if (!return_value_sp)
198206
return result;
199207

200-
CreateHistoryThreadFromValueObject(process_sp, return_value_sp, "free",
201-
"Memory deallocated by", result);
202-
CreateHistoryThreadFromValueObject(process_sp, return_value_sp, "alloc",
203-
"Memory allocated by", result);
208+
CreateHistoryThreadFromValueObject(process_sp, return_value_sp, pc_type,
209+
"free", "Memory deallocated by", result);
210+
CreateHistoryThreadFromValueObject(process_sp, return_value_sp, pc_type,
211+
"alloc", "Memory allocated by", result);
204212

205213
return result;
206214
}

lldb/source/Plugins/Process/Utility/HistoryUnwind.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ HistoryUnwind::DoCreateRegisterContextForFrame(StackFrame *frame) {
5151
return rctx;
5252
}
5353

54+
static bool BehavesLikeZerothFrame(HistoryPCType pc_type, uint32_t frame_idx) {
55+
switch (pc_type) {
56+
case HistoryPCType::Returns:
57+
return (frame_idx == 0);
58+
case HistoryPCType::ReturnsNoZerothFrame:
59+
return false;
60+
case HistoryPCType::Calls:
61+
return true;
62+
}
63+
}
64+
5465
bool HistoryUnwind::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
5566
lldb::addr_t &pc,
5667
bool &behaves_like_zeroth_frame) {
@@ -60,10 +71,7 @@ bool HistoryUnwind::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
6071
if (frame_idx < m_pcs.size()) {
6172
cfa = frame_idx;
6273
pc = m_pcs[frame_idx];
63-
if (m_pc_type == HistoryPCType::Calls)
64-
behaves_like_zeroth_frame = true;
65-
else
66-
behaves_like_zeroth_frame = (frame_idx == 0);
74+
behaves_like_zeroth_frame = BehavesLikeZerothFrame(m_pc_type, frame_idx);
6775
return true;
6876
}
6977
return false;

lldb/test/API/functionalities/asan/TestMemoryHistory.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,16 @@ def setUp(self):
4141
self.line_free = line_number("main.c", "// free line")
4242
self.line_breakpoint = line_number("main.c", "// break line")
4343

44-
# Test line numbers: rdar://126237493
45-
# for libsanitizers and remove `skip_line_numbers` parameter
46-
def check_traces(self, skip_line_numbers=False):
44+
def check_traces(self):
4745
self.expect(
4846
"memory history 'pointer'",
4947
substrs=[
5048
"Memory deallocated by Thread",
5149
"a.out`f2",
52-
"main.c" if skip_line_numbers else f"main.c:{self.line_free}",
50+
f"main.c:{self.line_free}",
5351
"Memory allocated by Thread",
5452
"a.out`f1",
55-
"main.c" if skip_line_numbers else f"main.c:{self.line_malloc}",
53+
f"main.c:{self.line_malloc}",
5654
],
5755
)
5856

@@ -76,15 +74,15 @@ def libsanitizers_traces_tests(self):
7674
self.runCmd("env SanitizersAllocationTraces=all")
7775

7876
self.run_to_breakpoint(target)
79-
self.check_traces(skip_line_numbers=True)
77+
self.check_traces()
8078

8179
def libsanitizers_asan_tests(self):
8280
target = self.createTestTarget()
8381

8482
self.runCmd("env SanitizersAddress=1 MallocSanitizerZone=1")
8583

8684
self.run_to_breakpoint(target)
87-
self.check_traces(skip_line_numbers=True)
85+
self.check_traces()
8886

8987
self.runCmd("continue")
9088

@@ -94,7 +92,7 @@ def libsanitizers_asan_tests(self):
9492
"Process should be stopped due to ASan report",
9593
substrs=["stopped", "stop reason = Use of deallocated memory"],
9694
)
97-
self.check_traces(skip_line_numbers=True)
95+
self.check_traces()
9896

9997
# do the same using SB API
10098
process = self.dbg.GetSelectedTarget().process

0 commit comments

Comments
 (0)