diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Plugins/Trace')
30 files changed, 5811 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp new file mode 100644 index 000000000000..564a52eaaf82 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.cpp @@ -0,0 +1,43 @@ +//===-- ThreadPostMortemTrace.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 "ThreadPostMortemTrace.h" + +#include <memory> +#include <optional> + +#include "Plugins/Process/Utility/RegisterContextHistory.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +void ThreadPostMortemTrace::RefreshStateAfterStop() {} + +RegisterContextSP ThreadPostMortemTrace::GetRegisterContext() { + if (!m_reg_context_sp) + m_reg_context_sp = CreateRegisterContextForFrame(nullptr); + + return m_reg_context_sp; +} + +RegisterContextSP +ThreadPostMortemTrace::CreateRegisterContextForFrame(StackFrame *frame) { + // Eventually this will calculate the register context based on the current + // trace position. + return std::make_shared<RegisterContextHistory>( + *this, 0, GetProcess()->GetAddressByteSize(), LLDB_INVALID_ADDRESS); +} + +bool ThreadPostMortemTrace::CalculateStopInfo() { return false; } + +const std::optional<FileSpec> &ThreadPostMortemTrace::GetTraceFile() const { + return m_trace_file; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h b/contrib/llvm-project/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h new file mode 100644 index 000000000000..39f53fe09f88 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/common/ThreadPostMortemTrace.h @@ -0,0 +1,59 @@ +//===-- ThreadPostMortemTrace.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_THREADPOSTMORTEMTRACE_H +#define LLDB_TARGET_THREADPOSTMORTEMTRACE_H + +#include "lldb/Target/Thread.h" +#include <optional> + +namespace lldb_private { + +/// \class ThreadPostMortemTrace ThreadPostMortemTrace.h +/// +/// Thread implementation used for representing threads gotten from trace +/// session files, which are similar to threads from core files. +/// +class ThreadPostMortemTrace : 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. + ThreadPostMortemTrace(Process &process, lldb::tid_t tid, + const std::optional<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 std::optional<FileSpec> &GetTraceFile() const; + +protected: + bool CalculateStopInfo() override; + + lldb::RegisterContextSP m_thread_reg_ctx_sp; + +private: + std::optional<FileSpec> m_trace_file; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_THREADPOSTMORTEMTRACE_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp new file mode 100644 index 000000000000..44224229e625 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.cpp @@ -0,0 +1,212 @@ +//===-- CommandObjectTraceStartIntelPT.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 "CommandObjectTraceStartIntelPT.h" +#include "TraceIntelPT.h" +#include "TraceIntelPTConstants.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandOptionArgumentTable.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Trace.h" +#include <optional> + +using namespace lldb; +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 CommandObjectThreadTraceStartIntelPT::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': { + if (std::optional<uint64_t> bytes = + ParsingUtils::ParseUserFriendlySizeExpression(option_arg)) + m_ipt_trace_size = *bytes; + else + error.SetErrorStringWithFormat("invalid bytes expression for '%s'", + option_arg.str().c_str()); + 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 CommandObjectThreadTraceStartIntelPT::CommandOptions:: + OptionParsingStarting(ExecutionContext *execution_context) { + m_ipt_trace_size = kDefaultIptTraceSize; + m_enable_tsc = kDefaultEnableTscValue; + m_psb_period = kDefaultPsbPeriod; +} + +llvm::ArrayRef<OptionDefinition> +CommandObjectThreadTraceStartIntelPT::CommandOptions::GetDefinitions() { + return llvm::ArrayRef(g_thread_trace_start_intel_pt_options); +} + +bool CommandObjectThreadTraceStartIntelPT::DoExecuteOnThreads( + Args &command, CommandReturnObject &result, + llvm::ArrayRef<lldb::tid_t> tids) { + if (Error err = m_trace.Start(tids, m_options.m_ipt_trace_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': { + if (std::optional<uint64_t> bytes = + ParsingUtils::ParseUserFriendlySizeExpression(option_arg)) + m_ipt_trace_size = *bytes; + else + error.SetErrorStringWithFormat("invalid bytes expression for '%s'", + option_arg.str().c_str()); + break; + } + case 'l': { + if (std::optional<uint64_t> bytes = + ParsingUtils::ParseUserFriendlySizeExpression(option_arg)) + m_process_buffer_size_limit = *bytes; + else + error.SetErrorStringWithFormat("invalid bytes expression for '%s'", + option_arg.str().c_str()); + break; + } + case 't': { + m_enable_tsc = true; + break; + } + case 'c': { + m_per_cpu_tracing = true; + break; + } + case 'd': { + m_disable_cgroup_filtering = 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_ipt_trace_size = kDefaultIptTraceSize; + m_process_buffer_size_limit = kDefaultProcessBufferSizeLimit; + m_enable_tsc = kDefaultEnableTscValue; + m_psb_period = kDefaultPsbPeriod; + m_per_cpu_tracing = kDefaultPerCpuTracing; + m_disable_cgroup_filtering = kDefaultDisableCgroupFiltering; +} + +llvm::ArrayRef<OptionDefinition> +CommandObjectProcessTraceStartIntelPT::CommandOptions::GetDefinitions() { + return llvm::ArrayRef(g_process_trace_start_intel_pt_options); +} + +void CommandObjectProcessTraceStartIntelPT::DoExecute( + Args &command, CommandReturnObject &result) { + if (Error err = m_trace.Start( + m_options.m_ipt_trace_size, m_options.m_process_buffer_size_limit, + m_options.m_enable_tsc, m_options.m_psb_period, + m_options.m_per_cpu_tracing, m_options.m_disable_cgroup_filtering)) + result.SetError(Status(std::move(err))); + else + result.SetStatus(eReturnStatusSuccessFinishResult); +} + +std::optional<uint64_t> +ParsingUtils::ParseUserFriendlySizeExpression(llvm::StringRef size_expression) { + if (size_expression.empty()) { + return std::nullopt; + } + const uint64_t kBytesMultiplier = 1; + const uint64_t kKibiBytesMultiplier = 1024; + const uint64_t kMebiBytesMultiplier = 1024 * 1024; + + DenseMap<StringRef, uint64_t> multipliers = { + {"mib", kMebiBytesMultiplier}, {"mb", kMebiBytesMultiplier}, + {"m", kMebiBytesMultiplier}, {"kib", kKibiBytesMultiplier}, + {"kb", kKibiBytesMultiplier}, {"k", kKibiBytesMultiplier}, + {"b", kBytesMultiplier}, {"", kBytesMultiplier}}; + + const auto non_digit_index = size_expression.find_first_not_of("0123456789"); + if (non_digit_index == 0) { // expression starts from from non-digit char. + return std::nullopt; + } + + const llvm::StringRef number_part = + non_digit_index == llvm::StringRef::npos + ? size_expression + : size_expression.substr(0, non_digit_index); + uint64_t parsed_number; + if (number_part.getAsInteger(10, parsed_number)) { + return std::nullopt; + } + + if (non_digit_index != llvm::StringRef::npos) { // if expression has units. + const auto multiplier = size_expression.substr(non_digit_index).lower(); + + auto it = multipliers.find(multiplier); + if (it == multipliers.end()) + return std::nullopt; + + return parsed_number * it->second; + } else { + return parsed_number; + } +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h new file mode 100644 index 000000000000..82714dea3fcd --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/CommandObjectTraceStartIntelPT.h @@ -0,0 +1,134 @@ +//===-- CommandObjectTraceStartIntelPT.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_COMMANDOBJECTTRACESTARTINTELPT_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H + +#include "../../../../source/Commands/CommandObjectTrace.h" +#include "TraceIntelPT.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +class CommandObjectThreadTraceStartIntelPT + : public CommandObjectMultipleThreads { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + 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; + + uint64_t m_ipt_trace_size; + bool m_enable_tsc; + std::optional<uint64_t> m_psb_period; + }; + + 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 including future threads.", + "thread trace start [<thread-index> <thread-index> ...] " + "[<intel-pt-options>]", + lldb::eCommandRequiresProcess | lldb::eCommandTryTargetAPILock | + lldb::eCommandProcessMustBeLaunched | + lldb::eCommandProcessMustBePaused), + 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); } + + 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; + + uint64_t m_ipt_trace_size; + uint64_t m_process_buffer_size_limit; + bool m_enable_tsc; + std::optional<uint64_t> m_psb_period; + bool m_per_cpu_tracing; + bool m_disable_cgroup_filtering; + }; + + CommandObjectProcessTraceStartIntelPT(TraceIntelPT &trace, + CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "process trace start", + "Start tracing this process with intel-pt, including future " + "threads. If --per-cpu-tracing is not provided, this traces each " + "thread independently, thus using a trace buffer per thread. " + "Threads traced with the \"thread trace start\" command are left " + "unaffected ant not retraced. This is the recommended option " + "unless the number of threads is huge. If --per-cpu-tracing is " + "passed, each cpu core is traced instead of each thread, which " + "uses a fixed number of trace buffers, but might result in less " + "data available for less frequent threads.", + "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: + void DoExecute(Args &command, CommandReturnObject &result) override; + + TraceIntelPT &m_trace; + CommandOptions m_options; +}; + +namespace ParsingUtils { +/// Convert an integral size expression like 12KiB or 4MB into bytes. The units +/// are taken loosely to help users input sizes into LLDB, e.g. KiB and KB are +/// considered the same (2^20 bytes) for simplicity. +/// +/// \param[in] size_expression +/// String expression which is an integral number plus a unit that can be +/// lower or upper case. Supported units: K, KB and KiB for 2^10 bytes; M, +/// MB and MiB for 2^20 bytes; and B for bytes. A single integral number is +/// considered bytes. +/// \return +/// The converted number of bytes or \a std::nullopt if the expression is +/// invalid. +std::optional<uint64_t> +ParseUserFriendlySizeExpression(llvm::StringRef size_expression); +} // namespace ParsingUtils + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_COMMANDOBJECTTRACESTARTINTELPT_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp new file mode 100644 index 000000000000..9c075398d547 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/DecodedThread.cpp @@ -0,0 +1,262 @@ +//===-- 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" +#include "TraceCursorIntelPT.h" +#include <intel-pt.h> +#include <memory> +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +char IntelPTError::ID; + +IntelPTError::IntelPTError(int libipt_error_code, lldb::addr_t address) + : m_libipt_error_code(libipt_error_code), m_address(address) { + assert(libipt_error_code < 0); +} + +void IntelPTError::log(llvm::raw_ostream &OS) const { + OS << pt_errstr(pt_errcode(m_libipt_error_code)); + if (m_address != LLDB_INVALID_ADDRESS && m_address > 0) + OS << formatv(": {0:x+16}", m_address); +} + +bool DecodedThread::TSCRange::InRange(uint64_t item_index) const { + return item_index >= first_item_index && + item_index < first_item_index + items_count; +} + +bool DecodedThread::NanosecondsRange::InRange(uint64_t item_index) const { + return item_index >= first_item_index && + item_index < first_item_index + items_count; +} + +double DecodedThread::NanosecondsRange::GetInterpolatedTime( + uint64_t item_index, uint64_t begin_of_time_nanos, + const LinuxPerfZeroTscConversion &tsc_conversion) const { + uint64_t items_since_last_tsc = item_index - first_item_index; + + auto interpolate = [&](uint64_t next_range_start_ns) { + if (next_range_start_ns == nanos) { + // If the resolution of the conversion formula is bad enough to consider + // these two timestamps as equal, then we just increase the next one by 1 + // for correction + next_range_start_ns++; + } + long double item_duration = + static_cast<long double>(items_count) / (next_range_start_ns - nanos); + return (nanos - begin_of_time_nanos) + items_since_last_tsc * item_duration; + }; + + if (!next_range) { + // If this is the last TSC range, so we have to extrapolate. In this case, + // we assume that each instruction took one TSC, which is what an + // instruction would take if no parallelism is achieved and the frequency + // multiplier is 1. + return interpolate(tsc_conversion.ToNanos(tsc + items_count)); + } + if (items_count < (next_range->tsc - tsc)) { + // If the numbers of items in this range is less than the total TSC duration + // of this range, i.e. each instruction taking longer than 1 TSC, then we + // can assume that something else happened between these TSCs (e.g. a + // context switch, change to kernel, decoding errors, etc). In this case, we + // also assume that each instruction took 1 TSC. A proper way to improve + // this would be to analize the next events in the trace looking for context + // switches or trace disablement events, but for now, as we only want an + // approximation, we keep it simple. We are also guaranteed that the time in + // nanos of the next range is different to the current one, just because of + // the definition of a NanosecondsRange. + return interpolate( + std::min(tsc_conversion.ToNanos(tsc + items_count), next_range->nanos)); + } + + // In this case, each item took less than 1 TSC, so some parallelism was + // achieved, which is an indication that we didn't suffered of any kind of + // interruption. + return interpolate(next_range->nanos); +} + +uint64_t DecodedThread::GetItemsCount() const { return m_item_data.size(); } + +lldb::addr_t +DecodedThread::GetInstructionLoadAddress(uint64_t item_index) const { + return std::get<lldb::addr_t>(m_item_data[item_index]); +} + +lldb::addr_t +DecodedThread::GetSyncPointOffsetByIndex(uint64_t item_index) const { + return m_psb_offsets.find(item_index)->second; +} + +ThreadSP DecodedThread::GetThread() { return m_thread_sp; } + +template <typename Data> +DecodedThread::TraceItemStorage & +DecodedThread::CreateNewTraceItem(lldb::TraceItemKind kind, Data &&data) { + m_item_data.emplace_back(data); + + if (m_last_tsc) + (*m_last_tsc)->second.items_count++; + if (m_last_nanoseconds) + (*m_last_nanoseconds)->second.items_count++; + + return m_item_data.back(); +} + +void DecodedThread::NotifySyncPoint(lldb::addr_t psb_offset) { + m_psb_offsets.try_emplace(GetItemsCount(), psb_offset); + AppendEvent(lldb::eTraceEventSyncPoint); +} + +void DecodedThread::NotifyTsc(TSC tsc) { + if (m_last_tsc && (*m_last_tsc)->second.tsc == tsc) + return; + if (m_last_tsc) + assert(tsc >= (*m_last_tsc)->second.tsc && + "We can't have decreasing times"); + + m_last_tsc = + m_tscs.emplace(GetItemsCount(), TSCRange{tsc, 0, GetItemsCount()}).first; + + if (m_tsc_conversion) { + uint64_t nanos = m_tsc_conversion->ToNanos(tsc); + if (!m_last_nanoseconds || (*m_last_nanoseconds)->second.nanos != nanos) { + m_last_nanoseconds = + m_nanoseconds + .emplace(GetItemsCount(), NanosecondsRange{nanos, tsc, nullptr, 0, + GetItemsCount()}) + .first; + if (*m_last_nanoseconds != m_nanoseconds.begin()) { + auto prev_range = prev(*m_last_nanoseconds); + prev_range->second.next_range = &(*m_last_nanoseconds)->second; + } + } + } + AppendEvent(lldb::eTraceEventHWClockTick); +} + +void DecodedThread::NotifyCPU(lldb::cpu_id_t cpu_id) { + if (!m_last_cpu || *m_last_cpu != cpu_id) { + m_cpus.emplace(GetItemsCount(), cpu_id); + m_last_cpu = cpu_id; + AppendEvent(lldb::eTraceEventCPUChanged); + } +} + +lldb::cpu_id_t DecodedThread::GetCPUByIndex(uint64_t item_index) const { + auto it = m_cpus.upper_bound(item_index); + return it == m_cpus.begin() ? LLDB_INVALID_CPU_ID : prev(it)->second; +} + +std::optional<DecodedThread::TSCRange> +DecodedThread::GetTSCRangeByIndex(uint64_t item_index) const { + auto next_it = m_tscs.upper_bound(item_index); + if (next_it == m_tscs.begin()) + return std::nullopt; + return prev(next_it)->second; +} + +std::optional<DecodedThread::NanosecondsRange> +DecodedThread::GetNanosecondsRangeByIndex(uint64_t item_index) { + auto next_it = m_nanoseconds.upper_bound(item_index); + if (next_it == m_nanoseconds.begin()) + return std::nullopt; + return prev(next_it)->second; +} + +uint64_t DecodedThread::GetTotalInstructionCount() const { + return m_insn_count; +} + +void DecodedThread::AppendEvent(lldb::TraceEvent event) { + CreateNewTraceItem(lldb::eTraceItemKindEvent, event); + m_events_stats.RecordEvent(event); +} + +void DecodedThread::AppendInstruction(const pt_insn &insn) { + CreateNewTraceItem(lldb::eTraceItemKindInstruction, insn.ip); + m_insn_count++; +} + +void DecodedThread::AppendError(const IntelPTError &error) { + CreateNewTraceItem(lldb::eTraceItemKindError, error.message()); + m_error_stats.RecordError(/*fatal=*/false); +} + +void DecodedThread::AppendCustomError(StringRef err, bool fatal) { + CreateNewTraceItem(lldb::eTraceItemKindError, err.str()); + m_error_stats.RecordError(fatal); +} + +lldb::TraceEvent DecodedThread::GetEventByIndex(int item_index) const { + return std::get<lldb::TraceEvent>(m_item_data[item_index]); +} + +const DecodedThread::EventsStats &DecodedThread::GetEventsStats() const { + return m_events_stats; +} + +void DecodedThread::EventsStats::RecordEvent(lldb::TraceEvent event) { + events_counts[event]++; + total_count++; +} + +uint64_t DecodedThread::ErrorStats::GetTotalCount() const { + uint64_t total = 0; + for (const auto &[kind, count] : libipt_errors) + total += count; + + return total + other_errors + fatal_errors; +} + +void DecodedThread::ErrorStats::RecordError(bool fatal) { + if (fatal) + fatal_errors++; + else + other_errors++; +} + +void DecodedThread::ErrorStats::RecordError(int libipt_error_code) { + libipt_errors[pt_errstr(pt_errcode(libipt_error_code))]++; +} + +const DecodedThread::ErrorStats &DecodedThread::GetErrorStats() const { + return m_error_stats; +} + +lldb::TraceItemKind +DecodedThread::GetItemKindByIndex(uint64_t item_index) const { + return std::visit( + llvm::makeVisitor( + [](const std::string &) { return lldb::eTraceItemKindError; }, + [](lldb::TraceEvent) { return lldb::eTraceItemKindEvent; }, + [](lldb::addr_t) { return lldb::eTraceItemKindInstruction; }), + m_item_data[item_index]); +} + +llvm::StringRef DecodedThread::GetErrorByIndex(uint64_t item_index) const { + if (item_index >= m_item_data.size()) + return llvm::StringRef(); + return std::get<std::string>(m_item_data[item_index]); +} + +DecodedThread::DecodedThread( + ThreadSP thread_sp, + const std::optional<LinuxPerfZeroTscConversion> &tsc_conversion) + : m_thread_sp(thread_sp), m_tsc_conversion(tsc_conversion) {} + +size_t DecodedThread::CalculateApproximateMemoryUsage() const { + return sizeof(TraceItemStorage) * m_item_data.size() + + (sizeof(uint64_t) + sizeof(TSC)) * m_tscs.size() + + (sizeof(uint64_t) + sizeof(uint64_t)) * m_nanoseconds.size() + + (sizeof(uint64_t) + sizeof(lldb::cpu_id_t)) * m_cpus.size(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h new file mode 100644 index 000000000000..a48c55cc76df --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/DecodedThread.h @@ -0,0 +1,330 @@ +//===-- 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 "intel-pt.h" +#include "lldb/Target/Trace.h" +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include <deque> +#include <optional> +#include <utility> +#include <variant> + +namespace lldb_private { +namespace trace_intel_pt { + +/// Class for representing a libipt decoding error. +class IntelPTError : public llvm::ErrorInfo<IntelPTError> { +public: + static char ID; + + /// \param[in] libipt_error_code + /// Negative number returned by libipt when decoding the trace and + /// signaling errors. + /// + /// \param[in] address + /// Optional instruction address. When decoding an individual instruction, + /// its address might be available in the \a pt_insn object, and should be + /// passed to this constructor. Other errors don't have an associated + /// address. + IntelPTError(int libipt_error_code, + lldb::addr_t address = LLDB_INVALID_ADDRESS); + + std::error_code convertToErrorCode() const override { + return llvm::errc::not_supported; + } + + int GetLibiptErrorCode() const { return m_libipt_error_code; } + + void log(llvm::raw_ostream &OS) const override; + +private: + int m_libipt_error_code; + lldb::addr_t m_address; +}; + +/// \class DecodedThread +/// Class holding the instructions and function call hierarchy obtained from +/// decoding a trace, as well as a position cursor used when reverse debugging +/// the trace. +/// +/// Each decoded thread contains a cursor to the current position the user is +/// stopped at. See \a Trace::GetCursorPosition for more information. +class DecodedThread : public std::enable_shared_from_this<DecodedThread> { +public: + using TSC = uint64_t; + + /// A structure that represents a maximal range of trace items associated to + /// the same TSC value. + struct TSCRange { + TSC tsc; + /// Number of trace items in this range. + uint64_t items_count; + /// Index of the first trace item in this range. + uint64_t first_item_index; + + /// \return + /// \b true if and only if the given \p item_index is covered by this + /// range. + bool InRange(uint64_t item_index) const; + }; + + /// A structure that represents a maximal range of trace items associated to + /// the same non-interpolated timestamps in nanoseconds. + struct NanosecondsRange { + /// The nanoseconds value for this range. + uint64_t nanos; + /// The corresponding TSC value for this range. + TSC tsc; + /// A nullable pointer to the next range. + NanosecondsRange *next_range; + /// Number of trace items in this range. + uint64_t items_count; + /// Index of the first trace item in this range. + uint64_t first_item_index; + + /// Calculate an interpolated timestamp in nanoseconds for the given item + /// index. It's guaranteed that two different item indices will produce + /// different interpolated values. + /// + /// \param[in] item_index + /// The index of the item whose timestamp will be estimated. It has to be + /// part of this range. + /// + /// \param[in] beginning_of_time_nanos + /// The timestamp at which tracing started. + /// + /// \param[in] tsc_conversion + /// The tsc -> nanos conversion utility + /// + /// \return + /// An interpolated timestamp value for the given trace item. + double + GetInterpolatedTime(uint64_t item_index, uint64_t beginning_of_time_nanos, + const LinuxPerfZeroTscConversion &tsc_conversion) const; + + /// \return + /// \b true if and only if the given \p item_index is covered by this + /// range. + bool InRange(uint64_t item_index) const; + }; + + // Struct holding counts for events + struct EventsStats { + /// A count for each individual event kind. We use an unordered map instead + /// of a DenseMap because DenseMap can't understand enums. + /// + /// Note: We can't use DenseMap because lldb::TraceEvent is not + /// automatically handled correctly by DenseMap. We'd need to implement a + /// custom DenseMapInfo struct for TraceEvent and that's a bit too much for + /// such a simple structure. + std::unordered_map<lldb::TraceEvent, uint64_t> events_counts; + uint64_t total_count = 0; + + void RecordEvent(lldb::TraceEvent event); + }; + + // Struct holding counts for errors + struct ErrorStats { + /// The following counters are mutually exclusive + /// \{ + uint64_t other_errors = 0; + uint64_t fatal_errors = 0; + // libipt error -> count + llvm::DenseMap<const char *, uint64_t> libipt_errors; + /// \} + + uint64_t GetTotalCount() const; + + void RecordError(int libipt_error_code); + + void RecordError(bool fatal); + }; + + DecodedThread( + lldb::ThreadSP thread_sp, + const std::optional<LinuxPerfZeroTscConversion> &tsc_conversion); + + /// Get the total number of instruction, errors and events from the decoded + /// trace. + uint64_t GetItemsCount() const; + + /// \return + /// The error associated with a given trace item. + llvm::StringRef GetErrorByIndex(uint64_t item_index) const; + + /// \return + /// The trace item kind given an item index. + lldb::TraceItemKind GetItemKindByIndex(uint64_t item_index) const; + + /// \return + /// The underlying event type for the given trace item index. + lldb::TraceEvent GetEventByIndex(int item_index) const; + + /// Get the most recent CPU id before or at the given trace item index. + /// + /// \param[in] item_index + /// The trace item index to compare with. + /// + /// \return + /// The requested cpu id, or \a LLDB_INVALID_CPU_ID if not available. + lldb::cpu_id_t GetCPUByIndex(uint64_t item_index) const; + + /// \return + /// The PSB offset associated with the given item index. + lldb::addr_t GetSyncPointOffsetByIndex(uint64_t item_index) const; + + /// Get a maximal range of trace items that include the given \p item_index + /// that have the same TSC value. + /// + /// \param[in] item_index + /// The trace item index to compare with. + /// + /// \return + /// The requested TSC range, or \a std::nullopt if not available. + std::optional<DecodedThread::TSCRange> + GetTSCRangeByIndex(uint64_t item_index) const; + + /// Get a maximal range of trace items that include the given \p item_index + /// that have the same nanoseconds timestamp without interpolation. + /// + /// \param[in] item_index + /// The trace item index to compare with. + /// + /// \return + /// The requested nanoseconds range, or \a std::nullopt if not available. + std::optional<DecodedThread::NanosecondsRange> + GetNanosecondsRangeByIndex(uint64_t item_index); + + /// \return + /// The load address of the instruction at the given index. + lldb::addr_t GetInstructionLoadAddress(uint64_t item_index) const; + + /// \return + /// The number of instructions in this trace (not trace items). + uint64_t GetTotalInstructionCount() const; + + /// Return an object with statistics of the trace events that happened. + /// + /// \return + /// The stats object of all the events. + const EventsStats &GetEventsStats() const; + + /// Return an object with statistics of the trace errors that happened. + /// + /// \return + /// The stats object of all the events. + const ErrorStats &GetErrorStats() const; + + /// The approximate size in bytes used by this instance, + /// including all the already decoded instructions. + size_t CalculateApproximateMemoryUsage() const; + + lldb::ThreadSP GetThread(); + + /// Notify this object that a new tsc has been seen. + /// If this a new TSC, an event will be created. + void NotifyTsc(TSC tsc); + + /// Notify this object that a CPU has been seen. + /// If this a new CPU, an event will be created. + void NotifyCPU(lldb::cpu_id_t cpu_id); + + /// Notify this object that a new PSB has been seen. + void NotifySyncPoint(lldb::addr_t psb_offset); + + /// Append a decoding error. + void AppendError(const IntelPTError &error); + + /// Append a custom decoding. + /// + /// \param[in] error + /// The error message. + /// + /// \param[in] fatal + /// If \b true, then the whole decoded thread should be discarded because a + /// fatal anomaly has been found. + void AppendCustomError(llvm::StringRef error, bool fatal = false); + + /// Append an event. + void AppendEvent(lldb::TraceEvent); + + /// Append an instruction. + void AppendInstruction(const pt_insn &insn); + +private: + /// When adding new members to this class, make sure + /// to update \a CalculateApproximateMemoryUsage() accordingly. + lldb::ThreadSP m_thread_sp; + + using TraceItemStorage = + std::variant<std::string, lldb::TraceEvent, lldb::addr_t>; + + /// Create a new trace item. + /// + /// \return + /// The index of the new item. + template <typename Data> + DecodedThread::TraceItemStorage &CreateNewTraceItem(lldb::TraceItemKind kind, + Data &&data); + + /// Most of the trace data is stored here. + std::deque<TraceItemStorage> m_item_data; + + /// This map contains the TSCs of the decoded trace items. It maps + /// `item index -> TSC`, where `item index` is the first index + /// at which the mapped TSC first appears. We use this representation because + /// TSCs are sporadic and we can think of them as ranges. + std::map<uint64_t, TSCRange> m_tscs; + /// This is the chronologically last TSC that has been added. + std::optional<std::map<uint64_t, TSCRange>::iterator> m_last_tsc = + std::nullopt; + /// This map contains the non-interpolated nanoseconds timestamps of the + /// decoded trace items. It maps `item index -> nanoseconds`, where `item + /// index` is the first index at which the mapped nanoseconds first appears. + /// We use this representation because timestamps are sporadic and we think of + /// them as ranges. + std::map<uint64_t, NanosecondsRange> m_nanoseconds; + std::optional<std::map<uint64_t, NanosecondsRange>::iterator> + m_last_nanoseconds = std::nullopt; + + // The cpu information is stored as a map. It maps `item index -> CPU`. + // A CPU is associated with the next instructions that follow until the next + // cpu is seen. + std::map<uint64_t, lldb::cpu_id_t> m_cpus; + /// This is the chronologically last CPU ID. + std::optional<uint64_t> m_last_cpu; + + // The PSB offsets are stored as a map. It maps `item index -> psb offset`. + llvm::DenseMap<uint64_t, lldb::addr_t> m_psb_offsets; + + /// TSC -> nanos conversion utility. + std::optional<LinuxPerfZeroTscConversion> m_tsc_conversion; + + /// Statistics of all tracing errors. + ErrorStats m_error_stats; + + /// Statistics of all tracing events. + EventsStats m_events_stats; + /// Total amount of time spent decoding. + std::chrono::milliseconds m_total_decoding_time{0}; + + /// Total number of instructions in the trace. + uint64_t m_insn_count = 0; +}; + +using DecodedThreadSP = std::shared_ptr<DecodedThread>; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_DECODEDTHREAD_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.cpp new file mode 100644 index 000000000000..f8241ef6a793 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.cpp @@ -0,0 +1,783 @@ +//===-- LibiptDecoder.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 "LibiptDecoder.h" +#include "TraceIntelPT.h" +#include "lldb/Target/Process.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +bool IsLibiptError(int status) { return status < 0; } + +bool IsEndOfStream(int status) { + assert(status >= 0 && "We can't check if we reached the end of the stream if " + "we got a failed status"); + return status & pts_eos; +} + +bool HasEvents(int status) { + assert(status >= 0 && "We can't check for events if we got a failed status"); + return status & pts_event_pending; +} + +// RAII deleter for libipt's decoders +auto InsnDecoderDeleter = [](pt_insn_decoder *decoder) { + pt_insn_free_decoder(decoder); +}; + +auto QueryDecoderDeleter = [](pt_query_decoder *decoder) { + pt_qry_free_decoder(decoder); +}; + +using PtInsnDecoderUP = + std::unique_ptr<pt_insn_decoder, decltype(InsnDecoderDeleter)>; + +using PtQueryDecoderUP = + std::unique_ptr<pt_query_decoder, decltype(QueryDecoderDeleter)>; + +/// Create a basic configuration object limited to a given buffer that can be +/// used for many different decoders. +static Expected<pt_config> CreateBasicLibiptConfig(TraceIntelPT &trace_intel_pt, + ArrayRef<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 = *cpu_info; + + int status = pt_cpu_errata(&config.errata, &config.cpu); + if (IsLibiptError(status)) + return make_error<IntelPTError>(status); + + // The libipt library does not modify the trace buffer, hence the + // following casts are safe. + config.begin = const_cast<uint8_t *>(buffer.data()); + config.end = const_cast<uint8_t *>(buffer.data() + buffer.size()); + return config; +} + +/// 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 = static_cast<Process *>(context); + + Status error; + int bytes_read = process->ReadMemory(pc, buffer, size, error); + if (error.Fail()) + return -pte_nomap; + return bytes_read; +} + +/// Set up the memory image callback for the given decoder. +static Error SetupMemoryImage(pt_insn_decoder *decoder, Process &process) { + pt_image *image = pt_insn_get_image(decoder); + + int status = pt_image_set_callback(image, ReadProcessMemory, &process); + if (IsLibiptError(status)) + return make_error<IntelPTError>(status); + return Error::success(); +} + +/// Create an instruction decoder for the given buffer and the given process. +static Expected<PtInsnDecoderUP> +CreateInstructionDecoder(TraceIntelPT &trace_intel_pt, ArrayRef<uint8_t> buffer, + Process &process) { + Expected<pt_config> config = CreateBasicLibiptConfig(trace_intel_pt, buffer); + if (!config) + return config.takeError(); + + pt_insn_decoder *decoder_ptr = pt_insn_alloc_decoder(&*config); + if (!decoder_ptr) + return make_error<IntelPTError>(-pte_nomem); + + PtInsnDecoderUP decoder_up(decoder_ptr, InsnDecoderDeleter); + + if (Error err = SetupMemoryImage(decoder_ptr, process)) + return std::move(err); + + return decoder_up; +} + +/// Create a query decoder for the given buffer. The query decoder is the +/// highest level decoder that operates directly on packets and doesn't perform +/// actual instruction decoding. That's why it can be useful for inspecting a +/// raw trace without pinning it to a particular process. +static Expected<PtQueryDecoderUP> +CreateQueryDecoder(TraceIntelPT &trace_intel_pt, ArrayRef<uint8_t> buffer) { + Expected<pt_config> config = CreateBasicLibiptConfig(trace_intel_pt, buffer); + if (!config) + return config.takeError(); + + pt_query_decoder *decoder_ptr = pt_qry_alloc_decoder(&*config); + if (!decoder_ptr) + return make_error<IntelPTError>(-pte_nomem); + + return PtQueryDecoderUP(decoder_ptr, QueryDecoderDeleter); +} + +/// Class used to identify anomalies in traces, which should often indicate a +/// fatal error in the trace. +class PSBBlockAnomalyDetector { +public: + PSBBlockAnomalyDetector(pt_insn_decoder &decoder, + TraceIntelPT &trace_intel_pt, + DecodedThread &decoded_thread) + : m_decoder(decoder), m_decoded_thread(decoded_thread) { + m_infinite_decoding_loop_threshold = + trace_intel_pt.GetGlobalProperties() + .GetInfiniteDecodingLoopVerificationThreshold(); + m_extremely_large_decoding_threshold = + trace_intel_pt.GetGlobalProperties() + .GetExtremelyLargeDecodingThreshold(); + m_next_infinite_decoding_loop_threshold = + m_infinite_decoding_loop_threshold; + } + + /// \return + /// An \a llvm::Error if an anomaly that includes the last instruction item + /// in the trace, or \a llvm::Error::success otherwise. + Error DetectAnomaly() { + RefreshPacketOffset(); + uint64_t insn_added_since_last_packet_offset = + m_decoded_thread.GetTotalInstructionCount() - + m_insn_count_at_last_packet_offset; + + // We want to check if we might have fallen in an infinite loop. As this + // check is not a no-op, we want to do it when we have a strong suggestion + // that things went wrong. First, we check how many instructions we have + // decoded since we processed an Intel PT packet for the last time. This + // number should be low, because at some point we should see branches, jumps + // or interrupts that require a new packet to be processed. Once we reach + // certain threshold we start analyzing the trace. + // + // We use the number of decoded instructions since the last Intel PT packet + // as a proxy because, in fact, we don't expect a single packet to give, + // say, 100k instructions. That would mean that there are 100k sequential + // instructions without any single branch, which is highly unlikely, or that + // we found an infinite loop using direct jumps, e.g. + // + // 0x0A: nop or pause + // 0x0C: jump to 0x0A + // + // which is indeed code that is found in the kernel. I presume we reach + // this kind of code in the decoder because we don't handle self-modified + // code in post-mortem kernel traces. + // + // We are right now only signaling the anomaly as a trace error, but it + // would be more conservative to also discard all the trace items found in + // this PSB. I prefer not to do that for the time being to give more + // exposure to this kind of anomalies and help debugging. Discarding the + // trace items would just make investigation harded. + // + // Finally, if the user wants to see if a specific thread has an anomaly, + // it's enough to run the `thread trace dump info` command and look for the + // count of this kind of errors. + + if (insn_added_since_last_packet_offset >= + m_extremely_large_decoding_threshold) { + // In this case, we have decoded a massive amount of sequential + // instructions that don't loop. Honestly I wonder if this will ever + // happen, but better safe than sorry. + return createStringError( + inconvertibleErrorCode(), + "anomalous trace: possible infinite trace detected"); + } + if (insn_added_since_last_packet_offset == + m_next_infinite_decoding_loop_threshold) { + if (std::optional<uint64_t> loop_size = TryIdentifyInfiniteLoop()) { + return createStringError( + inconvertibleErrorCode(), + "anomalous trace: possible infinite loop detected of size %" PRIu64, + *loop_size); + } + m_next_infinite_decoding_loop_threshold *= 2; + } + return Error::success(); + } + +private: + std::optional<uint64_t> TryIdentifyInfiniteLoop() { + // The infinite decoding loops we'll encounter are due to sequential + // instructions that repeat themselves due to direct jumps, therefore in a + // cycle each individual address will only appear once. We use this + // information to detect cycles by finding the last 2 ocurrences of the last + // instruction added to the trace. Then we traverse the trace making sure + // that these two instructions where the ends of a repeating loop. + + // This is a utility that returns the most recent instruction index given a + // position in the trace. If the given position is an instruction, that + // position is returned. It skips non-instruction items. + auto most_recent_insn_index = + [&](uint64_t item_index) -> std::optional<uint64_t> { + while (true) { + if (m_decoded_thread.GetItemKindByIndex(item_index) == + lldb::eTraceItemKindInstruction) { + return item_index; + } + if (item_index == 0) + return std::nullopt; + item_index--; + } + return std::nullopt; + }; + // Similar to most_recent_insn_index but skips the starting position. + auto prev_insn_index = [&](uint64_t item_index) -> std::optional<uint64_t> { + if (item_index == 0) + return std::nullopt; + return most_recent_insn_index(item_index - 1); + }; + + // We first find the most recent instruction. + std::optional<uint64_t> last_insn_index_opt = + *prev_insn_index(m_decoded_thread.GetItemsCount()); + if (!last_insn_index_opt) + return std::nullopt; + uint64_t last_insn_index = *last_insn_index_opt; + + // We then find the most recent previous occurrence of that last + // instruction. + std::optional<uint64_t> last_insn_copy_index = + prev_insn_index(last_insn_index); + uint64_t loop_size = 1; + while (last_insn_copy_index && + m_decoded_thread.GetInstructionLoadAddress(*last_insn_copy_index) != + m_decoded_thread.GetInstructionLoadAddress(last_insn_index)) { + last_insn_copy_index = prev_insn_index(*last_insn_copy_index); + loop_size++; + } + if (!last_insn_copy_index) + return std::nullopt; + + // Now we check if the segment between these last positions of the last + // instruction address is in fact a repeating loop. + uint64_t loop_elements_visited = 1; + uint64_t insn_index_a = last_insn_index, + insn_index_b = *last_insn_copy_index; + while (loop_elements_visited < loop_size) { + if (std::optional<uint64_t> prev = prev_insn_index(insn_index_a)) + insn_index_a = *prev; + else + return std::nullopt; + if (std::optional<uint64_t> prev = prev_insn_index(insn_index_b)) + insn_index_b = *prev; + else + return std::nullopt; + if (m_decoded_thread.GetInstructionLoadAddress(insn_index_a) != + m_decoded_thread.GetInstructionLoadAddress(insn_index_b)) + return std::nullopt; + loop_elements_visited++; + } + return loop_size; + } + + // Refresh the internal counters if a new packet offset has been visited + void RefreshPacketOffset() { + lldb::addr_t new_packet_offset; + if (!IsLibiptError(pt_insn_get_offset(&m_decoder, &new_packet_offset)) && + new_packet_offset != m_last_packet_offset) { + m_last_packet_offset = new_packet_offset; + m_next_infinite_decoding_loop_threshold = + m_infinite_decoding_loop_threshold; + m_insn_count_at_last_packet_offset = + m_decoded_thread.GetTotalInstructionCount(); + } + } + + pt_insn_decoder &m_decoder; + DecodedThread &m_decoded_thread; + lldb::addr_t m_last_packet_offset = LLDB_INVALID_ADDRESS; + uint64_t m_insn_count_at_last_packet_offset = 0; + uint64_t m_infinite_decoding_loop_threshold; + uint64_t m_next_infinite_decoding_loop_threshold; + uint64_t m_extremely_large_decoding_threshold; +}; + +/// Class that decodes a raw buffer for a single PSB block using the low level +/// libipt library. It assumes that kernel and user mode instructions are not +/// mixed in the same PSB block. +/// +/// Throughout this code, the status of the decoder will be used to identify +/// events needed to be processed or errors in the decoder. The values can be +/// - negative: actual errors +/// - positive or zero: not an error, but a list of bits signaling the status +/// of the decoder, e.g. whether there are events that need to be decoded or +/// not. +class PSBBlockDecoder { +public: + /// \param[in] decoder + /// A decoder configured to start and end within the boundaries of the + /// given \p psb_block. + /// + /// \param[in] psb_block + /// The PSB block to decode. + /// + /// \param[in] next_block_ip + /// The starting ip at the next PSB block of the same thread if available. + /// + /// \param[in] decoded_thread + /// A \a DecodedThread object where the decoded instructions will be + /// appended to. It might have already some instructions. + /// + /// \param[in] tsc_upper_bound + /// Maximum allowed value of TSCs decoded from this PSB block. + /// Any of this PSB's data occurring after this TSC will be excluded. + PSBBlockDecoder(PtInsnDecoderUP &&decoder_up, const PSBBlock &psb_block, + std::optional<lldb::addr_t> next_block_ip, + DecodedThread &decoded_thread, TraceIntelPT &trace_intel_pt, + std::optional<DecodedThread::TSC> tsc_upper_bound) + : m_decoder_up(std::move(decoder_up)), m_psb_block(psb_block), + m_next_block_ip(next_block_ip), m_decoded_thread(decoded_thread), + m_anomaly_detector(*m_decoder_up, trace_intel_pt, decoded_thread), + m_tsc_upper_bound(tsc_upper_bound) {} + + /// \param[in] trace_intel_pt + /// The main Trace object that own the PSB block. + /// + /// \param[in] decoder + /// A decoder configured to start and end within the boundaries of the + /// given \p psb_block. + /// + /// \param[in] psb_block + /// The PSB block to decode. + /// + /// \param[in] buffer + /// The raw intel pt trace for this block. + /// + /// \param[in] process + /// The process to decode. It provides the memory image to use for + /// decoding. + /// + /// \param[in] next_block_ip + /// The starting ip at the next PSB block of the same thread if available. + /// + /// \param[in] decoded_thread + /// A \a DecodedThread object where the decoded instructions will be + /// appended to. It might have already some instructions. + static Expected<PSBBlockDecoder> + Create(TraceIntelPT &trace_intel_pt, const PSBBlock &psb_block, + ArrayRef<uint8_t> buffer, Process &process, + std::optional<lldb::addr_t> next_block_ip, + DecodedThread &decoded_thread, + std::optional<DecodedThread::TSC> tsc_upper_bound) { + Expected<PtInsnDecoderUP> decoder_up = + CreateInstructionDecoder(trace_intel_pt, buffer, process); + if (!decoder_up) + return decoder_up.takeError(); + + return PSBBlockDecoder(std::move(*decoder_up), psb_block, next_block_ip, + decoded_thread, trace_intel_pt, tsc_upper_bound); + } + + void DecodePSBBlock() { + int status = pt_insn_sync_forward(m_decoder_up.get()); + assert(status >= 0 && + "Synchronization shouldn't fail because this PSB was previously " + "decoded correctly."); + + // We emit a TSC before a sync event to more easily associate a timestamp to + // the sync event. If present, the current block's TSC would be the first + // TSC we'll see when processing events. + if (m_psb_block.tsc) + m_decoded_thread.NotifyTsc(*m_psb_block.tsc); + + m_decoded_thread.NotifySyncPoint(m_psb_block.psb_offset); + + DecodeInstructionsAndEvents(status); + } + +private: + /// Append an instruction and return \b false if and only if a serious anomaly + /// has been detected. + bool AppendInstructionAndDetectAnomalies(const pt_insn &insn) { + m_decoded_thread.AppendInstruction(insn); + + if (Error err = m_anomaly_detector.DetectAnomaly()) { + m_decoded_thread.AppendCustomError(toString(std::move(err)), + /*fatal=*/true); + return false; + } + return true; + } + /// Decode all the instructions and events of the given PSB block. The + /// decoding loop might stop abruptly if an infinite decoding loop is + /// detected. + void DecodeInstructionsAndEvents(int status) { + pt_insn insn; + + while (true) { + status = ProcessPTEvents(status); + + if (IsLibiptError(status)) + return; + else if (IsEndOfStream(status)) + break; + + // The status returned by pt_insn_next will need to be processed + // by ProcessPTEvents in the next loop if it is not an error. + std::memset(&insn, 0, sizeof insn); + status = pt_insn_next(m_decoder_up.get(), &insn, sizeof(insn)); + + if (IsLibiptError(status)) { + m_decoded_thread.AppendError(IntelPTError(status, insn.ip)); + return; + } else if (IsEndOfStream(status)) { + break; + } + + if (!AppendInstructionAndDetectAnomalies(insn)) + return; + } + + // We need to keep querying non-branching instructions until we hit the + // starting point of the next PSB. We won't see events at this point. This + // is based on + // https://github.com/intel/libipt/blob/master/doc/howto_libipt.md#parallel-decode + if (m_next_block_ip && insn.ip != 0) { + while (insn.ip != *m_next_block_ip) { + if (!AppendInstructionAndDetectAnomalies(insn)) + return; + + status = pt_insn_next(m_decoder_up.get(), &insn, sizeof(insn)); + + if (IsLibiptError(status)) { + m_decoded_thread.AppendError(IntelPTError(status, insn.ip)); + return; + } + } + } + } + + /// Process the TSC of a decoded PT event. Specifically, check if this TSC + /// is below the TSC upper bound for this PSB. If the TSC exceeds the upper + /// bound, return an error to abort decoding. Otherwise add the it to the + /// underlying DecodedThread and decoding should continue as expected. + /// + /// \param[in] tsc + /// The TSC of the a decoded event. + Error ProcessPTEventTSC(DecodedThread::TSC tsc) { + if (m_tsc_upper_bound && tsc >= *m_tsc_upper_bound) { + // This event and all the remaining events of this PSB have a TSC + // outside the range of the "owning" ThreadContinuousExecution. For + // now we drop all of these events/instructions, future work can + // improve upon this by determining the "owning" + // ThreadContinuousExecution of the remaining PSB data. + std::string err_msg = formatv("decoding truncated: TSC {0} exceeds " + "maximum TSC value {1}, will skip decoding" + " the remaining data of the PSB", + tsc, *m_tsc_upper_bound) + .str(); + + uint64_t offset; + int status = pt_insn_get_offset(m_decoder_up.get(), &offset); + if (!IsLibiptError(status)) { + err_msg = formatv("{2} (skipping {0} of {1} bytes)", offset, + m_psb_block.size, err_msg) + .str(); + } + m_decoded_thread.AppendCustomError(err_msg); + return createStringError(inconvertibleErrorCode(), err_msg); + } else { + m_decoded_thread.NotifyTsc(tsc); + return Error::success(); + } + } + + /// Before querying instructions, we need to query the events associated with + /// that instruction, e.g. timing and trace disablement events. + /// + /// \param[in] status + /// The status gotten from the previous instruction decoding or PSB + /// synchronization. + /// + /// \return + /// The pte_status after decoding events. + int ProcessPTEvents(int status) { + while (HasEvents(status)) { + pt_event event; + std::memset(&event, 0, sizeof event); + status = pt_insn_event(m_decoder_up.get(), &event, sizeof(event)); + + if (IsLibiptError(status)) { + m_decoded_thread.AppendError(IntelPTError(status)); + return status; + } + + if (event.has_tsc) { + if (Error err = ProcessPTEventTSC(event.tsc)) { + consumeError(std::move(err)); + return -pte_internal; + } + } + + switch (event.type) { + case ptev_disabled: + // The CPU paused tracing the program, e.g. due to ip filtering. + m_decoded_thread.AppendEvent(lldb::eTraceEventDisabledHW); + break; + case ptev_async_disabled: + // The kernel or user code paused tracing the program, e.g. + // a breakpoint or a ioctl invocation pausing the trace, or a + // context switch happened. + m_decoded_thread.AppendEvent(lldb::eTraceEventDisabledSW); + break; + case ptev_overflow: + // The CPU internal buffer had an overflow error and some instructions + // were lost. A OVF packet comes with an FUP packet (harcoded address) + // according to the documentation, so we'll continue seeing instructions + // after this event. + m_decoded_thread.AppendError(IntelPTError(-pte_overflow)); + break; + default: + break; + } + } + + return status; + } + +private: + PtInsnDecoderUP m_decoder_up; + PSBBlock m_psb_block; + std::optional<lldb::addr_t> m_next_block_ip; + DecodedThread &m_decoded_thread; + PSBBlockAnomalyDetector m_anomaly_detector; + std::optional<DecodedThread::TSC> m_tsc_upper_bound; +}; + +Error lldb_private::trace_intel_pt::DecodeSingleTraceForThread( + DecodedThread &decoded_thread, TraceIntelPT &trace_intel_pt, + ArrayRef<uint8_t> buffer) { + Expected<std::vector<PSBBlock>> blocks = + SplitTraceIntoPSBBlock(trace_intel_pt, buffer, /*expect_tscs=*/false); + if (!blocks) + return blocks.takeError(); + + for (size_t i = 0; i < blocks->size(); i++) { + PSBBlock &block = blocks->at(i); + + Expected<PSBBlockDecoder> decoder = PSBBlockDecoder::Create( + trace_intel_pt, block, buffer.slice(block.psb_offset, block.size), + *decoded_thread.GetThread()->GetProcess(), + i + 1 < blocks->size() ? blocks->at(i + 1).starting_ip : std::nullopt, + decoded_thread, std::nullopt); + if (!decoder) + return decoder.takeError(); + + decoder->DecodePSBBlock(); + } + + return Error::success(); +} + +Error lldb_private::trace_intel_pt::DecodeSystemWideTraceForThread( + DecodedThread &decoded_thread, TraceIntelPT &trace_intel_pt, + const DenseMap<lldb::cpu_id_t, llvm::ArrayRef<uint8_t>> &buffers, + const std::vector<IntelPTThreadContinousExecution> &executions) { + bool has_seen_psbs = false; + for (size_t i = 0; i < executions.size(); i++) { + const IntelPTThreadContinousExecution &execution = executions[i]; + + auto variant = execution.thread_execution.variant; + + // We emit the first valid tsc + if (execution.psb_blocks.empty()) { + decoded_thread.NotifyTsc(execution.thread_execution.GetLowestKnownTSC()); + } else { + assert(execution.psb_blocks.front().tsc && + "per cpu decoding expects TSCs"); + decoded_thread.NotifyTsc( + std::min(execution.thread_execution.GetLowestKnownTSC(), + *execution.psb_blocks.front().tsc)); + } + + // We then emit the CPU, which will be correctly associated with a tsc. + decoded_thread.NotifyCPU(execution.thread_execution.cpu_id); + + // If we haven't seen a PSB yet, then it's fine not to show errors + if (has_seen_psbs) { + if (execution.psb_blocks.empty()) { + decoded_thread.AppendCustomError( + formatv("Unable to find intel pt data a thread " + "execution on cpu id = {0}", + execution.thread_execution.cpu_id) + .str()); + } + + // A hinted start is a non-initial execution that doesn't have a switch + // in. An only end is an initial execution that doesn't have a switch in. + // Any of those cases represent a gap because we have seen a PSB before. + if (variant == ThreadContinuousExecution::Variant::HintedStart || + variant == ThreadContinuousExecution::Variant::OnlyEnd) { + decoded_thread.AppendCustomError( + formatv("Unable to find the context switch in for a thread " + "execution on cpu id = {0}", + execution.thread_execution.cpu_id) + .str()); + } + } + + for (size_t j = 0; j < execution.psb_blocks.size(); j++) { + const PSBBlock &psb_block = execution.psb_blocks[j]; + + Expected<PSBBlockDecoder> decoder = PSBBlockDecoder::Create( + trace_intel_pt, psb_block, + buffers.lookup(execution.thread_execution.cpu_id) + .slice(psb_block.psb_offset, psb_block.size), + *decoded_thread.GetThread()->GetProcess(), + j + 1 < execution.psb_blocks.size() + ? execution.psb_blocks[j + 1].starting_ip + : std::nullopt, + decoded_thread, execution.thread_execution.GetEndTSC()); + if (!decoder) + return decoder.takeError(); + + has_seen_psbs = true; + decoder->DecodePSBBlock(); + } + + // If we haven't seen a PSB yet, then it's fine not to show errors + if (has_seen_psbs) { + // A hinted end is a non-ending execution that doesn't have a switch out. + // An only start is an ending execution that doesn't have a switch out. + // Any of those cases represent a gap if we still have executions to + // process and we have seen a PSB before. + if (i + 1 != executions.size() && + (variant == ThreadContinuousExecution::Variant::OnlyStart || + variant == ThreadContinuousExecution::Variant::HintedEnd)) { + decoded_thread.AppendCustomError( + formatv("Unable to find the context switch out for a thread " + "execution on cpu id = {0}", + execution.thread_execution.cpu_id) + .str()); + } + } + } + return Error::success(); +} + +bool IntelPTThreadContinousExecution::operator<( + const IntelPTThreadContinousExecution &o) const { + // As the context switch might be incomplete, we look first for the first real + // PSB packet, which is a valid TSC. Otherwise, We query the thread execution + // itself for some tsc. + auto get_tsc = [](const IntelPTThreadContinousExecution &exec) { + return exec.psb_blocks.empty() ? exec.thread_execution.GetLowestKnownTSC() + : exec.psb_blocks.front().tsc; + }; + + return get_tsc(*this) < get_tsc(o); +} + +Expected<std::vector<PSBBlock>> +lldb_private::trace_intel_pt::SplitTraceIntoPSBBlock( + TraceIntelPT &trace_intel_pt, llvm::ArrayRef<uint8_t> buffer, + bool expect_tscs) { + // This follows + // https://github.com/intel/libipt/blob/master/doc/howto_libipt.md#parallel-decode + + Expected<PtQueryDecoderUP> decoder_up = + CreateQueryDecoder(trace_intel_pt, buffer); + if (!decoder_up) + return decoder_up.takeError(); + + pt_query_decoder *decoder = decoder_up.get().get(); + + std::vector<PSBBlock> executions; + + while (true) { + uint64_t maybe_ip = LLDB_INVALID_ADDRESS; + int decoding_status = pt_qry_sync_forward(decoder, &maybe_ip); + if (IsLibiptError(decoding_status)) + break; + + uint64_t psb_offset; + int offset_status = pt_qry_get_sync_offset(decoder, &psb_offset); + assert(offset_status >= 0 && + "This can't fail because we were able to synchronize"); + + std::optional<uint64_t> ip; + if (!(pts_ip_suppressed & decoding_status)) + ip = maybe_ip; + + std::optional<uint64_t> tsc; + // Now we fetch the first TSC that comes after the PSB. + while (HasEvents(decoding_status)) { + pt_event event; + decoding_status = pt_qry_event(decoder, &event, sizeof(event)); + if (IsLibiptError(decoding_status)) + break; + if (event.has_tsc) { + tsc = event.tsc; + break; + } + } + if (IsLibiptError(decoding_status)) { + // We continue to the next PSB. This effectively merges this PSB with the + // previous one, and that should be fine because this PSB might be the + // direct continuation of the previous thread and it's better to show an + // error in the decoded thread than to hide it. If this is the first PSB, + // we are okay losing it. Besides that, an error at processing events + // means that we wouldn't be able to get any instruction out of it. + continue; + } + + if (expect_tscs && !tsc) + return createStringError(inconvertibleErrorCode(), + "Found a PSB without TSC."); + + executions.push_back({ + psb_offset, + tsc, + 0, + ip, + }); + } + if (!executions.empty()) { + // We now adjust the sizes of each block + executions.back().size = buffer.size() - executions.back().psb_offset; + for (int i = (int)executions.size() - 2; i >= 0; i--) { + executions[i].size = + executions[i + 1].psb_offset - executions[i].psb_offset; + } + } + return executions; +} + +Expected<std::optional<uint64_t>> +lldb_private::trace_intel_pt::FindLowestTSCInTrace(TraceIntelPT &trace_intel_pt, + ArrayRef<uint8_t> buffer) { + Expected<PtQueryDecoderUP> decoder_up = + CreateQueryDecoder(trace_intel_pt, buffer); + if (!decoder_up) + return decoder_up.takeError(); + + pt_query_decoder *decoder = decoder_up.get().get(); + uint64_t ip = LLDB_INVALID_ADDRESS; + int status = pt_qry_sync_forward(decoder, &ip); + if (IsLibiptError(status)) + return std::nullopt; + + while (HasEvents(status)) { + pt_event event; + status = pt_qry_event(decoder, &event, sizeof(event)); + if (IsLibiptError(status)) + return std::nullopt; + if (event.has_tsc) + return event.tsc; + } + return std::nullopt; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.h new file mode 100644 index 000000000000..bc8a5b963d6f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.h @@ -0,0 +1,125 @@ +//===-- LibiptDecoder.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_LIBIPT_DECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_LIBIPT_DECODER_H + +#include "DecodedThread.h" +#include "PerfContextSwitchDecoder.h" +#include "forward-declarations.h" +#include "intel-pt.h" +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +/// This struct represents a contiguous section of a trace that starts at a PSB +/// and ends right before the next PSB or the end of the trace. +struct PSBBlock { + /// The memory offset of a PSB packet that is a synchronization point for the + /// decoder. A decoder normally looks first for a PSB packet and then it + /// starts decoding. + uint64_t psb_offset; + /// The timestamp associated with the PSB packet above. + std::optional<uint64_t> tsc; + /// Size in bytes of this block + uint64_t size; + /// The first ip for this PSB block. + /// This is \a std::nullopt if tracing was disabled when the PSB block was + /// emitted. This means that eventually there's be an enablement event that + /// will come with an ip. + std::optional<lldb::addr_t> starting_ip; +}; + +/// This struct represents a continuous execution of a thread in a cpu, +/// delimited by a context switch in and out, and a list of Intel PT subtraces +/// that belong to this execution. +struct IntelPTThreadContinousExecution { + ThreadContinuousExecution thread_execution; + std::vector<PSBBlock> psb_blocks; + + IntelPTThreadContinousExecution( + const ThreadContinuousExecution &thread_execution) + : thread_execution(thread_execution) {} + + /// Comparator by time + bool operator<(const IntelPTThreadContinousExecution &o) const; +}; + +/// Decode a raw Intel PT trace for a single thread given in \p buffer and +/// append the decoded instructions and errors in \p decoded_thread. It uses the +/// low level libipt library underneath. +/// +/// \return +/// An \a llvm::Error if the decoder couldn't be properly set up. +llvm::Error DecodeSingleTraceForThread(DecodedThread &decoded_thread, + TraceIntelPT &trace_intel_pt, + llvm::ArrayRef<uint8_t> buffer); + +/// Decode a raw Intel PT trace for a single thread that was collected in a per +/// cpu core basis. +/// +/// \param[out] decoded_thread +/// All decoded instructions, errors and events will be appended to this +/// object. +/// +/// \param[in] trace_intel_pt +/// The main Trace object that contains all the information related to the +/// trace session. +/// +/// \param[in] buffers +/// A map from cpu core id to raw intel pt buffers. +/// +/// \param[in] executions +/// A list of chunks of timed executions of the same given thread. It is used +/// to identify if some executions have missing intel pt data and also to +/// determine in which core a certain part of the execution ocurred. +/// +/// \return +/// An \a llvm::Error if the decoder couldn't be properly set up, i.e. no +/// instructions were attempted to be decoded. +llvm::Error DecodeSystemWideTraceForThread( + DecodedThread &decoded_thread, TraceIntelPT &trace_intel_pt, + const llvm::DenseMap<lldb::cpu_id_t, llvm::ArrayRef<uint8_t>> &buffers, + const std::vector<IntelPTThreadContinousExecution> &executions); + +/// Given an intel pt trace, split it in chunks delimited by PSB packets. Each +/// of these chunks is guaranteed to have been executed continuously. +/// +/// \param[in] trace_intel_pt +/// The main Trace object that contains all the information related to the +/// trace session. +/// +/// \param[in] buffer +/// The intel pt buffer that belongs to a single thread or to a single cpu +/// core. +/// +/// \param[in] expect_tscs +/// If \b true, an error is return if a packet without TSC is found. +/// +/// \return +/// A list of continuous executions sorted by time, or an \a llvm::Error in +/// case of failures. +llvm::Expected<std::vector<PSBBlock>> +SplitTraceIntoPSBBlock(TraceIntelPT &trace_intel_pt, + llvm::ArrayRef<uint8_t> buffer, bool expect_tscs); + +/// Find the lowest TSC in the given trace. +/// +/// \return +/// The lowest TSC value in this trace if available, \a std::nullopt if the +/// trace is empty or the trace contains no timing information, or an \a +/// llvm::Error if it was not possible to set up the decoder. +llvm::Expected<std::optional<uint64_t>> +FindLowestTSCInTrace(TraceIntelPT &trace_intel_pt, + llvm::ArrayRef<uint8_t> buffer); + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_LIBIPT_DECODER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.cpp new file mode 100644 index 000000000000..1aa2a3cc097b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.cpp @@ -0,0 +1,332 @@ +//===-- PerfContextSwitchDecoder.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 "PerfContextSwitchDecoder.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +/// Copied from <linux/perf_event.h> to avoid depending on perf_event.h on +/// non-linux platforms. +/// \{ +#define PERF_RECORD_MISC_SWITCH_OUT (1 << 13) + +#define PERF_RECORD_LOST 2 +#define PERF_RECORD_THROTTLE 5 +#define PERF_RECORD_UNTHROTTLE 6 +#define PERF_RECORD_LOST_SAMPLES 13 +#define PERF_RECORD_SWITCH_CPU_WIDE 15 +#define PERF_RECORD_MAX 19 + +struct perf_event_header { + uint32_t type; + uint16_t misc; + uint16_t size; + + /// \return + /// An \a llvm::Error if the record looks obviously wrong, or \a + /// llvm::Error::success() otherwise. + Error SanityCheck() const { + // The following checks are based on visual inspection of the records and + // enums in + // https://elixir.bootlin.com/linux/v4.8/source/include/uapi/linux/perf_event.h + // See PERF_RECORD_MAX, PERF_RECORD_SWITCH and the data similar records + // hold. + + // A record of too many uint64_t's or more should mean that the data is + // wrong + const uint64_t max_valid_size_bytes = 8000; + if (size == 0 || size > max_valid_size_bytes) + return createStringError( + inconvertibleErrorCode(), + formatv("A record of {0} bytes was found.", size)); + + // We add some numbers to PERF_RECORD_MAX because some systems might have + // custom records. In any case, we are looking only for abnormal data. + if (type >= PERF_RECORD_MAX + 100) + return createStringError( + inconvertibleErrorCode(), + formatv("Invalid record type {0} was found.", type)); + return Error::success(); + } + + bool IsContextSwitchRecord() const { + return type == PERF_RECORD_SWITCH_CPU_WIDE; + } + + bool IsErrorRecord() const { + return type == PERF_RECORD_LOST || type == PERF_RECORD_THROTTLE || + type == PERF_RECORD_UNTHROTTLE || type == PERF_RECORD_LOST_SAMPLES; + } +}; +/// \} + +/// Record found in the perf_event context switch traces. It might contain +/// additional fields in memory, but header.size should have the actual size +/// of the record. +struct PerfContextSwitchRecord { + struct perf_event_header header; + uint32_t next_prev_pid; + uint32_t next_prev_tid; + uint32_t pid, tid; + uint64_t time_in_nanos; + + bool IsOut() const { return header.misc & PERF_RECORD_MISC_SWITCH_OUT; } +}; + +/// Record produced after parsing the raw context switch trace produce by +/// perf_event. A major difference between this struct and +/// PerfContextSwitchRecord is that this one uses tsc instead of nanos. +struct ContextSwitchRecord { + uint64_t tsc; + /// Whether the switch is in or out + bool is_out; + /// pid = 0 and tid = 0 indicate the swapper or idle process, which normally + /// runs after a context switch out of a normal user thread. + lldb::pid_t pid; + lldb::tid_t tid; + + bool IsOut() const { return is_out; } + + bool IsIn() const { return !is_out; } +}; + +uint64_t ThreadContinuousExecution::GetLowestKnownTSC() const { + switch (variant) { + case Variant::Complete: + return tscs.complete.start; + case Variant::OnlyStart: + return tscs.only_start.start; + case Variant::OnlyEnd: + return tscs.only_end.end; + case Variant::HintedEnd: + return tscs.hinted_end.start; + case Variant::HintedStart: + return tscs.hinted_start.end; + } +} + +uint64_t ThreadContinuousExecution::GetStartTSC() const { + switch (variant) { + case Variant::Complete: + return tscs.complete.start; + case Variant::OnlyStart: + return tscs.only_start.start; + case Variant::OnlyEnd: + return 0; + case Variant::HintedEnd: + return tscs.hinted_end.start; + case Variant::HintedStart: + return tscs.hinted_start.hinted_start; + } +} + +uint64_t ThreadContinuousExecution::GetEndTSC() const { + switch (variant) { + case Variant::Complete: + return tscs.complete.end; + case Variant::OnlyStart: + return std::numeric_limits<uint64_t>::max(); + case Variant::OnlyEnd: + return tscs.only_end.end; + case Variant::HintedEnd: + return tscs.hinted_end.hinted_end; + case Variant::HintedStart: + return tscs.hinted_start.end; + } +} + +ThreadContinuousExecution ThreadContinuousExecution::CreateCompleteExecution( + lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start, + uint64_t end) { + ThreadContinuousExecution o(cpu_id, tid, pid); + o.variant = Variant::Complete; + o.tscs.complete.start = start; + o.tscs.complete.end = end; + return o; +} + +ThreadContinuousExecution ThreadContinuousExecution::CreateHintedStartExecution( + lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, + uint64_t hinted_start, uint64_t end) { + ThreadContinuousExecution o(cpu_id, tid, pid); + o.variant = Variant::HintedStart; + o.tscs.hinted_start.hinted_start = hinted_start; + o.tscs.hinted_start.end = end; + return o; +} + +ThreadContinuousExecution ThreadContinuousExecution::CreateHintedEndExecution( + lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start, + uint64_t hinted_end) { + ThreadContinuousExecution o(cpu_id, tid, pid); + o.variant = Variant::HintedEnd; + o.tscs.hinted_end.start = start; + o.tscs.hinted_end.hinted_end = hinted_end; + return o; +} + +ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyEndExecution( + lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t end) { + ThreadContinuousExecution o(cpu_id, tid, pid); + o.variant = Variant::OnlyEnd; + o.tscs.only_end.end = end; + return o; +} + +ThreadContinuousExecution ThreadContinuousExecution::CreateOnlyStartExecution( + lldb::cpu_id_t cpu_id, lldb::tid_t tid, lldb::pid_t pid, uint64_t start) { + ThreadContinuousExecution o(cpu_id, tid, pid); + o.variant = Variant::OnlyStart; + o.tscs.only_start.start = start; + return o; +} + +static Error RecoverExecutionsFromConsecutiveRecords( + cpu_id_t cpu_id, const LinuxPerfZeroTscConversion &tsc_conversion, + const ContextSwitchRecord ¤t_record, + const std::optional<ContextSwitchRecord> &prev_record, + std::function<void(const ThreadContinuousExecution &execution)> + on_new_execution) { + if (!prev_record) { + if (current_record.IsOut()) { + on_new_execution(ThreadContinuousExecution::CreateOnlyEndExecution( + cpu_id, current_record.tid, current_record.pid, current_record.tsc)); + } + // The 'in' case will be handled later when we try to look for its end + return Error::success(); + } + + const ContextSwitchRecord &prev = *prev_record; + if (prev.tsc >= current_record.tsc) + return createStringError( + inconvertibleErrorCode(), + formatv("A context switch record doesn't happen after the previous " + "record. Previous TSC= {0}, current TSC = {1}.", + prev.tsc, current_record.tsc)); + + if (current_record.IsIn() && prev.IsIn()) { + // We found two consecutive ins, which means that we didn't capture + // the end of the previous execution. + on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution( + cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1)); + } else if (current_record.IsOut() && prev.IsOut()) { + // We found two consecutive outs, that means that we didn't capture + // the beginning of the current execution. + on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution( + cpu_id, current_record.tid, current_record.pid, prev.tsc + 1, + current_record.tsc)); + } else if (current_record.IsOut() && prev.IsIn()) { + if (current_record.pid == prev.pid && current_record.tid == prev.tid) { + /// A complete execution + on_new_execution(ThreadContinuousExecution::CreateCompleteExecution( + cpu_id, current_record.tid, current_record.pid, prev.tsc, + current_record.tsc)); + } else { + // An out after the in of a different thread. The first one doesn't + // have an end, and the second one doesn't have a start. + on_new_execution(ThreadContinuousExecution::CreateHintedEndExecution( + cpu_id, prev.tid, prev.pid, prev.tsc, current_record.tsc - 1)); + on_new_execution(ThreadContinuousExecution::CreateHintedStartExecution( + cpu_id, current_record.tid, current_record.pid, prev.tsc + 1, + current_record.tsc)); + } + } + return Error::success(); +} + +Expected<std::vector<ThreadContinuousExecution>> +lldb_private::trace_intel_pt::DecodePerfContextSwitchTrace( + ArrayRef<uint8_t> data, cpu_id_t cpu_id, + const LinuxPerfZeroTscConversion &tsc_conversion) { + + std::vector<ThreadContinuousExecution> executions; + + // This offset is used to create the error message in case of failures. + size_t offset = 0; + + auto do_decode = [&]() -> Error { + std::optional<ContextSwitchRecord> prev_record; + while (offset < data.size()) { + const perf_event_header &perf_record = + *reinterpret_cast<const perf_event_header *>(data.data() + offset); + if (Error err = perf_record.SanityCheck()) + return err; + + if (perf_record.IsContextSwitchRecord()) { + const PerfContextSwitchRecord &context_switch_record = + *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() + + offset); + ContextSwitchRecord record{ + tsc_conversion.ToTSC(context_switch_record.time_in_nanos), + context_switch_record.IsOut(), + static_cast<lldb::pid_t>(context_switch_record.pid), + static_cast<lldb::tid_t>(context_switch_record.tid)}; + + if (Error err = RecoverExecutionsFromConsecutiveRecords( + cpu_id, tsc_conversion, record, prev_record, + [&](const ThreadContinuousExecution &execution) { + executions.push_back(execution); + })) + return err; + + prev_record = record; + } + offset += perf_record.size; + } + + // We might have an incomplete last record + if (prev_record && prev_record->IsIn()) + executions.push_back(ThreadContinuousExecution::CreateOnlyStartExecution( + cpu_id, prev_record->tid, prev_record->pid, prev_record->tsc)); + return Error::success(); + }; + + if (Error err = do_decode()) + return createStringError(inconvertibleErrorCode(), + formatv("Malformed perf context switch trace for " + "cpu {0} at offset {1}. {2}", + cpu_id, offset, toString(std::move(err)))); + + return executions; +} + +Expected<std::vector<uint8_t>> +lldb_private::trace_intel_pt::FilterProcessesFromContextSwitchTrace( + llvm::ArrayRef<uint8_t> data, const std::set<lldb::pid_t> &pids) { + size_t offset = 0; + std::vector<uint8_t> out_data; + + while (offset < data.size()) { + const perf_event_header &perf_record = + *reinterpret_cast<const perf_event_header *>(data.data() + offset); + if (Error err = perf_record.SanityCheck()) + return std::move(err); + bool should_copy = false; + if (perf_record.IsContextSwitchRecord()) { + const PerfContextSwitchRecord &context_switch_record = + *reinterpret_cast<const PerfContextSwitchRecord *>(data.data() + + offset); + if (pids.count(context_switch_record.pid)) + should_copy = true; + } else if (perf_record.IsErrorRecord()) { + should_copy = true; + } + + if (should_copy) { + for (size_t i = 0; i < perf_record.size; i++) { + out_data.push_back(data[offset + i]); + } + } + + offset += perf_record.size; + } + return out_data; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.h new file mode 100644 index 000000000000..4ea7738810ce --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/PerfContextSwitchDecoder.h @@ -0,0 +1,148 @@ +//===-- PerfContextSwitchDecoder.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_PERFCONTEXTSWITCHDECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_PERFCONTEXTSWITCHDECODER_H + +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" +#include <set> +#include <vector> + +namespace lldb_private { +namespace trace_intel_pt { + +/// This class indicates the time interval in which a thread was running +/// continuously on a cpu core. +struct ThreadContinuousExecution { + + /// In most cases both the start and end of a continuous execution can be + /// accurately recovered from the context switch trace, but in some cases one + /// of these endpoints might be guessed or not known at all, due to contention + /// problems in the trace or because tracing was interrupted, e.g. with ioctl + /// calls, which causes gaps in the trace. Because of that, we identify which + /// situation we fall into with the following variants. + enum class Variant { + /// Both endpoints are known. + Complete, + /// The end is known and we have a lower bound for the start, i.e. the + /// previous execution in the same cpu happens strictly before the hinted + /// start. + HintedStart, + /// The start is known and we have an upper bound for the end, i.e. the next + /// execution in the same cpu happens strictly after the hinted end. + HintedEnd, + /// We only know the start. This might be the last entry of a cpu trace. + OnlyStart, + /// We only know the end. This might be the first entry or a cpu trace. + OnlyEnd, + } variant; + + /// \return + /// The lowest tsc that we are sure of, i.e. not hinted. + uint64_t GetLowestKnownTSC() const; + + /// \return + /// The known or hinted start tsc, or 0 if the variant is \a OnlyEnd. + uint64_t GetStartTSC() const; + + /// \return + /// The known or hinted end tsc, or max \a uint64_t if the variant is \a + /// OnlyStart. + uint64_t GetEndTSC() const; + + /// Constructors for the different variants of this object + /// + /// \{ + static ThreadContinuousExecution + CreateCompleteExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, + lldb::pid_t pid, uint64_t start, uint64_t end); + + static ThreadContinuousExecution + CreateHintedStartExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, + lldb::pid_t pid, uint64_t hinted_start, + uint64_t end); + + static ThreadContinuousExecution + CreateHintedEndExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, + lldb::pid_t pid, uint64_t start, + uint64_t hinted_end); + + static ThreadContinuousExecution CreateOnlyEndExecution(lldb::cpu_id_t cpu_id, + lldb::tid_t tid, + lldb::pid_t pid, + uint64_t end); + + static ThreadContinuousExecution + CreateOnlyStartExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, + lldb::pid_t pid, uint64_t start); + /// \} + + union { + struct { + uint64_t start; + uint64_t end; + } complete; + struct { + uint64_t start; + } only_start; + struct { + uint64_t end; + } only_end; + /// The following 'hinted' structures are useful when there are contention + /// problems in the trace + struct { + uint64_t hinted_start; + uint64_t end; + } hinted_start; + struct { + uint64_t start; + uint64_t hinted_end; + } hinted_end; + } tscs; + + lldb::cpu_id_t cpu_id; + lldb::tid_t tid; + lldb::pid_t pid; + +private: + /// We keep this constructor private to force the usage of the static named + /// constructors. + ThreadContinuousExecution(lldb::cpu_id_t cpu_id, lldb::tid_t tid, + lldb::pid_t pid) + : cpu_id(cpu_id), tid(tid), pid(pid) {} +}; + +/// Decodes a context switch trace collected with perf_event_open. +/// +/// \param[in] data +/// The context switch trace in binary format. +/// +/// \param[i] cpu_id +/// The cpu_id where the trace were gotten from. +/// +/// \param[in] tsc_conversion +/// The conversion values used to confert nanoseconds to TSC. +/// +/// \return +/// A list of continuous executions recovered from the raw trace sorted by +/// time, or an \a llvm::Error if the data is malformed. +llvm::Expected<std::vector<ThreadContinuousExecution>> +DecodePerfContextSwitchTrace(llvm::ArrayRef<uint8_t> data, + lldb::cpu_id_t cpu_id, + const LinuxPerfZeroTscConversion &tsc_conversion); + +llvm::Expected<std::vector<uint8_t>> +FilterProcessesFromContextSwitchTrace(llvm::ArrayRef<uint8_t> data, + const std::set<lldb::pid_t> &pids); + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_PERFCONTEXTSWITCHDECODER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TaskTimer.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TaskTimer.cpp new file mode 100644 index 000000000000..55d48ae35ff0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TaskTimer.cpp @@ -0,0 +1,32 @@ +//===-- TaskTimer.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 "TaskTimer.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +void ScopedTaskTimer::ForEachTimedTask( + std::function<void(const std::string &event, + std::chrono::milliseconds duration)> + callback) { + for (const auto &kv : m_timed_tasks) { + callback(kv.first, kv.second); + } +} + +ScopedTaskTimer &TaskTimer::ForThread(lldb::tid_t tid) { + auto it = m_thread_timers.find(tid); + if (it == m_thread_timers.end()) + it = m_thread_timers.try_emplace(tid, ScopedTaskTimer{}).first; + return it->second; +} + +ScopedTaskTimer &TaskTimer::ForGlobal() { return m_global_timer; } diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TaskTimer.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TaskTimer.h new file mode 100644 index 000000000000..fb196e8fc32a --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TaskTimer.h @@ -0,0 +1,78 @@ +//===-- TaskTimer.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_TASKTIMER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TASKTIMER_H + +#include "lldb/lldb-types.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" +#include <chrono> +#include <functional> +#include <unordered_map> + +namespace lldb_private { +namespace trace_intel_pt { + +/// Class used to track the duration of long running tasks related to a single +/// scope for reporting. +class ScopedTaskTimer { +public: + /// Execute the given \p task and record its duration. + /// + /// \param[in] name + /// The name used to identify this task for reporting. + /// + /// \param[in] task + /// The task function. + /// + /// \return + /// The return value of the task. + template <typename C> auto TimeTask(llvm::StringRef name, C task) { + auto start = std::chrono::steady_clock::now(); + auto result = task(); + auto end = std::chrono::steady_clock::now(); + std::chrono::milliseconds duration = + std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + m_timed_tasks.insert({name.str(), duration}); + return result; + } + + /// Executive the given \p callback on each recorded task. + /// + /// \param[in] callback + /// The first parameter of the callback is the name of the recorded task, + /// and the second parameter is the duration of that task. + void ForEachTimedTask(std::function<void(const std::string &name, + std::chrono::milliseconds duration)> + callback); + +private: + std::unordered_map<std::string, std::chrono::milliseconds> m_timed_tasks; +}; + +/// Class used to track the duration of long running tasks for reporting. +class TaskTimer { +public: + /// \return + /// The timer object for the given thread. + ScopedTaskTimer &ForThread(lldb::tid_t tid); + + /// \return + /// The timer object for global tasks. + ScopedTaskTimer &ForGlobal(); + +private: + llvm::DenseMap<lldb::tid_t, ScopedTaskTimer> m_thread_timers; + ScopedTaskTimer m_global_timer; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TASKTIMER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/ThreadDecoder.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/ThreadDecoder.cpp new file mode 100644 index 000000000000..75d74edd44a1 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/ThreadDecoder.cpp @@ -0,0 +1,67 @@ +//===-- ThreadDecoder.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 "ThreadDecoder.h" +#include "../common/ThreadPostMortemTrace.h" +#include "LibiptDecoder.h" +#include "TraceIntelPT.h" +#include "llvm/Support/MemoryBuffer.h" +#include <optional> +#include <utility> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +ThreadDecoder::ThreadDecoder(const ThreadSP &thread_sp, TraceIntelPT &trace) + : m_thread_sp(thread_sp), m_trace(trace) {} + +Expected<std::optional<uint64_t>> ThreadDecoder::FindLowestTSC() { + std::optional<uint64_t> lowest_tsc; + Error err = m_trace.OnThreadBufferRead( + m_thread_sp->GetID(), [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error { + Expected<std::optional<uint64_t>> tsc = + FindLowestTSCInTrace(m_trace, data); + if (!tsc) + return tsc.takeError(); + lowest_tsc = *tsc; + return Error::success(); + }); + if (err) + return std::move(err); + return lowest_tsc; +} + +Expected<DecodedThreadSP> ThreadDecoder::Decode() { + if (!m_decoded_thread.has_value()) { + if (Expected<DecodedThreadSP> decoded_thread = DoDecode()) { + m_decoded_thread = *decoded_thread; + } else { + return decoded_thread.takeError(); + } + } + return *m_decoded_thread; +} + +llvm::Expected<DecodedThreadSP> ThreadDecoder::DoDecode() { + return m_trace.GetThreadTimer(m_thread_sp->GetID()) + .TimeTask("Decoding instructions", [&]() -> Expected<DecodedThreadSP> { + DecodedThreadSP decoded_thread_sp = std::make_shared<DecodedThread>( + m_thread_sp, m_trace.GetPerfZeroTscConversion()); + + Error err = m_trace.OnThreadBufferRead( + m_thread_sp->GetID(), [&](llvm::ArrayRef<uint8_t> data) { + return DecodeSingleTraceForThread(*decoded_thread_sp, m_trace, + data); + }); + + if (err) + return std::move(err); + return decoded_thread_sp; + }); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/ThreadDecoder.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/ThreadDecoder.h new file mode 100644 index 000000000000..289915c02810 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/ThreadDecoder.h @@ -0,0 +1,59 @@ +//===-- ThreadDecoder.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_THREAD_DECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_THREAD_DECODER_H + +#include "DecodedThread.h" +#include "forward-declarations.h" +#include "intel-pt.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +/// Class that handles the decoding of a thread and caches the result. +class ThreadDecoder { +public: + /// \param[in] thread_sp + /// The thread whose intel pt trace buffer will be decoded. + /// + /// \param[in] trace + /// The main Trace object who owns this decoder and its data. + ThreadDecoder(const lldb::ThreadSP &thread_sp, TraceIntelPT &trace); + + /// Decode the thread and store the result internally, to avoid + /// recomputations. + /// + /// \return + /// A \a DecodedThread instance. + llvm::Expected<DecodedThreadSP> Decode(); + + /// \return + /// The lowest TSC value in this trace if available, \a std::nullopt if + /// the trace is empty or the trace contains no timing information, or an + /// \a llvm::Error if it was not possible to set up the decoder. + llvm::Expected<std::optional<uint64_t>> FindLowestTSC(); + + ThreadDecoder(const ThreadDecoder &other) = delete; + ThreadDecoder &operator=(const ThreadDecoder &other) = delete; + +private: + llvm::Expected<DecodedThreadSP> DoDecode(); + + lldb::ThreadSP m_thread_sp; + TraceIntelPT &m_trace; + std::optional<DecodedThreadSP> m_decoded_thread; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_THREAD_DECODER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp new file mode 100644 index 000000000000..dda6cd74343f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.cpp @@ -0,0 +1,146 @@ +//===-- 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> +#include <optional> + +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, + const std::optional<LinuxPerfZeroTscConversion> &tsc_conversion, + std::optional<uint64_t> beginning_of_time_nanos) + : TraceCursor(thread_sp), m_decoded_thread_sp(decoded_thread_sp), + m_tsc_conversion(tsc_conversion), + m_beginning_of_time_nanos(beginning_of_time_nanos) { + Seek(0, lldb::eTraceCursorSeekTypeEnd); +} + +void TraceCursorIntelPT::Next() { + m_pos += IsForwards() ? 1 : -1; + ClearTimingRangesIfInvalid(); +} + +void TraceCursorIntelPT::ClearTimingRangesIfInvalid() { + if (m_tsc_range_calculated) { + if (!m_tsc_range || m_pos < 0 || !m_tsc_range->InRange(m_pos)) { + m_tsc_range = std::nullopt; + m_tsc_range_calculated = false; + } + } + + if (m_nanoseconds_range_calculated) { + if (!m_nanoseconds_range || m_pos < 0 || + !m_nanoseconds_range->InRange(m_pos)) { + m_nanoseconds_range = std::nullopt; + m_nanoseconds_range_calculated = false; + } + } +} + +const std::optional<DecodedThread::TSCRange> & +TraceCursorIntelPT::GetTSCRange() const { + if (!m_tsc_range_calculated) { + m_tsc_range_calculated = true; + m_tsc_range = m_decoded_thread_sp->GetTSCRangeByIndex(m_pos); + } + return m_tsc_range; +} + +const std::optional<DecodedThread::NanosecondsRange> & +TraceCursorIntelPT::GetNanosecondsRange() const { + if (!m_nanoseconds_range_calculated) { + m_nanoseconds_range_calculated = true; + m_nanoseconds_range = + m_decoded_thread_sp->GetNanosecondsRangeByIndex(m_pos); + } + return m_nanoseconds_range; +} + +bool TraceCursorIntelPT::Seek(int64_t offset, + lldb::TraceCursorSeekType origin) { + switch (origin) { + case lldb::eTraceCursorSeekTypeBeginning: + m_pos = offset; + break; + case lldb::eTraceCursorSeekTypeEnd: + m_pos = m_decoded_thread_sp->GetItemsCount() - 1 + offset; + break; + case lldb::eTraceCursorSeekTypeCurrent: + m_pos += offset; + } + + ClearTimingRangesIfInvalid(); + + return HasValue(); +} + +bool TraceCursorIntelPT::HasValue() const { + return m_pos >= 0 && + static_cast<uint64_t>(m_pos) < m_decoded_thread_sp->GetItemsCount(); +} + +lldb::TraceItemKind TraceCursorIntelPT::GetItemKind() const { + return m_decoded_thread_sp->GetItemKindByIndex(m_pos); +} + +llvm::StringRef TraceCursorIntelPT::GetError() const { + return m_decoded_thread_sp->GetErrorByIndex(m_pos); +} + +lldb::addr_t TraceCursorIntelPT::GetLoadAddress() const { + return m_decoded_thread_sp->GetInstructionLoadAddress(m_pos); +} + +std::optional<uint64_t> TraceCursorIntelPT::GetHWClock() const { + if (const std::optional<DecodedThread::TSCRange> &range = GetTSCRange()) + return range->tsc; + return std::nullopt; +} + +std::optional<double> TraceCursorIntelPT::GetWallClockTime() const { + if (const std::optional<DecodedThread::NanosecondsRange> &range = + GetNanosecondsRange()) + return range->GetInterpolatedTime(m_pos, *m_beginning_of_time_nanos, + *m_tsc_conversion); + return std::nullopt; +} + +lldb::cpu_id_t TraceCursorIntelPT::GetCPU() const { + return m_decoded_thread_sp->GetCPUByIndex(m_pos); +} + +lldb::TraceEvent TraceCursorIntelPT::GetEventType() const { + return m_decoded_thread_sp->GetEventByIndex(m_pos); +} + +bool TraceCursorIntelPT::GoToId(user_id_t id) { + if (!HasId(id)) + return false; + m_pos = id; + ClearTimingRangesIfInvalid(); + return true; +} + +bool TraceCursorIntelPT::HasId(lldb::user_id_t id) const { + return id < m_decoded_thread_sp->GetItemsCount(); +} + +user_id_t TraceCursorIntelPT::GetId() const { return m_pos; } + +std::optional<std::string> TraceCursorIntelPT::GetSyncPointMetadata() const { + return formatv("offset = 0x{0:x}", + m_decoded_thread_sp->GetSyncPointOffsetByIndex(m_pos)) + .str(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h new file mode 100644 index 000000000000..14240d9d0028 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceCursorIntelPT.h @@ -0,0 +1,93 @@ +//===-- 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 "ThreadDecoder.h" +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceCursorIntelPT : public TraceCursor { +public: + TraceCursorIntelPT( + lldb::ThreadSP thread_sp, DecodedThreadSP decoded_thread_sp, + const std::optional<LinuxPerfZeroTscConversion> &tsc_conversion, + std::optional<uint64_t> beginning_of_time_nanos); + + bool Seek(int64_t offset, lldb::TraceCursorSeekType origin) override; + + void Next() override; + + bool HasValue() const override; + + llvm::StringRef GetError() const override; + + lldb::addr_t GetLoadAddress() const override; + + lldb::TraceEvent GetEventType() const override; + + lldb::cpu_id_t GetCPU() const override; + + std::optional<uint64_t> GetHWClock() const override; + + lldb::TraceItemKind GetItemKind() const override; + + bool GoToId(lldb::user_id_t id) override; + + lldb::user_id_t GetId() const override; + + bool HasId(lldb::user_id_t id) const override; + + std::optional<double> GetWallClockTime() const override; + + std::optional<std::string> GetSyncPointMetadata() const override; + +private: + /// Clear the current TSC and nanoseconds ranges if after moving they are not + /// valid anymore. + void ClearTimingRangesIfInvalid(); + + /// Get or calculate the TSC range that includes the current trace item. + const std::optional<DecodedThread::TSCRange> &GetTSCRange() const; + + /// Get or calculate the TSC range that includes the current trace item. + const std::optional<DecodedThread::NanosecondsRange> & + GetNanosecondsRange() const; + + /// Storage of the actual instructions + DecodedThreadSP m_decoded_thread_sp; + /// Internal instruction index currently pointing at. + int64_t m_pos; + + /// Timing information and cached values. + /// \{ + + /// TSC -> nanos conversion utility. \a std::nullopt if not available at all. + std::optional<LinuxPerfZeroTscConversion> m_tsc_conversion; + /// Lowest nanoseconds timestamp seen in any thread trace, \a std::nullopt if + /// not available at all. + std::optional<uint64_t> m_beginning_of_time_nanos; + /// Range of trace items with the same TSC that includes the current trace + /// item, \a std::nullopt if not calculated or not available. + std::optional<DecodedThread::TSCRange> mutable m_tsc_range; + bool mutable m_tsc_range_calculated = false; + /// Range of trace items with the same non-interpolated timestamps in + /// nanoseconds that includes the current trace item, \a std::nullopt if not + /// calculated or not available. + std::optional<DecodedThread::NanosecondsRange> mutable m_nanoseconds_range; + bool mutable m_nanoseconds_range_calculated = false; + /// \} +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACECURSORINTELPT_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp new file mode 100644 index 000000000000..72e9948ffe81 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -0,0 +1,744 @@ +//===-- TraceIntelPT.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 "TraceIntelPT.h" + +#include "../common/ThreadPostMortemTrace.h" +#include "CommandObjectTraceStartIntelPT.h" +#include "DecodedThread.h" +#include "TraceCursorIntelPT.h" +#include "TraceIntelPTBundleLoader.h" +#include "TraceIntelPTBundleSaver.h" +#include "TraceIntelPTConstants.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +LLDB_PLUGIN_DEFINE(TraceIntelPT) + +lldb::CommandObjectSP +TraceIntelPT::GetProcessTraceStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP( + new CommandObjectProcessTraceStartIntelPT(*this, interpreter)); +} + +lldb::CommandObjectSP +TraceIntelPT::GetThreadTraceStartCommand(CommandInterpreter &interpreter) { + return CommandObjectSP( + new CommandObjectThreadTraceStartIntelPT(*this, interpreter)); +} + +#define LLDB_PROPERTIES_traceintelpt +#include "TraceIntelPTProperties.inc" + +enum { +#define LLDB_PROPERTIES_traceintelpt +#include "TraceIntelPTPropertiesEnum.inc" +}; + +llvm::StringRef TraceIntelPT::PluginProperties::GetSettingName() { + return TraceIntelPT::GetPluginNameStatic(); +} + +TraceIntelPT::PluginProperties::PluginProperties() : Properties() { + m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); + m_collection_sp->Initialize(g_traceintelpt_properties); +} + +uint64_t +TraceIntelPT::PluginProperties::GetInfiniteDecodingLoopVerificationThreshold() { + const uint32_t idx = ePropertyInfiniteDecodingLoopVerificationThreshold; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_traceintelpt_properties[idx].default_uint_value); +} + +uint64_t TraceIntelPT::PluginProperties::GetExtremelyLargeDecodingThreshold() { + const uint32_t idx = ePropertyExtremelyLargeDecodingThreshold; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_traceintelpt_properties[idx].default_uint_value); +} + +TraceIntelPT::PluginProperties &TraceIntelPT::GetGlobalProperties() { + static TraceIntelPT::PluginProperties g_settings; + return g_settings; +} + +void TraceIntelPT::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), "Intel Processor Trace", + CreateInstanceForTraceBundle, CreateInstanceForLiveProcess, + TraceIntelPTBundleLoader::GetSchema(), DebuggerInitialize); +} + +void TraceIntelPT::DebuggerInitialize(Debugger &debugger) { + if (!PluginManager::GetSettingForProcessPlugin( + debugger, PluginProperties::GetSettingName())) { + const bool is_global_setting = true; + PluginManager::CreateSettingForTracePlugin( + debugger, GetGlobalProperties().GetValueProperties(), + "Properties for the intel-pt trace plug-in.", is_global_setting); + } +} + +void TraceIntelPT::Terminate() { + PluginManager::UnregisterPlugin(CreateInstanceForTraceBundle); +} + +StringRef TraceIntelPT::GetSchema() { + return TraceIntelPTBundleLoader::GetSchema(); +} + +void TraceIntelPT::Dump(Stream *s) const {} + +Expected<FileSpec> TraceIntelPT::SaveToDisk(FileSpec directory, bool compact) { + RefreshLiveProcessState(); + return TraceIntelPTBundleSaver().SaveToDisk(*this, directory, compact); +} + +Expected<TraceSP> TraceIntelPT::CreateInstanceForTraceBundle( + const json::Value &bundle_description, StringRef bundle_dir, + Debugger &debugger) { + return TraceIntelPTBundleLoader(debugger, bundle_description, bundle_dir) + .Load(); +} + +Expected<TraceSP> TraceIntelPT::CreateInstanceForLiveProcess(Process &process) { + TraceSP instance(new TraceIntelPT(process)); + process.GetTarget().SetTrace(instance); + return instance; +} + +TraceIntelPTSP TraceIntelPT::GetSharedPtr() { + return std::static_pointer_cast<TraceIntelPT>(shared_from_this()); +} + +TraceIntelPT::TraceMode TraceIntelPT::GetTraceMode() { return trace_mode; } + +TraceIntelPTSP TraceIntelPT::CreateInstanceForPostmortemTrace( + JSONTraceBundleDescription &bundle_description, + ArrayRef<ProcessSP> traced_processes, + ArrayRef<ThreadPostMortemTraceSP> traced_threads, TraceMode trace_mode) { + TraceIntelPTSP trace_sp( + new TraceIntelPT(bundle_description, traced_processes, trace_mode)); + trace_sp->m_storage.tsc_conversion = + bundle_description.tsc_perf_zero_conversion; + + if (bundle_description.cpus) { + std::vector<cpu_id_t> cpus; + + for (const JSONCpu &cpu : *bundle_description.cpus) { + trace_sp->SetPostMortemCpuDataFile(cpu.id, IntelPTDataKinds::kIptTrace, + FileSpec(cpu.ipt_trace)); + + trace_sp->SetPostMortemCpuDataFile( + cpu.id, IntelPTDataKinds::kPerfContextSwitchTrace, + FileSpec(cpu.context_switch_trace)); + cpus.push_back(cpu.id); + } + + if (trace_mode == TraceMode::UserMode) { + trace_sp->m_storage.multicpu_decoder.emplace(trace_sp); + } + } + + if (!bundle_description.cpus || trace_mode == TraceMode::KernelMode) { + for (const ThreadPostMortemTraceSP &thread : traced_threads) { + trace_sp->m_storage.thread_decoders.try_emplace( + thread->GetID(), std::make_unique<ThreadDecoder>(thread, *trace_sp)); + if (const std::optional<FileSpec> &trace_file = thread->GetTraceFile()) { + trace_sp->SetPostMortemThreadDataFile( + thread->GetID(), IntelPTDataKinds::kIptTrace, *trace_file); + } + } + } + + for (const ProcessSP &process_sp : traced_processes) + process_sp->GetTarget().SetTrace(trace_sp); + return trace_sp; +} + +TraceIntelPT::TraceIntelPT(JSONTraceBundleDescription &bundle_description, + ArrayRef<ProcessSP> traced_processes, + TraceMode trace_mode) + : Trace(traced_processes, bundle_description.GetCpuIds()), + m_cpu_info(bundle_description.cpu_info), trace_mode(trace_mode) {} + +Expected<DecodedThreadSP> TraceIntelPT::Decode(Thread &thread) { + if (const char *error = RefreshLiveProcessState()) + return createStringError(inconvertibleErrorCode(), error); + + Storage &storage = GetUpdatedStorage(); + if (storage.multicpu_decoder) + return storage.multicpu_decoder->Decode(thread); + + auto it = storage.thread_decoders.find(thread.GetID()); + if (it == storage.thread_decoders.end()) + return createStringError(inconvertibleErrorCode(), "thread not traced"); + return it->second->Decode(); +} + +Expected<std::optional<uint64_t>> TraceIntelPT::FindBeginningOfTimeNanos() { + Storage &storage = GetUpdatedStorage(); + if (storage.beginning_of_time_nanos_calculated) + return storage.beginning_of_time_nanos; + storage.beginning_of_time_nanos_calculated = true; + + if (!storage.tsc_conversion) + return std::nullopt; + + std::optional<uint64_t> lowest_tsc; + + if (storage.multicpu_decoder) { + if (Expected<std::optional<uint64_t>> tsc = + storage.multicpu_decoder->FindLowestTSC()) { + lowest_tsc = *tsc; + } else { + return tsc.takeError(); + } + } + + for (auto &decoder : storage.thread_decoders) { + Expected<std::optional<uint64_t>> tsc = decoder.second->FindLowestTSC(); + if (!tsc) + return tsc.takeError(); + + if (*tsc && (!lowest_tsc || *lowest_tsc > **tsc)) + lowest_tsc = **tsc; + } + + if (lowest_tsc) { + storage.beginning_of_time_nanos = + storage.tsc_conversion->ToNanos(*lowest_tsc); + } + return storage.beginning_of_time_nanos; +} + +llvm::Expected<lldb::TraceCursorSP> +TraceIntelPT::CreateNewCursor(Thread &thread) { + if (Expected<DecodedThreadSP> decoded_thread = Decode(thread)) { + if (Expected<std::optional<uint64_t>> beginning_of_time = + FindBeginningOfTimeNanos()) + return std::make_shared<TraceCursorIntelPT>( + thread.shared_from_this(), *decoded_thread, m_storage.tsc_conversion, + *beginning_of_time); + else + return beginning_of_time.takeError(); + } else + return decoded_thread.takeError(); +} + +void TraceIntelPT::DumpTraceInfo(Thread &thread, Stream &s, bool verbose, + bool json) { + Storage &storage = GetUpdatedStorage(); + + lldb::tid_t tid = thread.GetID(); + if (json) { + DumpTraceInfoAsJson(thread, s, verbose); + return; + } + + s.Format("\nthread #{0}: tid = {1}", thread.GetIndexID(), thread.GetID()); + if (!IsTraced(tid)) { + s << ", not traced\n"; + return; + } + s << "\n"; + + Expected<DecodedThreadSP> decoded_thread_sp_or_err = Decode(thread); + if (!decoded_thread_sp_or_err) { + s << toString(decoded_thread_sp_or_err.takeError()) << "\n"; + return; + } + + DecodedThreadSP &decoded_thread_sp = *decoded_thread_sp_or_err; + + Expected<std::optional<uint64_t>> raw_size_or_error = GetRawTraceSize(thread); + if (!raw_size_or_error) { + s.Format(" {0}\n", toString(raw_size_or_error.takeError())); + return; + } + std::optional<uint64_t> raw_size = *raw_size_or_error; + + s.Format("\n Trace technology: {0}\n", GetPluginName()); + + /// Instruction stats + { + uint64_t items_count = decoded_thread_sp->GetItemsCount(); + uint64_t mem_used = decoded_thread_sp->CalculateApproximateMemoryUsage(); + + s.Format("\n Total number of trace items: {0}\n", items_count); + + s << "\n Memory usage:\n"; + if (raw_size) + s.Format(" Raw trace size: {0} KiB\n", *raw_size / 1024); + + s.Format( + " Total approximate memory usage (excluding raw trace): {0:2} KiB\n", + (double)mem_used / 1024); + if (items_count != 0) + s.Format(" Average memory usage per item (excluding raw trace): " + "{0:2} bytes\n", + (double)mem_used / items_count); + } + + // Timing + { + s << "\n Timing for this thread:\n"; + auto print_duration = [&](const std::string &name, + std::chrono::milliseconds duration) { + s.Format(" {0}: {1:2}s\n", name, duration.count() / 1000.0); + }; + GetThreadTimer(tid).ForEachTimedTask(print_duration); + + s << "\n Timing for global tasks:\n"; + GetGlobalTimer().ForEachTimedTask(print_duration); + } + + // Instruction events stats + { + const DecodedThread::EventsStats &events_stats = + decoded_thread_sp->GetEventsStats(); + s << "\n Events:\n"; + s.Format(" Number of individual events: {0}\n", + events_stats.total_count); + for (const auto &event_to_count : events_stats.events_counts) { + s.Format(" {0}: {1}\n", + TraceCursor::EventKindToString(event_to_count.first), + event_to_count.second); + } + } + // Trace error stats + { + const DecodedThread::ErrorStats &error_stats = + decoded_thread_sp->GetErrorStats(); + s << "\n Errors:\n"; + s.Format(" Number of individual errors: {0}\n", + error_stats.GetTotalCount()); + s.Format(" Number of fatal errors: {0}\n", error_stats.fatal_errors); + for (const auto &[kind, count] : error_stats.libipt_errors) { + s.Format(" Number of libipt errors of kind [{0}]: {1}\n", kind, + count); + } + s.Format(" Number of other errors: {0}\n", error_stats.other_errors); + } + + if (storage.multicpu_decoder) { + s << "\n Multi-cpu decoding:\n"; + s.Format(" Total number of continuous executions found: {0}\n", + storage.multicpu_decoder->GetTotalContinuousExecutionsCount()); + s.Format( + " Number of continuous executions for this thread: {0}\n", + storage.multicpu_decoder->GetNumContinuousExecutionsForThread(tid)); + s.Format(" Total number of PSB blocks found: {0}\n", + storage.multicpu_decoder->GetTotalPSBBlocksCount()); + s.Format(" Number of PSB blocks for this thread: {0}\n", + storage.multicpu_decoder->GePSBBlocksCountForThread(tid)); + s.Format(" Total number of unattributed PSB blocks found: {0}\n", + storage.multicpu_decoder->GetUnattributedPSBBlocksCount()); + } +} + +void TraceIntelPT::DumpTraceInfoAsJson(Thread &thread, Stream &s, + bool verbose) { + Storage &storage = GetUpdatedStorage(); + + lldb::tid_t tid = thread.GetID(); + json::OStream json_str(s.AsRawOstream(), 2); + if (!IsTraced(tid)) { + s << "error: thread not traced\n"; + return; + } + + Expected<std::optional<uint64_t>> raw_size_or_error = GetRawTraceSize(thread); + if (!raw_size_or_error) { + s << "error: " << toString(raw_size_or_error.takeError()) << "\n"; + return; + } + + Expected<DecodedThreadSP> decoded_thread_sp_or_err = Decode(thread); + if (!decoded_thread_sp_or_err) { + s << "error: " << toString(decoded_thread_sp_or_err.takeError()) << "\n"; + return; + } + DecodedThreadSP &decoded_thread_sp = *decoded_thread_sp_or_err; + + json_str.object([&] { + json_str.attribute("traceTechnology", "intel-pt"); + json_str.attributeObject("threadStats", [&] { + json_str.attribute("tid", tid); + + uint64_t insn_len = decoded_thread_sp->GetItemsCount(); + json_str.attribute("traceItemsCount", insn_len); + + // Instruction stats + uint64_t mem_used = decoded_thread_sp->CalculateApproximateMemoryUsage(); + json_str.attributeObject("memoryUsage", [&] { + json_str.attribute("totalInBytes", std::to_string(mem_used)); + std::optional<double> avg; + if (insn_len != 0) + avg = double(mem_used) / insn_len; + json_str.attribute("avgPerItemInBytes", avg); + }); + + // Timing + json_str.attributeObject("timingInSeconds", [&] { + GetTimer().ForThread(tid).ForEachTimedTask( + [&](const std::string &name, std::chrono::milliseconds duration) { + json_str.attribute(name, duration.count() / 1000.0); + }); + }); + + // Instruction events stats + const DecodedThread::EventsStats &events_stats = + decoded_thread_sp->GetEventsStats(); + json_str.attributeObject("events", [&] { + json_str.attribute("totalCount", events_stats.total_count); + json_str.attributeObject("individualCounts", [&] { + for (const auto &event_to_count : events_stats.events_counts) { + json_str.attribute( + TraceCursor::EventKindToString(event_to_count.first), + event_to_count.second); + } + }); + }); + // Trace error stats + const DecodedThread::ErrorStats &error_stats = + decoded_thread_sp->GetErrorStats(); + json_str.attributeObject("errors", [&] { + json_str.attribute("totalCount", error_stats.GetTotalCount()); + json_str.attributeObject("libiptErrors", [&] { + for (const auto &[kind, count] : error_stats.libipt_errors) { + json_str.attribute(kind, count); + } + }); + json_str.attribute("fatalErrors", error_stats.fatal_errors); + json_str.attribute("otherErrors", error_stats.other_errors); + }); + + if (storage.multicpu_decoder) { + json_str.attribute( + "continuousExecutions", + storage.multicpu_decoder->GetNumContinuousExecutionsForThread(tid)); + json_str.attribute( + "PSBBlocks", + storage.multicpu_decoder->GePSBBlocksCountForThread(tid)); + } + }); + + json_str.attributeObject("globalStats", [&] { + json_str.attributeObject("timingInSeconds", [&] { + GetTimer().ForGlobal().ForEachTimedTask( + [&](const std::string &name, std::chrono::milliseconds duration) { + json_str.attribute(name, duration.count() / 1000.0); + }); + }); + if (storage.multicpu_decoder) { + json_str.attribute( + "totalUnattributedPSBBlocks", + storage.multicpu_decoder->GetUnattributedPSBBlocksCount()); + json_str.attribute( + "totalCountinuosExecutions", + storage.multicpu_decoder->GetTotalContinuousExecutionsCount()); + json_str.attribute("totalPSBBlocks", + storage.multicpu_decoder->GetTotalPSBBlocksCount()); + json_str.attribute( + "totalContinuousExecutions", + storage.multicpu_decoder->GetTotalContinuousExecutionsCount()); + } + }); + }); +} + +llvm::Expected<std::optional<uint64_t>> +TraceIntelPT::GetRawTraceSize(Thread &thread) { + if (GetUpdatedStorage().multicpu_decoder) + return std::nullopt; // TODO: calculate the amount of intel pt raw trace associated + // with the given thread. + if (GetLiveProcess()) + return GetLiveThreadBinaryDataSize(thread.GetID(), + IntelPTDataKinds::kIptTrace); + uint64_t size; + auto callback = [&](llvm::ArrayRef<uint8_t> data) { + size = data.size(); + return Error::success(); + }; + if (Error err = OnThreadBufferRead(thread.GetID(), callback)) + return std::move(err); + + return size; +} + +Expected<pt_cpu> TraceIntelPT::GetCPUInfoForLiveProcess() { + Expected<std::vector<uint8_t>> cpu_info = + GetLiveProcessBinaryData(IntelPTDataKinds::kProcFsCpuInfo); + 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; +} + +std::optional<LinuxPerfZeroTscConversion> +TraceIntelPT::GetPerfZeroTscConversion() { + return GetUpdatedStorage().tsc_conversion; +} + +TraceIntelPT::Storage &TraceIntelPT::GetUpdatedStorage() { + RefreshLiveProcessState(); + return m_storage; +} + +Error TraceIntelPT::DoRefreshLiveProcessState(TraceGetStateResponse state, + StringRef json_response) { + m_storage = Storage(); + + Expected<TraceIntelPTGetStateResponse> intelpt_state = + json::parse<TraceIntelPTGetStateResponse>(json_response, + "TraceIntelPTGetStateResponse"); + if (!intelpt_state) + return intelpt_state.takeError(); + + m_storage.tsc_conversion = intelpt_state->tsc_perf_zero_conversion; + + if (!intelpt_state->cpus) { + for (const TraceThreadState &thread_state : state.traced_threads) { + ThreadSP thread_sp = + GetLiveProcess()->GetThreadList().FindThreadByID(thread_state.tid); + m_storage.thread_decoders.try_emplace( + thread_state.tid, std::make_unique<ThreadDecoder>(thread_sp, *this)); + } + } else { + std::vector<cpu_id_t> cpus; + for (const TraceCpuState &cpu : *intelpt_state->cpus) + cpus.push_back(cpu.id); + + std::vector<tid_t> tids; + for (const TraceThreadState &thread : intelpt_state->traced_threads) + tids.push_back(thread.tid); + + if (!intelpt_state->tsc_perf_zero_conversion) + return createStringError(inconvertibleErrorCode(), + "Missing perf time_zero conversion values"); + m_storage.multicpu_decoder.emplace(GetSharedPtr()); + } + + if (m_storage.tsc_conversion) { + Log *log = GetLog(LLDBLog::Target); + LLDB_LOG(log, "TraceIntelPT found TSC conversion information"); + } + return Error::success(); +} + +bool TraceIntelPT::IsTraced(lldb::tid_t tid) { + Storage &storage = GetUpdatedStorage(); + if (storage.multicpu_decoder) + return storage.multicpu_decoder->TracesThread(tid); + return storage.thread_decoders.count(tid); +} + +// 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() { + static std::optional<std::string> message; + if (!message) { + message.emplace(formatv(R"(Parameters: + + See the jLLDBTraceStart section in lldb/docs/lldb-gdb-remote.txt for a + description of each parameter below. + + - int iptTraceSize (defaults to {0} bytes): + [process and thread tracing] + + - boolean enableTsc (default to {1}): + [process and thread tracing] + + - int psbPeriod (defaults to {2}): + [process and thread tracing] + + - boolean perCpuTracing (default to {3}): + [process tracing only] + + - int processBufferSizeLimit (defaults to {4} MiB): + [process tracing only] + + - boolean disableCgroupFiltering (default to {5}): + [process tracing only])", + kDefaultIptTraceSize, kDefaultEnableTscValue, + kDefaultPsbPeriod, kDefaultPerCpuTracing, + kDefaultProcessBufferSizeLimit / 1024 / 1024, + kDefaultDisableCgroupFiltering)); + } + return message->c_str(); +} + +Error TraceIntelPT::Start(uint64_t ipt_trace_size, + uint64_t total_buffer_size_limit, bool enable_tsc, + std::optional<uint64_t> psb_period, + bool per_cpu_tracing, bool disable_cgroup_filtering) { + TraceIntelPTStartRequest request; + request.ipt_trace_size = ipt_trace_size; + request.process_buffer_size_limit = total_buffer_size_limit; + request.enable_tsc = enable_tsc; + request.psb_period = psb_period; + request.type = GetPluginName().str(); + request.per_cpu_tracing = per_cpu_tracing; + request.disable_cgroup_filtering = disable_cgroup_filtering; + return Trace::Start(toJSON(request)); +} + +Error TraceIntelPT::Start(StructuredData::ObjectSP configuration) { + uint64_t ipt_trace_size = kDefaultIptTraceSize; + uint64_t process_buffer_size_limit = kDefaultProcessBufferSizeLimit; + bool enable_tsc = kDefaultEnableTscValue; + std::optional<uint64_t> psb_period = kDefaultPsbPeriod; + bool per_cpu_tracing = kDefaultPerCpuTracing; + bool disable_cgroup_filtering = kDefaultDisableCgroupFiltering; + + if (configuration) { + if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { + dict->GetValueForKeyAsInteger("iptTraceSize", ipt_trace_size); + dict->GetValueForKeyAsInteger("processBufferSizeLimit", + process_buffer_size_limit); + dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); + dict->GetValueForKeyAsInteger("psbPeriod", psb_period); + dict->GetValueForKeyAsBoolean("perCpuTracing", per_cpu_tracing); + dict->GetValueForKeyAsBoolean("disableCgroupFiltering", + disable_cgroup_filtering); + } else { + return createStringError(inconvertibleErrorCode(), + "configuration object is not a dictionary"); + } + } + + return Start(ipt_trace_size, process_buffer_size_limit, enable_tsc, + psb_period, per_cpu_tracing, disable_cgroup_filtering); +} + +llvm::Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids, + uint64_t ipt_trace_size, bool enable_tsc, + std::optional<uint64_t> psb_period) { + TraceIntelPTStartRequest request; + request.ipt_trace_size = ipt_trace_size; + request.enable_tsc = enable_tsc; + request.psb_period = psb_period; + request.type = GetPluginName().str(); + 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) { + uint64_t ipt_trace_size = kDefaultIptTraceSize; + bool enable_tsc = kDefaultEnableTscValue; + std::optional<uint64_t> psb_period = kDefaultPsbPeriod; + + if (configuration) { + if (StructuredData::Dictionary *dict = configuration->GetAsDictionary()) { + llvm::StringRef ipt_trace_size_not_parsed; + if (dict->GetValueForKeyAsString("iptTraceSize", + ipt_trace_size_not_parsed)) { + if (std::optional<uint64_t> bytes = + ParsingUtils::ParseUserFriendlySizeExpression( + ipt_trace_size_not_parsed)) + ipt_trace_size = *bytes; + else + return createStringError(inconvertibleErrorCode(), + "iptTraceSize is wrong bytes expression"); + } else { + dict->GetValueForKeyAsInteger("iptTraceSize", ipt_trace_size); + } + + dict->GetValueForKeyAsBoolean("enableTsc", enable_tsc); + dict->GetValueForKeyAsInteger("psbPeriod", psb_period); + } else { + return createStringError(inconvertibleErrorCode(), + "configuration object is not a dictionary"); + } + } + + return Start(tids, ipt_trace_size, enable_tsc, psb_period); +} + +Error TraceIntelPT::OnThreadBufferRead(lldb::tid_t tid, + OnBinaryDataReadCallback callback) { + return OnThreadBinaryDataRead(tid, IntelPTDataKinds::kIptTrace, callback); +} + +TaskTimer &TraceIntelPT::GetTimer() { return GetUpdatedStorage().task_timer; } + +ScopedTaskTimer &TraceIntelPT::GetThreadTimer(lldb::tid_t tid) { + return GetTimer().ForThread(tid); +} + +ScopedTaskTimer &TraceIntelPT::GetGlobalTimer() { + return GetTimer().ForGlobal(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h new file mode 100644 index 000000000000..da9cefe9ed95 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.h @@ -0,0 +1,293 @@ +//===-- TraceIntelPT.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_TRACEINTELPT_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H + +#include "TaskTimer.h" +#include "ThreadDecoder.h" +#include "TraceIntelPTBundleLoader.h" +#include "TraceIntelPTMultiCpuDecoder.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/raw_ostream.h" +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT : public Trace { +public: + /// Properties to be used with the `settings` command. + class PluginProperties : public Properties { + public: + static llvm::StringRef GetSettingName(); + + PluginProperties(); + + ~PluginProperties() override = default; + + uint64_t GetInfiniteDecodingLoopVerificationThreshold(); + + uint64_t GetExtremelyLargeDecodingThreshold(); + }; + + /// Return the global properties for this trace plug-in. + static PluginProperties &GetGlobalProperties(); + + void Dump(Stream *s) const override; + + llvm::Expected<FileSpec> SaveToDisk(FileSpec directory, + bool compact) override; + + ~TraceIntelPT() override = default; + + /// PluginInterface protocol + /// \{ + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + static void Initialize(); + + static void Terminate(); + + /// Create an instance of this class from a trace bundle. + /// + /// \param[in] trace_bundle_description + /// The description of the trace bundle. See \a Trace::FindPlugin. + /// + /// \param[in] bundle_dir + /// The path to the directory that contains the trace bundle. + /// + /// \param[in] debugger + /// The debugger instance where new Targets will be created as part of the + /// JSON data parsing. + /// + /// \return + /// A trace instance or an error in case of failures. + static llvm::Expected<lldb::TraceSP> CreateInstanceForTraceBundle( + const llvm::json::Value &trace_bundle_description, + llvm::StringRef bundle_dir, Debugger &debugger); + + static llvm::Expected<lldb::TraceSP> + CreateInstanceForLiveProcess(Process &process); + + static llvm::StringRef GetPluginNameStatic() { return "intel-pt"; } + + static void DebuggerInitialize(Debugger &debugger); + /// \} + + lldb::CommandObjectSP + GetProcessTraceStartCommand(CommandInterpreter &interpreter) override; + + lldb::CommandObjectSP + GetThreadTraceStartCommand(CommandInterpreter &interpreter) override; + + llvm::StringRef GetSchema() override; + + llvm::Expected<lldb::TraceCursorSP> CreateNewCursor(Thread &thread) override; + + void DumpTraceInfo(Thread &thread, Stream &s, bool verbose, + bool json) override; + + llvm::Expected<std::optional<uint64_t>> GetRawTraceSize(Thread &thread); + + llvm::Error DoRefreshLiveProcessState(TraceGetStateResponse state, + llvm::StringRef json_response) override; + + bool IsTraced(lldb::tid_t tid) override; + + const char *GetStartConfigurationHelp() override; + + /// Start tracing a live process. + /// + /// More information on the parameters below can be found in the + /// jLLDBTraceStart section in lldb/docs/lldb-gdb-remote.txt. + /// + /// \param[in] ipt_trace_size + /// Trace size per thread in bytes. + /// + /// \param[in] total_buffer_size_limit + /// Maximum total trace size per process in bytes. + /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// + /// \param[in] psb_period + /// This value defines the period in which PSB packets will be generated. + /// + /// \param[in] per_cpu_tracing + /// This value defines whether to have an intel pt trace buffer per thread + /// or per cpu core. + /// + /// \param[in] disable_cgroup_filtering + /// Disable the cgroup filtering that is automatically applied when doing + /// per cpu tracing. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error Start(uint64_t ipt_trace_size, uint64_t total_buffer_size_limit, + bool enable_tsc, std::optional<uint64_t> psb_period, + bool m_per_cpu_tracing, bool disable_cgroup_filtering); + + /// \copydoc Trace::Start + llvm::Error Start(StructuredData::ObjectSP configuration = + StructuredData::ObjectSP()) override; + + /// Start tracing live threads. + /// + /// More information on the parameters below can be found in the + /// jLLDBTraceStart section in lldb/docs/lldb-gdb-remote.txt. + /// + /// \param[in] tids + /// Threads to trace. + /// + /// \param[in] ipt_trace_size + /// Trace size per thread or per cpu core in bytes. + /// + /// \param[in] enable_tsc + /// Whether to use enable TSC timestamps or not. + /// + /// \param[in] psb_period + /// This value defines the period in which PSB packets will be generated. + /// + /// \return + /// \a llvm::Error::success if the operation was successful, or + /// \a llvm::Error otherwise. + llvm::Error Start(llvm::ArrayRef<lldb::tid_t> tids, uint64_t ipt_trace_size, + bool enable_tsc, std::optional<uint64_t> psb_period); + + /// \copydoc Trace::Start + llvm::Error Start(llvm::ArrayRef<lldb::tid_t> tids, + StructuredData::ObjectSP configuration = + StructuredData::ObjectSP()) override; + + /// See \a Trace::OnThreadBinaryDataRead(). + llvm::Error OnThreadBufferRead(lldb::tid_t tid, + OnBinaryDataReadCallback callback); + + /// Get or fetch the cpu information from, for example, /proc/cpuinfo. + llvm::Expected<pt_cpu> GetCPUInfo(); + + /// Get or fetch the values used to convert to and from TSCs and nanos. + std::optional<LinuxPerfZeroTscConversion> GetPerfZeroTscConversion(); + + /// \return + /// The timer object for this trace. + TaskTimer &GetTimer(); + + /// \return + /// The ScopedTaskTimer object for the given thread in this trace. + ScopedTaskTimer &GetThreadTimer(lldb::tid_t tid); + + /// \return + /// The global copedTaskTimer object for this trace. + ScopedTaskTimer &GetGlobalTimer(); + + TraceIntelPTSP GetSharedPtr(); + + enum class TraceMode { UserMode, KernelMode }; + + TraceMode GetTraceMode(); + +private: + friend class TraceIntelPTBundleLoader; + + llvm::Expected<pt_cpu> GetCPUInfoForLiveProcess(); + + /// Postmortem trace constructor + /// + /// \param[in] bundle_description + /// The definition file for the postmortem bundle. + /// + /// \param[in] traced_processes + /// The processes traced in the postmortem session. + /// + /// \param[in] trace_threads + /// The threads traced in the postmortem session. They must belong to the + /// processes mentioned above. + /// + /// \param[in] trace_mode + /// The tracing mode of the postmortem session. + /// + /// \return + /// A TraceIntelPT shared pointer instance. + /// \{ + static TraceIntelPTSP CreateInstanceForPostmortemTrace( + JSONTraceBundleDescription &bundle_description, + llvm::ArrayRef<lldb::ProcessSP> traced_processes, + llvm::ArrayRef<lldb::ThreadPostMortemTraceSP> traced_threads, + TraceMode trace_mode); + + /// This constructor is used by CreateInstanceForPostmortemTrace to get the + /// instance ready before using shared pointers, which is a limitation of C++. + TraceIntelPT(JSONTraceBundleDescription &bundle_description, + llvm::ArrayRef<lldb::ProcessSP> traced_processes, + TraceMode trace_mode); + /// \} + + /// Constructor for live processes + TraceIntelPT(Process &live_process) + : Trace(live_process), trace_mode(TraceMode::UserMode){}; + + /// Decode the trace of the given thread that, i.e. recontruct the traced + /// 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 shared pointer with the decoded instructions. Any + /// errors are embedded in the instruction list. An \a llvm::Error is + /// returned if the decoder couldn't be properly set up. + llvm::Expected<DecodedThreadSP> Decode(Thread &thread); + + /// \return + /// The lowest timestamp in nanoseconds in all traces if available, \a + /// std::nullopt if all the traces were empty or no trace contained no + /// timing information, or an \a llvm::Error if it was not possible to set + /// up the decoder for some trace. + llvm::Expected<std::optional<uint64_t>> FindBeginningOfTimeNanos(); + + // Dump out trace info in JSON format + void DumpTraceInfoAsJson(Thread &thread, Stream &s, bool verbose); + + /// We package all the data that can change upon process stops to make sure + /// this contract is very visible. + /// This variable should only be accessed directly by constructores or live + /// process data refreshers. + struct Storage { + std::optional<TraceIntelPTMultiCpuDecoder> multicpu_decoder; + /// These decoders are used for the non-per-cpu case + llvm::DenseMap<lldb::tid_t, std::unique_ptr<ThreadDecoder>> thread_decoders; + /// Helper variable used to track long running operations for telemetry. + TaskTimer task_timer; + /// It is provided by either a trace bundle or a live process to convert TSC + /// counters to and from nanos. It might not be available on all hosts. + std::optional<LinuxPerfZeroTscConversion> tsc_conversion; + std::optional<uint64_t> beginning_of_time_nanos; + bool beginning_of_time_nanos_calculated = false; + } m_storage; + + /// It is provided by either a trace bundle or a live process' "cpuInfo" + /// binary data. We don't put it in the Storage because this variable doesn't + /// change. + std::optional<pt_cpu> m_cpu_info; + + /// Get the storage after refreshing the data in the case of a live process. + Storage &GetUpdatedStorage(); + + /// The tracing mode of post mortem trace. + TraceMode trace_mode; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPT_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp new file mode 100644 index 000000000000..1a9f6fe30509 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.cpp @@ -0,0 +1,437 @@ +//===-- TraceIntelPTBundleLoader.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 "TraceIntelPTBundleLoader.h" + +#include "../common/ThreadPostMortemTrace.h" +#include "TraceIntelPT.h" +#include "TraceIntelPTConstants.h" +#include "TraceIntelPTJSONStructs.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/ProcessTrace.h" +#include "lldb/Target/Target.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +FileSpec TraceIntelPTBundleLoader::NormalizePath(const std::string &path) { + FileSpec file_spec(path); + if (file_spec.IsRelative()) + file_spec.PrependPathComponent(m_bundle_dir); + return file_spec; +} + +Error TraceIntelPTBundleLoader::ParseModule(Target &target, + const JSONModule &module) { + auto do_parse = [&]() -> Error { + FileSpec system_file_spec(module.system_path); + + FileSpec local_file_spec(module.file.has_value() ? *module.file + : module.system_path); + + ModuleSpec module_spec; + module_spec.GetFileSpec() = local_file_spec; + module_spec.GetPlatformFileSpec() = system_file_spec; + + if (module.uuid.has_value()) + module_spec.GetUUID().SetFromStringRef(*module.uuid); + + Status error; + ModuleSP module_sp = + target.GetOrCreateModule(module_spec, /*notify*/ false, &error); + + if (error.Fail()) + return error.ToError(); + + bool load_addr_changed = false; + module_sp->SetLoadAddress(target, module.load_address.value, false, + load_addr_changed); + return Error::success(); + }; + if (Error err = do_parse()) + return createStringError( + inconvertibleErrorCode(), "Error when parsing module %s. %s", + module.system_path.c_str(), toString(std::move(err)).c_str()); + return Error::success(); +} + +Error TraceIntelPTBundleLoader::CreateJSONError(json::Path::Root &root, + const json::Value &value) { + std::string err; + raw_string_ostream os(err); + root.printErrorContext(value, os); + return createStringError( + std::errc::invalid_argument, "%s\n\nContext:\n%s\n\nSchema:\n%s", + toString(root.getError()).c_str(), os.str().c_str(), GetSchema().data()); +} + +ThreadPostMortemTraceSP +TraceIntelPTBundleLoader::ParseThread(Process &process, + const JSONThread &thread) { + lldb::tid_t tid = static_cast<lldb::tid_t>(thread.tid); + + std::optional<FileSpec> trace_file; + if (thread.ipt_trace) + trace_file = FileSpec(*thread.ipt_trace); + + ThreadPostMortemTraceSP thread_sp = + std::make_shared<ThreadPostMortemTrace>(process, tid, trace_file); + process.GetThreadList().AddThread(thread_sp); + return thread_sp; +} + +Expected<TraceIntelPTBundleLoader::ParsedProcess> +TraceIntelPTBundleLoader::CreateEmptyProcess(lldb::pid_t pid, + llvm::StringRef triple) { + TargetSP target_sp; + Status error = m_debugger.GetTargetList().CreateTarget( + m_debugger, /*user_exe_path*/ StringRef(), triple, eLoadDependentsNo, + /*platform_options*/ nullptr, target_sp); + + if (!target_sp) + return error.ToError(); + + ParsedProcess parsed_process; + parsed_process.target_sp = target_sp; + + ProcessTrace::Initialize(); + ProcessSP process_sp = target_sp->CreateProcess( + /*listener*/ nullptr, "trace", + /*crash_file*/ nullptr, + /*can_connect*/ false); + + process_sp->SetID(static_cast<lldb::pid_t>(pid)); + + return parsed_process; +} + +Expected<TraceIntelPTBundleLoader::ParsedProcess> +TraceIntelPTBundleLoader::ParseProcess(const JSONProcess &process) { + Expected<ParsedProcess> parsed_process = + CreateEmptyProcess(process.pid, process.triple.value_or("")); + + if (!parsed_process) + return parsed_process.takeError(); + + ProcessSP process_sp = parsed_process->target_sp->GetProcessSP(); + + for (const JSONThread &thread : process.threads) + parsed_process->threads.push_back(ParseThread(*process_sp, thread)); + + for (const JSONModule &module : process.modules) + if (Error err = ParseModule(*parsed_process->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 parsed_process; +} + +Expected<TraceIntelPTBundleLoader::ParsedProcess> +TraceIntelPTBundleLoader::ParseKernel( + const JSONTraceBundleDescription &bundle_description) { + Expected<ParsedProcess> parsed_process = + CreateEmptyProcess(kDefaultKernelProcessID, ""); + + if (!parsed_process) + return parsed_process.takeError(); + + ProcessSP process_sp = parsed_process->target_sp->GetProcessSP(); + + // Add cpus as fake threads + for (const JSONCpu &cpu : *bundle_description.cpus) { + ThreadPostMortemTraceSP thread_sp = std::make_shared<ThreadPostMortemTrace>( + *process_sp, static_cast<lldb::tid_t>(cpu.id), FileSpec(cpu.ipt_trace)); + thread_sp->SetName(formatv("kernel_cpu_{0}", cpu.id).str().c_str()); + process_sp->GetThreadList().AddThread(thread_sp); + parsed_process->threads.push_back(thread_sp); + } + + // Add kernel image + FileSpec file_spec(bundle_description.kernel->file); + ModuleSpec module_spec; + module_spec.GetFileSpec() = file_spec; + + Status error; + ModuleSP module_sp = + parsed_process->target_sp->GetOrCreateModule(module_spec, false, &error); + + if (error.Fail()) + return error.ToError(); + + lldb::addr_t load_address = + bundle_description.kernel->load_address + ? bundle_description.kernel->load_address->value + : kDefaultKernelLoadAddress; + + bool load_addr_changed = false; + module_sp->SetLoadAddress(*parsed_process->target_sp, load_address, false, + load_addr_changed); + + 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 parsed_process; +} + +Expected<std::vector<TraceIntelPTBundleLoader::ParsedProcess>> +TraceIntelPTBundleLoader::LoadBundle( + const JSONTraceBundleDescription &bundle_description) { + std::vector<ParsedProcess> parsed_processes; + + auto HandleError = [&](Error &&err) { + // Delete all targets that were created so far in case of failures + for (ParsedProcess &parsed_process : parsed_processes) + m_debugger.GetTargetList().DeleteTarget(parsed_process.target_sp); + return std::move(err); + }; + + if (bundle_description.processes) { + for (const JSONProcess &process : *bundle_description.processes) { + if (Expected<ParsedProcess> parsed_process = ParseProcess(process)) + parsed_processes.push_back(std::move(*parsed_process)); + else + return HandleError(parsed_process.takeError()); + } + } + + if (bundle_description.kernel) { + if (Expected<ParsedProcess> kernel_process = + ParseKernel(bundle_description)) + parsed_processes.push_back(std::move(*kernel_process)); + else + return HandleError(kernel_process.takeError()); + } + + return parsed_processes; +} + +StringRef TraceIntelPTBundleLoader::GetSchema() { + static std::string schema; + if (schema.empty()) { + schema = R"({ + "type": "intel-pt", + "cpuInfo": { + // CPU information gotten from, for example, /proc/cpuinfo. + + "vendor": "GenuineIntel" | "unknown", + "family": integer, + "model": integer, + "stepping": integer + }, + "processes?": [ + { + "pid": integer, + "triple"?: string, + // Optional clang/llvm target triple. + // This must be provided if the trace will be created not using the + // CLI or on a machine other than where the target was traced. + "threads": [ + // A list of known threads for the given process. When context switch + // data is provided, LLDB will automatically create threads for the + // this process whenever it finds new threads when traversing the + // context switches, so passing values to this list in this case is + // optional. + { + "tid": integer, + "iptTrace"?: string + // Path to the raw Intel PT buffer file for this thread. + } + ], + "modules": [ + { + "systemPath": string, + // Original path of the module at runtime. + "file"?: string, + // Path to a copy of the file if not available at "systemPath". + "loadAddress": integer | string decimal | hex string, + // Lowest address of the sections of the module loaded on memory. + "uuid"?: string, + // Build UUID for the file for sanity checks. + } + ] + } + ], + "cpus"?: [ + { + "id": integer, + // Id of this CPU core. + "iptTrace": string, + // Path to the raw Intel PT buffer for this cpu core. + "contextSwitchTrace": string, + // Path to the raw perf_event_open context switch trace file for this cpu core. + // The perf_event must have been configured with PERF_SAMPLE_TID and + // PERF_SAMPLE_TIME, as well as sample_id_all = 1. + } + ], + "tscPerfZeroConversion"?: { + // Values used to convert between TSCs and nanoseconds. See the time_zero + // section in https://man7.org/linux/man-pages/man2/perf_event_open.2.html + // for information. + + "timeMult": integer, + "timeShift": integer, + "timeZero": integer | string decimal | hex string, + }, + "kernel"?: { + "loadAddress"?: integer | string decimal | hex string, + // Kernel's image load address. Defaults to 0xffffffff81000000, which + // is a load address of x86 architecture if KASLR is not enabled. + "file": string, + // Path to the kernel image. + } +} + +Notes: + +- All paths are either absolute or relative to folder containing the bundle + description file. +- "cpus" is provided if and only if processes[].threads[].iptTrace is not provided. +- "tscPerfZeroConversion" must be provided if "cpus" is provided. +- If "kernel" is provided, then the "processes" section must be empty or not + passed at all, and the "cpus" section must be provided. This configuration + indicates that the kernel was traced and user processes weren't. Besides + that, the kernel is treated as a single process with one thread per CPU + core. This doesn't handle actual kernel threads, but instead treats + all the instructions executed by the kernel on each core as an + individual thread.})"; + } + return schema; +} + +Error TraceIntelPTBundleLoader::AugmentThreadsFromContextSwitches( + JSONTraceBundleDescription &bundle_description) { + if (!bundle_description.cpus || !bundle_description.processes) + return Error::success(); + + if (!bundle_description.tsc_perf_zero_conversion) + return createStringError(inconvertibleErrorCode(), + "TSC to nanos conversion values are needed when " + "context switch information is provided."); + + DenseMap<lldb::pid_t, JSONProcess *> indexed_processes; + DenseMap<JSONProcess *, DenseSet<tid_t>> indexed_threads; + + for (JSONProcess &process : *bundle_description.processes) { + indexed_processes[process.pid] = &process; + for (JSONThread &thread : process.threads) + indexed_threads[&process].insert(thread.tid); + } + + auto on_thread_seen = [&](lldb::pid_t pid, tid_t tid) { + auto proc = indexed_processes.find(pid); + if (proc == indexed_processes.end()) + return; + if (indexed_threads[proc->second].count(tid)) + return; + indexed_threads[proc->second].insert(tid); + proc->second->threads.push_back({tid, /*ipt_trace=*/std::nullopt}); + }; + + for (const JSONCpu &cpu : *bundle_description.cpus) { + Error err = Trace::OnDataFileRead( + FileSpec(cpu.context_switch_trace), + [&](ArrayRef<uint8_t> data) -> Error { + Expected<std::vector<ThreadContinuousExecution>> executions = + DecodePerfContextSwitchTrace( + data, cpu.id, *bundle_description.tsc_perf_zero_conversion); + if (!executions) + return executions.takeError(); + for (const ThreadContinuousExecution &execution : *executions) + on_thread_seen(execution.pid, execution.tid); + return Error::success(); + }); + if (err) + return err; + } + return Error::success(); +} + +Expected<TraceSP> TraceIntelPTBundleLoader::CreateTraceIntelPTInstance( + JSONTraceBundleDescription &bundle_description, + std::vector<ParsedProcess> &parsed_processes) { + std::vector<ThreadPostMortemTraceSP> threads; + std::vector<ProcessSP> processes; + for (const ParsedProcess &parsed_process : parsed_processes) { + processes.push_back(parsed_process.target_sp->GetProcessSP()); + threads.insert(threads.end(), parsed_process.threads.begin(), + parsed_process.threads.end()); + } + + TraceIntelPT::TraceMode trace_mode = bundle_description.kernel + ? TraceIntelPT::TraceMode::KernelMode + : TraceIntelPT::TraceMode::UserMode; + + TraceSP trace_instance = TraceIntelPT::CreateInstanceForPostmortemTrace( + bundle_description, processes, threads, trace_mode); + for (const ParsedProcess &parsed_process : parsed_processes) + parsed_process.target_sp->SetTrace(trace_instance); + + return trace_instance; +} + +void TraceIntelPTBundleLoader::NormalizeAllPaths( + JSONTraceBundleDescription &bundle_description) { + if (bundle_description.processes) { + for (JSONProcess &process : *bundle_description.processes) { + for (JSONModule &module : process.modules) { + module.system_path = NormalizePath(module.system_path).GetPath(); + if (module.file) + module.file = NormalizePath(*module.file).GetPath(); + } + for (JSONThread &thread : process.threads) { + if (thread.ipt_trace) + thread.ipt_trace = NormalizePath(*thread.ipt_trace).GetPath(); + } + } + } + if (bundle_description.cpus) { + for (JSONCpu &cpu : *bundle_description.cpus) { + cpu.context_switch_trace = + NormalizePath(cpu.context_switch_trace).GetPath(); + cpu.ipt_trace = NormalizePath(cpu.ipt_trace).GetPath(); + } + } + if (bundle_description.kernel) { + bundle_description.kernel->file = + NormalizePath(bundle_description.kernel->file).GetPath(); + } +} + +Expected<TraceSP> TraceIntelPTBundleLoader::Load() { + json::Path::Root root("traceBundle"); + JSONTraceBundleDescription bundle_description; + if (!fromJSON(m_bundle_description, bundle_description, root)) + return CreateJSONError(root, m_bundle_description); + + NormalizeAllPaths(bundle_description); + + if (Error err = AugmentThreadsFromContextSwitches(bundle_description)) + return std::move(err); + + if (Expected<std::vector<ParsedProcess>> parsed_processes = + LoadBundle(bundle_description)) + return CreateTraceIntelPTInstance(bundle_description, *parsed_processes); + else + return parsed_processes.takeError(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.h new file mode 100644 index 000000000000..691a3f4fa08c --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleLoader.h @@ -0,0 +1,130 @@ +//===-- TraceIntelPTBundleLoader.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_TRACEINTELPTBUNDLELOADER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTBUNDLELOADER_H + +#include "../common/ThreadPostMortemTrace.h" +#include "TraceIntelPTJSONStructs.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT; + +class TraceIntelPTBundleLoader { +public: + /// Helper struct holding the objects created when parsing a process + struct ParsedProcess { + lldb::TargetSP target_sp; + std::vector<lldb::ThreadPostMortemTraceSP> threads; + }; + + /// \param[in] debugger + /// The debugger that will own the targets to create. + /// + /// \param[in] bundle_description + /// The JSON description of a trace bundle that follows the schema of the + /// intel pt trace plug-in. + /// + /// \param[in] bundle_dir + /// The folder where the trace bundle is located. + TraceIntelPTBundleLoader(Debugger &debugger, + const llvm::json::Value &bundle_description, + llvm::StringRef bundle_dir) + : m_debugger(debugger), m_bundle_description(bundle_description), + m_bundle_dir(bundle_dir) {} + + /// \return + /// The JSON schema for the bundle description. + static llvm::StringRef GetSchema(); + + /// Parse the trace bundle description and create the corresponding \a + /// Target objects. In case of an error, no targets are created. + /// + /// \return + /// A \a lldb::TraceSP instance created according to the trace bundle + /// information. In case of errors, return a null pointer. + llvm::Expected<lldb::TraceSP> Load(); + +private: + /// Resolve non-absolute paths relative to the bundle folder. + FileSpec NormalizePath(const std::string &path); + + /// Create a post-mortem thread associated with the given \p process + /// using the definition from \p thread. + lldb::ThreadPostMortemTraceSP ParseThread(Process &process, + const JSONThread &thread); + + /// Given a bundle description and a list of fully parsed processes, + /// create an actual Trace instance that "traces" these processes. + llvm::Expected<lldb::TraceSP> + CreateTraceIntelPTInstance(JSONTraceBundleDescription &bundle_description, + std::vector<ParsedProcess> &parsed_processes); + + /// Create an empty Process object with given pid and target. + llvm::Expected<ParsedProcess> CreateEmptyProcess(lldb::pid_t pid, + llvm::StringRef triple); + + /// Create the corresponding Threads and Process objects given the JSON + /// process definition. + /// + /// \param[in] process + /// The JSON process definition + llvm::Expected<ParsedProcess> ParseProcess(const JSONProcess &process); + + /// Create a module associated with the given \p target using the definition + /// from \p module. + llvm::Error ParseModule(Target &target, const JSONModule &module); + + /// Create a kernel process and cpu threads given the JSON kernel definition. + llvm::Expected<ParsedProcess> + ParseKernel(const JSONTraceBundleDescription &bundle_description); + + /// Create a user-friendly error message upon a JSON-parsing failure using the + /// \a json::ObjectMapper functionality. + /// + /// \param[in] root + /// The \a llvm::json::Path::Root used to parse the JSON \a value. + /// + /// \param[in] value + /// The json value that failed to parse. + /// + /// \return + /// An \a llvm::Error containing the user-friendly error message. + llvm::Error CreateJSONError(llvm::json::Path::Root &root, + const llvm::json::Value &value); + + /// Create the corresponding Process, Thread and Module objects given this + /// bundle description. + llvm::Expected<std::vector<ParsedProcess>> + LoadBundle(const JSONTraceBundleDescription &bundle_description); + + /// When applicable, augment the list of threads in the trace bundle by + /// inspecting the context switch trace. This only applies for threads of + /// processes already specified in this bundle description. + /// + /// \return + /// An \a llvm::Error in case if failures, or \a llvm::Error::success + /// otherwise. + llvm::Error AugmentThreadsFromContextSwitches( + JSONTraceBundleDescription &bundle_description); + + /// Modifiy the bundle description by normalizing all the paths relative to + /// the session file directory. + void NormalizeAllPaths(JSONTraceBundleDescription &bundle_description); + + Debugger &m_debugger; + const llvm::json::Value &m_bundle_description; + const std::string m_bundle_dir; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTBUNDLELOADER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.cpp new file mode 100644 index 000000000000..a09bb372bb01 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.cpp @@ -0,0 +1,402 @@ +//===-- TraceIntelPTBundleSaver.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 "TraceIntelPTBundleSaver.h" +#include "PerfContextSwitchDecoder.h" +#include "TraceIntelPT.h" +#include "TraceIntelPTConstants.h" +#include "TraceIntelPTJSONStructs.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" +#include <fstream> +#include <iostream> +#include <optional> +#include <sstream> +#include <string> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +/// Strip the \p directory component from the given \p path. It assumes that \p +/// directory is a prefix of \p path. +static std::string GetRelativePath(const FileSpec &directory, + const FileSpec &path) { + return path.GetPath().substr(directory.GetPath().size() + 1); +} + +/// Write a stream of bytes from \p data to the given output file. +/// It creates or overwrites the output file, but not append. +static llvm::Error WriteBytesToDisk(FileSpec &output_file, + ArrayRef<uint8_t> data) { + std::basic_fstream<char> out_fs = std::fstream( + output_file.GetPath().c_str(), std::ios::out | std::ios::binary); + if (!data.empty()) + out_fs.write(reinterpret_cast<const char *>(&data[0]), data.size()); + + out_fs.close(); + if (!out_fs) + return createStringError(inconvertibleErrorCode(), + formatv("couldn't write to the file {0}", + output_file.GetPath().c_str())); + return Error::success(); +} + +/// Save the trace bundle description JSON object inside the given directory +/// as a file named \a trace.json. +/// +/// \param[in] trace_bundle_description +/// The trace bundle description as JSON Object. +/// +/// \param[in] directory +/// The directory where the JSON file will be saved. +/// +/// \return +/// A \a FileSpec pointing to the bundle description file, or an \a +/// llvm::Error otherwise. +static Expected<FileSpec> +SaveTraceBundleDescription(const llvm::json::Value &trace_bundle_description, + const FileSpec &directory) { + FileSpec trace_path = directory; + trace_path.AppendPathComponent("trace.json"); + std::ofstream os(trace_path.GetPath()); + os << formatv("{0:2}", trace_bundle_description).str(); + os.close(); + if (!os) + return createStringError(inconvertibleErrorCode(), + formatv("couldn't write to the file {0}", + trace_path.GetPath().c_str())); + return trace_path; +} + +/// Build the threads sub-section of the trace bundle description file. +/// Any associated binary files are created inside the given directory. +/// +/// \param[in] process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where files will be saved when building the threads +/// section. +/// +/// \return +/// The threads section or \a llvm::Error in case of failures. +static llvm::Expected<std::vector<JSONThread>> +BuildThreadsSection(Process &process, FileSpec directory) { + std::vector<JSONThread> json_threads; + TraceSP trace_sp = process.GetTarget().GetTrace(); + + FileSpec threads_dir = directory; + threads_dir.AppendPathComponent("threads"); + sys::fs::create_directories(threads_dir.GetPath().c_str()); + + for (ThreadSP thread_sp : process.Threads()) { + lldb::tid_t tid = thread_sp->GetID(); + if (!trace_sp->IsTraced(tid)) + continue; + + JSONThread json_thread; + json_thread.tid = tid; + + if (trace_sp->GetTracedCpus().empty()) { + FileSpec output_file = threads_dir; + output_file.AppendPathComponent(std::to_string(tid) + ".intelpt_trace"); + json_thread.ipt_trace = GetRelativePath(directory, output_file); + + llvm::Error err = process.GetTarget().GetTrace()->OnThreadBinaryDataRead( + tid, IntelPTDataKinds::kIptTrace, + [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error { + return WriteBytesToDisk(output_file, data); + }); + if (err) + return std::move(err); + } + + json_threads.push_back(std::move(json_thread)); + } + return json_threads; +} + +/// \return +/// an \a llvm::Error in case of failures, \a std::nullopt if the trace is not +/// written to disk because the trace is empty and the \p compact flag is +/// present, or the FileSpec of the trace file on disk. +static Expected<std::optional<FileSpec>> +WriteContextSwitchTrace(TraceIntelPT &trace_ipt, lldb::cpu_id_t cpu_id, + const FileSpec &cpus_dir, bool compact) { + FileSpec output_context_switch_trace = cpus_dir; + output_context_switch_trace.AppendPathComponent(std::to_string(cpu_id) + + ".perf_context_switch_trace"); + + bool should_skip = false; + + Error err = trace_ipt.OnCpuBinaryDataRead( + cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace, + [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error { + if (!compact) + return WriteBytesToDisk(output_context_switch_trace, data); + + std::set<lldb::pid_t> pids; + for (Process *process : trace_ipt.GetAllProcesses()) + pids.insert(process->GetID()); + + Expected<std::vector<uint8_t>> compact_context_switch_trace = + FilterProcessesFromContextSwitchTrace(data, pids); + if (!compact_context_switch_trace) + return compact_context_switch_trace.takeError(); + + if (compact_context_switch_trace->empty()) { + should_skip = true; + return Error::success(); + } + + return WriteBytesToDisk(output_context_switch_trace, + *compact_context_switch_trace); + }); + if (err) + return std::move(err); + + if (should_skip) + return std::nullopt; + return output_context_switch_trace; +} + +static Expected<FileSpec> WriteIntelPTTrace(TraceIntelPT &trace_ipt, + lldb::cpu_id_t cpu_id, + const FileSpec &cpus_dir) { + FileSpec output_trace = cpus_dir; + output_trace.AppendPathComponent(std::to_string(cpu_id) + ".intelpt_trace"); + + Error err = trace_ipt.OnCpuBinaryDataRead( + cpu_id, IntelPTDataKinds::kIptTrace, + [&](llvm::ArrayRef<uint8_t> data) -> llvm::Error { + return WriteBytesToDisk(output_trace, data); + }); + if (err) + return std::move(err); + return output_trace; +} + +static llvm::Expected<std::optional<std::vector<JSONCpu>>> +BuildCpusSection(TraceIntelPT &trace_ipt, FileSpec directory, bool compact) { + if (trace_ipt.GetTracedCpus().empty()) + return std::nullopt; + + std::vector<JSONCpu> json_cpus; + FileSpec cpus_dir = directory; + cpus_dir.AppendPathComponent("cpus"); + sys::fs::create_directories(cpus_dir.GetPath().c_str()); + + for (lldb::cpu_id_t cpu_id : trace_ipt.GetTracedCpus()) { + JSONCpu json_cpu; + json_cpu.id = cpu_id; + Expected<std::optional<FileSpec>> context_switch_trace_path = + WriteContextSwitchTrace(trace_ipt, cpu_id, cpus_dir, compact); + if (!context_switch_trace_path) + return context_switch_trace_path.takeError(); + if (!*context_switch_trace_path) + continue; + json_cpu.context_switch_trace = + GetRelativePath(directory, **context_switch_trace_path); + + if (Expected<FileSpec> ipt_trace_path = + WriteIntelPTTrace(trace_ipt, cpu_id, cpus_dir)) + json_cpu.ipt_trace = GetRelativePath(directory, *ipt_trace_path); + else + return ipt_trace_path.takeError(); + + json_cpus.push_back(std::move(json_cpu)); + } + return json_cpus; +} + +/// Build modules sub-section of the trace bundle. The original modules +/// will be copied over to the \a <directory/modules> folder. Invalid modules +/// are skipped. +/// Copying the modules has the benefit of making these +/// directories self-contained, as the raw traces and modules are part of the +/// output directory and can be sent to another machine, where lldb can load +/// them and replicate exactly the same trace session. +/// +/// \param[in] process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where the modules files will be saved when building +/// the modules section. +/// Example: If a module \a libbar.so exists in the path +/// \a /usr/lib/foo/libbar.so, then it will be copied to +/// \a <directory>/modules/usr/lib/foo/libbar.so. +/// +/// \return +/// The modules section or \a llvm::Error in case of failures. +static llvm::Expected<std::vector<JSONModule>> +BuildModulesSection(Process &process, FileSpec directory) { + std::vector<JSONModule> json_modules; + ModuleList module_list = process.GetTarget().GetImages(); + for (size_t i = 0; i < module_list.GetSize(); ++i) { + ModuleSP module_sp(module_list.GetModuleAtIndex(i)); + if (!module_sp) + continue; + std::string system_path = module_sp->GetPlatformFileSpec().GetPath(); + // TODO: support memory-only libraries like [vdso] + if (!module_sp->GetFileSpec().IsAbsolute()) + continue; + + std::string file = module_sp->GetFileSpec().GetPath(); + ObjectFile *objfile = module_sp->GetObjectFile(); + if (objfile == nullptr) + continue; + + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + Address base_addr(objfile->GetBaseAddress()); + if (base_addr.IsValid() && + !process.GetTarget().GetSectionLoadList().IsEmpty()) + load_addr = base_addr.GetLoadAddress(&process.GetTarget()); + + if (load_addr == LLDB_INVALID_ADDRESS) + continue; + + FileSpec path_to_copy_module = directory; + path_to_copy_module.AppendPathComponent("modules"); + path_to_copy_module.AppendPathComponent(system_path); + sys::fs::create_directories(path_to_copy_module.GetDirectory().AsCString()); + + if (std::error_code ec = + llvm::sys::fs::copy_file(file, path_to_copy_module.GetPath())) + return createStringError( + inconvertibleErrorCode(), + formatv("couldn't write to the file. {0}", ec.message())); + + json_modules.push_back( + JSONModule{system_path, GetRelativePath(directory, path_to_copy_module), + JSONUINT64{load_addr}, module_sp->GetUUID().GetAsString()}); + } + return json_modules; +} + +/// Build the processes section of the trace bundle description object. Besides +/// returning the processes information, this method saves to disk all modules +/// and raw traces corresponding to the traced threads of the given process. +/// +/// \param[in] process +/// The process being traced. +/// +/// \param[in] directory +/// The directory where files will be saved when building the processes +/// section. +/// +/// \return +/// The processes section or \a llvm::Error in case of failures. +static llvm::Expected<JSONProcess> +BuildProcessSection(Process &process, const FileSpec &directory) { + Expected<std::vector<JSONThread>> json_threads = + BuildThreadsSection(process, directory); + if (!json_threads) + return json_threads.takeError(); + + Expected<std::vector<JSONModule>> json_modules = + BuildModulesSection(process, directory); + if (!json_modules) + return json_modules.takeError(); + + return JSONProcess{ + process.GetID(), + process.GetTarget().GetArchitecture().GetTriple().getTriple(), + json_threads.get(), json_modules.get()}; +} + +/// See BuildProcessSection() +static llvm::Expected<std::vector<JSONProcess>> +BuildProcessesSection(TraceIntelPT &trace_ipt, const FileSpec &directory) { + std::vector<JSONProcess> processes; + for (Process *process : trace_ipt.GetAllProcesses()) { + if (llvm::Expected<JSONProcess> json_process = + BuildProcessSection(*process, directory)) + processes.push_back(std::move(*json_process)); + else + return json_process.takeError(); + } + return processes; +} + +static llvm::Expected<JSONKernel> +BuildKernelSection(TraceIntelPT &trace_ipt, const FileSpec &directory) { + JSONKernel json_kernel; + std::vector<Process *> processes = trace_ipt.GetAllProcesses(); + Process *kernel_process = processes[0]; + + assert(processes.size() == 1 && "User processeses exist in kernel mode"); + assert(kernel_process->GetID() == kDefaultKernelProcessID && + "Kernel process not exist"); + + Expected<std::vector<JSONModule>> json_modules = + BuildModulesSection(*kernel_process, directory); + if (!json_modules) + return json_modules.takeError(); + + JSONModule kernel_image = json_modules.get()[0]; + return JSONKernel{kernel_image.load_address, kernel_image.system_path}; +} + +Expected<FileSpec> TraceIntelPTBundleSaver::SaveToDisk(TraceIntelPT &trace_ipt, + FileSpec directory, + bool compact) { + if (std::error_code ec = + sys::fs::create_directories(directory.GetPath().c_str())) + return llvm::errorCodeToError(ec); + + Expected<pt_cpu> cpu_info = trace_ipt.GetCPUInfo(); + if (!cpu_info) + return cpu_info.takeError(); + + FileSystem::Instance().Resolve(directory); + + Expected<std::optional<std::vector<JSONCpu>>> json_cpus = + BuildCpusSection(trace_ipt, directory, compact); + if (!json_cpus) + return json_cpus.takeError(); + + std::optional<std::vector<JSONProcess>> json_processes; + std::optional<JSONKernel> json_kernel; + + if (trace_ipt.GetTraceMode() == TraceIntelPT::TraceMode::KernelMode) { + Expected<std::optional<JSONKernel>> exp_json_kernel = + BuildKernelSection(trace_ipt, directory); + if (!exp_json_kernel) + return exp_json_kernel.takeError(); + else + json_kernel = *exp_json_kernel; + } else { + Expected<std::optional<std::vector<JSONProcess>>> exp_json_processes = + BuildProcessesSection(trace_ipt, directory); + if (!exp_json_processes) + return exp_json_processes.takeError(); + else + json_processes = *exp_json_processes; + } + + JSONTraceBundleDescription json_intel_pt_bundle_desc{ + "intel-pt", + *cpu_info, + json_processes, + *json_cpus, + trace_ipt.GetPerfZeroTscConversion(), + json_kernel}; + + return SaveTraceBundleDescription(toJSON(json_intel_pt_bundle_desc), + directory); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.h new file mode 100644 index 000000000000..f5a0301217c7 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTBundleSaver.h @@ -0,0 +1,48 @@ +//===-- TraceIntelPTBundleSaver.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_TRACEINTELPTBUNDLESAVER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTBUNDLESAVER_H + +#include "TraceIntelPT.h" +#include "TraceIntelPTJSONStructs.h" + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPTBundleSaver { +public: + /// Save the Intel PT trace of a live process to the specified directory, + /// which will be created if needed. This will also create a file + /// \a <directory>/trace.json with the description of the trace + /// bundle, along with others files which contain the actual trace data. + /// The trace.json file can be used later as input for the "trace load" + /// command to load the trace in LLDB. + /// + /// \param[in] trace_ipt + /// The Intel PT trace to be saved to disk. + /// + /// \param[in] directory + /// The directory where the trace bundle will be created. + /// + /// \param[in] compact + /// Filter out information irrelevant to the traced processes in the + /// context switch and intel pt traces when using per-cpu mode. This + /// effectively reduces the size of those traces. + /// + /// \return + /// A \a FileSpec pointing to the bundle description file, or an \a + /// llvm::Error otherwise. + llvm::Expected<FileSpec> SaveToDisk(TraceIntelPT &trace_ipt, + FileSpec directory, bool compact); +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTBUNDLESAVER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h new file mode 100644 index 000000000000..e80f512457cf --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTConstants.h @@ -0,0 +1,36 @@ +//===-- 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 "lldb/lldb-types.h" +#include <cstddef> +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +const size_t kDefaultIptTraceSize = 4 * 1024; // 4KB +const size_t kDefaultProcessBufferSizeLimit = 5 * 1024 * 1024; // 500MB +const bool kDefaultEnableTscValue = false; +const std::optional<size_t> kDefaultPsbPeriod; +const bool kDefaultPerCpuTracing = false; +const bool kDefaultDisableCgroupFiltering = false; + +// Physical address where the kernel is loaded in x86 architecture. Refer to +// https://github.com/torvalds/linux/blob/master/Documentation/x86/x86_64/mm.rst +// for the start address of kernel text section. +// The kernel entry point is 0x1000000 by default when KASLR is disabled. +const lldb::addr_t kDefaultKernelLoadAddress = 0xffffffff81000000; +const lldb::pid_t kDefaultKernelProcessID = 1; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_CONSTANTS_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp new file mode 100644 index 000000000000..52ca69420587 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.cpp @@ -0,0 +1,189 @@ +//===-- TraceIntelPTJSONStructs.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 "TraceIntelPTJSONStructs.h" +#include "llvm/Support/JSON.h" +#include <optional> +#include <string> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; +using namespace llvm::json; + +namespace lldb_private { +namespace trace_intel_pt { + +std::optional<std::vector<lldb::cpu_id_t>> +JSONTraceBundleDescription::GetCpuIds() { + if (!cpus) + return std::nullopt; + std::vector<lldb::cpu_id_t> cpu_ids; + for (const JSONCpu &cpu : *cpus) + cpu_ids.push_back(cpu.id); + return cpu_ids; +} + +json::Value toJSON(const JSONModule &module) { + json::Object json_module; + json_module["systemPath"] = module.system_path; + if (module.file) + json_module["file"] = *module.file; + json_module["loadAddress"] = toJSON(module.load_address, true); + if (module.uuid) + json_module["uuid"] = *module.uuid; + return std::move(json_module); +} + +bool fromJSON(const json::Value &value, JSONModule &module, Path path) { + ObjectMapper o(value, path); + return o && o.map("systemPath", module.system_path) && + o.map("file", module.file) && + o.map("loadAddress", module.load_address) && + o.map("uuid", module.uuid); +} + +json::Value toJSON(const JSONThread &thread) { + json::Object obj{{"tid", thread.tid}}; + if (thread.ipt_trace) + obj["iptTrace"] = *thread.ipt_trace; + return obj; +} + +bool fromJSON(const json::Value &value, JSONThread &thread, Path path) { + ObjectMapper o(value, path); + return o && o.map("tid", thread.tid) && o.map("iptTrace", thread.ipt_trace); +} + +json::Value toJSON(const JSONProcess &process) { + return Object{ + {"pid", process.pid}, + {"triple", process.triple}, + {"threads", process.threads}, + {"modules", process.modules}, + }; +} + +bool fromJSON(const json::Value &value, JSONProcess &process, Path path) { + ObjectMapper o(value, path); + return o && o.map("pid", process.pid) && o.map("triple", process.triple) && + o.map("threads", process.threads) && o.map("modules", process.modules); +} + +json::Value toJSON(const JSONCpu &cpu) { + return Object{ + {"id", cpu.id}, + {"iptTrace", cpu.ipt_trace}, + {"contextSwitchTrace", cpu.context_switch_trace}, + }; +} + +bool fromJSON(const json::Value &value, JSONCpu &cpu, Path path) { + ObjectMapper o(value, path); + uint64_t cpu_id; + if (!(o && o.map("id", cpu_id) && o.map("iptTrace", cpu.ipt_trace) && + o.map("contextSwitchTrace", cpu.context_switch_trace))) + return false; + cpu.id = cpu_id; + return true; +} + +json::Value toJSON(const pt_cpu &cpu_info) { + return Object{ + {"vendor", cpu_info.vendor == pcv_intel ? "GenuineIntel" : "Unknown"}, + {"family", cpu_info.family}, + {"model", cpu_info.model}, + {"stepping", cpu_info.stepping}, + }; +} + +bool fromJSON(const json::Value &value, pt_cpu &cpu_info, Path path) { + ObjectMapper o(value, path); + std::string vendor; + uint64_t family, model, stepping; + if (!(o && o.map("vendor", vendor) && o.map("family", family) && + o.map("model", model) && o.map("stepping", stepping))) + return false; + cpu_info.vendor = vendor == "GenuineIntel" ? pcv_intel : pcv_unknown; + cpu_info.family = family; + cpu_info.model = model; + cpu_info.stepping = stepping; + return true; +} + +json::Value toJSON(const JSONKernel &kernel) { + json::Object json_module; + if (kernel.load_address) + json_module["loadAddress"] = toJSON(*kernel.load_address, true); + json_module["file"] = kernel.file; + return std::move(json_module); +} + +bool fromJSON(const json::Value &value, JSONKernel &kernel, Path path) { + ObjectMapper o(value, path); + return o && o.map("loadAddress", kernel.load_address) && + o.map("file", kernel.file); +} + +json::Value toJSON(const JSONTraceBundleDescription &bundle_description) { + return Object{ + {"type", bundle_description.type}, + {"processes", bundle_description.processes}, + // We have to do this because the compiler fails at doing it + // automatically because pt_cpu is not in a namespace + {"cpuInfo", toJSON(bundle_description.cpu_info)}, + {"cpus", bundle_description.cpus}, + {"tscPerfZeroConversion", bundle_description.tsc_perf_zero_conversion}, + {"kernel", bundle_description.kernel}}; +} + +bool fromJSON(const json::Value &value, + JSONTraceBundleDescription &bundle_description, Path path) { + ObjectMapper o(value, path); + if (!(o && o.map("processes", bundle_description.processes) && + o.map("type", bundle_description.type) && + o.map("cpus", bundle_description.cpus) && + o.map("tscPerfZeroConversion", + bundle_description.tsc_perf_zero_conversion) && + o.map("kernel", bundle_description.kernel))) + return false; + if (bundle_description.cpus && !bundle_description.tsc_perf_zero_conversion) { + path.report( + "\"tscPerfZeroConversion\" is required when \"cpus\" is provided"); + return false; + } + // We have to do this because the compiler fails at doing it automatically + // because pt_cpu is not in a namespace + if (!fromJSON(*value.getAsObject()->get("cpuInfo"), + bundle_description.cpu_info, path.field("cpuInfo"))) + return false; + + // When kernel section is present, this is kernel-only tracing. Thus, throw an + // error if the "processes" section is non-empty or the "cpus" section is not + // present. + if (bundle_description.kernel) { + if (bundle_description.processes && + !bundle_description.processes->empty()) { + path.report("\"processes\" must be empty when \"kernel\" is provided"); + return false; + } + if (!bundle_description.cpus) { + path.report("\"cpus\" is required when \"kernel\" is provided"); + return false; + } + } else if (!bundle_description.processes) { + // Usermode tracing requires processes section. + path.report("\"processes\" is required when \"kernel\" is not provided"); + return false; + } + return true; +} + +} // namespace trace_intel_pt +} // namespace lldb_private diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h new file mode 100644 index 000000000000..93156aa4aa9e --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTJSONStructs.h @@ -0,0 +1,101 @@ +//===-- TraceIntelPTJSONStructs.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_TRACEINTELPTJSONSTRUCTS_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H + +#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/JSON.h" +#include <intel-pt.h> +#include <optional> +#include <vector> + +namespace lldb_private { +namespace trace_intel_pt { + +struct JSONModule { + std::string system_path; + std::optional<std::string> file; + JSONUINT64 load_address; + std::optional<std::string> uuid; +}; + +struct JSONThread { + uint64_t tid; + std::optional<std::string> ipt_trace; +}; + +struct JSONProcess { + uint64_t pid; + std::optional<std::string> triple; + std::vector<JSONThread> threads; + std::vector<JSONModule> modules; +}; + +struct JSONCpu { + lldb::cpu_id_t id; + std::string ipt_trace; + std::string context_switch_trace; +}; + +struct JSONKernel { + std::optional<JSONUINT64> load_address; + std::string file; +}; + +struct JSONTraceBundleDescription { + std::string type; + pt_cpu cpu_info; + std::optional<std::vector<JSONProcess>> processes; + std::optional<std::vector<JSONCpu>> cpus; + std::optional<LinuxPerfZeroTscConversion> tsc_perf_zero_conversion; + std::optional<JSONKernel> kernel; + + std::optional<std::vector<lldb::cpu_id_t>> GetCpuIds(); +}; + +llvm::json::Value toJSON(const JSONModule &module); + +llvm::json::Value toJSON(const JSONThread &thread); + +llvm::json::Value toJSON(const JSONProcess &process); + +llvm::json::Value toJSON(const JSONCpu &cpu); + +llvm::json::Value toJSON(const pt_cpu &cpu_info); + +llvm::json::Value toJSON(const JSONKernel &kernel); + +llvm::json::Value toJSON(const JSONTraceBundleDescription &bundle_description); + +bool fromJSON(const llvm::json::Value &value, JSONModule &module, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, JSONThread &thread, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, JSONProcess &process, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, JSONCpu &cpu, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, pt_cpu &cpu_info, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, JSONModule &kernel, + llvm::json::Path path); + +bool fromJSON(const llvm::json::Value &value, + JSONTraceBundleDescription &bundle_description, + llvm::json::Path path); +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTJSONSTRUCTS_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp new file mode 100644 index 000000000000..e2ca8b4d1db0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.cpp @@ -0,0 +1,238 @@ +//===-- TraceIntelPTMultiCpuDecoder.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 "TraceIntelPTMultiCpuDecoder.h" +#include "TraceIntelPT.h" +#include "llvm/Support/Error.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::trace_intel_pt; +using namespace llvm; + +TraceIntelPTMultiCpuDecoder::TraceIntelPTMultiCpuDecoder( + TraceIntelPTSP trace_sp) + : m_trace_wp(trace_sp) { + for (Process *proc : trace_sp->GetAllProcesses()) { + for (ThreadSP thread_sp : proc->GetThreadList().Threads()) { + m_tids.insert(thread_sp->GetID()); + } + } +} + +TraceIntelPTSP TraceIntelPTMultiCpuDecoder::GetTrace() { + return m_trace_wp.lock(); +} + +bool TraceIntelPTMultiCpuDecoder::TracesThread(lldb::tid_t tid) const { + return m_tids.count(tid); +} + +Expected<std::optional<uint64_t>> TraceIntelPTMultiCpuDecoder::FindLowestTSC() { + std::optional<uint64_t> lowest_tsc; + TraceIntelPTSP trace_sp = GetTrace(); + + Error err = GetTrace()->OnAllCpusBinaryDataRead( + IntelPTDataKinds::kIptTrace, + [&](const DenseMap<cpu_id_t, ArrayRef<uint8_t>> &buffers) -> Error { + for (auto &cpu_id_to_buffer : buffers) { + Expected<std::optional<uint64_t>> tsc = + FindLowestTSCInTrace(*trace_sp, cpu_id_to_buffer.second); + if (!tsc) + return tsc.takeError(); + if (*tsc && (!lowest_tsc || *lowest_tsc > **tsc)) + lowest_tsc = **tsc; + } + return Error::success(); + }); + if (err) + return std::move(err); + return lowest_tsc; +} + +Expected<DecodedThreadSP> TraceIntelPTMultiCpuDecoder::Decode(Thread &thread) { + if (Error err = CorrelateContextSwitchesAndIntelPtTraces()) + return std::move(err); + + TraceIntelPTSP trace_sp = GetTrace(); + + return trace_sp->GetThreadTimer(thread.GetID()) + .TimeTask("Decoding instructions", [&]() -> Expected<DecodedThreadSP> { + auto it = m_decoded_threads.find(thread.GetID()); + if (it != m_decoded_threads.end()) + return it->second; + + DecodedThreadSP decoded_thread_sp = std::make_shared<DecodedThread>( + thread.shared_from_this(), trace_sp->GetPerfZeroTscConversion()); + + Error err = trace_sp->OnAllCpusBinaryDataRead( + IntelPTDataKinds::kIptTrace, + [&](const DenseMap<cpu_id_t, ArrayRef<uint8_t>> &buffers) -> Error { + auto it = + m_continuous_executions_per_thread->find(thread.GetID()); + if (it != m_continuous_executions_per_thread->end()) + return DecodeSystemWideTraceForThread( + *decoded_thread_sp, *trace_sp, buffers, it->second); + + return Error::success(); + }); + if (err) + return std::move(err); + + m_decoded_threads.try_emplace(thread.GetID(), decoded_thread_sp); + return decoded_thread_sp; + }); +} + +static Expected<std::vector<PSBBlock>> GetPSBBlocksForCPU(TraceIntelPT &trace, + cpu_id_t cpu_id) { + std::vector<PSBBlock> psb_blocks; + Error err = trace.OnCpuBinaryDataRead( + cpu_id, IntelPTDataKinds::kIptTrace, + [&](ArrayRef<uint8_t> data) -> Error { + Expected<std::vector<PSBBlock>> split_trace = + SplitTraceIntoPSBBlock(trace, data, /*expect_tscs=*/true); + if (!split_trace) + return split_trace.takeError(); + + psb_blocks = std::move(*split_trace); + return Error::success(); + }); + if (err) + return std::move(err); + return psb_blocks; +} + +Expected<DenseMap<lldb::tid_t, std::vector<IntelPTThreadContinousExecution>>> +TraceIntelPTMultiCpuDecoder::DoCorrelateContextSwitchesAndIntelPtTraces() { + DenseMap<lldb::tid_t, std::vector<IntelPTThreadContinousExecution>> + continuous_executions_per_thread; + TraceIntelPTSP trace_sp = GetTrace(); + + std::optional<LinuxPerfZeroTscConversion> conv_opt = + trace_sp->GetPerfZeroTscConversion(); + if (!conv_opt) + return createStringError( + inconvertibleErrorCode(), + "TSC to nanoseconds conversion values were not found"); + + LinuxPerfZeroTscConversion tsc_conversion = *conv_opt; + + for (cpu_id_t cpu_id : trace_sp->GetTracedCpus()) { + Expected<std::vector<PSBBlock>> psb_blocks = + GetPSBBlocksForCPU(*trace_sp, cpu_id); + if (!psb_blocks) + return psb_blocks.takeError(); + + m_total_psb_blocks += psb_blocks->size(); + // We'll be iterating through the thread continuous executions and the intel + // pt subtraces sorted by time. + auto it = psb_blocks->begin(); + auto on_new_thread_execution = + [&](const ThreadContinuousExecution &thread_execution) { + IntelPTThreadContinousExecution execution(thread_execution); + + for (; it != psb_blocks->end() && + *it->tsc < thread_execution.GetEndTSC(); + it++) { + if (*it->tsc > thread_execution.GetStartTSC()) { + execution.psb_blocks.push_back(*it); + } else { + m_unattributed_psb_blocks++; + } + } + continuous_executions_per_thread[thread_execution.tid].push_back( + execution); + }; + Error err = trace_sp->OnCpuBinaryDataRead( + cpu_id, IntelPTDataKinds::kPerfContextSwitchTrace, + [&](ArrayRef<uint8_t> data) -> Error { + Expected<std::vector<ThreadContinuousExecution>> executions = + DecodePerfContextSwitchTrace(data, cpu_id, tsc_conversion); + if (!executions) + return executions.takeError(); + for (const ThreadContinuousExecution &exec : *executions) + on_new_thread_execution(exec); + return Error::success(); + }); + if (err) + return std::move(err); + + m_unattributed_psb_blocks += psb_blocks->end() - it; + } + // We now sort the executions of each thread to have them ready for + // instruction decoding + for (auto &tid_executions : continuous_executions_per_thread) + std::sort(tid_executions.second.begin(), tid_executions.second.end()); + + return continuous_executions_per_thread; +} + +Error TraceIntelPTMultiCpuDecoder::CorrelateContextSwitchesAndIntelPtTraces() { + if (m_setup_error) + return createStringError(inconvertibleErrorCode(), m_setup_error->c_str()); + + if (m_continuous_executions_per_thread) + return Error::success(); + + Error err = GetTrace()->GetGlobalTimer().TimeTask( + "Context switch and Intel PT traces correlation", [&]() -> Error { + if (auto correlation = DoCorrelateContextSwitchesAndIntelPtTraces()) { + m_continuous_executions_per_thread.emplace(std::move(*correlation)); + return Error::success(); + } else { + return correlation.takeError(); + } + }); + if (err) { + m_setup_error = toString(std::move(err)); + return createStringError(inconvertibleErrorCode(), m_setup_error->c_str()); + } + return Error::success(); +} + +size_t TraceIntelPTMultiCpuDecoder::GetNumContinuousExecutionsForThread( + lldb::tid_t tid) const { + if (!m_continuous_executions_per_thread) + return 0; + auto it = m_continuous_executions_per_thread->find(tid); + if (it == m_continuous_executions_per_thread->end()) + return 0; + return it->second.size(); +} + +size_t TraceIntelPTMultiCpuDecoder::GetTotalContinuousExecutionsCount() const { + if (!m_continuous_executions_per_thread) + return 0; + size_t count = 0; + for (const auto &kv : *m_continuous_executions_per_thread) + count += kv.second.size(); + return count; +} + +size_t +TraceIntelPTMultiCpuDecoder::GePSBBlocksCountForThread(lldb::tid_t tid) const { + if (!m_continuous_executions_per_thread) + return 0; + size_t count = 0; + auto it = m_continuous_executions_per_thread->find(tid); + if (it == m_continuous_executions_per_thread->end()) + return 0; + for (const IntelPTThreadContinousExecution &execution : it->second) + count += execution.psb_blocks.size(); + return count; +} + +size_t TraceIntelPTMultiCpuDecoder::GetUnattributedPSBBlocksCount() const { + return m_unattributed_psb_blocks; +} + +size_t TraceIntelPTMultiCpuDecoder::GetTotalPSBBlocksCount() const { + return m_total_psb_blocks; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h new file mode 100644 index 000000000000..1633cd1e8088 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTMultiCpuDecoder.h @@ -0,0 +1,110 @@ +//===-- TraceIntelPTMultiCpuDecoder.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_TRACEINTELPTMULTICPUDECODER_H +#define LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTMULTICPUDECODER_H + +#include "LibiptDecoder.h" +#include "PerfContextSwitchDecoder.h" +#include "ThreadDecoder.h" +#include "forward-declarations.h" +#include <optional> + +namespace lldb_private { +namespace trace_intel_pt { + +/// Class used to decode a multi-cpu Intel PT trace. It assumes that each +/// thread could have potentially been executed on different cpu cores. It uses +/// a context switch trace per CPU with timestamps to identify which thread owns +/// each Intel PT decoded instruction and in which order. It also assumes that +/// the Intel PT data and context switches might have gaps in their traces due +/// to contention or race conditions. Finally, it assumes that a tid is not +/// repeated twice for two different threads because of the shortness of the +/// intel pt trace. +/// +/// This object should be recreated after every stop in the case of live +/// processes. +class TraceIntelPTMultiCpuDecoder { +public: + /// \param[in] TraceIntelPT + /// The trace object to be decoded + TraceIntelPTMultiCpuDecoder(TraceIntelPTSP trace_sp); + + /// \return + /// A \a DecodedThread for the \p thread by decoding its instructions on all + /// CPUs, sorted by TSCs. An \a llvm::Error is returned if the decoder + /// couldn't be properly set up. + llvm::Expected<DecodedThreadSP> Decode(Thread &thread); + + /// \return + /// \b true if the given \p tid is managed by this decoder, regardless of + /// whether there's tracing data associated to it or not. + bool TracesThread(lldb::tid_t tid) const; + + /// \return + /// The number of continuous executions found for the given \p tid. + size_t GetNumContinuousExecutionsForThread(lldb::tid_t tid) const; + + /// \return + /// The number of PSB blocks for a given thread in all cores. + size_t GePSBBlocksCountForThread(lldb::tid_t tid) const; + + /// \return + /// The total number of continuous executions found across CPUs. + size_t GetTotalContinuousExecutionsCount() const; + + /// \return + /// The number of psb blocks in all cores that couldn't be matched with a + /// thread execution coming from context switch traces. + size_t GetUnattributedPSBBlocksCount() const; + + /// \return + /// The total number of PSB blocks in all cores. + size_t GetTotalPSBBlocksCount() const; + + /// \return + /// The lowest TSC value in this trace if available, \a std::nullopt if + /// the trace is empty or the trace contains no timing information, or an + /// \a llvm::Error if it was not possible to set up the decoder. + llvm::Expected<std::optional<uint64_t>> FindLowestTSC(); + +private: + /// Traverse the context switch traces and the basic intel pt continuous + /// subtraces and produce a list of continuous executions for each process and + /// thread. + /// + /// See \a DoCorrelateContextSwitchesAndIntelPtTraces. + /// + /// Any errors are stored in \a m_setup_error. + llvm::Error CorrelateContextSwitchesAndIntelPtTraces(); + + /// Produce a mapping from thread ids to the list of continuos executions with + /// their associated intel pt subtraces. + llvm::Expected< + llvm::DenseMap<lldb::tid_t, std::vector<IntelPTThreadContinousExecution>>> + DoCorrelateContextSwitchesAndIntelPtTraces(); + + TraceIntelPTSP GetTrace(); + + std::weak_ptr<TraceIntelPT> m_trace_wp; + std::set<lldb::tid_t> m_tids; + std::optional< + llvm::DenseMap<lldb::tid_t, std::vector<IntelPTThreadContinousExecution>>> + m_continuous_executions_per_thread; + llvm::DenseMap<lldb::tid_t, DecodedThreadSP> m_decoded_threads; + /// This variable will not be std::nullopt if a severe error happened during + /// the setup of the decoder and we don't want decoding to be reattempted. + std::optional<std::string> m_setup_error; + uint64_t m_unattributed_psb_blocks = 0; + uint64_t m_total_psb_blocks = 0; +}; + +} // namespace trace_intel_pt +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_TRACEINTELPTMULTICPUDECODER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td new file mode 100644 index 000000000000..4fb79448a3a0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTOptions.td @@ -0,0 +1,132 @@ +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< + "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. It's possible to " + "specify size using multiples of unit bytes, e.g., 4KB, 1MB, 1MiB, " + "where 1K is 1024 bytes and 1M is 1048576 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_buffer_size + : Option<"buffer-size", "s">, + Group<1>, + Arg<"Value">, + Desc< + "Size in bytes used by each individual per-thread or per-cpu trace " + "buffer. It must be a power of 2 greater than or equal to 4096 " + "(2^12) " + "bytes. It's possible to specify a unit for these bytes, like 4KB, " + "16KiB or 1MB. Lower case units are allowed for convenience.">; + def process_trace_start_intel_pt_per_cpu_tracing + : Option<"per-cpu-tracing", "c">, + Group<1>, + Desc< + "Instead of having an individual trace buffer per thread, which " + "uses " + "a number trace buffers proportional to the number of running " + "threads, this option triggers the collection on a per cpu core " + "basis. This effectively traces the entire activity on all cpus " + "using a limited amount of trace buffers regardless of the number " + "of " + "threads. This might cause data loss for less frequent threads. " + "This " + "option forces the capture of TSC timestamps (see --tsc). Also, " + "this " + "option can't be used simulatenously with any other trace sessions " + "because of its system-wide nature.">; + 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 and cpu 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. It's possible to specify a unit for " + "these bytes, like 4KB, 16KiB or 1MB. Lower case units are allowed " + "for convenience.">; + 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<"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.">; + def process_trace_start_intel_pt_disable_cgroup_filtering + : Option<"disable-cgroup-filtering", "d">, + Desc<"Disable the automatic cgroup filtering that is applied if " + "--per-cpu " + "is provided. Cgroup filtering allows collecting intel pt data " + "exclusively of processes of the same cgroup as the target.">; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTProperties.td b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTProperties.td new file mode 100644 index 000000000000..d338df1df5cb --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTProperties.td @@ -0,0 +1,24 @@ +include "../../../../include/lldb/Core/PropertiesBase.td" + +let Definition = "traceintelpt" in { + def InfiniteDecodingLoopVerificationThreshold: + Property<"infinite-decoding-loop-verification-threshold", "UInt64">, + Global, + DefaultUnsignedValue<10000>, + Desc<"Specify how many instructions following an individual Intel PT " + "packet must have been decoded before triggering the verification of " + "infinite decoding loops. If no decoding loop has been found after this " + "threshold T, another attempt will be done after 2T instructions, then " + "4T, 8T and so on, which guarantees a total linear time spent checking " + "this anomaly. If a loop is found, then decoding of the corresponding " + "PSB block is stopped. An error is hence emitted in the trace and " + "decoding is resumed in the next PSB block.">; + def ExtremelyLargeDecodingThreshold: + Property<"extremely-large-decoding-threshold", "UInt64">, + Global, + DefaultUnsignedValue<500000>, + Desc<"Specify how many instructions following an individual Intel PT " + "packet must have been decoded before stopping the decoding of the " + "corresponding PSB block. An error is hence emitted in the trace and " + "decoding is resumed in the next PSB block.">; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h new file mode 100644 index 000000000000..a05bf224bf07 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/forward-declarations.h @@ -0,0 +1,24 @@ +//===-- 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 + +#include <memory> + +namespace lldb_private { +namespace trace_intel_pt { + +class TraceIntelPT; +class ThreadDecoder; + +using TraceIntelPTSP = std::shared_ptr<TraceIntelPT>; + +} // namespace trace_intel_pt +} // namespace lldb_private +#endif // LLDB_SOURCE_PLUGINS_TRACE_INTEL_PT_FORWARD_DECLARATIONS_H |