//===-- Trace.cpp ---------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Target/Trace.h" #include "llvm/Support/Format.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/Symbol/Function.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Thread.h" #include "lldb/Target/ThreadPostMortemTrace.h" #include "lldb/Utility/Stream.h" using namespace lldb; using namespace lldb_private; using namespace llvm; // Helper structs used to extract the type of a trace session json without // having to parse the entire object. struct JSONSimplePluginSettings { std::string type; }; struct JSONSimpleTraceSession { JSONSimplePluginSettings trace; }; namespace llvm { namespace json { bool fromJSON(const Value &value, JSONSimplePluginSettings &plugin_settings, Path path) { json::ObjectMapper o(value, path); return o && o.map("type", plugin_settings.type); } bool fromJSON(const Value &value, JSONSimpleTraceSession &session, Path path) { json::ObjectMapper o(value, path); return o && o.map("trace", session.trace); } } // namespace json } // namespace llvm static Error createInvalidPlugInError(StringRef plugin_name) { return createStringError( std::errc::invalid_argument, "no trace plug-in matches the specified type: \"%s\"", plugin_name.data()); } Expected Trace::FindPluginForPostMortemProcess(Debugger &debugger, const json::Value &trace_session_file, StringRef session_file_dir) { JSONSimpleTraceSession json_session; json::Path::Root root("traceSession"); if (!json::fromJSON(trace_session_file, json_session, root)) return root.getError(); ConstString plugin_name(json_session.trace.type); if (auto create_callback = PluginManager::GetTraceCreateCallback(plugin_name)) return create_callback(trace_session_file, session_file_dir, debugger); return createInvalidPlugInError(json_session.trace.type); } Expected Trace::FindPluginForLiveProcess(llvm::StringRef plugin_name, Process &process) { if (!process.IsLiveDebugSession()) return createStringError(inconvertibleErrorCode(), "Can't trace non-live processes"); ConstString name(plugin_name); if (auto create_callback = PluginManager::GetTraceCreateCallbackForLiveProcess(name)) return create_callback(process); return createInvalidPlugInError(plugin_name); } Expected Trace::FindPluginSchema(StringRef name) { ConstString plugin_name(name); StringRef schema = PluginManager::GetTraceSchema(plugin_name); if (!schema.empty()) return schema; return createInvalidPlugInError(name); } static int GetNumberOfDigits(size_t num) { return num == 0 ? 1 : static_cast(log10(num)) + 1; } /// \return /// \b true if the provided line entries match line, column and source file. /// This function assumes that the line entries are valid. static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) { if (a.line != b.line) return false; if (a.column != b.column) return false; return a.file == b.file; } // This custom LineEntry validator is neded because some line_entries have // 0 as line, which is meaningless. Notice that LineEntry::IsValid only // checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX. static bool IsLineEntryValid(const LineEntry &line_entry) { return line_entry.IsValid() && line_entry.line > 0; } /// Helper structure for \a TraverseInstructionsWithSymbolInfo. struct InstructionSymbolInfo { SymbolContext sc; Address address; lldb::addr_t load_address; lldb::DisassemblerSP disassembler; lldb::InstructionSP instruction; lldb_private::ExecutionContext exe_ctx; }; /// InstructionSymbolInfo object with symbol information for the given /// instruction, calculated efficiently. /// /// \param[in] symbol_scope /// If not \b 0, then the \a InstructionSymbolInfo will have its /// SymbolContext calculated up to that level. /// /// \param[in] include_disassembler /// If \b true, then the \a InstructionSymbolInfo will have the /// \a disassembler and \a instruction objects calculated. static void TraverseInstructionsWithSymbolInfo( Trace &trace, Thread &thread, size_t position, Trace::TraceDirection direction, SymbolContextItem symbol_scope, bool include_disassembler, std::function insn)> callback) { InstructionSymbolInfo prev_insn; Target &target = thread.GetProcess()->GetTarget(); ExecutionContext exe_ctx; target.CalculateExecutionContext(exe_ctx); const ArchSpec &arch = target.GetArchitecture(); // Find the symbol context for the given address reusing the previous // instruction's symbol context when possible. auto calculate_symbol_context = [&](const Address &address) { AddressRange range; if (prev_insn.sc.GetAddressRange(symbol_scope, 0, /*inline_block_range*/ false, range) && range.Contains(address)) return prev_insn.sc; SymbolContext sc; address.CalculateSymbolContext(&sc, symbol_scope); return sc; }; // Find the disassembler for the given address reusing the previous // instruction's disassembler when possible. auto calculate_disass = [&](const Address &address, const SymbolContext &sc) { if (prev_insn.disassembler) { if (InstructionSP instruction = prev_insn.disassembler->GetInstructionList() .GetInstructionAtAddress(address)) return std::make_tuple(prev_insn.disassembler, instruction); } if (sc.function) { if (DisassemblerSP disassembler = sc.function->GetInstructions(exe_ctx, nullptr)) { if (InstructionSP instruction = disassembler->GetInstructionList().GetInstructionAtAddress( address)) return std::make_tuple(disassembler, instruction); } } // We fallback to a single instruction disassembler AddressRange range(address, arch.GetMaximumOpcodeByteSize()); DisassemblerSP disassembler = Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, /*flavor*/ nullptr, target, range); return std::make_tuple(disassembler, disassembler ? disassembler->GetInstructionList() .GetInstructionAtAddress(address) : InstructionSP()); }; trace.TraverseInstructions( thread, position, direction, [&](size_t index, Expected load_address) -> bool { if (!load_address) return callback(index, load_address.takeError()); InstructionSymbolInfo insn; insn.load_address = *load_address; insn.exe_ctx = exe_ctx; insn.address.SetLoadAddress(*load_address, &target); if (symbol_scope != 0) insn.sc = calculate_symbol_context(insn.address); if (include_disassembler) std::tie(insn.disassembler, insn.instruction) = calculate_disass(insn.address, insn.sc); prev_insn = insn; return callback(index, insn); }); } /// Compare the symbol contexts of the provided \a InstructionSymbolInfo /// objects. /// /// \return /// \a true if both instructions belong to the same scope level analized /// in the following order: /// - module /// - symbol /// - function /// - line static bool IsSameInstructionSymbolContext(const InstructionSymbolInfo &prev_insn, const InstructionSymbolInfo &insn) { // module checks if (insn.sc.module_sp != prev_insn.sc.module_sp) return false; // symbol checks if (insn.sc.symbol != prev_insn.sc.symbol) return false; // function checks if (!insn.sc.function && !prev_insn.sc.function) return true; else if (insn.sc.function != prev_insn.sc.function) return false; // line entry checks const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry); const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry); if (curr_line_valid && prev_line_valid) return FileLineAndColumnMatches(insn.sc.line_entry, prev_insn.sc.line_entry); return curr_line_valid == prev_line_valid; } /// Dump the symbol context of the given instruction address if it's different /// from the symbol context of the previous instruction in the trace. /// /// \param[in] prev_sc /// The symbol context of the previous instruction in the trace. /// /// \param[in] address /// The address whose symbol information will be dumped. /// /// \return /// The symbol context of the current address, which might differ from the /// previous one. static void DumpInstructionSymbolContext(Stream &s, Optional prev_insn, InstructionSymbolInfo &insn) { if (prev_insn && IsSameInstructionSymbolContext(*prev_insn, insn)) return; s.Printf(" "); if (!insn.sc.module_sp) s.Printf("(none)"); else if (!insn.sc.function && !insn.sc.symbol) s.Printf("%s`(none)", insn.sc.module_sp->GetFileSpec().GetFilename().AsCString()); else insn.sc.DumpStopContext(&s, insn.exe_ctx.GetTargetPtr(), insn.address, /*show_fullpath*/ false, /*show_module*/ true, /*show_inlined_frames*/ false, /*show_function_arguments*/ true, /*show_function_name*/ true); s.Printf("\n"); } static void DumpInstructionDisassembly(Stream &s, InstructionSymbolInfo &insn) { if (!insn.instruction) return; s.Printf(" "); insn.instruction->Dump(&s, /*show_address*/ false, /*show_bytes*/ false, /*max_opcode_byte_size*/ 0, &insn.exe_ctx, &insn.sc, /*prev_sym_ctx*/ nullptr, /*disassembly_addr_format*/ nullptr, /*max_address_text_size*/ 0); } void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, size_t end_position, bool raw) { Optional instructions_count = GetInstructionCount(thread); if (!instructions_count) { s.Printf("thread #%u: tid = %" PRIu64 ", not traced\n", thread.GetIndexID(), thread.GetID()); return; } s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", thread.GetIndexID(), thread.GetID(), *instructions_count); if (count == 0 || end_position >= *instructions_count) return; int digits_count = GetNumberOfDigits(end_position); size_t start_position = end_position + 1 < count ? 0 : end_position + 1 - count; auto printInstructionIndex = [&](size_t index) { s.Printf(" [%*zu] ", digits_count, index); }; bool was_prev_instruction_an_error = false; Optional prev_insn; TraverseInstructionsWithSymbolInfo( *this, thread, start_position, TraceDirection::Forwards, eSymbolContextEverything, /*disassembler*/ true, [&](size_t index, Expected insn) -> bool { if (!insn) { printInstructionIndex(index); s << toString(insn.takeError()); prev_insn = None; was_prev_instruction_an_error = true; } else { if (was_prev_instruction_an_error) s.Printf(" ...missing instructions\n"); if (!raw) DumpInstructionSymbolContext(s, prev_insn, *insn); printInstructionIndex(index); s.Printf("0x%016" PRIx64, insn->load_address); if (!raw) DumpInstructionDisassembly(s, *insn); prev_insn = *insn; was_prev_instruction_an_error = false; } s.Printf("\n"); return index < end_position; }); } Error Trace::Start(const llvm::json::Value &request) { if (!m_live_process) return createStringError(inconvertibleErrorCode(), "Tracing requires a live process."); return m_live_process->TraceStart(request); } Error Trace::StopProcess() { if (!m_live_process) return createStringError(inconvertibleErrorCode(), "Tracing requires a live process."); return m_live_process->TraceStop( TraceStopRequest(GetPluginName().AsCString())); } Error Trace::StopThreads(const std::vector &tids) { if (!m_live_process) return createStringError(inconvertibleErrorCode(), "Tracing requires a live process."); return m_live_process->TraceStop( TraceStopRequest(GetPluginName().AsCString(), tids)); } Expected Trace::GetLiveProcessState() { if (!m_live_process) return createStringError(inconvertibleErrorCode(), "Tracing requires a live process."); return m_live_process->TraceGetState(GetPluginName().AsCString()); } Optional Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, llvm::StringRef kind) { auto it = m_live_thread_data.find(tid); if (it == m_live_thread_data.end()) return None; std::unordered_map &single_thread_data = it->second; auto single_thread_data_it = single_thread_data.find(kind.str()); if (single_thread_data_it == single_thread_data.end()) return None; return single_thread_data_it->second; } Optional Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { auto data_it = m_live_process_data.find(kind.str()); if (data_it == m_live_process_data.end()) return None; return data_it->second; } Expected> Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { if (!m_live_process) return createStringError(inconvertibleErrorCode(), "Tracing requires a live process."); llvm::Optional size = GetLiveThreadBinaryDataSize(tid, kind); if (!size) return createStringError( inconvertibleErrorCode(), "Tracing data \"%s\" is not available for thread %" PRIu64 ".", kind.data(), tid); TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), static_cast(tid), 0, static_cast(*size)}; return m_live_process->TraceGetBinaryData(request); } Expected> Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { if (!m_live_process) return createStringError(inconvertibleErrorCode(), "Tracing requires a live process."); llvm::Optional size = GetLiveProcessBinaryDataSize(kind); if (!size) return createStringError( inconvertibleErrorCode(), "Tracing data \"%s\" is not available for the process.", kind.data()); TraceGetBinaryDataRequest request{GetPluginName().AsCString(), kind.str(), None, 0, static_cast(*size)}; return m_live_process->TraceGetBinaryData(request); } void Trace::RefreshLiveProcessState() { if (!m_live_process) return; uint32_t new_stop_id = m_live_process->GetStopID(); if (new_stop_id == m_stop_id) return; m_stop_id = new_stop_id; m_live_thread_data.clear(); Expected json_string = GetLiveProcessState(); if (!json_string) { DoRefreshLiveProcessState(json_string.takeError()); return; } Expected live_process_state = json::parse(*json_string, "TraceGetStateResponse"); if (!live_process_state) { DoRefreshLiveProcessState(live_process_state.takeError()); return; } for (const TraceThreadState &thread_state : live_process_state->tracedThreads) { for (const TraceBinaryData &item : thread_state.binaryData) m_live_thread_data[thread_state.tid][item.kind] = item.size; } for (const TraceBinaryData &item : live_process_state->processBinaryData) m_live_process_data[item.kind] = item.size; DoRefreshLiveProcessState(std::move(live_process_state)); }