#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;
}
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)>;
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);
config.begin = const_cast<uint8_t *>(buffer.data());
config.end = const_cast<uint8_t *>(buffer.data() + buffer.size());
return config;
}
static int ReadProcessMemory(uint8_t *buffer, size_t size,
const pt_asid * , 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;
}
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();
}
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;
}
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 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;
}
Error DetectAnomaly() {
RefreshPacketOffset();
uint64_t insn_added_since_last_packet_offset =
m_decoded_thread.GetTotalInstructionCount() -
m_insn_count_at_last_packet_offset;
if (insn_added_since_last_packet_offset >=
m_extremely_large_decoding_threshold) {
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() {
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;
};
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);
};
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;
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;
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;
}
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 PSBBlockDecoder {
public:
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) {}
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.");
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:
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)),
true);
return false;
}
return true;
}
void DecodeInstructionsAndEvents(int status) {
pt_insn insn;
while (true) {
status = ProcessPTEvents(status);
if (IsLibiptError(status))
return;
else if (IsEndOfStream(status))
break;
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;
}
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;
}
}
}
}
Error ProcessPTEventTSC(DecodedThread::TSC tsc) {
if (m_tsc_upper_bound && tsc >= *m_tsc_upper_bound) {
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();
}
}
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:
m_decoded_thread.AppendEvent(lldb::eTraceEventDisabledHW);
break;
case ptev_async_disabled:
m_decoded_thread.AppendEvent(lldb::eTraceEventDisabledSW);
break;
case ptev_overflow:
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, 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;
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));
}
decoded_thread.NotifyCPU(execution.thread_execution.cpu_id);
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());
}
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 (has_seen_psbs) {
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 {
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) {
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;
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)) {
continue;
}
if (expect_tscs && !tsc)
return createStringError(inconvertibleErrorCode(),
"Found a PSB without TSC.");
executions.push_back({
psb_offset,
tsc,
0,
ip,
});
}
if (!executions.empty()) {
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;
}