diff options
Diffstat (limited to 'lldb/source/Plugins/Trace/intel-pt')
15 files changed, 1121 insertions, 231 deletions
diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp index e1758dfcfc41..5650af657c5e 100644 --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -8,7 +8,10 @@ #include "CommandObjectTraceStartIntelPT.h" +#include "TraceIntelPT.h" +#include "TraceIntelPTConstants.h" #include "lldb/Host/OptionParser.h" +#include "lldb/Target/Process.h" #include "lldb/Target/Trace.h" using namespace lldb; @@ -16,10 +19,12 @@ using namespace lldb_private; using namespace lldb_private::trace_intel_pt; using namespace llvm; +// CommandObjectThreadTraceStartIntelPT + #define LLDB_OPTIONS_thread_trace_start_intel_pt #include "TraceIntelPTCommandOptions.inc" -Status CommandObjectTraceStartIntelPT::CommandOptions::SetOptionValue( +Status CommandObjectThreadTraceStartIntelPT::CommandOptions::SetOptionValue( uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) { Status error; @@ -27,23 +32,27 @@ Status CommandObjectTraceStartIntelPT::CommandOptions::SetOptionValue( switch (short_option) { case 's': { - int32_t size_in_kb; - if (option_arg.empty() || option_arg.getAsInteger(0, size_in_kb) || - size_in_kb < 0) + int64_t thread_buffer_size; + if (option_arg.empty() || option_arg.getAsInteger(0, thread_buffer_size) || + thread_buffer_size < 0) error.SetErrorStringWithFormat("invalid integer value for option '%s'", option_arg.str().c_str()); else - m_size_in_kb = size_in_kb; + m_thread_buffer_size = thread_buffer_size; + break; + } + case 't': { + m_enable_tsc = true; break; } - case 'c': { - int32_t custom_config; - if (option_arg.empty() || option_arg.getAsInteger(0, custom_config) || - custom_config < 0) + case 'p': { + int64_t psb_period; + if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) || + psb_period < 0) error.SetErrorStringWithFormat("invalid integer value for option '%s'", option_arg.str().c_str()); else - m_custom_config = custom_config; + m_psb_period = psb_period; break; } default: @@ -52,22 +61,104 @@ Status CommandObjectTraceStartIntelPT::CommandOptions::SetOptionValue( return error; } -void CommandObjectTraceStartIntelPT::CommandOptions::OptionParsingStarting( - ExecutionContext *execution_context) { - m_size_in_kb = 4; - m_custom_config = 0; +void CommandObjectThreadTraceStartIntelPT::CommandOptions:: + OptionParsingStarting(ExecutionContext *execution_context) { + m_thread_buffer_size = kDefaultThreadBufferSize; + m_enable_tsc = kDefaultEnableTscValue; + m_psb_period = kDefaultPsbPeriod; } llvm::ArrayRef<OptionDefinition> -CommandObjectTraceStartIntelPT::CommandOptions::GetDefinitions() { +CommandObjectThreadTraceStartIntelPT::CommandOptions::GetDefinitions() { return llvm::makeArrayRef(g_thread_trace_start_intel_pt_options); } -bool CommandObjectTraceStartIntelPT::HandleOneThread( - lldb::tid_t tid, CommandReturnObject &result) { - result.AppendMessageWithFormat( - "would trace tid %" PRIu64 " with size_in_kb %zu and custom_config %d\n", - tid, m_options.m_size_in_kb, m_options.m_custom_config); - result.SetStatus(eReturnStatusSuccessFinishResult); +bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads( + Args &command, CommandReturnObject &result, + llvm::ArrayRef<lldb::tid_t> tids) { + if (Error err = m_trace.Start(tids, m_options.m_thread_buffer_size, + m_options.m_enable_tsc, m_options.m_psb_period)) + result.SetError(Status(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + + return result.Succeeded(); +} + +/// CommandObjectProcessTraceStartIntelPT + +#define LLDB_OPTIONS_process_trace_start_intel_pt +#include "TraceIntelPTCommandOptions.inc" + +Status CommandObjectProcessTraceStartIntelPT::CommandOptions::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 's': { + int64_t thread_buffer_size; + if (option_arg.empty() || option_arg.getAsInteger(0, thread_buffer_size) || + thread_buffer_size < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_thread_buffer_size = thread_buffer_size; + break; + } + case 'l': { + int64_t process_buffer_size_limit; + if (option_arg.empty() || + option_arg.getAsInteger(0, process_buffer_size_limit) || + process_buffer_size_limit < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_process_buffer_size_limit = process_buffer_size_limit; + break; + } + case 't': { + m_enable_tsc = true; + break; + } + case 'p': { + int64_t psb_period; + if (option_arg.empty() || option_arg.getAsInteger(0, psb_period) || + psb_period < 0) + error.SetErrorStringWithFormat("invalid integer value for option '%s'", + option_arg.str().c_str()); + else + m_psb_period = psb_period; + break; + } + default: + llvm_unreachable("Unimplemented option"); + } + return error; +} + +void CommandObjectProcessTraceStartIntelPT::CommandOptions:: + OptionParsingStarting(ExecutionContext *execution_context) { + m_thread_buffer_size = kDefaultThreadBufferSize; + m_process_buffer_size_limit = kDefaultProcessBufferSizeLimit; + m_enable_tsc = kDefaultEnableTscValue; + m_psb_period = kDefaultPsbPeriod; +} + +llvm::ArrayRef<OptionDefinition> +CommandObjectProcessTraceStartIntelPT::CommandOptions::GetDefinitions() { + return llvm::makeArrayRef(g_process_trace_start_intel_pt_options); +} + +bool CommandObjectProcessTraceStartIntelPT::DoExecute( + Args &command, CommandReturnObject &result) { + if (Error err = m_trace.Start(m_options.m_thread_buffer_size, + m_options.m_process_buffer_size_limit, + m_options.m_enable_tsc, m_options.m_psb_period)) + result.SetError(Status(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + return result.Succeeded(); } diff --git a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h index 265569c553fa..2f3d53a86406 100644 --- a/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -9,21 +9,21 @@ #ifndef LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H -#include "../../../../source/Commands/CommandObjectThreadUtil.h" +#include "../../../../source/Commands/CommandObjectTrace.h" +#include "TraceIntelPT.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" namespace lldb_private { namespace trace_intel_pt { -class CommandObjectTraceStartIntelPT : public CommandObjectIterateOverThreads { +class CommandObjectThreadTraceStartIntelPT + : public CommandObjectMultipleThreads { public: class CommandOptions : public Options { public: CommandOptions() : Options() { OptionParsingStarting(nullptr); } - ~CommandOptions() override = default; - Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, ExecutionContext *execution_context) override; @@ -31,31 +31,76 @@ public: llvm::ArrayRef<OptionDefinition> GetDefinitions() override; - size_t m_size_in_kb; - uint32_t m_custom_config; + size_t m_thread_buffer_size; + bool m_enable_tsc; + llvm::Optional<size_t> m_psb_period; }; - CommandObjectTraceStartIntelPT(CommandInterpreter &interpreter) - : CommandObjectIterateOverThreads( + CommandObjectThreadTraceStartIntelPT(TraceIntelPT &trace, + CommandInterpreter &interpreter) + : CommandObjectMultipleThreads( interpreter, "thread trace start", "Start tracing one or more threads with intel-pt. " "Defaults to the current thread. Thread indices can be " "specified as arguments.\n Use the thread-index \"all\" to trace " - "all threads.", + "all threads including future threads.", "thread trace start [<thread-index> <thread-index> ...] " "[<intel-pt-options>]", lldb::eCommandRequiresProcess | lldb::eCommandTryTargetAPILock | lldb::eCommandProcessMustBeLaunched | lldb::eCommandProcessMustBePaused), - m_options() {} + m_trace(trace), m_options() {} + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecuteOnThreads(Args &command, CommandReturnObject &result, + llvm::ArrayRef<lldb::tid_t> tids) override; + + TraceIntelPT &m_trace; + CommandOptions m_options; +}; + +class CommandObjectProcessTraceStartIntelPT : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } - ~CommandObjectTraceStartIntelPT() override = default; + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override; + + void OptionParsingStarting(ExecutionContext *execution_context) override; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override; + + size_t m_thread_buffer_size; + size_t m_process_buffer_size_limit; + bool m_enable_tsc; + llvm::Optional<size_t> m_psb_period; + }; + + CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, + CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process trace start", + "Start tracing this process with intel-pt, including future " + "threads. " + "This is implemented by tracing each thread independently. " + "Threads traced with the \"thread trace start\" command are left " + "unaffected ant not retraced.", + "process trace start [<intel-pt-options>]", + lldb::eCommandRequiresProcess | lldb::eCommandTryTargetAPILock | + lldb::eCommandProcessMustBeLaunched | + lldb::eCommandProcessMustBePaused), + m_trace(trace), m_options() {} Options *GetOptions() override { return &m_options; } protected: - bool HandleOneThread(lldb::tid_t tid, CommandReturnObject &result) override; + bool DoExecute(Args &command, CommandReturnObject &result) override; + TraceIntelPT &m_trace; CommandOptions m_options; }; diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp index 6b8b06564052..4822a786c68c 100644 --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -8,8 +8,13 @@ #include "DecodedThread.h" +#include <intel-pt.h> +#include <memory> + +#include "TraceCursorIntelPT.h" #include "lldb/Utility/StreamString.h" +using namespace lldb; using namespace lldb_private; using namespace lldb_private::trace_intel_pt; using namespace llvm; @@ -30,12 +35,21 @@ void IntelPTError::log(llvm::raw_ostream &OS) const { OS << "error: " << libipt_error_message; } +IntelPTInstruction::IntelPTInstruction(llvm::Error err) { + llvm::handleAllErrors(std::move(err), + [&](std::unique_ptr<llvm::ErrorInfoBase> info) { + m_error = std::move(info); + }); + m_pt_insn.ip = LLDB_INVALID_ADDRESS; + m_pt_insn.iclass = ptic_error; +} + bool IntelPTInstruction::IsError() const { return (bool)m_error; } -Expected<lldb::addr_t> IntelPTInstruction::GetLoadAddress() const { - if (IsError()) - return ToError(); - return m_pt_insn.ip; +lldb::addr_t IntelPTInstruction::GetLoadAddress() const { return m_pt_insn.ip; } + +Optional<uint64_t> IntelPTInstruction::GetTimestampCounter() const { + return m_timestamp; } Error IntelPTInstruction::ToError() const { @@ -47,18 +61,58 @@ Error IntelPTInstruction::ToError() const { return make_error<StringError>(m_error->message(), m_error->convertToErrorCode()); } +size_t DecodedThread::GetRawTraceSize() const { return m_raw_trace_size; } + +TraceInstructionControlFlowType +IntelPTInstruction::GetControlFlowType(lldb::addr_t next_load_address) const { + if (IsError()) + return (TraceInstructionControlFlowType)0; -size_t DecodedThread::GetLastPosition() const { - return m_instructions.empty() ? 0 : m_instructions.size() - 1; + TraceInstructionControlFlowType mask = + eTraceInstructionControlFlowTypeInstruction; + + switch (m_pt_insn.iclass) { + case ptic_cond_jump: + case ptic_jump: + case ptic_far_jump: + mask |= eTraceInstructionControlFlowTypeBranch; + if (m_pt_insn.ip + m_pt_insn.size != next_load_address) + mask |= eTraceInstructionControlFlowTypeTakenBranch; + break; + case ptic_return: + case ptic_far_return: + mask |= eTraceInstructionControlFlowTypeReturn; + break; + case ptic_call: + case ptic_far_call: + mask |= eTraceInstructionControlFlowTypeCall; + break; + default: + break; + } + + return mask; } ArrayRef<IntelPTInstruction> DecodedThread::GetInstructions() const { return makeArrayRef(m_instructions); } -size_t DecodedThread::GetCursorPosition() const { return m_position; } +DecodedThread::DecodedThread(ThreadSP thread_sp, Error error) + : m_thread_sp(thread_sp) { + m_instructions.emplace_back(std::move(error)); +} + +DecodedThread::DecodedThread(ThreadSP thread_sp, + std::vector<IntelPTInstruction> &&instructions, + size_t raw_trace_size) + : m_thread_sp(thread_sp), m_instructions(std::move(instructions)), + m_raw_trace_size(raw_trace_size) { + if (m_instructions.empty()) + m_instructions.emplace_back( + createStringError(inconvertibleErrorCode(), "empty trace")); +} -size_t DecodedThread::SetCursorPosition(size_t new_position) { - m_position = std::min(new_position, GetLastPosition()); - return m_position; +lldb::TraceCursorUP DecodedThread::GetCursor() { + return std::make_unique<TraceCursorIntelPT>(m_thread_sp, shared_from_this()); } diff --git a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h index 3c7e030414cb..592c402cd0e5 100644 --- a/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h +++ b/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -15,6 +15,7 @@ #include "llvm/Support/Error.h" #include "lldb/Target/Trace.h" +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "intel-pt.h" @@ -60,17 +61,15 @@ private: /// As mentioned, any gap is represented as an error in this class. class IntelPTInstruction { public: + IntelPTInstruction(const pt_insn &pt_insn, uint64_t timestamp) + : m_pt_insn(pt_insn), m_timestamp(timestamp) {} + IntelPTInstruction(const pt_insn &pt_insn) : m_pt_insn(pt_insn) {} /// Error constructor /// /// libipt errors should use the underlying \a IntelPTError class. - IntelPTInstruction(llvm::Error err) { - llvm::handleAllErrors(std::move(err), - [&](std::unique_ptr<llvm::ErrorInfoBase> info) { - m_error = std::move(info); - }); - } + IntelPTInstruction(llvm::Error err); /// Check if this object represents an error (i.e. a gap). /// @@ -79,15 +78,34 @@ public: bool IsError() const; /// \return - /// The instruction pointer address, or an \a llvm::Error if it is an - /// error. - llvm::Expected<lldb::addr_t> GetLoadAddress() const; + /// The instruction pointer address, or \a LLDB_INVALID_ADDRESS if it is + /// an error. + lldb::addr_t GetLoadAddress() const; /// \return /// An \a llvm::Error object if this class corresponds to an Error, or an /// \a llvm::Error::success otherwise. llvm::Error ToError() const; + /// Get the timestamp associated with the current instruction. The timestamp + /// is similar to what a rdtsc instruction would return. + /// + /// \return + /// The timestamp or \b llvm::None if not available. + llvm::Optional<uint64_t> GetTimestampCounter() const; + + /// Get the \a lldb::TraceInstructionControlFlowType categories of the + /// instruction. + /// + /// \param[in] next_load_address + /// The address of the next instruction in the trace or \b + /// LLDB_INVALID_ADDRESS if not available. + /// + /// \return + /// The control flow categories, or \b 0 if the instruction is an error. + lldb::TraceInstructionControlFlowType + GetControlFlowType(lldb::addr_t next_load_address) const; + IntelPTInstruction(IntelPTInstruction &&other) = default; private: @@ -95,6 +113,7 @@ private: const IntelPTInstruction &operator=(const IntelPTInstruction &other) = delete; pt_insn m_pt_insn; + llvm::Optional<uint64_t> m_timestamp; std::unique_ptr<llvm::ErrorInfoBase> m_error; }; @@ -105,11 +124,15 @@ private: /// /// Each decoded thread contains a cursor to the current position the user is /// stopped at. See \a Trace::GetCursorPosition for more information. -class DecodedThread { +class DecodedThread : public std::enable_shared_from_this<DecodedThread> { public: - DecodedThread(std::vector<IntelPTInstruction> &&instructions) - : m_instructions(std::move(instructions)), m_position(GetLastPosition()) { - } + DecodedThread(lldb::ThreadSP thread_sp, + std::vector<IntelPTInstruction> &&instructions, + size_t raw_trace_size); + + /// Constructor with a single error signaling a complete failure of the + /// decoding process. + DecodedThread(lldb::ThreadSP thread_sp, llvm::Error error); /// Get the instructions from the decoded trace. Some of them might indicate /// errors (i.e. gaps) in the trace. @@ -118,28 +141,23 @@ public: /// The instructions of the trace. llvm::ArrayRef<IntelPTInstruction> GetInstructions() const; - /// \return - /// The current position of the cursor of this trace, or 0 if there are no - /// instructions. - size_t GetCursorPosition() const; + /// Get a new cursor for the decoded thread. + lldb::TraceCursorUP GetCursor(); - /// Change the position of the cursor of this trace. If this value is to high, - /// the new position will be set as the last instruction of the trace. + /// Get the size in bytes of the corresponding Intel PT raw trace /// /// \return - /// The effective new position. - size_t SetCursorPosition(size_t new_position); - /// \} + /// The size of the trace. + size_t GetRawTraceSize() const; private: - /// \return - /// The index of the last element of the trace, or 0 if empty. - size_t GetLastPosition() const; - + lldb::ThreadSP m_thread_sp; std::vector<IntelPTInstruction> m_instructions; - size_t m_position; + size_t m_raw_trace_size; }; +using DecodedThreadSP = std::shared_ptr<DecodedThread>; + } // namespace trace_intel_pt } // namespace lldb_private diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp index b6e8ae808632..3827881454c7 100644 --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.cpp @@ -1,5 +1,4 @@ -//===-- IntelPTDecoder.cpp --------------------------------------*- C++ -*-===// -// +//===-- IntelPTDecoder.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 @@ -10,10 +9,13 @@ #include "llvm/Support/MemoryBuffer.h" +#include "../common/ThreadPostMortemTrace.h" +#include "DecodedThread.h" +#include "TraceIntelPT.h" #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" #include "lldb/Target/Target.h" -#include "lldb/Target/ThreadTrace.h" +#include "lldb/Utility/StringExtractor.h" using namespace lldb; using namespace lldb_private; @@ -85,7 +87,7 @@ static int ProcessPTEvents(pt_insn_decoder &decoder, int errcode) { return errcode; } return 0; -}; +} /// Decode all the instructions from a configured decoder. /// The decoding flow is based on @@ -135,7 +137,23 @@ DecodeInstructions(pt_insn_decoder &decoder) { break; } - instructions.emplace_back(insn); + uint64_t time; + int time_error = pt_insn_time(&decoder, &time, nullptr, nullptr); + if (time_error == -pte_invalid) { + // This happens if we invoke the pt_insn_time method incorrectly, + // but the instruction is good though. + instructions.emplace_back( + make_error<IntelPTError>(time_error, insn.ip)); + instructions.emplace_back(insn); + break; + } + if (time_error == -pte_no_time) { + // We simply don't have time information, i.e. None of TSC, MTC or CYC + // was enabled. + instructions.emplace_back(insn); + } else { + instructions.emplace_back(insn, time); + } } } @@ -158,39 +176,26 @@ static int ReadProcessMemory(uint8_t *buffer, size_t size, return bytes_read; } -static std::vector<IntelPTInstruction> makeInstructionListFromError(Error err) { - std::vector<IntelPTInstruction> instructions; - instructions.emplace_back(std::move(err)); - return instructions; -} - -static std::vector<IntelPTInstruction> -CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, - const FileSpec &trace_file) { - ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error = - MemoryBuffer::getFile(trace_file.GetPath()); - if (std::error_code err = trace_or_error.getError()) - return makeInstructionListFromError(errorCodeToError(err)); - - MemoryBuffer &trace = **trace_or_error; +static Expected<std::vector<IntelPTInstruction>> +DecodeInMemoryTrace(Process &process, TraceIntelPT &trace_intel_pt, + MutableArrayRef<uint8_t> buffer) { + Expected<pt_cpu> cpu_info = trace_intel_pt.GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); pt_config config; pt_config_init(&config); - config.cpu = pt_cpu; + config.cpu = *cpu_info; if (int errcode = pt_cpu_errata(&config.errata, &config.cpu)) - return makeInstructionListFromError(make_error<IntelPTError>(errcode)); + return make_error<IntelPTError>(errcode); - // The libipt library does not modify the trace buffer, hence the following - // cast is safe. - config.begin = - reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferStart())); - config.end = - reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferEnd())); + config.begin = buffer.data(); + config.end = buffer.data() + buffer.size(); pt_insn_decoder *decoder = pt_insn_alloc_decoder(&config); if (!decoder) - return makeInstructionListFromError(make_error<IntelPTError>(-pte_nomem)); + return make_error<IntelPTError>(-pte_nomem); pt_image *image = pt_insn_get_image(decoder); @@ -204,12 +209,71 @@ CreateDecoderAndDecode(Process &process, const pt_cpu &pt_cpu, return instructions; } -const DecodedThread &ThreadTraceDecoder::Decode() { - if (!m_decoded_thread.hasValue()) { - m_decoded_thread = DecodedThread( - CreateDecoderAndDecode(*m_trace_thread->GetProcess(), m_pt_cpu, - m_trace_thread->GetTraceFile())); - } +static Expected<std::vector<IntelPTInstruction>> +DecodeTraceFile(Process &process, TraceIntelPT &trace_intel_pt, + const FileSpec &trace_file, size_t &raw_trace_size) { + ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error = + MemoryBuffer::getFile(trace_file.GetPath()); + if (std::error_code err = trace_or_error.getError()) + return errorCodeToError(err); + + MemoryBuffer &trace = **trace_or_error; + MutableArrayRef<uint8_t> trace_data( + // The libipt library does not modify the trace buffer, hence the + // following cast is safe. + reinterpret_cast<uint8_t *>(const_cast<char *>(trace.getBufferStart())), + trace.getBufferSize()); + raw_trace_size = trace_data.size(); + return DecodeInMemoryTrace(process, trace_intel_pt, trace_data); +} + +static Expected<std::vector<IntelPTInstruction>> +DecodeLiveThread(Thread &thread, TraceIntelPT &trace, size_t &raw_trace_size) { + Expected<std::vector<uint8_t>> buffer = + trace.GetLiveThreadBuffer(thread.GetID()); + if (!buffer) + return buffer.takeError(); + raw_trace_size = buffer->size(); + if (Expected<pt_cpu> cpu_info = trace.GetCPUInfo()) + return DecodeInMemoryTrace(*thread.GetProcess(), trace, + MutableArrayRef<uint8_t>(*buffer)); + else + return cpu_info.takeError(); +} +DecodedThreadSP ThreadDecoder::Decode() { + if (!m_decoded_thread.hasValue()) + m_decoded_thread = DoDecode(); return *m_decoded_thread; } + +PostMortemThreadDecoder::PostMortemThreadDecoder( + const lldb::ThreadPostMortemTraceSP &trace_thread, TraceIntelPT &trace) + : m_trace_thread(trace_thread), m_trace(trace) {} + +DecodedThreadSP PostMortemThreadDecoder::DoDecode() { + size_t raw_trace_size = 0; + if (Expected<std::vector<IntelPTInstruction>> instructions = + DecodeTraceFile(*m_trace_thread->GetProcess(), m_trace, + m_trace_thread->GetTraceFile(), raw_trace_size)) + return std::make_shared<DecodedThread>(m_trace_thread->shared_from_this(), + std::move(*instructions), + raw_trace_size); + else + return std::make_shared<DecodedThread>(m_trace_thread->shared_from_this(), + instructions.takeError()); +} + +LiveThreadDecoder::LiveThreadDecoder(Thread &thread, TraceIntelPT &trace) + : m_thread_sp(thread.shared_from_this()), m_trace(trace) {} + +DecodedThreadSP LiveThreadDecoder::DoDecode() { + size_t raw_trace_size = 0; + if (Expected<std::vector<IntelPTInstruction>> instructions = + DecodeLiveThread(*m_thread_sp, m_trace, raw_trace_size)) + return std::make_shared<DecodedThread>( + m_thread_sp, std::move(*instructions), raw_trace_size); + else + return std::make_shared<DecodedThread>(m_thread_sp, + instructions.takeError()); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h index 2e67f9bf6da7..e969db579e52 100644 --- a/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h +++ b/lldb/source/Plugins/Trace/intel-pt/IntelPTDecoder.h @@ -12,38 +12,73 @@ #include "intel-pt.h" #include "DecodedThread.h" +#include "forward-declarations.h" #include "lldb/Target/Process.h" #include "lldb/Utility/FileSpec.h" namespace lldb_private { namespace trace_intel_pt { -/// \a lldb_private::ThreadTrace decoder that stores the output from decoding, -/// avoiding recomputations, as decoding is expensive. -class ThreadTraceDecoder { +/// Base class that handles the decoding of a thread and caches the result. +class ThreadDecoder { public: - /// \param[in] trace_thread - /// The thread whose trace file will be decoded. + virtual ~ThreadDecoder() = default; + + ThreadDecoder() = default; + + /// Decode the thread and store the result internally, to avoid + /// recomputations. /// - /// \param[in] pt_cpu - /// The libipt cpu used when recording the trace. - ThreadTraceDecoder(const std::shared_ptr<ThreadTrace> &trace_thread, - const pt_cpu &pt_cpu) - : m_trace_thread(trace_thread), m_pt_cpu(pt_cpu), m_decoded_thread() {} + /// \return + /// A \a DecodedThread instance. + DecodedThreadSP Decode(); - /// Decode the thread and store the result internally. + ThreadDecoder(const ThreadDecoder &other) = delete; + ThreadDecoder &operator=(const ThreadDecoder &other) = delete; + +protected: + /// Decode the thread. /// /// \return /// A \a DecodedThread instance. - const DecodedThread &Decode(); + virtual DecodedThreadSP DoDecode() = 0; + + llvm::Optional<DecodedThreadSP> m_decoded_thread; +}; + +/// Decoder implementation for \a lldb_private::ThreadPostMortemTrace, which are +/// non-live processes that come trace session files. +class PostMortemThreadDecoder : public ThreadDecoder { +public: + /// \param[in] trace_thread + /// The thread whose trace file will be decoded. + /// + /// \param[in] trace + /// The main Trace object who owns this decoder and its data. + PostMortemThreadDecoder(const lldb::ThreadPostMortemTraceSP &trace_thread, + TraceIntelPT &trace); + +private: + DecodedThreadSP DoDecode() override; + + lldb::ThreadPostMortemTraceSP m_trace_thread; + TraceIntelPT &m_trace; +}; + +class LiveThreadDecoder : public ThreadDecoder { +public: + /// \param[in] thread + /// The thread whose traces will be decoded. + /// + /// \param[in] trace + /// The main Trace object who owns this decoder and its data. + LiveThreadDecoder(Thread &thread, TraceIntelPT &trace); private: - ThreadTraceDecoder(const ThreadTraceDecoder &other) = delete; - ThreadTraceDecoder &operator=(const ThreadTraceDecoder &other) = delete; + DecodedThreadSP DoDecode() override; - std::shared_ptr<ThreadTrace> m_trace_thread; - pt_cpu m_pt_cpu; - llvm::Optional<DecodedThread> m_decoded_thread; + lldb::ThreadSP m_thread_sp; + TraceIntelPT &m_trace; }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp new file mode 100644 index 000000000000..edefdd0d3486 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -0,0 +1,100 @@ +//===-- TraceCursorIntelPT.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 "TraceCursorIntelPT.h" +#include "DecodedThread.h" +#include "TraceIntelPT.h" + +#include <cstdlib> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +TraceCursorIntelPT::TraceCursorIntelPT(ThreadSP thread_sp, + DecodedThreadSP decoded_thread_sp) + : TraceCursor(thread_sp), m_decoded_thread_sp(decoded_thread_sp) { + assert(!m_decoded_thread_sp->GetInstructions().empty() && + "a trace should have at least one instruction or error"); + m_pos = m_decoded_thread_sp->GetInstructions().size() - 1; +} + +size_t TraceCursorIntelPT::GetInternalInstructionSize() { + return m_decoded_thread_sp->GetInstructions().size(); +} + +bool TraceCursorIntelPT::Next() { + auto canMoveOne = [&]() { + if (IsForwards()) + return m_pos + 1 < GetInternalInstructionSize(); + return m_pos > 0; + }; + + size_t initial_pos = m_pos; + + while (canMoveOne()) { + m_pos += IsForwards() ? 1 : -1; + if (!m_ignore_errors && IsError()) + return true; + if (GetInstructionControlFlowType() & m_granularity) + return true; + } + + // Didn't find any matching instructions + m_pos = initial_pos; + return false; +} + +size_t TraceCursorIntelPT::Seek(int64_t offset, SeekType origin) { + int64_t last_index = GetInternalInstructionSize() - 1; + + auto fitPosToBounds = [&](int64_t raw_pos) -> int64_t { + return std::min(std::max((int64_t)0, raw_pos), last_index); + }; + + switch (origin) { + case TraceCursor::SeekType::Set: + m_pos = fitPosToBounds(offset); + return m_pos; + case TraceCursor::SeekType::End: + m_pos = fitPosToBounds(offset + last_index); + return last_index - m_pos; + case TraceCursor::SeekType::Current: + int64_t new_pos = fitPosToBounds(offset + m_pos); + int64_t dist = m_pos - new_pos; + m_pos = new_pos; + return std::abs(dist); + } +} + +bool TraceCursorIntelPT::IsError() { + return m_decoded_thread_sp->GetInstructions()[m_pos].IsError(); +} + +Error TraceCursorIntelPT::GetError() { + return m_decoded_thread_sp->GetInstructions()[m_pos].ToError(); +} + +lldb::addr_t TraceCursorIntelPT::GetLoadAddress() { + return m_decoded_thread_sp->GetInstructions()[m_pos].GetLoadAddress(); +} + +Optional<uint64_t> TraceCursorIntelPT::GetTimestampCounter() { + return m_decoded_thread_sp->GetInstructions()[m_pos].GetTimestampCounter(); +} + +TraceInstructionControlFlowType +TraceCursorIntelPT::GetInstructionControlFlowType() { + lldb::addr_t next_load_address = + m_pos + 1 < GetInternalInstructionSize() + ? m_decoded_thread_sp->GetInstructions()[m_pos + 1].GetLoadAddress() + : LLDB_INVALID_ADDRESS; + return m_decoded_thread_sp->GetInstructions()[m_pos].GetControlFlowType( + next_load_address); +} diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h new file mode 100644 index 000000000000..29d3792bb489 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -0,0 +1,50 @@ +//===-- TraceCursorIntelPT.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_TRACECURSORINTELPT_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACECURSORINTELPT_H + +#include "IntelPTDecoder.h" +#include "TraceIntelPTSessionFileParser.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceCursorIntelPT : public TraceCursor { +public: + TraceCursorIntelPT(lldb::ThreadSP thread_sp, + DecodedThreadSP decoded_thread_sp); + + size_t Seek(int64_t offset, SeekType origin) override; + + virtual bool Next() override; + + llvm::Error GetError() override; + + lldb::addr_t GetLoadAddress() override; + + llvm::Optional<uint64_t> GetTimestampCounter() override; + + lldb::TraceInstructionControlFlowType + GetInstructionControlFlowType() override; + + bool IsError() override; + +private: + size_t GetInternalInstructionSize(); + + /// Storage of the actual instructions + DecodedThreadSP m_decoded_thread_sp; + /// Internal instruction index currently pointing at. + size_t m_pos; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACECURSORINTELPT_H diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp index 63a8b2dff4a7..c12bcd3523e3 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -8,12 +8,14 @@ #include "TraceIntelPT.h" +#include "../common/ThreadPostMortemTrace.h" #include "CommandObjectTraceStartIntelPT.h" +#include "DecodedThread.h" +#include "TraceIntelPTConstants.h" #include "TraceIntelPTSessionFileParser.h" #include "lldb/Core/PluginManager.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" -#include "lldb/Target/ThreadTrace.h" using namespace lldb; using namespace lldb_private; @@ -22,18 +24,27 @@ using namespace llvm; LLDB_PLUGIN_DEFINE(TraceIntelPT) -CommandObjectSP GetStartCommand(CommandInterpreter &interpreter) { - return CommandObjectSP(new CommandObjectTraceStartIntelPT(interpreter)); +lldb::CommandObjectSP +TraceIntelPT::GetProcessTraceStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP( + new CommandObjectProcessTraceStartIntelPT(*this, interpreter)); +} + +lldb::CommandObjectSP +TraceIntelPT::GetThreadTraceStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP( + new CommandObjectThreadTraceStartIntelPT(*this, interpreter)); } void TraceIntelPT::Initialize() { - PluginManager::RegisterPlugin( - GetPluginNameStatic(), "Intel Processor Trace", CreateInstance, - TraceIntelPTSessionFileParser::GetSchema(), GetStartCommand); + PluginManager::RegisterPlugin(GetPluginNameStatic(), "Intel Processor Trace", + CreateInstanceForSessionFile, + CreateInstanceForLiveProcess, + TraceIntelPTSessionFileParser::GetSchema()); } void TraceIntelPT::Terminate() { - PluginManager::UnregisterPlugin(CreateInstance); + PluginManager::UnregisterPlugin(CreateInstanceForSessionFile); } ConstString TraceIntelPT::GetPluginNameStatic() { @@ -55,60 +66,278 @@ uint32_t TraceIntelPT::GetPluginVersion() { return 1; } void TraceIntelPT::Dump(Stream *s) const {} -Expected<TraceSP> -TraceIntelPT::CreateInstance(const json::Value &trace_session_file, - StringRef session_file_dir, Debugger &debugger) { +Expected<TraceSP> TraceIntelPT::CreateInstanceForSessionFile( + const json::Value &trace_session_file, StringRef session_file_dir, + Debugger &debugger) { return TraceIntelPTSessionFileParser(debugger, trace_session_file, session_file_dir) .Parse(); } +Expected<TraceSP> TraceIntelPT::CreateInstanceForLiveProcess(Process &process) { + TraceSP instance(new TraceIntelPT(process)); + process.GetTarget().SetTrace(instance); + return instance; +} + TraceIntelPT::TraceIntelPT( - const pt_cpu &pt_cpu, - const std::vector<std::shared_ptr<ThreadTrace>> &traced_threads) - : m_pt_cpu(pt_cpu) { - for (const std::shared_ptr<ThreadTrace> &thread : traced_threads) - m_trace_threads.emplace( - std::piecewise_construct, - std::forward_as_tuple(thread->GetProcess()->GetID(), thread->GetID()), - std::forward_as_tuple(thread, pt_cpu)); -} - -const DecodedThread *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 nullptr; - return &it->second.Decode(); -} - -size_t TraceIntelPT::GetCursorPosition(const Thread &thread) { - const DecodedThread *decoded_thread = Decode(thread); - if (!decoded_thread) - return 0; - return decoded_thread->GetCursorPosition(); -} - -void TraceIntelPT::TraverseInstructions( - const Thread &thread, size_t position, TraceDirection direction, - std::function<bool(size_t index, Expected<lldb::addr_t> load_addr)> - callback) { - const DecodedThread *decoded_thread = Decode(thread); - if (!decoded_thread) - return; + const pt_cpu &cpu_info, + const std::vector<ThreadPostMortemTraceSP> &traced_threads) + : m_cpu_info(cpu_info) { + for (const ThreadPostMortemTraceSP &thread : traced_threads) + m_thread_decoders.emplace( + thread.get(), std::make_unique<PostMortemThreadDecoder>(thread, *this)); +} + +DecodedThreadSP TraceIntelPT::Decode(Thread &thread) { + RefreshLiveProcessState(); + if (m_live_refresh_error.hasValue()) + return std::make_shared<DecodedThread>( + thread.shared_from_this(), + createStringError(inconvertibleErrorCode(), *m_live_refresh_error)); + + auto it = m_thread_decoders.find(&thread); + if (it == m_thread_decoders.end()) + return std::make_shared<DecodedThread>( + thread.shared_from_this(), + createStringError(inconvertibleErrorCode(), "thread not traced")); + return it->second->Decode(); +} - ArrayRef<IntelPTInstruction> instructions = decoded_thread->GetInstructions(); +lldb::TraceCursorUP TraceIntelPT::GetCursor(Thread &thread) { + return Decode(thread)->GetCursor(); +} - ssize_t delta = direction == TraceDirection::Forwards ? 1 : -1; - for (ssize_t i = position; i < (ssize_t)instructions.size() && i >= 0; - i += delta) - if (!callback(i, instructions[i].GetLoadAddress())) - break; +void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose) { + Optional<size_t> raw_size = GetRawTraceSize(thread); + s.Printf("\nthread #%u: tid = %" PRIu64, thread.GetIndexID(), thread.GetID()); + if (!raw_size) { + s.Printf(", not traced\n"); + return; + } + s.Printf("\n Raw trace size: %zu bytes\n", *raw_size); + return; } -size_t TraceIntelPT::GetInstructionCount(const Thread &thread) { - if (const DecodedThread *decoded_thread = Decode(thread)) - return decoded_thread->GetInstructions().size(); +Optional<size_t> TraceIntelPT::GetRawTraceSize(Thread &thread) { + if (IsTraced(thread)) + return Decode(thread)->GetRawTraceSize(); else - return 0; + return None; +} + +Expected<pt_cpu> TraceIntelPT::GetCPUInfoForLiveProcess() { + Expected<std::vector<uint8_t>> cpu_info = GetLiveProcessBinaryData("cpuInfo"); + if (!cpu_info) + return cpu_info.takeError(); + + int64_t cpu_family = -1; + int64_t model = -1; + int64_t stepping = -1; + std::string vendor_id; + + StringRef rest(reinterpret_cast<const char *>(cpu_info->data()), + cpu_info->size()); + while (!rest.empty()) { + StringRef line; + std::tie(line, rest) = rest.split('\n'); + + SmallVector<StringRef, 2> columns; + line.split(columns, StringRef(":"), -1, false); + + if (columns.size() < 2) + continue; // continue searching + + columns[1] = columns[1].trim(" "); + if (columns[0].contains("cpu family") && + columns[1].getAsInteger(10, cpu_family)) + continue; + + else if (columns[0].contains("model") && columns[1].getAsInteger(10, model)) + continue; + + else if (columns[0].contains("stepping") && + columns[1].getAsInteger(10, stepping)) + continue; + + else if (columns[0].contains("vendor_id")) { + vendor_id = columns[1].str(); + if (!vendor_id.empty()) + continue; + } + + if ((cpu_family != -1) && (model != -1) && (stepping != -1) && + (!vendor_id.empty())) { + return pt_cpu{vendor_id == "GenuineIntel" ? pcv_intel : pcv_unknown, + static_cast<uint16_t>(cpu_family), + static_cast<uint8_t>(model), + static_cast<uint8_t>(stepping)}; + } + } + return createStringError(inconvertibleErrorCode(), + "Failed parsing the target's /proc/cpuinfo file"); +} + +Expected<pt_cpu> TraceIntelPT::GetCPUInfo() { + if (!m_cpu_info) { + if (llvm::Expected<pt_cpu> cpu_info = GetCPUInfoForLiveProcess()) + m_cpu_info = *cpu_info; + else + return cpu_info.takeError(); + } + return *m_cpu_info; +} + +void TraceIntelPT::DoRefreshLiveProcessState( + Expected<TraceGetStateResponse> state) { + m_thread_decoders.clear(); + + if (!state) { + m_live_refresh_error = toString(state.takeError()); + return; + } + + for (const TraceThreadState &thread_state : state->tracedThreads) { + Thread &thread = + *m_live_process->GetThreadList().FindThreadByID(thread_state.tid); + m_thread_decoders.emplace( + &thread, std::make_unique<LiveThreadDecoder>(thread, *this)); + } +} + +bool TraceIntelPT::IsTraced(const Thread &thread) { + RefreshLiveProcessState(); + return m_thread_decoders.count(&thread); +} + +// The information here should match the description of the intel-pt section +// of the jLLDBTraceStart packet in the lldb/docs/lldb-gdb-remote.txt +// documentation file. Similarly, it should match the CLI help messages of the +// TraceIntelPTOptions.td file. +const char *TraceIntelPT::GetStartConfigurationHelp() { + return R"(Parameters: + + Note: If a parameter is not specified, a default value will be used. + + - int threadBufferSize (defaults to 4096 bytes): + [process and thread tracing] + Trace size in bytes per thread. It must be a power of 2 greater + than or equal to 4096 (2^12). The trace is circular keeping the + the most recent data. + + - boolean enableTsc (default to false): + [process and thread tracing] + Whether to use enable TSC timestamps or not. This is supported on + all devices that support intel-pt. + + - psbPeriod (defaults to null): + [process and thread tracing] + This value defines the period in which PSB packets will be generated. + A PSB packet is a synchronization packet that contains a TSC + timestamp and the current absolute instruction pointer. + + This parameter can only be used if + + /sys/bus/event_source/devices/intel_pt/caps/psb_cyc + + is 1. Otherwise, the PSB period will be defined by the processor. + + If supported, valid values for this period can be found in + + /sys/bus/event_source/devices/intel_pt/caps/psb_periods + + which contains a hexadecimal number, whose bits represent + valid values e.g. if bit 2 is set, then value 2 is valid. + + The psb_period value is converted to the approximate number of + raw trace bytes between PSB packets as: + + 2 ^ (value + 11) + + e.g. value 3 means 16KiB between PSB packets. Defaults to 0 if + supported. + + - int processBufferSizeLimit (defaults to 500 MB): + [process tracing only] + Maximum total trace size per process in bytes. This limit applies + to the sum of the sizes of all thread traces of this process, + excluding the ones created explicitly with "thread tracing". + Whenever a thread is attempted to be traced due to this command + and the limit would be reached, the process is stopped with a + "processor trace" reason, so that the user can retrace the process + if needed.)"; +} + +Error TraceIntelPT::Start(size_t thread_buffer_size, + size_t total_buffer_size_limit, bool enable_tsc, + Optional<size_t> psb_period) { + TraceIntelPTStartRequest request; + request.threadBufferSize = thread_buffer_size; + request.processBufferSizeLimit = total_buffer_size_limit; + request.enableTsc = enable_tsc; + request.psbPeriod = psb_period.map([](size_t val) { return (int64_t)val; }); + request.type = GetPluginName().AsCString(); + return Trace::Start(toJSON(request)); +} + +Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) { + size_t thread_buffer_size = kDefaultThreadBufferSize; + size_t process_buffer_size_limit = kDefaultProcessBufferSizeLimit; + bool enable_tsc = kDefaultEnableTscValue; + Optional<size_t> psb_period = kDefaultPsbPeriod; + + if (configuration) { + if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { + dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size); + dict->GetValueForKeyAsInteger("processBufferSizeLimit", + process_buffer_size_limit); + dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); + dict->GetValueForKeyAsInteger("psbPeriod", psb_period); + } else { + return createStringError(inconvertibleErrorCode(), + "configuration object is not a dictionary"); + } + } + + return Start(thread_buffer_size, process_buffer_size_limit, enable_tsc, + psb_period); +} + +llvm::Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids, + size_t thread_buffer_size, bool enable_tsc, + Optional<size_t> psb_period) { + TraceIntelPTStartRequest request; + request.threadBufferSize = thread_buffer_size; + request.enableTsc = enable_tsc; + request.psbPeriod = psb_period.map([](size_t val) { return (int64_t)val; }); + request.type = GetPluginName().AsCString(); + request.tids.emplace(); + for (lldb::tid_t tid : tids) + request.tids->push_back(tid); + return Trace::Start(toJSON(request)); +} + +Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids, + StructuredData::ObjectSP configuration) { + size_t thread_buffer_size = kDefaultThreadBufferSize; + bool enable_tsc = kDefaultEnableTscValue; + Optional<size_t> psb_period = kDefaultPsbPeriod; + + if (configuration) { + if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { + dict->GetValueForKeyAsInteger("threadBufferSize", thread_buffer_size); + dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); + dict->GetValueForKeyAsInteger("psbPeriod", psb_period); + } else { + return createStringError(inconvertibleErrorCode(), + "configuration object is not a dictionary"); + } + } + + return Start(tids, thread_buffer_size, enable_tsc, psb_period); +} + +Expected<std::vector<uint8_t>> +TraceIntelPT::GetLiveThreadBuffer(lldb::tid_t tid) { + return Trace::GetLiveThreadBinaryData(tid, "threadTraceBuffer"); } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h index 5058e6fd32f2..e3b247112ae1 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -45,49 +45,134 @@ public: /// \return /// A trace instance or an error in case of failures. static llvm::Expected<lldb::TraceSP> - CreateInstance(const llvm::json::Value &trace_session_file, - llvm::StringRef session_file_dir, Debugger &debugger); + CreateInstanceForSessionFile(const llvm::json::Value &trace_session_file, + llvm::StringRef session_file_dir, + Debugger &debugger); + + static llvm::Expected<lldb::TraceSP> + CreateInstanceForLiveProcess(Process &process); static ConstString GetPluginNameStatic(); uint32_t GetPluginVersion() override; /// \} + lldb::CommandObjectSP + GetProcessTraceStartCommand(CommandInterpreter &interpreter) override; + + lldb::CommandObjectSP + GetThreadTraceStartCommand(CommandInterpreter &interpreter) override; + llvm::StringRef GetSchema() override; - void TraverseInstructions( - const Thread &thread, size_t position, TraceDirection direction, - std::function<bool(size_t index, llvm::Expected<lldb::addr_t> load_addr)> - callback) override; + lldb::TraceCursorUP GetCursor(Thread &thread) override; + + void DumpTraceInfo(Thread &thread, Stream &s, bool verbose) override; + + llvm::Optional<size_t> GetRawTraceSize(Thread &thread); + + void DoRefreshLiveProcessState( + llvm::Expected<TraceGetStateResponse> state) override; + + bool IsTraced(const Thread &thread) override; + + const char *GetStartConfigurationHelp() override; + + /// Start tracing a live process. + /// + /// \param[in] thread_buffer_size + /// Trace size per thread in bytes. + /// + /// \param[in] total_buffer_size_limit + /// Maximum total trace size per process in bytes. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] psb_period + /// + /// This value defines the period in which PSB packets will be generated. + /// More information in TraceIntelPT::GetStartConfigurationHelp(); + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error Start(size_t thread_buffer_size, size_t total_buffer_size_limit, + bool enable_tsc, llvm::Optional<size_t> psb_period); + + /// \copydoc Trace::Start + llvm::Error Start(StructuredData::ObjectSP configuration = + StructuredData::ObjectSP()) override; - size_t GetInstructionCount(const Thread &thread) override; + /// Start tracing live threads. + /// + /// \param[in] tids + /// Threads to trace. + /// + /// \param[in] thread_buffer_size + /// Trace size per thread in bytes. + /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \param[in] psb_period + /// + /// This value defines the period in which PSB packets will be generated. + /// More information in TraceIntelPT::GetStartConfigurationHelp(). + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error Start(llvm::ArrayRef<lldb::tid_t> tids, size_t thread_buffer_size, + bool enable_tsc, llvm::Optional<size_t> psb_period); + + /// \copydoc Trace::Start + llvm::Error Start(llvm::ArrayRef<lldb::tid_t> tids, + StructuredData::ObjectSP configuration = + StructuredData::ObjectSP()) override; + + /// Get the thread buffer content for a live thread + llvm::Expected<std::vector<uint8_t>> GetLiveThreadBuffer(lldb::tid_t tid); - size_t GetCursorPosition(const Thread &thread) override; + llvm::Expected<pt_cpu> GetCPUInfo(); private: friend class TraceIntelPTSessionFileParser; + llvm::Expected<pt_cpu> GetCPUInfoForLiveProcess(); + /// \param[in] trace_threads /// ThreadTrace instances, which are not live-processes and whose trace /// files are fixed. - TraceIntelPT(const pt_cpu &pt_cpu, - const std::vector<std::shared_ptr<ThreadTrace>> &traced_threads); + TraceIntelPT( + const pt_cpu &cpu_info, + const std::vector<lldb::ThreadPostMortemTraceSP> &traced_threads); + + /// Constructor for live processes + TraceIntelPT(Process &live_process) + : Trace(live_process), m_thread_decoders(){}; /// Decode the trace of the given thread that, i.e. recontruct the traced - /// instructions. That trace must be managed by this class. + /// instructions. /// /// \param[in] thread /// If \a thread is a \a ThreadTrace, then its internal trace file will be /// decoded. Live threads are not currently supported. /// /// \return - /// A \a DecodedThread instance if decoding was successful, or a \b - /// nullptr if the thread's trace is not managed by this class. - const DecodedThread *Decode(const Thread &thread); - - pt_cpu m_pt_cpu; - std::map<std::pair<lldb::pid_t, lldb::tid_t>, ThreadTraceDecoder> - m_trace_threads; + /// A \a DecodedThread shared pointer with the decoded instructions. Any + /// errors are embedded in the instruction list. + DecodedThreadSP Decode(Thread &thread); + + /// It is provided by either a session file or a live process' "cpuInfo" + /// binary data. + llvm::Optional<pt_cpu> m_cpu_info; + std::map<const Thread *, std::unique_ptr<ThreadDecoder>> m_thread_decoders; + /// Error gotten after a failed live process update, if any. + llvm::Optional<std::string> m_live_refresh_error; }; } // namespace trace_intel_pt diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h new file mode 100644 index 000000000000..c2bc1b57b2bd --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h @@ -0,0 +1,27 @@ +//===-- TraceIntelPTConstants.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_CONSTANTS_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_CONSTANTS_H + +#include <cstddef> + +#include <llvm/ADT/Optional.h> + +namespace lldb_private { +namespace trace_intel_pt { + +const size_t kDefaultThreadBufferSize = 4 * 1024; // 4KB +const size_t kDefaultProcessBufferSizeLimit = 5 * 1024 * 1024; // 500MB +const bool kDefaultEnableTscValue = false; +const llvm::Optional<size_t> kDefaultPsbPeriod = llvm::None; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_CONSTANTS_H diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td index 6ffe949dbe7b..9e8cab1ee5c4 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -1,16 +1,74 @@ include "../../../../source/Commands/OptionsBase.td" +// The information of the start commands here should match the description of +// the intel-pt section of the jLLDBTraceStart packet in the +// lldb/docs/lldb-gdb-remote.txt documentation file. Similarly, it should match +// the API help message of TraceIntelPT::GetStartConfigurationHelp(). + let Command = "thread trace start intel pt" in { def thread_trace_start_intel_pt_size: Option<"size", "s">, Group<1>, Arg<"Value">, - Desc<"The size of the trace in KB. The kernel rounds it down to the nearest" - " multiple of 4. Defaults to 4.">; - def thread_trace_start_intel_pt_custom_config: Option<"custom-config", "c">, + Desc<"Trace size in bytes per thread. It must be a power of 2 greater " + "than or equal to 4096 (2^12). The trace is circular keeping " + "the most recent data. Defaults to 4096 bytes.">; + def thread_trace_start_intel_pt_tsc: Option<"tsc", "t">, + Group<1>, + Desc<"Enable the use of TSC timestamps. This is supported on all devices " + "that support intel-pt.">; + def thread_trace_start_intel_pt_psb_period: Option<"psb-period", "p">, + Group<1>, + Arg<"Value">, + Desc<"This value defines the period in which PSB packets will be " + "generated. A PSB packet is a synchronization packet that contains a " + "TSC timestamp and the current absolute instruction pointer. " + "This parameter can only be used if " + "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc is 1. Otherwise, " + "the PSB period will be defined by the processor. If supported, valid " + "values for this period can be found in " + "/sys/bus/event_source/devices/intel_pt/caps/psb_periods which " + "contains a hexadecimal number, whose bits represent valid values " + "e.g. if bit 2 is set, then value 2 is valid. The psb_period value is " + "converted to the approximate number of raw trace bytes between PSB " + "packets as: 2 ^ (value + 11), e.g. value 3 means 16KiB between PSB " + "packets. Defaults to 0 if supported.">; +} + +let Command = "process trace start intel pt" in { + def process_trace_start_intel_pt_thread_size: Option<"thread-size", "s">, + Group<1>, + Arg<"Value">, + Desc<"Trace size in bytes per thread. It must be a power of 2 greater " + "than or equal to 4096 (2^12). The trace is circular keeping " + "the most recent data. Defaults to 4096 bytes.">; + def process_trace_start_intel_pt_process_size_limit: Option<"total-size-limit", "l">, + Group<1>, + Arg<"Value">, + Desc<"Maximum total trace size per process in bytes. This limit applies to " + "the sum of the sizes of all thread traces of this process, excluding " + "the ones created with the \"thread trace start\" command. " + "Whenever a thread is attempted to be traced due to this command and " + "the limit would be reached, the process is stopped with a " + "\"processor trace\" reason, so that the user can retrace the process " + "if needed. Defaults to 500MB.">; + def process_trace_start_intel_pt_tsc: Option<"tsc", "t">, + Group<1>, + Desc<"Enable the use of TSC timestamps. This is supported on all devices " + "that support intel-pt.">; + def process_trace_start_intel_pt_psb_period: Option<"psb-period", "p">, Group<1>, Arg<"Value">, - Desc<"Low level bitmask configuration for the kernel based on the values " - "in `grep -H /sys/bus/event_source/devices/intel_pt/format/*`. " - "See https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/perf-intel-pt.txt" - " for more information. Defaults to 0.">; + Desc<"This value defines the period in which PSB packets will be " + "generated. A PSB packet is a synchronization packet that contains a " + "TSC timestamp and the current absolute instruction pointer. " + "This parameter can only be used if " + "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc is 1. Otherwise, " + "the PSB period will be defined by the processor. If supported, valid " + "values for this period can be found in " + "/sys/bus/event_source/devices/intel_pt/caps/psb_periods which " + "contains a hexadecimal number, whose bits represent valid values " + "e.g. if bit 2 is set, then value 2 is valid. The psb_period value is " + "converted to the approximate number of raw trace bytes between PSB " + "packets as: 2 ^ (value + 11), e.g. value 3 means 16KiB between PSB " + "packets. Defaults to 0 if supported.">; } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp index beef5c3968ea..5af7c269d0cb 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.cpp @@ -8,11 +8,11 @@ #include "TraceIntelPTSessionFileParser.h" +#include "../common/ThreadPostMortemTrace.h" #include "lldb/Core/Debugger.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadList.h" -#include "lldb/Target/ThreadTrace.h" using namespace lldb; using namespace lldb_private; @@ -24,7 +24,7 @@ StringRef TraceIntelPTSessionFileParser::GetSchema() { if (schema.empty()) { schema = TraceSessionFileParser::BuildSchema(R"({ "type": "intel-pt", - "pt_cpu": { + "cpuInfo": { "vendor": "intel" | "unknown", "family": integer, "model": integer, @@ -35,21 +35,22 @@ StringRef TraceIntelPTSessionFileParser::GetSchema() { return schema; } -pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU(const JSONPTCPU &pt_cpu) { - return {pt_cpu.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, - static_cast<uint16_t>(pt_cpu.family), - static_cast<uint8_t>(pt_cpu.model), - static_cast<uint8_t>(pt_cpu.stepping)}; +pt_cpu TraceIntelPTSessionFileParser::ParsePTCPU( + const JSONTraceIntelPTCPUInfo &cpu_info) { + return {cpu_info.vendor.compare("intel") == 0 ? pcv_intel : pcv_unknown, + static_cast<uint16_t>(cpu_info.family), + static_cast<uint8_t>(cpu_info.model), + static_cast<uint8_t>(cpu_info.stepping)}; } TraceSP TraceIntelPTSessionFileParser::CreateTraceIntelPTInstance( - const pt_cpu &pt_cpu, std::vector<ParsedProcess> &parsed_processes) { - std::vector<ThreadTraceSP> threads; + const pt_cpu &cpu_info, std::vector<ParsedProcess> &parsed_processes) { + std::vector<ThreadPostMortemTraceSP> threads; for (const ParsedProcess &parsed_process : parsed_processes) threads.insert(threads.end(), parsed_process.threads.begin(), parsed_process.threads.end()); - TraceSP trace_instance(new TraceIntelPT(pt_cpu, threads)); + TraceSP trace_instance(new TraceIntelPT(cpu_info, threads)); for (const ParsedProcess &parsed_process : parsed_processes) parsed_process.target_sp->SetTrace(trace_instance); @@ -64,7 +65,7 @@ Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() { if (Expected<std::vector<ParsedProcess>> parsed_processes = ParseCommonSessionFile(session)) - return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.pt_cpu), + return CreateTraceIntelPTInstance(ParsePTCPU(session.trace.cpuInfo), *parsed_processes); else return parsed_processes.takeError(); @@ -73,25 +74,34 @@ Expected<TraceSP> TraceIntelPTSessionFileParser::Parse() { namespace llvm { namespace json { -bool fromJSON(const Value &value, - TraceIntelPTSessionFileParser::JSONPTCPU &pt_cpu, Path path) { - ObjectMapper o(value, path); - return o && o.map("vendor", pt_cpu.vendor) && - o.map("family", pt_cpu.family) && o.map("model", pt_cpu.model) && - o.map("stepping", pt_cpu.stepping); -} - bool fromJSON( const Value &value, TraceIntelPTSessionFileParser::JSONTraceIntelPTSettings &plugin_settings, Path path) { ObjectMapper o(value, path); - return o && o.map("pt_cpu", plugin_settings.pt_cpu) && + return o && o.map("cpuInfo", plugin_settings.cpuInfo) && fromJSON( value, (TraceSessionFileParser::JSONTracePluginSettings &)plugin_settings, path); } +bool fromJSON(const json::Value &value, + TraceIntelPTSessionFileParser::JSONTraceIntelPTCPUInfo &cpu_info, + Path path) { + ObjectMapper o(value, path); + return o && o.map("vendor", cpu_info.vendor) && + o.map("family", cpu_info.family) && o.map("model", cpu_info.model) && + o.map("stepping", cpu_info.stepping); +} + +Value toJSON( + const TraceIntelPTSessionFileParser::JSONTraceIntelPTCPUInfo &cpu_info) { + return Value(Object{{"family", cpu_info.family}, + {"model", cpu_info.model}, + {"stepping", cpu_info.stepping}, + {"vendor", cpu_info.vendor}}); +} + } // namespace json } // namespace llvm diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h index 6a896de09d00..b2667a882222 100644 --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionFileParser.h @@ -10,7 +10,8 @@ #define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTSESSIONFILEPARSER_H #include "TraceIntelPT.h" -#include "lldb/Target/TraceSessionFileParser.h" + +#include "../common/TraceSessionFileParser.h" namespace lldb_private { namespace trace_intel_pt { @@ -19,16 +20,16 @@ class TraceIntelPT; class TraceIntelPTSessionFileParser : public TraceSessionFileParser { public: - struct JSONPTCPU { - std::string vendor; + struct JSONTraceIntelPTCPUInfo { int64_t family; int64_t model; int64_t stepping; + std::string vendor; }; struct JSONTraceIntelPTSettings : TraceSessionFileParser::JSONTracePluginSettings { - JSONPTCPU pt_cpu; + JSONTraceIntelPTCPUInfo cpuInfo; }; /// See \a TraceSessionFileParser::TraceSessionFileParser for the description @@ -52,11 +53,11 @@ public: llvm::Expected<lldb::TraceSP> Parse(); lldb::TraceSP - CreateTraceIntelPTInstance(const pt_cpu &pt_cpu, + CreateTraceIntelPTInstance(const pt_cpu &cpu_info, std::vector<ParsedProcess> &parsed_processes); private: - pt_cpu ParsePTCPU(const JSONPTCPU &pt_cpu); + static pt_cpu ParsePTCPU(const JSONTraceIntelPTCPUInfo &cpu_info); const llvm::json::Value &m_trace_session_file; }; @@ -67,17 +68,20 @@ private: namespace llvm { namespace json { -bool fromJSON( - const Value &value, - lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser::JSONPTCPU - &pt_cpu, - Path path); - bool fromJSON(const Value &value, lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: JSONTraceIntelPTSettings &plugin_settings, Path path); +bool fromJSON(const llvm::json::Value &value, + lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: + JSONTraceIntelPTCPUInfo &packet, + llvm::json::Path path); + +llvm::json::Value +toJSON(const lldb_private::trace_intel_pt::TraceIntelPTSessionFileParser:: + JSONTraceIntelPTCPUInfo &packet); + } // namespace json } // namespace llvm diff --git a/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h b/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h new file mode 100644 index 000000000000..3c5f811acc10 --- /dev/null +++ b/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h @@ -0,0 +1,20 @@ +//===-- forward-declarations.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_FORWARD_DECLARATIONS_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_FORWARD_DECLARATIONS_H + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT; +class ThreadDecoder; + +} // namespace trace_intel_pt +} // namespace lldb_private +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_FORWARD_DECLARATIONS_H |