diff --git a/lldb/include/lldb/Target/Trace.h b/lldb/include/lldb/Target/Trace.h --- a/lldb/include/lldb/Target/Trace.h +++ b/lldb/include/lldb/Target/Trace.h @@ -18,11 +18,21 @@ namespace lldb_private { +/// The kind of information that will be printed as part of a Dump action. +enum class TraceDumpCategory { + GenericInfo, + Instructions, +}; + /// Helper class that holds the configuration of a Dump action. struct TraceDumpOptions { - Process *process = nullptr; // If NULL, dump all processes + Process *process = nullptr; // Must be specified std::set tids; // Thread IDs, if empty dump all threads bool verbose = false; + TraceDumpCategory category = TraceDumpCategory::GenericInfo; + + size_t count = 10; + size_t offset = 0; }; /// \class Trace Trace.h "lldb/Target/Trace.h" diff --git a/lldb/source/Commands/CommandObjectTrace.cpp b/lldb/source/Commands/CommandObjectTrace.cpp --- a/lldb/source/Commands/CommandObjectTrace.cpp +++ b/lldb/source/Commands/CommandObjectTrace.cpp @@ -161,6 +161,28 @@ m_dump_options.tids.insert(tid); break; } + case 'i': { + m_dump_options.category = TraceDumpCategory::Instructions; + break; + } + case 'c': { + size_t count; + if (option_arg.empty() || option_arg.getAsInteger(0, count)) + error.SetErrorStringWithFormat("invalid count '%s'", + option_arg.str().c_str()); + else + m_dump_options.count = count; + break; + } + case 'o': { + size_t offset; + if (option_arg.empty() || option_arg.getAsInteger(0, offset)) + error.SetErrorStringWithFormat("invalid offset '%s'", + option_arg.str().c_str()); + else + m_dump_options.offset = offset; + break; + } default: llvm_unreachable("Unimplemented option"); } diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1177,10 +1177,18 @@ } let Command = "trace dump" in { - def trace_dump_verbose : Option<"verbose", "v">, Group<1>, + def trace_dump_verbose : Option<"verbose", "v">, Groups<[1, 2]>, Desc<"Show verbose trace information.">; - def trace_dump_thread_id : Option<"thread-id", "t">, Group<1>, - Arg<"ThreadID">, Desc<"The thread id to dump trace information of.">; + def trace_dump_thread_id : Option<"thread-id", "t">, Groups<[1,2]>, + Arg<"ThreadID">, Desc<"The thread id to dump trace information of. Defaults to the currently selected thread. Can be specified multiple times">; + def trace_dump_instructions: Option<"instructions", "i">, Group<2>, + Desc<"Display the last instructions from the trace.">; + def trace_dump_count : Option<"count", "c">, Group<2>, + Arg<"Count">, + Desc<"The number of total instructions to display.">; + def trace_dump_offset: Option<"offset", "o">, Group<2>, + Arg<"Offset">, + Desc<"Offset from the last position to start displaying instructions from. Defaults to 0.">; } let Command = "trace schema" in { diff --git a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt --- a/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ b/lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt @@ -10,6 +10,9 @@ find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) add_lldb_library(lldbPluginTraceIntelPT PLUGIN + DecodedTrace.cpp + Decoder.cpp + IntelPTThread.cpp TraceIntelPT.cpp TraceIntelPTSettingsParser.cpp diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedTrace.h b/lldb/source/Plugins/Trace/intel-pt/DecodedTrace.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedTrace.h @@ -0,0 +1,87 @@ +//===-- IntelPTThread.h -----------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_DecodedTrace_h_ +#define liblldb_DecodedTrace_h_ + +#include + +#include "lldb/lldb-private.h" + +#include "intel-pt.h" + +/// \class IntelPTInstruction +/// An instruction obtained from decoding a trace. It is either an actual +/// instruction or an error indicating a gap in the trace. +/// +/// Gaps in the trace can come in a few flavors: +/// - tracing gaps (e.g. tracing was paused and then resumed) +/// - tracing errors (e.g. buffer overflow) +/// - decoding errors (e.g. some memory region couldn't be decoded) +/// As mentioned, any gap is represented as an error in this class. +class IntelPTInstruction { +public: + IntelPTInstruction() = delete; + + IntelPTInstruction(const pt_insn &pt_insn) + : m_pt_insn(pt_insn), m_libipt_error_code(0) {} + + IntelPTInstruction(int libipt_error_code) + : m_libipt_error_code(libipt_error_code) {} + + /// Check if this object represents an error (i.e. a gap). + /// + /// \return + /// Whether this object represents an error. + bool IsError() const; + + /// Get the instruction pointer if this is not an error. + /// + /// \return + /// The instruction pointer address. + lldb::addr_t GetIP() const; + + /// Return the libipt error code. + /// + /// libipt error codes are negative numbers. + /// + /// \return + /// 0 if it is not an error, or the error value otherwise. + int GetErrorCode() const; + + /// Return a descriptive error message if this is an error. + /// + /// \return + /// The error message. + const char *GetErrorMessage() const; + +private: + pt_insn m_pt_insn; + int m_libipt_error_code; +}; + +/// \class DecodedTrace +/// Class holding the instructions obtained from decoding a trace, as well as +/// other information obtained from that process. +class DecodedTrace { +public: + DecodedTrace(std::vector instructions) + : m_instructions(std::move(instructions)) {} + + /// Get the instructions from the decoded trace. Some of them might indicate + /// errors or gaps in the trace. + /// + /// \return + /// The instructions of the trace. + const std::vector &GetInstructions() const; + +private: + std::vector m_instructions; +}; + +#endif // liblldb_DecodedTrace_h_ diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedTrace.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedTrace.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedTrace.cpp @@ -0,0 +1,23 @@ +//===-- DecodedTrace.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 "DecodedTrace.h" + +bool IntelPTInstruction::IsError() const { return m_libipt_error_code < 0; } + +lldb::addr_t IntelPTInstruction::GetIP() const { return m_pt_insn.ip; } + +int IntelPTInstruction::GetErrorCode() const { return m_libipt_error_code; } + +const char *IntelPTInstruction::GetErrorMessage() const { + return pt_errstr(pt_errcode(GetErrorCode())); +} + +const std::vector &DecodedTrace::GetInstructions() const { + return m_instructions; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/Decoder.h b/lldb/source/Plugins/Trace/intel-pt/Decoder.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/Decoder.h @@ -0,0 +1,37 @@ +//===-- Decoder.h --======---------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_Decoder_h_ +#define liblldb_Decoder_h_ + +#include "intel-pt.h" + +#include "DecodedTrace.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" + +/// Decode a trace file associated with a given process and with a given Intel +/// CPU. +/// +/// \param[in] process +/// The process associated with the trace. +/// +/// \param[in] pt_cpu +/// The libipt \a pt_cpu information of the CPU where the trace was obtained +/// from. +/// +/// \param[in] trace_file +/// The binary trace file obtained from tracing the process. +/// +/// \return +/// A \a DecodedTrace instance, or an error if decoding failed. +llvm::Expected +DecodeTraceFile(lldb_private::Process &process, const pt_cpu &pt_cpu, + const lldb_private::FileSpec &trace_file); + +#endif // liblldb_Decoder_h_ diff --git a/lldb/source/Plugins/Trace/intel-pt/Decoder.cpp b/lldb/source/Plugins/Trace/intel-pt/Decoder.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/Decoder.cpp @@ -0,0 +1,211 @@ +//===-- Decoder.cpp ---------------------------------------------*- C++ -*-===// +// +// 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 "Decoder.h" + +#include + +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Target.h" +#include "lldb/lldb-private.h" + +/// Read the bytes from a trace file +static std::vector +ReadTraceFile(const lldb_private::FileSpec &trace_file) { + char file_path[PATH_MAX]; + trace_file.GetPath(file_path, sizeof(file_path)); + std::ifstream src(file_path, std::ios::in | std::ios_base::binary); + std::vector trace((std::istreambuf_iterator(src)), + std::istreambuf_iterator()); + return trace; +} + +/// Get a list of the sections of the given process that are Read or Exec, +/// which are the only sections that could correspond to instructions traced. +std::vector static GetReadExecSections( + lldb_private::Process &process) { + lldb_private::Target &target = process.GetTarget(); + + lldb_private::ModuleList &module_list = target.GetImages(); + std::vector sections; + for (size_t i = 0; i < module_list.GetSize(); i++) { + lldb::ModuleSP module_sp = module_list.GetModuleAtIndex(i); + lldb_private::SectionList *section_list = module_sp->GetSectionList(); + for (size_t j = 0; j < section_list->GetSize(); j++) { + lldb::SectionSP section_sp = section_list->GetSectionAtIndex(j); + if (!section_sp) + continue; + + uint32_t permissions = section_sp->GetPermissions(); + if ((permissions & lldb::Permissions::ePermissionsReadable) && + (permissions & lldb::Permissions::ePermissionsExecutable)) { + sections.push_back(section_sp); + } + } + } + return sections; +} + +/// Decode all the instructions from a configured decoder. +/// The decoding flow is based on +/// https://github.com/intel/libipt/blob/master/doc/howto_libipt.md#the-instruction-flow-decode-loop +/// but with some relaxation to allow for gaps in the trace. +/// +/// \param[in] decoder +/// A configured libipt \a pt_insn_decoder. +/// +/// \param[out] instructions +/// The instructions decoded. +void DecodeInstructions(pt_insn_decoder &decoder, + std::vector &instructions) { + // Error codes returned by libipt while decoding are: + // - negative: actual errors + // - positive or zero: not an error, but a list of bits signaling the status + // of the decoder + auto handle_pt_events = [&](int errcode) -> int { + while (errcode & pts_event_pending) { + pt_event event; + errcode = pt_insn_event(&decoder, &event, sizeof(event)); + if (errcode < 0) + return errcode; + + // The list of events are in + // https://github.com/intel/libipt/blob/master/doc/man/pt_qry_event.3.md + if (event.type == ptev_overflow || + (event.type == ptev_enabled && event.variant.enabled.resumed == 0)) { + instructions.push_back(IntelPTInstruction(-pte_nosync)); + } + // Other events don't signal stream errors + } + return 0; + }; + + int errcode = 0; + while (true) { + // Try to sync the decoder. If it fails, then get + // the decoder_offset and try to sync again from + // the next synchronization point. If the + // new_decoder_offset is same as decoder_offset + // then we can't move to the next synchronization + // point. Otherwise, keep resyncing until either + // end of trace stream (eos) is reached or + // pt_insn_sync_forward() passes. + errcode = pt_insn_sync_forward(&decoder); + if (errcode == -pte_eos) + return; + + if (errcode < 0) { + // There's a gap in the trace because we + // couldn't synchronize. + instructions.push_back(IntelPTInstruction(errcode)); + + uint64_t decoder_offset = 0; + int errcode_off = pt_insn_get_offset(&decoder, &decoder_offset); + if (errcode_off < 0) { + // We can't further synchronize. + return; + } + + while (true) { + errcode = pt_insn_sync_forward(&decoder); + if (errcode >= 0) + break; + + if (errcode == -pte_eos) + return; + + uint64_t new_decoder_offset = 0; + errcode_off = pt_insn_get_offset(&decoder, &new_decoder_offset); + if (errcode_off < 0) { + // We can't further synchronize. + return; + } else if (new_decoder_offset <= decoder_offset) { + // We tried resyncing the decoder and + // decoder didn't make any progress because + // the offset didn't change. We will not + // make any progress further. Hence, + // returning in this situation. + return; + } + // We'll try again starting from a new offset. + decoder_offset = new_decoder_offset; + } + } + + // We have synchronized, so we can start decoding + // instructions and events. + while (true) { + errcode = handle_pt_events(errcode); + if (errcode < 0) { + instructions.push_back(IntelPTInstruction(errcode)); + break; + } + pt_insn insn; + errcode = pt_insn_next(&decoder, &insn, sizeof(insn)); + if (insn.iclass != ptic_error) + instructions.push_back(IntelPTInstruction(insn)); + + if (errcode == -pte_eos) + return; + + if (errcode < 0) { + instructions.push_back(IntelPTInstruction(errcode)); + break; + } + } + } +} + +/// \return +/// A libipt error code in case of failure or 0 otherwise. +int CreateDecoderAndDecode(lldb_private::Process &process, const pt_cpu &pt_cpu, + std::vector &trace, + std::vector &instructions) { + pt_config config; + pt_config_init(&config); + config.cpu = pt_cpu; + + if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) + return errcode; + + config.begin = trace.data(); + config.end = trace.data() + trace.size(); + + pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); + if (decoder == nullptr) + return pte_nomem; + + pt_image *image = pt_insn_get_image(decoder); + for (lldb::SectionSP §ion_sp : GetReadExecSections(process)) { + char path[PATH_MAX]; + section_sp->GetModule()->GetPlatformFileSpec().GetPath(path, sizeof(path)); + if (int errcode = pt_image_add_file( + image, path, section_sp->GetFileOffset(), section_sp->GetByteSize(), + nullptr, section_sp->GetLoadBaseAddress(&process.GetTarget()))) { + pt_insn_free_decoder(decoder); + return errcode; + } + } + DecodeInstructions(*decoder, instructions); + pt_insn_free_decoder(decoder); + return pte_ok; +} + +llvm::Expected +DecodeTraceFile(lldb_private::Process &process, const pt_cpu &pt_cpu, + const lldb_private::FileSpec &trace_file) { + std::vector instructions; + std::vector trace = ReadTraceFile(trace_file); + if (int errcode = + CreateDecoderAndDecode(process, pt_cpu, trace, instructions)) + return llvm::createStringError(std::errc::invalid_argument, + "Intel PT decoding error %d. '%s'", errcode, + pt_errstr(pt_errcode(errcode))); + return DecodedTrace(std::move(instructions)); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTThread.h b/lldb/source/Plugins/Trace/intel-pt/IntelPTThread.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTThread.h @@ -0,0 +1,50 @@ +//===-- IntelPTThread.h -----------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_IntelPTThread_h_ +#define liblldb_IntelPTThread_h_ + +#include "intel-pt.h" + +#include "DecodedTrace.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" + +/// \class IntelPTThread +/// Class representing a thread already traced with Intel PT. +class IntelPTThread { +public: + IntelPTThread() = delete; + + IntelPTThread(const lldb_private::FileSpec &trace_file, lldb::tid_t tid, + const pt_cpu &pt_cpu) + : m_trace_file(trace_file), m_tid(tid), m_pt_cpu(pt_cpu) {} + + const lldb_private::FileSpec &GetTraceFile() const; + + lldb::tid_t GetThreadID() const; + + /// Decode the trace and obtain an object holding all the decoded information. + /// + /// This function performs caching, so it is okay to invoke it repeteadly. + /// + /// \param[in] process + /// The process associated with the trace file. + /// + /// \return + /// The \a DecodedTrace object, or an error if decoding failed. + llvm::Expected Decode(lldb_private::Process &process); + +private: + lldb_private::FileSpec m_trace_file; + lldb::tid_t m_tid; + llvm::Optional m_decoded_trace; + pt_cpu m_pt_cpu; +}; + +#endif // liblldb_IntelPTThread_h_ diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTThread.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTThread.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTThread.cpp @@ -0,0 +1,29 @@ +//===-- IntelPTThread.cpp ---------------------------------------*- C++ -*-===// +// +// 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 "IntelPTThread.h" + +#include "Decoder.h" + +const lldb_private::FileSpec &IntelPTThread::GetTraceFile() const { + return m_trace_file; +} + +lldb::tid_t IntelPTThread::GetThreadID() const { return m_tid; } + +llvm::Expected +IntelPTThread::Decode(lldb_private::Process &process) { + if (!m_decoded_trace.hasValue()) { + if (llvm::Expected decoded_trace = + DecodeTraceFile(process, m_pt_cpu, m_trace_file)) + m_decoded_trace = std::move(*decoded_trace); + else + return decoded_trace.takeError(); + } + return *m_decoded_trace; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -12,6 +12,7 @@ #include "intel-pt.h" #include "llvm/ADT/Optional.h" +#include "IntelPTThread.h" #include "TraceIntelPTSettingsParser.h" #include "lldb/Target/Trace.h" #include "lldb/lldb-private.h" @@ -46,8 +47,15 @@ TraceIntelPTSettingsParser &GetParser() override; private: + void DumpSettingsFile(lldb_private::Stream &s) const; + const pt_cpu &GetPTCPU() const; + llvm::DenseMap> + GetThreads() const { + return m_parser.m_threads; + } + TraceIntelPTSettingsParser m_parser; }; diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -8,10 +8,13 @@ #include "TraceIntelPT.h" +#include + #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" #include "TraceIntelPTSettingsParser.h" +#include "lldb/Core/Disassembler.h" #include "lldb/Core/PluginManager.h" #include "lldb/Target/Process.h" @@ -51,36 +54,74 @@ uint32_t TraceIntelPT::GetPluginVersion() { return 1; } +void TraceIntelPT::DumpSettingsFile(Stream &s) const { + s << "Settings:\n"; + s.Indent(); + std::string str; + llvm::raw_string_ostream OS(str); + json::Object obj = GetSettings(); + OS << llvm::formatv("{0:2}", json::Value(std::move(obj))); + OS.flush(); + s << OS.str(); + s.IndentLess(); + + s << "\n\nSettings directory:\n"; + s.Indent(); + s << GetSettingsDir() << "\n\n"; + s.IndentLess(); +} + void TraceIntelPT::Dump(Stream &s, const TraceDumpOptions &options) const { - if (options.verbose) { - s << "Settings:\n"; - s.Indent(); - std::string str; - llvm::raw_string_ostream OS(str); - json::Object obj = GetSettings(); - OS << llvm::formatv("{0:2}", json::Value(std::move(obj))); - OS.flush(); - s << OS.str(); - s.IndentLess(); - - s << "\n\nSettings directory:\n"; - s.Indent(); - s << GetSettingsDir() << "\n\n"; - s.IndentLess(); + if (options.verbose) + DumpSettingsFile(s); + + if (options.category == TraceDumpCategory::GenericInfo) + s << "Trace files:"; + else if (options.category == TraceDumpCategory::Instructions) { + s << "Instructions:"; } - s << "Trace files:"; + + auto dump_instructions = [&](IntelPTThread &thread) { + if (llvm::Expected trace = + thread.Decode(*options.process)) { + const std::vector &instructions = + trace->GetInstructions(); + + int to = std::max((int)instructions.size() - (int)options.offset, 0); + int from = std::max((int)to - (int)options.count, 0); + + for (int i = to - 1; i >= from; i--) { + const IntelPTInstruction &insn = instructions[i]; + if (insn.IsError()) + s.Printf("\n error %d. '%s'", insn.GetErrorCode(), + insn.GetErrorMessage()); + else + s.Printf("\n %" PRIu64, insn.GetIP()); + } + } else { + s << "\n" << llvm::toString(trace.takeError()); + } + }; + + auto dump_thread = [&](lldb::pid_t pid, IntelPTThread &thread) { + s.Printf("\npid: '%" PRIu64 "', tid: '%" PRIu64 "'", pid, + thread.GetThreadID()); + + if (options.category == TraceDumpCategory::GenericInfo) + s << "\n " << thread.GetTraceFile(); + else if (options.category == TraceDumpCategory::Instructions) + dump_instructions(thread); + }; // We go through all the processes and threads even when there are filters as // a way to simplify the implementation. - for (const auto &process_it : GetThreadToTraceFileMap()) { - lldb::pid_t pid = process_it.first; + for (auto &pid_tid_thread : GetThreads()) { + lldb::pid_t pid = pid_tid_thread.first; if (!options.process || options.process->GetID() == pid) { - for (const auto &thread_trace_file : process_it.second) { - lldb::tid_t tid = thread_trace_file.first; - if (options.tids.empty() || options.tids.count(tid)) { - s.Printf("\npid: '%" PRIu64 "', tid: '%" PRIu64 "' -> ", pid, tid); - s << thread_trace_file.second; - } + for (auto &tid_thread : pid_tid_thread.second) { + lldb::tid_t tid = tid_thread.first; + if (options.tids.empty() || options.tids.count(tid)) + dump_thread(pid, tid_thread.second); } } } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h @@ -11,6 +11,9 @@ #include "intel-pt.h" +#include "IntelPTThread.h" +#include "llvm/ADT/DenseMap.h" + #include "lldb/Target/TraceSettingsParser.h" #include "lldb/Utility/StructuredData.h" @@ -31,6 +34,8 @@ public: pt_cpu m_pt_cpu; + llvm::DenseMap> + m_threads; }; #endif // liblldb_TraceIntelPTSettingsParser_h_ diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp @@ -58,5 +58,15 @@ m_settings.getObjectOrError("trace"); if (!trace) return trace.takeError(); - return ParsePTCPU(**trace); + if (llvm::Error err = ParsePTCPU(**trace)) + return err; + + for (const auto &process_it : m_thread_to_trace_file_map) { + lldb::pid_t pid = process_it.first; + for (const auto &thread_trace_file : process_it.second) { + lldb::tid_t tid = thread_trace_file.first; + m_threads[pid].try_emplace(tid, thread_trace_file.second, tid, m_pt_cpu); + } + } + return llvm::Error::success(); } diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -2986,6 +2986,10 @@ tid); } } + if (options.tids.empty() && m_process_sp->GetThreadList().GetSize() > 0) { + options.tids.insert( + m_process_sp->GetThreadList().GetSelectedThread()->GetID()); + } m_trace_sp->Dump(s, options); } diff --git a/lldb/source/Target/TraceSettingsParser.cpp b/lldb/source/Target/TraceSettingsParser.cpp --- a/lldb/source/Target/TraceSettingsParser.cpp +++ b/lldb/source/Target/TraceSettingsParser.cpp @@ -12,6 +12,7 @@ #include "Plugins/Process/Utility/HistoryThread.h" #include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" #include "lldb/Target/Process.h" using namespace lldb; @@ -135,7 +136,6 @@ ModuleSpec module_spec; module_spec.GetFileSpec() = local_file_spec; module_spec.GetPlatformFileSpec() = system_file_spec; - module_spec.SetObjectOffset(*load_address); llvm::Expected> uuid_str = module.getOptionalStringOrError("uuid"); @@ -147,7 +147,13 @@ Status error; ModuleSP module_sp = target_sp->GetOrCreateModule(module_spec, /*notify*/ false, &error); - return error.ToError(); + if (error.Fail()) + return error.ToError(); + + bool load_addr_changed = false; + module_sp->SetLoadAddress(*target_sp, *load_address, false, + load_addr_changed); + return llvm::Error::success(); } llvm::Error TraceSettingsParser::ParseModules(TargetSP &target_sp, diff --git a/lldb/test/API/commands/trace/TestTraceDump.py b/lldb/test/API/commands/trace/TestTraceDump.py --- a/lldb/test/API/commands/trace/TestTraceDump.py +++ b/lldb/test/API/commands/trace/TestTraceDump.py @@ -26,23 +26,76 @@ # Only the specified thread should be printed self.expect("trace dump -t 22", substrs=["Trace files"], - patterns=["pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + patterns=["pid: '2', tid: '22'\n .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) # Verbose should output the entire JSON settings besides the thread specific information self.expect("trace dump -t 22 -v", substrs=["Settings", "3842849.trace", "intel-pt", "Settings directory"], - patterns=["pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + patterns=["pid: '2', tid: '22'\n .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) - # In this case all threads should be printed + # In this case only the selected thread should be printed self.expect("trace dump", substrs=["Trace files"], - patterns=["pid: '2', tid: '21' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace", - "pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + patterns=["pid: '2', tid: '21'\n .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) self.expect("trace dump -t 21 --thread-id 22", substrs=["Trace files"], - patterns=["pid: '2', tid: '21' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace", - "pid: '2', tid: '22' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + patterns=["pid: '2', tid: '21'\n .*/test/API/commands/trace/intelpt-trace/3842849.trace", + "pid: '2', tid: '22'\n .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) # We switch targets and check the threads of this target self.dbg.SetSelectedTarget(self.dbg.FindTargetWithProcessID(1)) self.expect("trace dump", substrs=["Trace files"], - patterns=["pid: '1', tid: '11' -> .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + patterns=["pid: '1', tid: '11'\n .*/test/API/commands/trace/intelpt-trace/3842849.trace"]) + + def testDumpInstructions(self): + src_dir = self.getSourceDir() + trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace2.json") + self.expect("trace load " + trace_definition_file) + + # Dump the instructions of the currently selected thread + self.expect("trace dump -i", substrs=['''Instructions: +pid: '2', tid: '21' + 4195629 + 4195625 + 4195621 + 4195617 + 4195629 + 4195625 + 4195621 + 4195617 + 4195629 + 4195625''']) + + # Dump the instructions of one specific thread + self.expect("trace dump -i -t 22", substrs=['''Instructions: +pid: '2', tid: '22' + 4195629 + 4195625 + 4195621 + 4195617 + 4195629 + 4195625 + 4195621 + 4195617 + 4195629 + 4195625''']) + + # Dump the instructions of two threads + self.expect("trace dump -i -t 22 -t 21", substrs=[ + 'Instructions:', + "pid: '2', tid: '21'\n 4195629", + "pid: '2', tid: '22'\n 4195629" + ]) + + # Dump specific instructions using --offset and --count + self.expect("trace dump -i -t 22 -c 3 -o 3", substrs=['''Instructions: +pid: '2', tid: '22' + 4195617 + 4195629 + 4195625''']) + + # Check errors of a failed decoding + trace_definition_file = os.path.join(src_dir, "intelpt-trace", "trace_bad_image.json") + self.expect("trace load " + trace_definition_file) + self.expect("trace dump -i", substrs=['''Instructions: +pid: '1234', tid: '3842849' + error -13. 'no memory mapped at this address''']) diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json b/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json @@ -0,0 +1,31 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 6, + "model": 79, + "stepping": 1 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 3842849, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000FFFFF0", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +}