wallace created this revision. wallace added a reviewer: clayborg. Herald added subscribers: lldb-commits, dang, aaron.ballman, mgorny. Herald added a reviewer: JDevlieghere. Herald added a project: LLDB. wallace requested review of this revision.
Depends on D86670 <https://reviews.llvm.org/D86670>. This adds the necessary logic that uses libipt (https://github.com/intel/libipt) to decode Intel PT traces. The basic usage is as follows: $ trace load /path/to/trace/settings/file.json $ trace dump -i [-t TID] $ pid: '2', tid: '22' $ 4195629 $ 4195625 $ 4195621 ... If no TID is specified, the currently selected thread ID of the current target is used. Corresponding tests were added. Note that this diff is just printing the instructions without any additional information, unlike the "disassemble" command. This will be done in a future diff. Also, in a future diff I'll be analyzing each instruction and reconstructing the stack traces. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D87589 Files: lldb/include/lldb/Target/Trace.h lldb/source/Commands/CommandObjectTrace.cpp lldb/source/Commands/Options.td lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt lldb/source/Plugins/Trace/intel-pt/DecodedTrace.cpp lldb/source/Plugins/Trace/intel-pt/DecodedTrace.h lldb/source/Plugins/Trace/intel-pt/Decoder.cpp lldb/source/Plugins/Trace/intel-pt/Decoder.h lldb/source/Plugins/Trace/intel-pt/IntelPTThread.cpp lldb/source/Plugins/Trace/intel-pt/IntelPTThread.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h lldb/source/Target/Target.cpp lldb/source/Target/TraceSettingsParser.cpp lldb/test/API/commands/trace/TestTraceDump.py lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json
Index: lldb/test/API/commands/trace/intelpt-trace/trace_bad_image.json =================================================================== --- /dev/null +++ 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" + } + ] + } + ] +} Index: lldb/test/API/commands/trace/TestTraceDump.py =================================================================== --- lldb/test/API/commands/trace/TestTraceDump.py +++ 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''']) Index: lldb/source/Target/TraceSettingsParser.cpp =================================================================== --- lldb/source/Target/TraceSettingsParser.cpp +++ 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<llvm::Optional<StringRef>> 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, Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ 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); } Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.h +++ 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<lldb::pid_t, llvm::DenseMap<lldb::tid_t, IntelPTThread>> + m_threads; }; #endif // liblldb_TraceIntelPTSettingsParser_h_ Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSettingsParser.cpp +++ 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(); } Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ 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<lldb::pid_t, llvm::DenseMap<lldb::tid_t, IntelPTThread>> + GetThreads() const { + return m_parser.m_threads; + } + TraceIntelPTSettingsParser m_parser; }; Index: lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp =================================================================== --- lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -8,10 +8,13 @@ #include "TraceIntelPT.h" +#include <algorithm> + #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<const DecodedTrace &> trace = + thread.Decode(*options.process)) { + const std::vector<IntelPTInstruction> &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); } } } Index: lldb/source/Plugins/Trace/intel-pt/IntelPTThread.h =================================================================== --- /dev/null +++ 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<const DecodedTrace &> Decode(lldb_private::Process &process); + +private: + lldb_private::FileSpec m_trace_file; + lldb::tid_t m_tid; + llvm::Optional<DecodedTrace> m_decoded_trace; + pt_cpu m_pt_cpu; +}; + +#endif // liblldb_IntelPTThread_h_ Index: lldb/source/Plugins/Trace/intel-pt/IntelPTThread.cpp =================================================================== --- /dev/null +++ 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<const DecodedTrace &> +IntelPTThread::Decode(lldb_private::Process &process) { + if (!m_decoded_trace.hasValue()) { + if (llvm::Expected<DecodedTrace> 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; +} Index: lldb/source/Plugins/Trace/intel-pt/Decoder.h =================================================================== --- /dev/null +++ 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<DecodedTrace> +DecodeTraceFile(lldb_private::Process &process, const pt_cpu &pt_cpu, + const lldb_private::FileSpec &trace_file); + +#endif // liblldb_Decoder_h_ Index: lldb/source/Plugins/Trace/intel-pt/Decoder.cpp =================================================================== --- /dev/null +++ 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 <fstream> + +#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<uint8_t> +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<uint8_t> trace((std::istreambuf_iterator<char>(src)), + std::istreambuf_iterator<char>()); + 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<lldb::SectionSP> static GetReadExecSections( + lldb_private::Process &process) { + lldb_private::Target &target = process.GetTarget(); + + lldb_private::ModuleList &module_list = target.GetImages(); + std::vector<lldb::SectionSP> 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<IntelPTInstruction> &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<uint8_t> &trace, + std::vector<IntelPTInstruction> &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<DecodedTrace> +DecodeTraceFile(lldb_private::Process &process, const pt_cpu &pt_cpu, + const lldb_private::FileSpec &trace_file) { + std::vector<IntelPTInstruction> instructions; + std::vector<uint8_t> 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)); +} Index: lldb/source/Plugins/Trace/intel-pt/DecodedTrace.h =================================================================== --- /dev/null +++ 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 <vector> + +#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<IntelPTInstruction> 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<IntelPTInstruction> &GetInstructions() const; + +private: + std::vector<IntelPTInstruction> m_instructions; +}; + +#endif // liblldb_DecodedTrace_h_ Index: lldb/source/Plugins/Trace/intel-pt/DecodedTrace.cpp =================================================================== --- /dev/null +++ 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<IntelPTInstruction> &DecodedTrace::GetInstructions() const { + return m_instructions; +} Index: lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Trace/intel-pt/CMakeLists.txt +++ 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 Index: lldb/source/Commands/Options.td =================================================================== --- lldb/source/Commands/Options.td +++ 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 { Index: lldb/source/Commands/CommandObjectTrace.cpp =================================================================== --- lldb/source/Commands/CommandObjectTrace.cpp +++ 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"); } Index: lldb/include/lldb/Target/Trace.h =================================================================== --- lldb/include/lldb/Target/Trace.h +++ 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<lldb::tid_t> 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"
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits