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 @@ -35,6 +35,29 @@ class Trace : public PluginInterface, public std::enable_shared_from_this { public: + /// Basic instruction gotten from a trace. + /// + /// Some instructions might represent errors (i.e. gaps) in the trace, as + /// there can be collection or reconstruction problems. Thus, users of this + /// class should always check if the instruction corresponds to an error or + /// not. + struct Instruction { + virtual ~Instruction() = default; + + /// \return + /// \b true if this instruction corresponds to an error, or \b false + /// otherwise. + virtual bool IsError() const = 0; + + /// \return + /// An error message if this instructions corresponds to an error. + virtual llvm::StringRef GetErrorMessage() const = 0; + + /// \return + /// The load address of this instruction if it is not an error. + virtual lldb::addr_t GetLoadAddress() const = 0; + }; + /// Dump the trace data that this plug-in has access to. /// /// This function will dump all of the trace data for all threads in a user @@ -117,7 +140,57 @@ /// \param[in] start_position /// The position of the first instruction to print. void DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const; + size_t start_position); + + /// Run the provided callback on the instructions of the trace of the given + /// thread. + /// + /// The instructions will be traversed starting at the \a from position + /// sequentially until the callback returns \b false, in which case no more + /// instructions are inspected. + /// + /// The purpose of this method is to allow inspecting traced instructions + /// without exposing the internal representation of how they are stored on + /// memory. + /// + /// \param[in] thread + /// The thread whose trace will be traversed. + /// + /// \param[in] callback + /// The callback to execute on each instruction. If it returns \b true for + /// the \a i-th instruction, then the instruction with index \a i + 1 will + /// be inspected. If it returns \b false, no more instructions will be + /// inspected. + /// + /// \param[in] from + /// The first index to execute the callback on. + virtual void ForEachInstruction( + const Thread &thread, + std::function callback, + size_t from = 0) = 0; + + /// Get the number of instructions of the trace of the given thread. + /// + /// \param[in] thread + /// The thread whose trace will be inspected. + /// + /// \return + /// The total number of instructions of the trace. + virtual size_t GetInstructionCount(const Thread &thread) = 0; + + /// Get any errors in case the trace of the given thread can't be + /// reconstructed. + /// + /// If this returns an actual error, then no instructions are available in the + /// trace. + /// + /// \param[in] thread + /// The thread whose trace will be inspected. + /// + /// \return + /// An \a llvm::Error::success instance if the trace can be reconstructed, + /// or an actual Error in case of failures. + virtual llvm::Error IsTraceFailed(const Thread &thread) = 0; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/TraceSessionFileParser.h b/lldb/include/lldb/Target/TraceSessionFileParser.h --- a/lldb/include/lldb/Target/TraceSessionFileParser.h +++ b/lldb/include/lldb/Target/TraceSessionFileParser.h @@ -61,9 +61,10 @@ }; /// \} - TraceSessionFileParser(llvm::StringRef session_file_dir, + TraceSessionFileParser(Debugger &debugger, llvm::StringRef session_file_dir, llvm::StringRef schema) - : m_session_file_dir(session_file_dir), m_schema(schema) {} + : m_debugger(debugger), m_session_file_dir(session_file_dir), + m_schema(schema) {} /// Build the full schema for a Trace plug-in. /// @@ -80,6 +81,10 @@ /// modifies the given file_spec. void NormalizePath(lldb_private::FileSpec &file_spec); + void ParseThread(lldb::ProcessSP &process_sp, const JSONThread &thread); + + llvm::Expected ParseProcess(const JSONProcess &process); + llvm::Error ParseModule(lldb::TargetSP &target_sp, const JSONModule &module); /// Create a user-friendly error message upon a JSON-parsing failure using the @@ -96,6 +101,7 @@ llvm::Error CreateJSONError(llvm::json::Path::Root &root, const llvm::json::Value &value); + Debugger &m_debugger; std::string m_session_file_dir; llvm::StringRef m_schema; }; diff --git a/lldb/include/lldb/Target/TraceThread.h b/lldb/include/lldb/Target/TraceThread.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/TraceThread.h @@ -0,0 +1,59 @@ +//===-- TraceThread.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 LLDB_TARGET_TRACETHREAD_H +#define LLDB_TARGET_TRACETHREAD_H + +#include "lldb/Target/Thread.h" + +namespace lldb_private { + +/// \class TraceThread TraceThread.h +/// +/// Thread implementation used for representing threads gotten from trace +/// session files, which are similar to threads from core files. +/// +/// See \a TraceSessionFileParser for more information regarding trace session +/// files. +class TraceThread : public Thread { +public: + /// \param[in] process + /// The process who owns this thread. + /// + /// \param[in] tid + /// The tid of this thread. + /// + /// \param[in] trace_file. + /// The file that contains the list of instructions that were traced when + /// this thread was being executed. + TraceThread(Process &process, lldb::tid_t tid, const FileSpec trace_file) + : Thread(process, tid), m_trace_file(trace_file) {} + + void RefreshStateAfterStop() override; + + lldb::RegisterContextSP GetRegisterContext() override; + + lldb::RegisterContextSP + CreateRegisterContextForFrame(StackFrame *frame) override; + + /// \return + /// The trace file of this thread. + const FileSpec &GetTraceFile() const; + +protected: + bool CalculateStopInfo() override; + + lldb::RegisterContextSP m_thread_reg_ctx_sp; + +private: + FileSpec m_trace_file; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_TRACETHREAD_H diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -228,6 +228,7 @@ class ThreadSpec; class Trace; class TraceSessionFileParser; +class TraceThread; class TraceOptions; class Type; class TypeAndOrName; 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,9 +10,10 @@ find_library(LIBIPT_LIBRARY ipt PATHS ${LIBIPT_LIBRARY_PATH} REQUIRED) add_lldb_library(lldbPluginTraceIntelPT PLUGIN + DecodedThread.cpp + IntelPTDecoder.cpp TraceIntelPT.cpp TraceIntelPTSessionFileParser.cpp - ThreadIntelPT.cpp LINK_LIBS lldbCore diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -0,0 +1,93 @@ +//===-- DecodedThread.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 LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H + +#include + +#include "lldb/Target/Trace.h" + +#include "intel-pt.h" + +namespace lldb_private { +namespace trace_intel_pt { + +/// \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 Trace::Instruction { +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 override; + + /// Get the instruction pointer if this is not an error. + /// + /// \return + /// The instruction pointer address. + lldb::addr_t GetLoadAddress() const override; + + /// 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. + llvm::StringRef GetErrorMessage() const override; + +private: + pt_insn m_pt_insn; + int m_libipt_error_code; +}; + +/// \class DecodedThread +/// Class holding the instructions and function call hierarchy obtained from +/// decoding a trace. +class DecodedThread { +public: + DecodedThread(std::vector &&instructions) + : m_instructions(instructions) {} + + /// Get the instructions from the decoded trace. Some of them might indicate + /// errors (i.e. gaps) in the trace. + /// + /// \return + /// The instructions of the trace. + const std::vector &GetInstructions() const; + +private: + std::vector m_instructions; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -0,0 +1,26 @@ +//===-- DecodedThread.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 "DecodedThread.h" + +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; + +bool IntelPTInstruction::IsError() const { return m_libipt_error_code < 0; } + +lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } + +int IntelPTInstruction::GetErrorCode() const { return m_libipt_error_code; } + +llvm::StringRef IntelPTInstruction::GetErrorMessage() const { + return pt_errstr(pt_errcode(GetErrorCode())); +} + +const std::vector &DecodedThread::GetInstructions() const { + return m_instructions; +} diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -0,0 +1,78 @@ +//===-- IntelPTDecoder.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 LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H + +#include "intel-pt.h" + +#include "DecodedThread.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class IntelPTDecoder { +public: + /// \param[in] process + /// The process associated with the traces to decode. + /// + /// \param[in] pt_cpu + /// The libipt \a pt_cpu information of the CPU where the traces were + /// recorded. + IntelPTDecoder(Process &process, const pt_cpu &pt_cpu) + : m_process(process), m_pt_cpu(pt_cpu) {} + + /// Decode a single-thread trace file. + /// + /// \param[in] trace_file + /// The binary trace file obtained from tracing a thread. + /// + /// \return + /// A \a DecodedTrace instance, or an error if decoding failed. + llvm::Expected + DecodeSingleThreadTraceFile(const FileSpec &trace_file); + +private: + Process &m_process; + pt_cpu m_pt_cpu; +}; + +/// \a lldb_private::TraceThread decoder that stores the output from decoding, +/// avoiding recomputations, as decoding is expensive. +class TraceThreadDecoder { +public: + /// \param[in] trace_thread + /// The thread whose trace file will be decoded. + /// + /// \param[in] pt_cpu + /// The libipt cpu used when recording the trace. + TraceThreadDecoder(const std::shared_ptr &trace_thread, + const pt_cpu &pt_cpu) + : m_trace_thread(trace_thread), m_pt_cpu(pt_cpu), m_decoded_thread() {} + + /// Decode the thread and store the result internally. + /// + /// \return + /// A \a DecodedThread instance or an error in case of failures. + llvm::Expected Decode(); + +private: + TraceThreadDecoder(const TraceThreadDecoder &other) = delete; + + std::shared_ptr m_trace_thread; + pt_cpu m_pt_cpu; + llvm::Optional m_decoded_thread; + llvm::Optional m_error_message; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODER_H diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -0,0 +1,244 @@ +//===-- IntelPTDecoder.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 "IntelPTDecoder.h" + +#include + +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/TraceThread.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +/// Read the bytes from a trace file +static std::vector ReadTraceFile(const 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(Process &process) { + Target &target = process.GetTarget(); + + ModuleList &module_list = target.GetImages(); + std::vector sections; + for (size_t i = 0; i < module_list.GetSize(); i++) { + ModuleSP module_sp = module_list.GetModuleAtIndex(i); + SectionList *section_list = module_sp->GetSectionList(); + for (size_t j = 0; j < section_list->GetSize(); j++) { + SectionSP section_sp = section_list->GetSectionAtIndex(j); + if (!section_sp) + continue; + + uint32_t permissions = section_sp->GetPermissions(); + if ((permissions & Permissions::ePermissionsReadable) && + (permissions & 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) + instructions.push_back(IntelPTInstruction(-pte_overflow)); + else if (event.type == ptev_enabled && + event.variant.enabled.resumed == 0 && !instructions.empty()) { + /// This means that tracing is enabled from a different IP where it + /// stopped before + instructions.push_back(IntelPTInstruction(-pte_noip)); + } + // 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; + } + } + } +} + +/// \param[out] instructions +/// Container were decoded instructions will be stored. +/// +/// \return +/// A libipt error code in case of failure or 0 otherwise. +int CreateDecoderAndDecode(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 (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); + // We'll need the instructions in reverse order chronologically, so we + // reverse them now + std::reverse(instructions.begin(), instructions.end()); + + pt_insn_free_decoder(decoder); + return pte_ok; +} + +Expected +IntelPTDecoder::DecodeSingleThreadTraceFile(const FileSpec &trace_file) { + std::vector instructions; + std::vector trace = ReadTraceFile(trace_file); + if (int errcode = + CreateDecoderAndDecode(m_process, m_pt_cpu, trace, instructions)) + return createStringError(std::errc::invalid_argument, + "Intel PT decoding error %d: '%s'", errcode, + pt_errstr(pt_errcode(errcode))); + return DecodedThread(std::move(instructions)); +} + +Expected TraceThreadDecoder::Decode() { + if (!m_decoded_thread.hasValue() && !m_error_message.hasValue()) { + if (llvm::Expected decoded_thread = + IntelPTDecoder(*m_trace_thread->GetProcess(), m_pt_cpu) + .DecodeSingleThreadTraceFile(m_trace_thread->GetTraceFile())) + m_decoded_thread = *decoded_thread; + else + // We create a copy of the error message, as we'll create a new instance + // of llvm::Error whenever this function is invoked. We have to do this + // because llvm::Error is only movable, not copyable. + m_error_message = llvm::toString(decoded_thread.takeError()); + } + + if (m_decoded_thread.hasValue()) + return *m_decoded_thread; + else + return llvm::createStringError(std::errc::invalid_argument, + m_error_message->c_str()); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.h deleted file mode 100644 --- a/lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.h +++ /dev/null @@ -1,54 +0,0 @@ -//===-- ThreadIntelPT.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 LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_THREADINTELPT_H -#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_THREADINTELPT_H - -#include "lldb/Target/Thread.h" - -namespace lldb_private { -namespace trace_intel_pt { - -class ThreadIntelPT : public Thread { -public: - /// Create an Intel PT-traced thread. - /// - /// \param[in] process - /// The process that owns this thread. - /// - /// \param[in] tid - /// The thread id of this thread. - /// - /// \param[in] trace_file - /// The trace file for this thread. - /// - /// \param[in] pt_cpu - /// The Intel CPU information required to decode the \a trace_file. - ThreadIntelPT(Process &process, lldb::tid_t tid, const FileSpec &trace_file) - : Thread(process, tid), m_trace_file(trace_file) {} - - void RefreshStateAfterStop() override; - - lldb::RegisterContextSP GetRegisterContext() override; - - lldb::RegisterContextSP - CreateRegisterContextForFrame(StackFrame *frame) override; - -protected: - bool CalculateStopInfo() override; - - lldb::RegisterContextSP m_thread_reg_ctx_sp; - -private: - FileSpec m_trace_file; -}; - -} // namespace trace_intel_pt -} // namespace lldb_private - -#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_THREADINTELPT_H 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 @@ -9,12 +9,8 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H -#include "intel-pt.h" -#include "llvm/ADT/Optional.h" - +#include "IntelPTDecoder.h" #include "TraceIntelPTSessionFileParser.h" -#include "lldb/Target/Trace.h" -#include "lldb/lldb-private.h" namespace lldb_private { namespace trace_intel_pt { @@ -52,21 +48,6 @@ CreateInstance(const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir, Debugger &debugger); - /// Create an instance of this class. - /// - /// \param[in] pt_cpu - /// The libipt.h cpu information needed for decoding correctling the - /// traces. - /// - /// \param[in] targets - /// The list of targets to associate with this trace instance - /// - /// \return - /// An intel-pt trace instance. - static lldb::TraceSP - CreateInstance(const pt_cpu &pt_cpu, - const std::vector &targets); - static ConstString GetPluginNameStatic(); uint32_t GetPluginVersion() override; @@ -74,15 +55,42 @@ llvm::StringRef GetSchema() override; + const pt_cpu &GetIntelPTCPU(); + + void ForEachInstruction( + const Thread &thread, + std::function callback, + size_t from = 0) override; + + size_t GetInstructionCount(const Thread &thread) override; + + llvm::Error IsTraceFailed(const Thread &thread) override; + private: - TraceIntelPT(const pt_cpu &pt_cpu, const std::vector &targets) - : m_pt_cpu(pt_cpu) { - for (const lldb::TargetSP &target_sp : targets) - m_targets.push_back(target_sp); - } + friend class TraceIntelPTSessionFileParser; + + /// \param[in] trace_threads + /// TraceThread instances, which are not live-processes and whose trace + /// files are fixed. + TraceIntelPT(const pt_cpu &pt_cpu, + const std::vector> &traced_threads); + + /// Decode the trace of the given thread that, i.e. recontruct the traced + /// instructions. That trace must be managed by this class. + /// + /// \param[in] thread + /// If \a thread is a \a TraceThread, then its internal trace file will be + /// decoded. Live threads are not currently supported. + /// + /// \return + /// A \a DecodedThread instance if decoding was successful, or an error if + /// the thread's trace is not managed by this class or if the trace couldn't + /// be decoded. + llvm::Expected Decode(const Thread &thread); pt_cpu m_pt_cpu; - std::vector> m_targets; + std::map, TraceThreadDecoder> + m_trace_threads; }; } // namespace trace_intel_pt 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 @@ -11,6 +11,7 @@ #include "TraceIntelPTSessionFileParser.h" #include "lldb/Core/PluginManager.h" #include "lldb/Target/Target.h" +#include "lldb/Target/TraceThread.h" using namespace lldb; using namespace lldb_private; @@ -56,11 +57,57 @@ .Parse(); } -TraceSP TraceIntelPT::CreateInstance(const pt_cpu &pt_cpu, - const std::vector &targets) { - TraceSP trace_instance(new TraceIntelPT(pt_cpu, targets)); - for (const TargetSP &target_sp : targets) - target_sp->SetTrace(trace_instance); +TraceIntelPT::TraceIntelPT( + const pt_cpu &pt_cpu, + const std::vector> &traced_threads) + : m_pt_cpu(pt_cpu) { + for (const std::shared_ptr &thread : traced_threads) + m_trace_threads.emplace( + std::piecewise_construct, + std::forward_as_tuple( + std::make_pair(thread->GetProcess()->GetID(), thread->GetID())), + std::forward_as_tuple(thread, pt_cpu)); +} + +const pt_cpu &TraceIntelPT::GetIntelPTCPU() { return m_pt_cpu; } + +Expected TraceIntelPT::Decode(const Thread &thread) { + auto it = m_trace_threads.find( + std::make_pair(thread.GetProcess()->GetID(), thread.GetID())); + if (it == m_trace_threads.end()) + return createStringError(std::errc::invalid_argument, + "The thread %" PRIu64 " is not traced.", + thread.GetID()); + + return it->second.Decode(); +} + +void TraceIntelPT::ForEachInstruction( + const Thread &thread, + std::function callback, + size_t from) { + Expected decoded_thread = Decode(thread); + if (!decoded_thread) + return; + + const std::vector &instructions = + decoded_thread->GetInstructions(); + for (size_t i = from; i < instructions.size(); i++) { + if (!callback(i, instructions[i])) + break; + } +} + +size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { + if (Expected decoded_thread = Decode(thread)) + return decoded_thread->GetInstructions().size(); + else + return 0; +} - return trace_instance; +Error TraceIntelPT::IsTraceFailed(const Thread &thread) { + if (Expected decoded_thread = Decode(thread)) + return Error::success(); + else + return decoded_thread.takeError(); } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h @@ -9,8 +9,6 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H -#include "intel-pt.h" - #include "TraceIntelPT.h" #include "lldb/Target/TraceSessionFileParser.h" @@ -38,8 +36,8 @@ TraceIntelPTSessionFileParser(Debugger &debugger, const llvm::json::Value &trace_session_file, llvm::StringRef session_file_dir) - : TraceSessionFileParser(session_file_dir, GetSchema()), - m_debugger(debugger), m_trace_session_file(trace_session_file) {} + : TraceSessionFileParser(debugger, session_file_dir, GetSchema()), + m_trace_session_file(trace_session_file) {} /// \return /// The JSON schema for the session data. @@ -53,24 +51,14 @@ /// errors, return a null pointer. llvm::Expected Parse(); -private: - llvm::Error ParseImpl(); - - llvm::Error ParseProcess(const TraceSessionFileParser::JSONProcess &process); + lldb::TraceSP + CreateTraceIntelPTInstance(const pt_cpu &pt_cpu, + std::vector &targets); - void ParseThread(lldb::ProcessSP &process_sp, - const TraceSessionFileParser::JSONThread &thread); - - void ParsePTCPU(const JSONPTCPU &pt_cpu); +private: + pt_cpu ParsePTCPU(const JSONPTCPU &pt_cpu); - Debugger &m_debugger; const llvm::json::Value &m_trace_session_file; - - /// Objects created as product of the parsing - /// \{ - pt_cpu m_pt_cpu; - std::vector m_targets; - /// \} }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -8,10 +8,10 @@ #include "TraceIntelPTSessionFileParser.h" -#include "ThreadIntelPT.h" #include "lldb/Core/Debugger.h" -#include "lldb/Target/Process.h" #include "lldb/Target/Target.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Target/TraceThread.h" using namespace lldb; using namespace lldb_private; @@ -34,88 +34,54 @@ return schema; } -void TraceIntelPTSessionFileParser::ParseThread( - ProcessSP &process_sp, const TraceSessionFileParser::JSONThread &thread) { - lldb::tid_t tid = static_cast(thread.tid); - - FileSpec trace_file(thread.trace_file); - NormalizePath(trace_file); - - ThreadSP thread_sp = - std::make_shared(*process_sp, tid, trace_file); - process_sp->GetThreadList().AddThread(thread_sp); +pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { + return {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, + static_cast(pt_cpu.family), + static_cast(pt_cpu.model), + static_cast(pt_cpu.stepping)}; } -Error TraceIntelPTSessionFileParser::ParseProcess( - const TraceSessionFileParser::JSONProcess &process) { - TargetSP target_sp; - Status error = m_debugger.GetTargetList().CreateTarget( - m_debugger, /*user_exe_path*/ StringRef(), process.triple, - eLoadDependentsNo, - /*platform_options*/ nullptr, target_sp); - - if (!target_sp) - return error.ToError(); - - m_targets.push_back(target_sp); - m_debugger.GetTargetList().SetSelectedTarget(target_sp.get()); - - ProcessSP process_sp(target_sp->CreateProcess( - /*listener*/ nullptr, "trace", - /*crash_file*/ nullptr)); - process_sp->SetID(static_cast(process.pid)); - - for (const TraceSessionFileParser::JSONThread &thread : process.threads) - ParseThread(process_sp, thread); - - for (const TraceSessionFileParser::JSONModule &module : process.modules) { - if (Error err = ParseModule(target_sp, module)) - return err; +TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance( + const pt_cpu &pt_cpu, std::vector &targets) { + std::vector> threads; + for (TargetSP &target_sp : targets) { + ThreadList &thread_list = target_sp->GetProcessSP()->GetThreadList(); + for (size_t i = 0; i < thread_list.GetSize(); i++) { + // The top-level parser creates TraceThreads, so this is safe + threads.push_back(std::static_pointer_cast( + thread_list.GetThreadAtIndex(i))); + } } - if (!process.threads.empty()) - process_sp->GetThreadList().SetSelectedThreadByIndexID(0); - - // We invoke DidAttach to create a correct stopped state for the process and - // its threads. - ArchSpec process_arch; - process_sp->DidAttach(process_arch); - - return llvm::Error::success(); -} + TraceSP trace_instance(new TraceIntelPT(pt_cpu, threads)); + for (const TargetSP &target_sp : targets) + target_sp->SetTrace(trace_instance); -void TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { - m_pt_cpu = {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, - static_cast(pt_cpu.family), - static_cast(pt_cpu.model), - static_cast(pt_cpu.stepping)}; + return trace_instance; } -Error TraceIntelPTSessionFileParser::ParseImpl() { +Expected TraceIntelPTSessionFileParser::Parse() { json::Path::Root root("traceSession"); TraceSessionFileParser::JSONTraceSession session; - if (!json::fromJSON(m_trace_session_file, session, root)) { + if (!json::fromJSON(m_trace_session_file, session, root)) return CreateJSONError(root, m_trace_session_file); - } - - ParsePTCPU(session.trace.pt_cpu); - for (const TraceSessionFileParser::JSONProcess &process : session.processes) { - if (Error err = ParseProcess(process)) - return err; - } - return Error::success(); -} -Expected TraceIntelPTSessionFileParser::Parse() { - if (Error err = ParseImpl()) { - // Delete all targets that were created - for (auto target_sp : m_targets) + std::vector targets; + auto onError = [this, &targets]() { + // Delete all targets that were created so far in case of failures + for (TargetSP &target_sp : targets) m_debugger.GetTargetList().DeleteTarget(target_sp); - m_targets.clear(); - return std::move(err); - } + }; - return TraceIntelPT::CreateInstance(m_pt_cpu, m_targets); + for (const TraceSessionFileParser::JSONProcess &process : session.processes) { + if (Expected target_sp = ParseProcess(process)) + targets.push_back(*target_sp); + else { + onError(); + return target_sp.takeError(); + } + } + return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.pt_cpu), targets); } namespace llvm { diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -67,6 +67,7 @@ ThreadSpec.cpp Trace.cpp TraceSessionFileParser.cpp + TraceThread.cpp UnixSignals.cpp UnwindAssembly.cpp UnwindLLDB.cpp diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -42,6 +42,7 @@ #include "lldb/Target/ThreadPlanStepThrough.h" #include "lldb/Target/ThreadPlanStepUntil.h" #include "lldb/Target/ThreadSpec.h" +#include "lldb/Target/Trace.h" #include "lldb/Target/UnwindLLDB.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegularExpression.h" @@ -51,6 +52,7 @@ #include "lldb/lldb-enumerations.h" #include +#include using namespace lldb; using namespace lldb_private; diff --git a/lldb/source/Target/Trace.cpp b/lldb/source/Target/Trace.cpp --- a/lldb/source/Target/Trace.cpp +++ b/lldb/source/Target/Trace.cpp @@ -8,8 +8,6 @@ #include "lldb/Target/Trace.h" -#include - #include "llvm/Support/Format.h" #include "lldb/Core/PluginManager.h" @@ -79,10 +77,40 @@ return createInvalidPlugInError(name); } +static int GetNumberOfDigits(size_t num) { + int digits_count = 0; + do { + digits_count++; + num /= 10; + } while (num); + return digits_count; +} + void Trace::DumpTraceInstructions(Thread &thread, Stream &s, size_t count, - size_t start_position) const { - s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = 1000\n", - thread.GetIndexID(), thread.GetID()); - s.Printf(" would print %zu instructions from position %zu\n", count, - start_position); + size_t start_position) { + size_t instruction_count = GetInstructionCount(thread); + s.Printf("thread #%u: tid = %" PRIu64 ", total instructions = %zu\n", + thread.GetIndexID(), thread.GetID(), instruction_count); + + int digits_count = + GetNumberOfDigits(std::min(instruction_count, start_position + count)); + + if (Error err = IsTraceFailed(thread)) + s.Printf(" %s\n", llvm::toString(std::move(err)).c_str()); + + ForEachInstruction(thread, + [&](size_t index, const Trace::Instruction &insn) -> bool { + if (index >= start_position + count) + return false; + + s.Printf(" [%*zu]", digits_count, index); + if (insn.IsError()) + s.Printf(" %s", insn.GetErrorMessage().data()); + else + s.Printf(" 0x%" PRIx64, insn.GetLoadAddress()); + s.Printf("\n"); + + return true; + }, + start_position); } diff --git a/lldb/source/Target/TraceSessionFileParser.cpp b/lldb/source/Target/TraceSessionFileParser.cpp --- a/lldb/source/Target/TraceSessionFileParser.cpp +++ b/lldb/source/Target/TraceSessionFileParser.cpp @@ -10,8 +10,11 @@ #include +#include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" +#include "lldb/Target/Process.h" #include "lldb/Target/Target.h" +#include "lldb/Target/TraceThread.h" using namespace lldb; using namespace lldb_private; @@ -34,7 +37,6 @@ ModuleSpec module_spec; module_spec.GetFileSpec() = local_file_spec; module_spec.GetPlatformFileSpec() = system_file_spec; - module_spec.SetObjectOffset(module.load_address.value); if (module.uuid.hasValue()) module_spec.GetUUID().SetFromStringRef(*module.uuid); @@ -42,7 +44,14 @@ 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, module.load_address.value, false, + load_addr_changed); + return llvm::Error::success(); } Error TraceSessionFileParser::CreateJSONError(json::Path::Root &root, @@ -87,6 +96,55 @@ return schema_builder.str(); } +void TraceSessionFileParser::ParseThread(ProcessSP &process_sp, + const JSONThread &thread) { + lldb::tid_t tid = static_cast(thread.tid); + + FileSpec trace_file(thread.trace_file); + NormalizePath(trace_file); + + ThreadSP thread_sp = + std::make_shared(*process_sp, tid, trace_file); + process_sp->GetThreadList().AddThread(thread_sp); +} + +Expected +TraceSessionFileParser::ParseProcess(const JSONProcess &process) { + TargetSP target_sp; + Status error = m_debugger.GetTargetList().CreateTarget( + m_debugger, /*user_exe_path*/ StringRef(), process.triple, + eLoadDependentsNo, + /*platform_options*/ nullptr, target_sp); + + if (!target_sp) + return error.ToError(); + + m_debugger.GetTargetList().SetSelectedTarget(target_sp.get()); + + ProcessSP process_sp(target_sp->CreateProcess( + /*listener*/ nullptr, "trace", + /*crash_file*/ nullptr)); + process_sp->SetID(static_cast(process.pid)); + + for (const JSONThread &thread : process.threads) + ParseThread(process_sp, thread); + + for (const JSONModule &module : process.modules) { + if (Error err = ParseModule(target_sp, module)) + return std::move(err); + } + + if (!process.threads.empty()) + process_sp->GetThreadList().SetSelectedThreadByIndexID(0); + + // We invoke DidAttach to create a correct stopped state for the process and + // its threads. + ArchSpec process_arch; + process_sp->DidAttach(process_arch); + + return target_sp; +} + namespace llvm { namespace json { diff --git a/lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.cpp b/lldb/source/Target/TraceThread.cpp rename from lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.cpp rename to lldb/source/Target/TraceThread.cpp --- a/lldb/source/Plugins/Trace/intel-pt/ThreadIntelPT.cpp +++ b/lldb/source/Target/TraceThread.cpp @@ -1,4 +1,4 @@ -//===-- ThreadIntelPT.cpp -------------------------------------------------===// +//===-- TraceThread.cpp -------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "ThreadIntelPT.h" +#include "lldb/Target/TraceThread.h" #include @@ -16,11 +16,10 @@ using namespace lldb; using namespace lldb_private; -using namespace lldb_private::trace_intel_pt; -void ThreadIntelPT::RefreshStateAfterStop() {} +void TraceThread::RefreshStateAfterStop() {} -RegisterContextSP ThreadIntelPT::GetRegisterContext() { +RegisterContextSP TraceThread::GetRegisterContext() { if (!m_reg_context_sp) m_reg_context_sp = CreateRegisterContextForFrame(nullptr); @@ -28,11 +27,13 @@ } RegisterContextSP -ThreadIntelPT::CreateRegisterContextForFrame(StackFrame *frame) { +TraceThread::CreateRegisterContextForFrame(StackFrame *frame) { // Eventually this will calculate the register context based on the current // trace position. return std::make_shared( *this, 0, GetProcess()->GetAddressByteSize(), LLDB_INVALID_ADDRESS); } -bool ThreadIntelPT::CalculateStopInfo() { return false; } +bool TraceThread::CalculateStopInfo() { return false; } + +const FileSpec &TraceThread::GetTraceFile() const { return m_trace_file; } diff --git a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py --- a/lldb/test/API/commands/trace/TestTraceDumpInstructions.py +++ b/lldb/test/API/commands/trace/TestTraceDumpInstructions.py @@ -39,18 +39,41 @@ substrs=["intel-pt"]) self.expect("thread trace dump instructions", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 20 instructions from position 0']) + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [ 0] 0x40052d + [ 1] 0x40052d + [ 2] 0x400529 + [ 3] 0x400525 + [ 4] 0x400521 + [ 5] 0x40052d + [ 6] 0x400529 + [ 7] 0x400525 + [ 8] 0x400521 + [ 9] 0x40052d + [10] 0x400529 + [11] 0x400525 + [12] 0x400521 + [13] 0x40052d + [14] 0x400529 + [15] 0x400525 + [16] 0x400521 + [17] 0x40052d + [18] 0x400529 + [19] 0x40051f''']) # We check if we can pass count and offset self.expect("thread trace dump instructions --count 5 --start-position 10", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 5 instructions from position 10']) + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [10] 0x400529 + [11] 0x400525 + [12] 0x400521 + [13] 0x40052d + [14] 0x400529''']) # We check if we can access the thread by index id self.expect("thread trace dump instructions 1", - substrs=['thread #1: tid = 3842849, total instructions = 1000', - 'would print 20 instructions from position 0']) + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [ 0] 0x40052d''']) # We check that we get an error when using an invalid thread index id self.expect("thread trace dump instructions 10", error=True, @@ -61,32 +84,68 @@ self.expect("trace load -v " + os.path.join(self.getSourceDir(), "intelpt-trace", "trace_2threads.json")) # We print the instructions of two threads simultaneously - self.expect("thread trace dump instructions 1 2", - substrs=['''thread #1: tid = 3842849, total instructions = 1000 - would print 20 instructions from position 0 -thread #2: tid = 3842850, total instructions = 1000 - would print 20 instructions from position 0''']) + self.expect("thread trace dump instructions 1 2 --count 2", + substrs=['''thread #1: tid = 3842849, total instructions = 22 + [0] 0x40052d + [1] 0x40052d +thread #2: tid = 3842850, total instructions = 22 + [0] 0x40052d + [1] 0x40052d''']) # We use custom --count and --start-position, saving the command to history for later ci = self.dbg.GetCommandInterpreter() result = lldb.SBCommandReturnObject() - ci.HandleCommand("thread trace dump instructions 1 2 --count 12 --start-position 5", result, True) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 5 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 5''', result.GetOutput()) + ci.HandleCommand("thread trace dump instructions 1 2 --count 4 --start-position 5", result, True) + self.assertIn('''thread #1: tid = 3842849, total instructions = 22 + [5] 0x40052d + [6] 0x400529 + [7] 0x400525 + [8] 0x400521 +thread #2: tid = 3842850, total instructions = 22 + [5] 0x40052d + [6] 0x400529 + [7] 0x400525 + [8] 0x400521''', result.GetOutput()) # We use a repeat command and ensure the previous count is used and the start-position has moved to the next position + result = lldb.SBCommandReturnObject() ci.HandleCommand("", result) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 17 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 17''', result.GetOutput()) + self.assertIn('''thread #1: tid = 3842849, total instructions = 22 + [ 9] 0x40052d + [10] 0x400529 + [11] 0x400525 + [12] 0x400521 +thread #2: tid = 3842850, total instructions = 22 + [ 9] 0x40052d + [10] 0x400529 + [11] 0x400525 + [12] 0x400521''', result.GetOutput()) + result = lldb.SBCommandReturnObject() ci.HandleCommand("", result) - self.assertIn('''thread #1: tid = 3842849, total instructions = 1000 - would print 12 instructions from position 29 -thread #2: tid = 3842850, total instructions = 1000 - would print 12 instructions from position 29''', result.GetOutput()) + self.assertIn('''thread #1: tid = 3842849, total instructions = 22 + [13] 0x40052d + [14] 0x400529 + [15] 0x400525 + [16] 0x400521 +thread #2: tid = 3842850, total instructions = 22 + [13] 0x40052d + [14] 0x400529 + [15] 0x400525 + [16] 0x400521''', result.GetOutput()) + + def testWrongImage(self): + trace_definition_file = os.path.join(self.getSourceDir(), "intelpt-trace", "trace_bad_image.json") + self.expect("trace load " + trace_definition_file) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 3 + [0] no memory mapped at this address''']) + + def testWrongCPU(self): + trace_definition_file = os.path.join(self.getSourceDir(), "intelpt-trace", "trace_wrong_cpu.json") + self.expect("trace load " + trace_definition_file) + self.expect("thread trace dump instructions", + substrs=['''thread #1: tid = 3842849, total instructions = 0 + Intel PT decoding error -27: 'unknown cpu''']) 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" + } + ] + } + ] +} diff --git a/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json b/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/trace/intelpt-trace/trace_wrong_cpu.json @@ -0,0 +1,31 @@ +{ + "trace": { + "type": "intel-pt", + "pt_cpu": { + "vendor": "intel", + "family": 2123123, + "model": 12123123, + "stepping": 1231231 + } + }, + "processes": [ + { + "pid": 1234, + "triple": "x86_64-*-linux", + "threads": [ + { + "tid": 3842849, + "traceFile": "3842849.trace" + } + ], + "modules": [ + { + "file": "a.out", + "systemPath": "a.out", + "loadAddress": "0x0000000000400000", + "uuid": "6AA9A4E2-6F28-2F33-377D-59FECE874C71-5B41261A" + } + ] + } + ] +}