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 @@ -117,7 +117,58 @@ /// \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 load_addr)> + 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 GetTraceErrorStatus(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/Process/Trace/ProcessTrace.h b/lldb/source/Plugins/Process/Trace/ProcessTrace.h --- a/lldb/source/Plugins/Process/Trace/ProcessTrace.h +++ b/lldb/source/Plugins/Process/Trace/ProcessTrace.h @@ -9,6 +9,7 @@ #ifndef LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H #define LLDB_SOURCE_PLUGINS_PROCESS_TRACE_PROCESSTRACE_H +#include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/Process.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Status.h" @@ -78,6 +79,13 @@ bool UpdateThreadList(ThreadList &old_thread_list, ThreadList &new_thread_list) override; + +private: + /// Create a map from virtual memory ranges to the sections that contain them. + const std::map &GetSectionMap(); + + llvm::Optional> + m_section_map; }; } // namespace process_trace diff --git a/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp b/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp --- a/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp +++ b/lldb/source/Plugins/Process/Trace/ProcessTrace.cpp @@ -12,6 +12,8 @@ #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" using namespace lldb; @@ -109,6 +111,33 @@ return GetTarget().GetArchitecture(); } +const std::map & +ProcessTrace::GetSectionMap() { + if (m_section_map) + return *m_section_map; + + m_section_map.emplace(); + + ModuleList &modules = GetTarget().GetImages(); + SectionLoadList &load_list = GetTarget().GetSectionLoadList(); + modules.ForEach([&](const ModuleSP &module_sp) { + SectionList *sections = module_sp->GetSectionList(); + for (size_t i = 0; i < sections->GetSize(); i++) { + SectionSP section_sp = sections->GetSectionAtIndex(i); + + addr_t load_addr = load_list.GetSectionLoadAddress(section_sp); + if (load_addr == LLDB_INVALID_ADDRESS) + continue; + MemoryRegionInfo::RangeType section_range(load_addr, + section_sp->GetByteSize()); + m_section_map->insert(std::make_pair(section_range, section_sp)); + } + return true; + }); + + return *m_section_map; +} + bool ProcessTrace::GetProcessInfo(ProcessInstanceInfo &info) { info.Clear(); info.SetProcessID(GetID()); @@ -124,5 +153,25 @@ size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size, Status &error) { + const std::map §ion_map = + GetSectionMap(); + + MemoryRegionInfo::RangeType address_range(addr, size); + // Doing (upper_bound - 1) gives us the maximum range whose base address is <= + // addr + auto it = section_map.upper_bound(address_range); + if (it != section_map.begin()) { + --it; + const SectionSP §ion = it->second; + const MemoryRegionInfo::RangeType §ion_range = it->first; + + if (section_range.Contains(address_range) && section->GetObjectFile()) { + section->GetObjectFile()->CopyData(addr - section_range.GetRangeBase(), + size, buf); + return size; + } + } + + error.SetErrorString("could not parse memory info"); return 0; } 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: + 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. + llvm::Expected GetLoadAddress() 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. + llvm::StringRef GetErrorMessage() const; + +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(std::move(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. + llvm::ArrayRef 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,32 @@ +//===-- 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; +using namespace llvm; + +bool IntelPTInstruction::IsError() const { return m_libipt_error_code < 0; } + +Expected IntelPTInstruction::GetLoadAddress() const { + if (IsError()) + return createStringError(std::errc::invalid_argument, + GetErrorMessage().data()); + 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())); +} + +ArrayRef DecodedThread::GetInstructions() const { + return makeArrayRef(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,231 @@ +//===-- 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 "llvm/Support/MemoryBuffer.h" + +#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; + +/// \return +/// A negative number with the libipt error if we couldn't synchronize. +/// Otherwise, a positive number with the synchronization status will be +/// returned. +int FindNextSynchronizationPoint(pt_insn_decoder &decoder) { + // 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. + int errcode = pt_insn_sync_forward(&decoder); + + if (errcode != -pte_eos && errcode < 0) { + uint64_t decoder_offset = 0; + int errcode_off = pt_insn_get_offset(&decoder, &decoder_offset); + if (errcode_off >= 0) { // we could get the offset + while (true) { + errcode = pt_insn_sync_forward(&decoder); + if (errcode >= 0 || errcode == -pte_eos) + break; + + uint64_t new_decoder_offset = 0; + errcode_off = pt_insn_get_offset(&decoder, &new_decoder_offset); + if (errcode_off < 0) + break; // We can't further synchronize. + 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, + // stopping in this situation. + break; + } + // We'll try again starting from a new offset. + decoder_offset = new_decoder_offset; + } + } + } + + return errcode; +} + +/// 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. +/// +/// \return +/// The decoded instructions. +static std::vector +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.emplace_back(-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.emplace_back(-pte_noip); + } + // Other events don't signal stream errors + } + return 0; + }; + + while (true) { + int errcode = FindNextSynchronizationPoint(decoder); + if (errcode == -pte_eos) + break; + + if (errcode < 0) { + instructions.emplace_back(errcode); + break; + } + + // We have synchronized, so we can start decoding + // instructions and events. + while (true) { + errcode = handle_pt_events(errcode); + if (errcode < 0) { + instructions.emplace_back(errcode); + break; + } + pt_insn insn; + errcode = pt_insn_next(&decoder, &insn, sizeof(insn)); + if (insn.iclass != ptic_error) + instructions.emplace_back(insn); + + if (errcode == -pte_eos) + break; + + if (errcode < 0) { + instructions.emplace_back(errcode); + break; + } + } + } + + return instructions; +} + +static Error CreateLibiptError(int errcode) { + return createStringError(std::errc::invalid_argument, + "Intel PT decoding error %d: '%s'", errcode, + pt_errstr(pt_errcode(errcode))); +} + +/// Callback used by libipt for reading the process memory. +/// +/// More information can be found in +/// https://github.com/intel/libipt/blob/master/doc/man/pt_image_set_callback.3.md +static int ReadProcessMemory(uint8_t *buffer, size_t size, + const pt_asid * /* unused */, uint64_t pc, + void *context) { + Process *process = reinterpret_cast(context); + + Status error; + int bytes_read = process->ReadMemory(pc, buffer, size, error); + if (error.Fail()) + return -pte_nomap; + return bytes_read; +} + +static Expected> +CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, + MemoryBuffer &trace) { + pt_config config; + pt_config_init(&config); + config.cpu = pt_cpu; + + if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) + return CreateLibiptError(errcode); + + config.begin = + reinterpret_cast(const_cast(trace.getBufferStart())); + config.end = + reinterpret_cast(const_cast(trace.getBufferEnd())); + + pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); + if (!decoder) + return CreateLibiptError(pte_nomem); + + pt_image *image = pt_insn_get_image(decoder); + if (int error = pt_image_set_callback(image, ReadProcessMemory, &process)) + return CreateLibiptError(error); + + std::vector instructions = DecodeInstructions(*decoder); + // 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 instructions; +} + +Expected +IntelPTDecoder::DecodeSingleThreadTraceFile(const FileSpec &trace_file) { + ErrorOr> buffer_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = buffer_or_error.getError()) + return errorCodeToError(err); + + if (Expected> instructions = + CreateDecoderAndDecode(m_process, m_pt_cpu, **buffer_or_error)) + return DecodedThread(std::move(*instructions)); + else + return instructions.takeError(); +} + +Expected TraceThreadDecoder::Decode() { + if (!m_decoded_thread.hasValue() && !m_error_message.hasValue()) { + IntelPTDecoder decoder(*m_trace_thread->GetProcess(), m_pt_cpu); + + if (llvm::Expected decoded_thread = + decoder.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,43 @@ llvm::StringRef GetSchema() override; + const pt_cpu &GetIntelPTCPU(); + + void ForEachInstruction( + const Thread &thread, + std::function load_addr)> + callback, + size_t from = 0) override; + + size_t GetInstructionCount(const Thread &thread) override; + + llvm::Error GetTraceErrorStatus(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,58 @@ .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 load_addr)> + 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].GetLoadAddress())) + 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::GetTraceErrorStatus(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,41 @@ 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 = GetTraceErrorStatus(thread)) + s.Printf(" %s\n", llvm::toString(std::move(err)).c_str()); + + ForEachInstruction( + thread, + [&](size_t index, Expected load_address) -> bool { + if (index >= start_position + count) + return false; + + s.Printf(" [%*zu]", digits_count, index); + if (load_address) + s.Printf(" 0x%" PRIx64, *load_address); + else + s.Printf(" %s", toString(load_address.takeError()).c_str()); + 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,40 @@ 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 = 21 + [ 0] 0x40052d + [ 1] 0x400529 + [ 2] 0x400525 + [ 3] 0x400521 + [ 4] 0x40052d + [ 5] 0x400529 + [ 6] 0x400525 + [ 7] 0x400521 + [ 8] 0x40052d + [ 9] 0x400529 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d + [13] 0x400529 + [14] 0x400525 + [15] 0x400521 + [16] 0x40052d + [17] 0x400529 + [18] 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 = 21 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d + [13] 0x400529 + [14] 0x400525''']) # 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 = 21 + [ 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 +83,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 = 21 + [0] 0x40052d + [1] 0x400529 +thread #2: tid = 3842850, total instructions = 21 + [0] 0x40052d + [1] 0x400529''']) # 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 = 21 + [5] 0x400529 + [6] 0x400525 + [7] 0x400521 + [8] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [5] 0x400529 + [6] 0x400525 + [7] 0x400521 + [8] 0x40052d''', 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 = 21 + [ 9] 0x400529 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [ 9] 0x400529 + [10] 0x400525 + [11] 0x400521 + [12] 0x40052d''', 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 = 21 + [13] 0x400529 + [14] 0x400525 + [15] 0x400521 + [16] 0x40052d +thread #2: tid = 3842850, total instructions = 21 + [13] 0x400529 + [14] 0x400525 + [15] 0x400521 + [16] 0x40052d''', 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" + } + ] + } + ] +}