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