-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[lldb] Fix source line annotations for libsanitizers traces #154247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-lldb Author: Julian Lettner (yln) ChangesWhen providing allocation and deallocation traces, On Darwin, system sanitizers (libsanitizers) Introduce and add handling for rdar://157596927 Commit 1 is a mechanical refactoring to introduce
Best reviewed commit by commit. Full diff: https://github.com/llvm/llvm-project/pull/154247.diff 10 Files Affected:
diff --git a/lldb/include/lldb/lldb-private-enumerations.h b/lldb/include/lldb/lldb-private-enumerations.h
index 98c1e956bf8f7..604945011e5d5 100644
--- a/lldb/include/lldb/lldb-private-enumerations.h
+++ b/lldb/include/lldb/lldb-private-enumerations.h
@@ -248,6 +248,8 @@ enum class IterationAction {
Stop,
};
+enum class TracePCType { Returns, ReturnsNoZerothFrame, Calls };
+
inline std::string GetStatDescription(lldb_private::StatisticKind K) {
switch (K) {
case StatisticKind::ExpressionSuccessful:
diff --git a/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.cpp b/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.cpp
index e67e60b4a3957..3c817f9171d7b 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.cpp
+++ b/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.cpp
@@ -266,9 +266,9 @@ InstrumentationRuntimeMainThreadChecker::GetBacktracesFromExtendedStopInfo(
// We gather symbolication addresses above, so no need for HistoryThread to
// try to infer the call addresses.
- bool pcs_are_call_addresses = true;
- ThreadSP new_thread_sp = std::make_shared<HistoryThread>(
- *process_sp, tid, PCs, pcs_are_call_addresses);
+ auto pc_type = TracePCType::Calls;
+ ThreadSP new_thread_sp =
+ std::make_shared<HistoryThread>(*process_sp, tid, PCs, pc_type);
// Save this in the Process' ExtendedThreadList so a strong pointer retains
// the object
diff --git a/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.cpp b/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.cpp
index c2db3540a797b..57a705715f1a0 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.cpp
+++ b/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.cpp
@@ -324,9 +324,9 @@ InstrumentationRuntimeUBSan::GetBacktracesFromExtendedStopInfo(
// We gather symbolication addresses above, so no need for HistoryThread to
// try to infer the call addresses.
- bool pcs_are_call_addresses = true;
- ThreadSP new_thread_sp = std::make_shared<HistoryThread>(
- *process_sp, tid, PCs, pcs_are_call_addresses);
+ auto pc_type = TracePCType::Calls;
+ ThreadSP new_thread_sp =
+ std::make_shared<HistoryThread>(*process_sp, tid, PCs, pc_type);
std::string stop_reason_description = GetStopReasonDescription(info);
new_thread_sp->SetName(stop_reason_description.c_str());
diff --git a/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp b/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp
index afaaa57b09587..9401673676d79 100644
--- a/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp
+++ b/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp
@@ -91,11 +91,9 @@ const char *memory_history_asan_command_format =
t;
)";
-static void CreateHistoryThreadFromValueObject(ProcessSP process_sp,
- ValueObjectSP return_value_sp,
- const char *type,
- const char *thread_name,
- HistoryThreads &result) {
+static void CreateHistoryThreadFromValueObject(
+ ProcessSP process_sp, ValueObjectSP return_value_sp, TracePCType pc_type,
+ const char *type, const char *thread_name, HistoryThreads &result) {
std::string count_path = "." + std::string(type) + "_count";
std::string tid_path = "." + std::string(type) + "_tid";
std::string trace_path = "." + std::string(type) + "_trace";
@@ -128,12 +126,8 @@ static void CreateHistoryThreadFromValueObject(ProcessSP process_sp,
pcs.push_back(pc);
}
- // The ASAN runtime already massages the return addresses into call
- // addresses, we don't want LLDB's unwinder to try to locate the previous
- // instruction again as this might lead to us reporting a different line.
- bool pcs_are_call_addresses = true;
HistoryThread *history_thread =
- new HistoryThread(*process_sp, tid, pcs, pcs_are_call_addresses);
+ new HistoryThread(*process_sp, tid, pcs, pc_type);
ThreadSP new_thread_sp(history_thread);
std::ostringstream thread_name_with_number;
thread_name_with_number << thread_name << " Thread " << tid;
@@ -176,10 +170,23 @@ HistoryThreads MemoryHistoryASan::GetHistoryThreads(lldb::addr_t address) {
options.SetAutoApplyFixIts(false);
options.SetLanguage(eLanguageTypeObjC_plus_plus);
- if (auto m = GetPreferredAsanModule(process_sp->GetTarget())) {
- SymbolContextList sc_list;
- sc_list.Append(SymbolContext(std::move(m)));
- options.SetPreferredSymbolContexts(std::move(sc_list));
+ // The ASan compiler-rt runtime already massages the return addresses into
+ // call addresses, so we don't want LLDB's unwinder to try to locate the
+ // previous instruction again as this might lead to us reporting a different
+ // line.
+ auto pc_type = TracePCType::Calls;
+
+ if (process_sp->GetTarget().GetArchitecture().GetTriple().isOSDarwin()) {
+ if (auto m = GetPreferredAsanModule(process_sp->GetTarget())) {
+ SymbolContextList sc_list;
+ sc_list.Append(SymbolContext(std::move(m)));
+ options.SetPreferredSymbolContexts(std::move(sc_list));
+ } else {
+ // Darwin, but not ASan compiler-rt implies libsanitizers which collects
+ // return addresses. It also discards a few non-user frames at the top of
+ // the stack.
+ pc_type = TracePCType::ReturnsNoZerothFrame;
+ }
}
ExpressionResults expr_result = UserExpression::Evaluate(
@@ -197,10 +204,10 @@ HistoryThreads MemoryHistoryASan::GetHistoryThreads(lldb::addr_t address) {
if (!return_value_sp)
return result;
- CreateHistoryThreadFromValueObject(process_sp, return_value_sp, "free",
- "Memory deallocated by", result);
- CreateHistoryThreadFromValueObject(process_sp, return_value_sp, "alloc",
- "Memory allocated by", result);
+ CreateHistoryThreadFromValueObject(process_sp, return_value_sp, pc_type,
+ "free", "Memory deallocated by", result);
+ CreateHistoryThreadFromValueObject(process_sp, return_value_sp, pc_type,
+ "alloc", "Memory allocated by", result);
return result;
}
diff --git a/lldb/source/Plugins/Process/Utility/HistoryThread.cpp b/lldb/source/Plugins/Process/Utility/HistoryThread.cpp
index bc06757c806a9..b5d2395f978a8 100644
--- a/lldb/source/Plugins/Process/Utility/HistoryThread.cpp
+++ b/lldb/source/Plugins/Process/Utility/HistoryThread.cpp
@@ -26,14 +26,12 @@ using namespace lldb_private;
// Constructor
HistoryThread::HistoryThread(lldb_private::Process &process, lldb::tid_t tid,
- std::vector<lldb::addr_t> pcs,
- bool pcs_are_call_addresses)
+ std::vector<lldb::addr_t> pcs, TracePCType pc_type)
: Thread(process, tid, true), m_framelist_mutex(), m_framelist(),
m_pcs(pcs), m_extended_unwind_token(LLDB_INVALID_ADDRESS), m_queue_name(),
m_thread_name(), m_originating_unique_thread_id(tid),
m_queue_id(LLDB_INVALID_QUEUE_ID) {
- m_unwinder_up =
- std::make_unique<HistoryUnwind>(*this, pcs, pcs_are_call_addresses);
+ m_unwinder_up = std::make_unique<HistoryUnwind>(*this, pcs, pc_type);
Log *log = GetLog(LLDBLog::Object);
LLDB_LOGF(log, "%p HistoryThread::HistoryThread", static_cast<void *>(this));
}
diff --git a/lldb/source/Plugins/Process/Utility/HistoryThread.h b/lldb/source/Plugins/Process/Utility/HistoryThread.h
index a66e0f2d4207c..2f6c48518672a 100644
--- a/lldb/source/Plugins/Process/Utility/HistoryThread.h
+++ b/lldb/source/Plugins/Process/Utility/HistoryThread.h
@@ -27,14 +27,14 @@ namespace lldb_private {
/// process execution
///
/// This subclass of Thread is used to provide a backtrace from earlier in
-/// process execution. It is given a backtrace list of pc addresses and it
-/// will create stack frames for them.
+/// process execution. It is given a backtrace list of pcs (return or call
+/// addresses) and it will create stack frames for them.
class HistoryThread : public lldb_private::Thread {
public:
HistoryThread(lldb_private::Process &process, lldb::tid_t tid,
std::vector<lldb::addr_t> pcs,
- bool pcs_are_call_addresses = false);
+ TracePCType pc_type = TracePCType::Returns);
~HistoryThread() override;
diff --git a/lldb/source/Plugins/Process/Utility/HistoryUnwind.cpp b/lldb/source/Plugins/Process/Utility/HistoryUnwind.cpp
index 7749dc6f5d514..3754e4320eaf5 100644
--- a/lldb/source/Plugins/Process/Utility/HistoryUnwind.cpp
+++ b/lldb/source/Plugins/Process/Utility/HistoryUnwind.cpp
@@ -24,9 +24,8 @@ using namespace lldb_private;
// Constructor
HistoryUnwind::HistoryUnwind(Thread &thread, std::vector<lldb::addr_t> pcs,
- bool pcs_are_call_addresses)
- : Unwind(thread), m_pcs(pcs),
- m_pcs_are_call_addresses(pcs_are_call_addresses) {}
+ TracePCType pc_type)
+ : Unwind(thread), m_pcs(pcs), m_pc_type(pc_type) {}
// Destructor
@@ -52,6 +51,17 @@ HistoryUnwind::DoCreateRegisterContextForFrame(StackFrame *frame) {
return rctx;
}
+static bool BehavesLikeZerothFrame(TracePCType pc_type, uint32_t frame_idx) {
+ switch (pc_type) {
+ case TracePCType::Returns:
+ return (frame_idx == 0);
+ case TracePCType::ReturnsNoZerothFrame:
+ return false;
+ case TracePCType::Calls:
+ return true;
+ }
+}
+
bool HistoryUnwind::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
lldb::addr_t &pc,
bool &behaves_like_zeroth_frame) {
@@ -61,10 +71,7 @@ bool HistoryUnwind::DoGetFrameInfoAtIndex(uint32_t frame_idx, lldb::addr_t &cfa,
if (frame_idx < m_pcs.size()) {
cfa = frame_idx;
pc = m_pcs[frame_idx];
- if (m_pcs_are_call_addresses)
- behaves_like_zeroth_frame = true;
- else
- behaves_like_zeroth_frame = (frame_idx == 0);
+ behaves_like_zeroth_frame = BehavesLikeZerothFrame(m_pc_type, frame_idx);
return true;
}
return false;
diff --git a/lldb/source/Plugins/Process/Utility/HistoryUnwind.h b/lldb/source/Plugins/Process/Utility/HistoryUnwind.h
index cb72b5d0a1764..95d1716f39776 100644
--- a/lldb/source/Plugins/Process/Utility/HistoryUnwind.h
+++ b/lldb/source/Plugins/Process/Utility/HistoryUnwind.h
@@ -19,7 +19,7 @@ namespace lldb_private {
class HistoryUnwind : public lldb_private::Unwind {
public:
HistoryUnwind(Thread &thread, std::vector<lldb::addr_t> pcs,
- bool pcs_are_call_addresses = false);
+ TracePCType pc_type = TracePCType::Returns);
~HistoryUnwind() override;
@@ -36,9 +36,7 @@ class HistoryUnwind : public lldb_private::Unwind {
private:
std::vector<lldb::addr_t> m_pcs;
- /// This boolean indicates that the PCs in the non-0 frames are call
- /// addresses and not return addresses.
- bool m_pcs_are_call_addresses;
+ TracePCType m_pc_type;
};
} // namespace lldb_private
diff --git a/lldb/source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp b/lldb/source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
index b23f64210cc80..a20f04b78492c 100644
--- a/lldb/source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
+++ b/lldb/source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
@@ -544,9 +544,9 @@ ThreadSP SystemRuntimeMacOSX::GetExtendedBacktraceThread(ThreadSP real_thread,
if (!thread_extended_info->ForEach(extract_frame_pc))
return {};
- originating_thread_sp =
- std::make_shared<HistoryThread>(*m_process, real_thread->GetIndexID(),
- app_specific_backtrace_pcs, true);
+ originating_thread_sp = std::make_shared<HistoryThread>(
+ *m_process, real_thread->GetIndexID(), app_specific_backtrace_pcs,
+ TracePCType::Calls);
originating_thread_sp->SetQueueName(type.AsCString());
}
return originating_thread_sp;
diff --git a/lldb/test/API/functionalities/asan/TestMemoryHistory.py b/lldb/test/API/functionalities/asan/TestMemoryHistory.py
index 1568140b355dc..66f6e3e7502c1 100644
--- a/lldb/test/API/functionalities/asan/TestMemoryHistory.py
+++ b/lldb/test/API/functionalities/asan/TestMemoryHistory.py
@@ -41,18 +41,16 @@ def setUp(self):
self.line_free = line_number("main.c", "// free line")
self.line_breakpoint = line_number("main.c", "// break line")
- # Test line numbers: rdar://126237493
- # for libsanitizers and remove `skip_line_numbers` parameter
- def check_traces(self, skip_line_numbers=False):
+ def check_traces(self):
self.expect(
"memory history 'pointer'",
substrs=[
"Memory deallocated by Thread",
"a.out`f2",
- "main.c" if skip_line_numbers else f"main.c:{self.line_free}",
+ f"main.c:{self.line_free}",
"Memory allocated by Thread",
"a.out`f1",
- "main.c" if skip_line_numbers else f"main.c:{self.line_malloc}",
+ f"main.c:{self.line_malloc}",
],
)
@@ -76,7 +74,7 @@ def libsanitizers_traces_tests(self):
self.runCmd("env SanitizersAllocationTraces=all")
self.run_to_breakpoint(target)
- self.check_traces(skip_line_numbers=True)
+ self.check_traces()
def libsanitizers_asan_tests(self):
target = self.createTestTarget()
@@ -84,7 +82,7 @@ def libsanitizers_asan_tests(self):
self.runCmd("env SanitizersAddress=1 MallocSanitizerZone=1")
self.run_to_breakpoint(target)
- self.check_traces(skip_line_numbers=True)
+ self.check_traces()
self.runCmd("continue")
@@ -94,7 +92,7 @@ def libsanitizers_asan_tests(self):
"Process should be stopped due to ASan report",
substrs=["stopped", "stop reason = Use of deallocated memory"],
)
- self.check_traces(skip_line_numbers=True)
+ self.check_traces()
# do the same using SB API
process = self.dbg.GetSelectedTarget().process
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both commits make sense to me.
Refactor of commit 1 looks good.
Logic + documentation make sense for commit 2.
Tests make sense.
Looks good.
@@ -248,6 +248,8 @@ enum class IterationAction { | |||
Stop, | |||
}; | |||
|
|||
enum class TracePCType { Returns, ReturnsNoZerothFrame, Calls }; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
if (process_sp->GetTarget().GetArchitecture().GetTriple().isOSDarwin()) { | ||
if (auto m = GetPreferredAsanModule(process_sp->GetTarget())) { | ||
SymbolContextList sc_list; | ||
sc_list.Append(SymbolContext(std::move(m))); | ||
options.SetPreferredSymbolContexts(std::move(sc_list)); | ||
} else { | ||
// Darwin, but not ASan compiler-rt implies libsanitizers which collects | ||
// return addresses. It also discards a few non-user frames at the top of | ||
// the stack. | ||
pc_type = TracePCType::ReturnsNoZerothFrame; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
cba1c0f
to
6ba21cb
Compare
/// - Some trace providers (e.g., ASan compiler-rt runtime) already perform this | ||
/// mapping, so we need to prevent LLDB from doing it again. | ||
/// - Other trace providers (e.g., libsanitizers traces) collect return | ||
/// addresses but prune the topmost frames, so we should skip the special | ||
/// treatment of frame 0. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
I'd wait a day or so before merging so @JDevlieghere or @jasonmolenda can chime in.
/// - Other trace providers (e.g., libsanitizers traces) collect return | ||
/// addresses but prune the topmost frames, so we should skip the special | ||
/// treatment of frame 0. | ||
enum class TracePCType { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
6ba21cb
to
d9157e0
Compare
/// - Other trace providers (e.g., libsanitizers traces) collect return | ||
/// addresses but prune the topmost frames, so we should skip the special | ||
/// treatment of frame 0. | ||
enum class TracePCType { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
if (auto m = GetPreferredAsanModule(process_sp->GetTarget())) { | ||
SymbolContextList sc_list; | ||
sc_list.Append(SymbolContext(std::move(m))); | ||
options.SetPreferredSymbolContexts(std::move(sc_list)); | ||
} else if (process_sp->GetTarget() | ||
.GetArchitecture() | ||
.GetTriple() | ||
.isOSDarwin()) { | ||
// Darwin, but not ASan compiler-rt implies libsanitizers which collects | ||
// return addresses. It also discards a few non-user frames at the top of | ||
// the stack. | ||
pc_type = TracePCType::ReturnsNoZerothFrame; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Introduce and adopt `HistoryPCType` enum to replace `pcs_are_call_addresses` bool to make handling of history back traces more clear and allow for extension of behavior. This change is a mechanical refactoring and preservers current behavior: ``` pcs_are_call_addresses: false -> HistoryPCType::Returns (default) true -> HistoryPCType::Calls ``` rdar://157596927
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
d9157e0
to
32ec465
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
When providing allocation and deallocation traces,
the ASan compiler-rt runtime already provides call
addresses (
TracePCType::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
TracePCType::ReturnsNoZerothFrame
to cover thiscase and enable libsanitizers traces line-level
testing.
rdar://157596927
Commit 1 is a mechanical refactoring to introduce
and adopt
TracePCType
enum to replacepcs_are_call_addresses
bool. It preserve thecurrent behavior:
Best reviewed commit by commit.