| 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 | |