1 | //===-- MemoryHistoryASan.cpp ---------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "MemoryHistoryASan.h" |
10 | |
11 | #include "lldb/Symbol/SymbolContext.h" |
12 | #include "lldb/Target/MemoryHistory.h" |
13 | |
14 | #include "Plugins/InstrumentationRuntime/Utility/Utility.h" |
15 | #include "Plugins/Process/Utility/HistoryThread.h" |
16 | #include "lldb/Core/Debugger.h" |
17 | #include "lldb/Core/Module.h" |
18 | #include "lldb/Core/PluginInterface.h" |
19 | #include "lldb/Core/PluginManager.h" |
20 | #include "lldb/Expression/UserExpression.h" |
21 | #include "lldb/Target/ExecutionContext.h" |
22 | #include "lldb/Target/Target.h" |
23 | #include "lldb/Target/Thread.h" |
24 | #include "lldb/Target/ThreadList.h" |
25 | #include "lldb/ValueObject/ValueObject.h" |
26 | #include "lldb/lldb-private.h" |
27 | |
28 | #include <sstream> |
29 | |
30 | using namespace lldb; |
31 | using namespace lldb_private; |
32 | |
33 | LLDB_PLUGIN_DEFINE(MemoryHistoryASan) |
34 | |
35 | MemoryHistorySP MemoryHistoryASan::CreateInstance(const ProcessSP &process_sp) { |
36 | if (!process_sp.get()) |
37 | return nullptr; |
38 | |
39 | Target &target = process_sp->GetTarget(); |
40 | |
41 | for (ModuleSP module_sp : target.GetImages().Modules()) { |
42 | const Symbol *symbol = module_sp->FindFirstSymbolWithNameAndType( |
43 | name: ConstString("__asan_get_alloc_stack" ), symbol_type: lldb::eSymbolTypeAny); |
44 | |
45 | if (symbol != nullptr) |
46 | return MemoryHistorySP(new MemoryHistoryASan(process_sp)); |
47 | } |
48 | |
49 | return MemoryHistorySP(); |
50 | } |
51 | |
52 | void MemoryHistoryASan::Initialize() { |
53 | PluginManager::RegisterPlugin( |
54 | name: GetPluginNameStatic(), description: "ASan memory history provider." , create_callback: CreateInstance); |
55 | } |
56 | |
57 | void MemoryHistoryASan::Terminate() { |
58 | PluginManager::UnregisterPlugin(create_callback: CreateInstance); |
59 | } |
60 | |
61 | MemoryHistoryASan::MemoryHistoryASan(const ProcessSP &process_sp) { |
62 | if (process_sp) |
63 | m_process_wp = process_sp; |
64 | } |
65 | |
66 | const char *memory_history_asan_command_prefix = R"( |
67 | extern "C" |
68 | { |
69 | size_t __asan_get_alloc_stack(void *addr, void **trace, size_t size, int *thread_id); |
70 | size_t __asan_get_free_stack(void *addr, void **trace, size_t size, int *thread_id); |
71 | } |
72 | )" ; |
73 | |
74 | const char *memory_history_asan_command_format = |
75 | R"( |
76 | struct { |
77 | void *alloc_trace[256]; |
78 | size_t alloc_count; |
79 | int alloc_tid; |
80 | |
81 | void *free_trace[256]; |
82 | size_t free_count; |
83 | int free_tid; |
84 | } t; |
85 | |
86 | t.alloc_count = __asan_get_alloc_stack((void *)0x%)" PRIx64 |
87 | R"(, t.alloc_trace, 256, &t.alloc_tid); |
88 | t.free_count = __asan_get_free_stack((void *)0x%)" PRIx64 |
89 | R"(, t.free_trace, 256, &t.free_tid); |
90 | |
91 | t; |
92 | )" ; |
93 | |
94 | static void CreateHistoryThreadFromValueObject(ProcessSP process_sp, |
95 | ValueObjectSP return_value_sp, |
96 | const char *type, |
97 | const char *thread_name, |
98 | HistoryThreads &result) { |
99 | std::string count_path = "." + std::string(type) + "_count" ; |
100 | std::string tid_path = "." + std::string(type) + "_tid" ; |
101 | std::string trace_path = "." + std::string(type) + "_trace" ; |
102 | |
103 | ValueObjectSP count_sp = |
104 | return_value_sp->GetValueForExpressionPath(expression: count_path.c_str()); |
105 | ValueObjectSP tid_sp = |
106 | return_value_sp->GetValueForExpressionPath(expression: tid_path.c_str()); |
107 | |
108 | if (!count_sp || !tid_sp) |
109 | return; |
110 | |
111 | int count = count_sp->GetValueAsUnsigned(fail_value: 0); |
112 | lldb::tid_t tid = tid_sp->GetValueAsUnsigned(fail_value: 0) + 1; |
113 | |
114 | if (count <= 0) |
115 | return; |
116 | |
117 | ValueObjectSP trace_sp = |
118 | return_value_sp->GetValueForExpressionPath(expression: trace_path.c_str()); |
119 | |
120 | if (!trace_sp) |
121 | return; |
122 | |
123 | std::vector<lldb::addr_t> pcs; |
124 | for (int i = 0; i < count; i++) { |
125 | addr_t pc = trace_sp->GetChildAtIndex(idx: i)->GetValueAsUnsigned(fail_value: 0); |
126 | if (pc == 0 || pc == 1 || pc == LLDB_INVALID_ADDRESS) |
127 | continue; |
128 | pcs.push_back(x: pc); |
129 | } |
130 | |
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 | bool pcs_are_call_addresses = true; |
135 | HistoryThread *history_thread = |
136 | new HistoryThread(*process_sp, tid, pcs, pcs_are_call_addresses); |
137 | ThreadSP new_thread_sp(history_thread); |
138 | std::ostringstream thread_name_with_number; |
139 | thread_name_with_number << thread_name << " Thread " << tid; |
140 | history_thread->SetThreadName(thread_name_with_number.str().c_str()); |
141 | // Save this in the Process' ExtendedThreadList so a strong pointer retains |
142 | // the object |
143 | process_sp->GetExtendedThreadList().AddThread(thread_sp: new_thread_sp); |
144 | result.push_back(x: new_thread_sp); |
145 | } |
146 | |
147 | HistoryThreads MemoryHistoryASan::GetHistoryThreads(lldb::addr_t address) { |
148 | HistoryThreads result; |
149 | |
150 | ProcessSP process_sp = m_process_wp.lock(); |
151 | if (!process_sp) |
152 | return result; |
153 | |
154 | ThreadSP thread_sp = |
155 | process_sp->GetThreadList().GetExpressionExecutionThread(); |
156 | if (!thread_sp) |
157 | return result; |
158 | |
159 | StackFrameSP frame_sp = |
160 | thread_sp->GetSelectedFrame(select_most_relevant: DoNoSelectMostRelevantFrame); |
161 | if (!frame_sp) |
162 | return result; |
163 | |
164 | ExecutionContext exe_ctx(frame_sp); |
165 | ValueObjectSP return_value_sp; |
166 | StreamString expr; |
167 | expr.Printf(format: memory_history_asan_command_format, address, address); |
168 | |
169 | EvaluateExpressionOptions options; |
170 | options.SetUnwindOnError(true); |
171 | options.SetTryAllThreads(true); |
172 | options.SetStopOthers(true); |
173 | options.SetIgnoreBreakpoints(true); |
174 | options.SetTimeout(process_sp->GetUtilityExpressionTimeout()); |
175 | options.SetPrefix(memory_history_asan_command_prefix); |
176 | options.SetAutoApplyFixIts(false); |
177 | options.SetLanguage(eLanguageTypeObjC_plus_plus); |
178 | |
179 | if (auto m = GetPreferredAsanModule(target: process_sp->GetTarget())) { |
180 | SymbolContextList sc_list; |
181 | sc_list.Append(sc: SymbolContext(std::move(m))); |
182 | options.SetPreferredSymbolContexts(std::move(sc_list)); |
183 | } |
184 | |
185 | ExpressionResults expr_result = UserExpression::Evaluate( |
186 | exe_ctx, options, expr_cstr: expr.GetString(), expr_prefix: "" , result_valobj_sp&: return_value_sp); |
187 | if (expr_result != eExpressionCompleted) { |
188 | StreamString ss; |
189 | ss << "cannot evaluate AddressSanitizer expression:\n" ; |
190 | if (return_value_sp) |
191 | ss << return_value_sp->GetError().AsCString(); |
192 | Debugger::ReportWarning(message: ss.GetString().str(), |
193 | debugger_id: process_sp->GetTarget().GetDebugger().GetID()); |
194 | return result; |
195 | } |
196 | |
197 | if (!return_value_sp) |
198 | return result; |
199 | |
200 | CreateHistoryThreadFromValueObject(process_sp, return_value_sp, type: "free" , |
201 | thread_name: "Memory deallocated by" , result); |
202 | CreateHistoryThreadFromValueObject(process_sp, return_value_sp, type: "alloc" , |
203 | thread_name: "Memory allocated by" , result); |
204 | |
205 | return result; |
206 | } |
207 | |