diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.cpp')
-rw-r--r-- | contrib/llvm-project/lldb/source/Plugins/Trace/intel-pt/LibiptDecoder.cpp | 783 |
1 files changed, 783 insertions, 0 deletions
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; +} |