[trace][intelpt] Support system-wide tracing [3] - Refactor IntelPTThreadTrace

I'm refactoring IntelPTThreadTrace into IntelPTSingleBufferTrace so that it can
both single threads or single cores. In this diff I'm basically renaming the
class, moving it to its own file, and removing all the pieces that are not used
along with some basic cleanup.

Differential Revision: https://reviews.llvm.org/D124648
This commit is contained in:
Walter Erquinigo 2022-04-28 17:11:57 -07:00
parent b8d1776fc5
commit 7b73de9ec2
16 changed files with 630 additions and 680 deletions

View File

@ -495,7 +495,7 @@ read packet: OK/E<error code>;AAAAAAAAA
// INTEL PT // INTEL PT
// //
// Binary data kinds: // Binary data kinds:
// - threadTraceBuffer: trace buffer for a thread. // - traceBuffer: trace buffer for a thread or a core.
// - procfsCpuInfo: contents of the /proc/cpuinfo file. // - procfsCpuInfo: contents of the /proc/cpuinfo file.
// //
// Counter info kinds: // Counter info kinds:
@ -550,7 +550,7 @@ read packet: {...object}/E<error code>;AAAAAAAAA
// INTEL PT // INTEL PT
// //
// Binary data kinds: // Binary data kinds:
// - threadTraceBuffer: trace buffer for a thread. // - traceBuffer: trace buffer for a thread or a core.
// - procfsCpuInfo: contents of the /proc/cpuinfo file. // - procfsCpuInfo: contents of the /proc/cpuinfo file.
//---------------------------------------------------------------------- //----------------------------------------------------------------------

View File

@ -21,7 +21,7 @@ namespace lldb_private {
// List of data kinds used by jLLDBGetState and jLLDBGetBinaryData. // List of data kinds used by jLLDBGetState and jLLDBGetBinaryData.
struct IntelPTDataKinds { struct IntelPTDataKinds {
static const char *kProcFsCpuInfo; static const char *kProcFsCpuInfo;
static const char *kThreadTraceBuffer; static const char *kTraceBuffer;
}; };
/// jLLDBTraceStart gdb-remote packet /// jLLDBTraceStart gdb-remote packet

View File

@ -1,5 +1,6 @@
add_lldb_library(lldbPluginProcessLinux add_lldb_library(lldbPluginProcessLinux
IntelPTCollector.cpp IntelPTCollector.cpp
IntelPTSingleBufferTrace.cpp
NativeProcessLinux.cpp NativeProcessLinux.cpp
NativeRegisterContextLinux.cpp NativeRegisterContextLinux.cpp
NativeRegisterContextLinux_arm.cpp NativeRegisterContextLinux_arm.cpp

View File

@ -32,394 +32,6 @@ using namespace lldb_private;
using namespace process_linux; using namespace process_linux;
using namespace llvm; using namespace llvm;
const char *kOSEventIntelPTTypeFile =
"/sys/bus/event_source/devices/intel_pt/type";
const char *kPSBPeriodCapFile =
"/sys/bus/event_source/devices/intel_pt/caps/psb_cyc";
const char *kPSBPeriodValidValuesFile =
"/sys/bus/event_source/devices/intel_pt/caps/psb_periods";
const char *kTSCBitOffsetFile =
"/sys/bus/event_source/devices/intel_pt/format/tsc";
const char *kPSBPeriodBitOffsetFile =
"/sys/bus/event_source/devices/intel_pt/format/psb_period";
enum IntelPTConfigFileType {
Hex = 0,
// 0 or 1
ZeroOne,
Decimal,
// a bit index file always starts with the prefix config: following by an int,
// which represents the offset of the perf_event_attr.config value where to
// store a given configuration.
BitOffset
};
static Expected<uint32_t> ReadIntelPTConfigFile(const char *file,
IntelPTConfigFileType type) {
ErrorOr<std::unique_ptr<MemoryBuffer>> stream =
MemoryBuffer::getFileAsStream(file);
if (!stream)
return createStringError(inconvertibleErrorCode(),
"Can't open the file '%s'", file);
uint32_t value = 0;
StringRef text_buffer = stream.get()->getBuffer();
if (type == BitOffset) {
const char *prefix = "config:";
if (!text_buffer.startswith(prefix))
return createStringError(inconvertibleErrorCode(),
"The file '%s' contents doesn't start with '%s'",
file, prefix);
text_buffer = text_buffer.substr(strlen(prefix));
}
auto getRadix = [&]() {
switch (type) {
case Hex:
return 16;
case ZeroOne:
case Decimal:
case BitOffset:
return 10;
}
llvm_unreachable("Fully covered switch above!");
};
auto createError = [&](const char *expected_value_message) {
return createStringError(
inconvertibleErrorCode(),
"The file '%s' has an invalid value. It should be %s.", file,
expected_value_message);
};
if (text_buffer.trim().consumeInteger(getRadix(), value) ||
(type == ZeroOne && value != 0 && value != 1)) {
switch (type) {
case Hex:
return createError("an unsigned hexadecimal int");
case ZeroOne:
return createError("0 or 1");
case Decimal:
case BitOffset:
return createError("an unsigned decimal int");
}
}
return value;
}
/// Return the Linux perf event type for Intel PT.
static Expected<uint32_t> GetOSEventType() {
return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile,
IntelPTConfigFileType::Decimal);
}
static Error CheckPsbPeriod(size_t psb_period) {
Expected<uint32_t> cap =
ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne);
if (!cap)
return cap.takeError();
if (*cap == 0)
return createStringError(inconvertibleErrorCode(),
"psb_period is unsupported in the system.");
Expected<uint32_t> valid_values = ReadIntelPTConfigFile(
kPSBPeriodValidValuesFile, IntelPTConfigFileType::Hex);
if (!valid_values)
return valid_values.takeError();
if (valid_values.get() & (1 << psb_period))
return Error::success();
std::ostringstream error;
// 0 is always a valid value
error << "Invalid psb_period. Valid values are: 0";
uint32_t mask = valid_values.get();
while (mask) {
int index = __builtin_ctz(mask);
if (index > 0)
error << ", " << index;
// clear the lowest bit
mask &= mask - 1;
}
error << ".";
return createStringError(inconvertibleErrorCode(), error.str().c_str());
}
size_t IntelPTThreadTrace::GetTraceBufferSize() const {
#ifndef PERF_ATTR_SIZE_VER5
llvm_unreachable("Intel PT Linux perf event not supported");
#else
return m_perf_event.GetAuxBuffer().size();
#endif
}
static Expected<uint64_t>
GeneratePerfEventConfigValue(bool enable_tsc, Optional<size_t> psb_period) {
uint64_t config = 0;
// tsc is always supported
if (enable_tsc) {
if (Expected<uint32_t> offset = ReadIntelPTConfigFile(
kTSCBitOffsetFile, IntelPTConfigFileType::BitOffset))
config |= 1 << *offset;
else
return offset.takeError();
}
if (psb_period) {
if (Error error = CheckPsbPeriod(*psb_period))
return std::move(error);
if (Expected<uint32_t> offset = ReadIntelPTConfigFile(
kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset))
config |= *psb_period << *offset;
else
return offset.takeError();
}
return config;
}
llvm::Expected<perf_event_attr>
IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(
bool enable_tsc, Optional<size_t> psb_period) {
perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.exclude_kernel = 1;
attr.sample_type = PERF_SAMPLE_TIME;
attr.sample_id_all = 1;
attr.exclude_hv = 1;
attr.exclude_idle = 1;
attr.mmap = 1;
if (Expected<uint64_t> config_value =
GeneratePerfEventConfigValue(enable_tsc, psb_period)) {
attr.config = *config_value;
} else {
return config_value.takeError();
}
if (Expected<uint32_t> intel_pt_type = GetOSEventType()) {
attr.type = *intel_pt_type;
} else {
return intel_pt_type.takeError();
}
return attr;
}
llvm::Expected<IntelPTThreadTraceUP>
IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size,
bool enable_tsc, Optional<size_t> psb_period) {
#ifndef PERF_ATTR_SIZE_VER5
llvm_unreachable("Intel PT Linux perf event not supported");
#else
Log *log = GetLog(POSIXLog::Ptrace);
LLDB_LOG(log, "called thread id {0}", tid);
if (__builtin_popcount(buffer_size) != 1 || buffer_size < 4096) {
return createStringError(
inconvertibleErrorCode(),
"The trace buffer size must be a power of 2 greater than or equal to "
"4096 (2^12) bytes. It was %" PRIu64 ".",
buffer_size);
}
uint64_t page_size = getpagesize();
uint64_t buffer_numpages = static_cast<uint64_t>(
llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size));
Expected<perf_event_attr> attr =
IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc,
psb_period);
if (!attr)
return attr.takeError();
LLDB_LOG(log, "buffer size {0} ", buffer_size);
if (Expected<PerfEvent> perf_event = PerfEvent::Init(*attr, tid)) {
if (Error mmap_err = perf_event->MmapMetadataAndBuffers(buffer_numpages,
buffer_numpages)) {
return std::move(mmap_err);
}
return IntelPTThreadTraceUP(
new IntelPTThreadTrace(std::move(*perf_event), tid));
} else {
return perf_event.takeError();
}
#endif
}
Expected<std::vector<uint8_t>>
IntelPTThreadTrace::GetIntelPTBuffer(size_t offset, size_t size) const {
std::vector<uint8_t> data(size, 0);
MutableArrayRef<uint8_t> buffer_ref(data);
Status error = ReadPerfTraceAux(buffer_ref, 0);
if (error.Fail())
return error.ToError();
return data;
}
Status
IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset) const {
#ifndef PERF_ATTR_SIZE_VER5
llvm_unreachable("perf event not supported");
#else
auto fd = m_perf_event.GetFd();
perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
// Disable the perf event to force a flush out of the CPU's internal buffer.
// Besides, we can guarantee that the CPU won't override any data as we are
// reading the buffer.
//
// The Intel documentation says:
//
// Packets are first buffered internally and then written out asynchronously.
// To collect packet output for postprocessing, a collector needs first to
// ensure that all packet data has been flushed from internal buffers.
// Software can ensure this by stopping packet generation by clearing
// IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in
// Section 35.2.7.2).
//
// This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned
// in the man page of perf_event_open.
ioctl(fd, PERF_EVENT_IOC_DISABLE);
Log *log = GetLog(POSIXLog::Ptrace);
Status error;
uint64_t head = mmap_metadata.aux_head;
LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head);
/**
* When configured as ring buffer, the aux buffer keeps wrapping around
* the buffer and its not possible to detect how many times the buffer
* wrapped. Initially the buffer is filled with zeros,as shown below
* so in order to get complete buffer we first copy firstpartsize, followed
* by any left over part from beginning to aux_head
*
* aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size
* aux_head->||<- firstpartsize ->|
*
* */
ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(),
static_cast<size_t>(head), offset);
LLDB_LOG(log, "ReadCyclic Buffer Done");
// Reenable tracing now we have read the buffer
ioctl(fd, PERF_EVENT_IOC_ENABLE);
return error;
#endif
}
Status
IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset) const {
#ifndef PERF_ATTR_SIZE_VER5
llvm_unreachable("perf event not supported");
#else
Log *log = GetLog(POSIXLog::Ptrace);
uint64_t bytes_remaining = buffer.size();
Status error;
perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
uint64_t head = mmap_metadata.data_head;
/*
* The data buffer and aux buffer have different implementations
* with respect to their definition of head pointer. In the case
* of Aux data buffer the head always wraps around the aux buffer
* and we don't need to care about it, whereas the data_head keeps
* increasing and needs to be wrapped by modulus operator
*/
LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining);
auto data_buffer = m_perf_event.GetDataBuffer();
if (head > data_buffer.size()) {
head = head % data_buffer.size();
LLDB_LOG(log, "Data size -{0} Head - {1}", mmap_metadata.data_size, head);
ReadCyclicBuffer(buffer, data_buffer, static_cast<size_t>(head), offset);
bytes_remaining -= buffer.size();
} else {
LLDB_LOG(log, "Head - {0}", head);
if (offset >= head) {
LLDB_LOG(log, "Invalid Offset ");
error.SetErrorString("invalid offset");
buffer = buffer.slice(buffer.size());
return error;
}
auto data = data_buffer.slice(offset, (head - offset));
auto remaining = std::copy(data.begin(), data.end(), buffer.begin());
bytes_remaining -= (remaining - buffer.begin());
}
buffer = buffer.drop_back(bytes_remaining);
return error;
#endif
}
void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
llvm::ArrayRef<uint8_t> src,
size_t src_cyc_index, size_t offset) {
Log *log = GetLog(POSIXLog::Ptrace);
if (dst.empty() || src.empty()) {
dst = dst.drop_back(dst.size());
return;
}
if (dst.data() == nullptr || src.data() == nullptr) {
dst = dst.drop_back(dst.size());
return;
}
if (src_cyc_index > src.size()) {
dst = dst.drop_back(dst.size());
return;
}
if (offset >= src.size()) {
LLDB_LOG(log, "Too Big offset ");
dst = dst.drop_back(dst.size());
return;
}
llvm::SmallVector<ArrayRef<uint8_t>, 2> parts = {
src.slice(src_cyc_index), src.take_front(src_cyc_index)};
if (offset > parts[0].size()) {
parts[1] = parts[1].slice(offset - parts[0].size());
parts[0] = parts[0].drop_back(parts[0].size());
} else if (offset == parts[0].size()) {
parts[0] = parts[0].drop_back(parts[0].size());
} else {
parts[0] = parts[0].slice(offset);
}
auto next = dst.begin();
auto bytes_left = dst.size();
for (auto part : parts) {
size_t chunk_size = std::min(part.size(), bytes_left);
next = std::copy_n(part.begin(), chunk_size, next);
bytes_left -= chunk_size;
}
dst = dst.drop_back(bytes_left);
}
TraceThreadState IntelPTThreadTrace::GetState() const {
return {static_cast<int64_t>(m_tid),
{TraceBinaryData{IntelPTDataKinds::kThreadTraceBuffer,
static_cast<int64_t>(GetTraceBufferSize())}}};
}
/// IntelPTThreadTraceCollection /// IntelPTThreadTraceCollection
bool IntelPTThreadTraceCollection::TracesThread(lldb::tid_t tid) const { bool IntelPTThreadTraceCollection::TracesThread(lldb::tid_t tid) const {
@ -442,9 +54,8 @@ Error IntelPTThreadTraceCollection::TraceStart(
return createStringError(inconvertibleErrorCode(), return createStringError(inconvertibleErrorCode(),
"Thread %" PRIu64 " already traced", tid); "Thread %" PRIu64 " already traced", tid);
Expected<IntelPTThreadTraceUP> trace_up = IntelPTThreadTrace::Create( Expected<IntelPTSingleBufferTraceUP> trace_up =
m_pid, tid, request.trace_buffer_size, request.enable_tsc, IntelPTSingleBufferTrace::Start(request, tid);
request.psb_period.map([](int64_t period) { return (size_t)period; }));
if (!trace_up) if (!trace_up)
return trace_up.takeError(); return trace_up.takeError();
@ -461,11 +72,14 @@ std::vector<TraceThreadState>
IntelPTThreadTraceCollection::GetThreadStates() const { IntelPTThreadTraceCollection::GetThreadStates() const {
std::vector<TraceThreadState> states; std::vector<TraceThreadState> states;
for (const auto &it : m_thread_traces) for (const auto &it : m_thread_traces)
states.push_back(it.second->GetState()); states.push_back({static_cast<int64_t>(it.first),
{TraceBinaryData{IntelPTDataKinds::kTraceBuffer,
static_cast<int64_t>(
it.second->GetTraceBufferSize())}}});
return states; return states;
} }
Expected<const IntelPTThreadTrace &> Expected<const IntelPTSingleBufferTrace &>
IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const { IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const {
auto it = m_thread_traces.find(tid); auto it = m_thread_traces.find(tid);
if (it == m_thread_traces.end()) if (it == m_thread_traces.end())
@ -510,8 +124,7 @@ IntelPTProcessTrace::GetThreadTraces() const {
/// IntelPTCollector /// IntelPTCollector
IntelPTCollector::IntelPTCollector(lldb::pid_t pid) IntelPTCollector::IntelPTCollector() {
: m_pid(pid), m_thread_traces(pid) {
if (Expected<LinuxPerfZeroTscConversion> tsc_conversion = if (Expected<LinuxPerfZeroTscConversion> tsc_conversion =
LoadPerfTscConversionParameters()) LoadPerfTscConversionParameters())
m_tsc_conversion = m_tsc_conversion =
@ -553,7 +166,7 @@ Error IntelPTCollector::TraceStart(
return createStringError(inconvertibleErrorCode(), return createStringError(inconvertibleErrorCode(),
"Per-core tracing is not supported."); "Per-core tracing is not supported.");
} }
m_process_trace = IntelPTProcessTrace(m_pid, request); m_process_trace = IntelPTProcessTrace(request);
Error error = Error::success(); Error error = Error::success();
for (lldb::tid_t tid : process_threads) for (lldb::tid_t tid : process_threads)
@ -604,7 +217,7 @@ Expected<json::Value> IntelPTCollector::GetState() const {
return toJSON(state); return toJSON(state);
} }
Expected<const IntelPTThreadTrace &> Expected<const IntelPTSingleBufferTrace &>
IntelPTCollector::GetTracedThread(lldb::tid_t tid) const { IntelPTCollector::GetTracedThread(lldb::tid_t tid) const {
if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid))
return m_process_trace->GetThreadTraces().GetTracedThread(tid); return m_process_trace->GetThreadTraces().GetTracedThread(tid);
@ -613,10 +226,10 @@ IntelPTCollector::GetTracedThread(lldb::tid_t tid) const {
Expected<std::vector<uint8_t>> Expected<std::vector<uint8_t>>
IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const { IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const {
if (request.kind == IntelPTDataKinds::kThreadTraceBuffer) { if (request.kind == IntelPTDataKinds::kTraceBuffer) {
if (Expected<const IntelPTThreadTrace &> trace = if (Expected<const IntelPTSingleBufferTrace &> trace =
GetTracedThread(*request.tid)) GetTracedThread(*request.tid))
return trace->GetIntelPTBuffer(request.offset, request.size); return trace->GetTraceBuffer(request.offset, request.size);
else else
return trace.takeError(); return trace.takeError();
} else if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) { } else if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) {
@ -630,12 +243,12 @@ IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const
void IntelPTCollector::ClearProcessTracing() { m_process_trace = None; } void IntelPTCollector::ClearProcessTracing() { m_process_trace = None; }
bool IntelPTCollector::IsSupported() { bool IntelPTCollector::IsSupported() {
Expected<uint32_t> intel_pt_type = GetOSEventType(); if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType()) {
if (!intel_pt_type) { return true;
} else {
llvm::consumeError(intel_pt_type.takeError()); llvm::consumeError(intel_pt_type.takeError());
return false; return false;
} }
return true;
} }
bool IntelPTCollector::IsProcessTracingEnabled() const { bool IntelPTCollector::IsProcessTracingEnabled() const {

View File

@ -11,6 +11,8 @@
#include "Perf.h" #include "Perf.h"
#include "IntelPTSingleBufferTrace.h"
#include "lldb/Utility/Status.h" #include "lldb/Utility/Status.h"
#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h"
#include "lldb/lldb-types.h" #include "lldb/lldb-types.h"
@ -23,120 +25,10 @@ namespace lldb_private {
namespace process_linux { namespace process_linux {
/// This class keeps track of one tracing instance of
/// Intel(R) Processor Trace on Linux OS at thread level.
///
/// The kernel interface for us is the perf_event_open.
class IntelPTThreadTrace;
typedef std::unique_ptr<IntelPTThreadTrace> IntelPTThreadTraceUP;
class IntelPTThreadTrace {
public:
/// Create a new \a IntelPTThreadTrace and start tracing the thread.
///
/// \param[in] pid
/// The pid of the process whose thread will be traced.
///
/// \param[in] tid
/// The tid of the thread to be traced.
///
/// \param[in] buffer_size
/// Size of the thread buffer in bytes.
///
/// \param[in] enable_tsc
/// Whether to use enable TSC timestamps or not.
/// More information in TraceIntelPT::GetStartConfigurationHelp().
///
/// \param[in] psb_period
/// This value defines the period in which PSB packets will be generated.
/// More information in TraceIntelPT::GetStartConfigurationHelp().
///
/// \return
/// A \a IntelPTThreadTrace instance if tracing was successful, or
/// an \a llvm::Error otherwise.
static llvm::Expected<IntelPTThreadTraceUP>
Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, bool enable_tsc,
llvm::Optional<size_t> psb_period);
/// Create a \a perf_event_attr configured for
/// an IntelPT event.
///
/// \return
/// A \a perf_event_attr if successful,
/// or an \a llvm::Error otherwise.
static llvm::Expected<perf_event_attr>
CreateIntelPTPerfEventConfiguration(bool enable_tsc,
llvm::Optional<size_t> psb_period);
/// Read the trace buffer of the currently traced thread.
///
/// \param[in] offset
/// Offset of the data to read.
///
/// \param[in] size
/// Number of bytes to read.
///
/// \return
/// A vector with the requested binary data. The vector will have the
/// size of the requested \a size. Non-available positions will be
/// filled with zeroes.
llvm::Expected<std::vector<uint8_t>> GetIntelPTBuffer(size_t offset,
size_t size) const;
Status ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset = 0) const;
Status ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer,
size_t offset = 0) const;
/// Get the size in bytes of the aux section of the thread or process traced
/// by this object.
size_t GetTraceBufferSize() const;
/// Read data from a cyclic buffer
///
/// \param[in] [out] buf
/// Destination buffer, the buffer will be truncated to written size.
///
/// \param[in] src
/// Source buffer which must be a cyclic buffer.
///
/// \param[in] src_cyc_index
/// The index pointer (start of the valid data in the cyclic
/// buffer).
///
/// \param[in] offset
/// The offset to begin reading the data in the cyclic buffer.
static void ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
llvm::ArrayRef<uint8_t> src,
size_t src_cyc_index, size_t offset);
/// Return the thread-specific part of the jLLDBTraceGetState packet.
TraceThreadState GetState() const;
private:
/// Construct new \a IntelPTThreadTrace. Users are supposed to create
/// instances of this class via the \a Create() method and not invoke this one
/// directly.
///
/// \param[in] perf_event
/// perf event configured for IntelPT.
///
/// \param[in] tid
/// The thread being traced.
IntelPTThreadTrace(PerfEvent &&perf_event, lldb::tid_t tid)
: m_perf_event(std::move(perf_event)), m_tid(tid) {}
/// perf event configured for IntelPT.
PerfEvent m_perf_event;
/// The thread being traced.
lldb::tid_t m_tid;
};
/// Manages a list of thread traces. /// Manages a list of thread traces.
class IntelPTThreadTraceCollection { class IntelPTThreadTraceCollection {
public: public:
IntelPTThreadTraceCollection(lldb::pid_t pid) : m_pid(pid) {} IntelPTThreadTraceCollection() {}
/// Dispose of all traces /// Dispose of all traces
void Clear(); void Clear();
@ -147,7 +39,7 @@ public:
std::vector<TraceThreadState> GetThreadStates() const; std::vector<TraceThreadState> GetThreadStates() const;
llvm::Expected<const IntelPTThreadTrace &> llvm::Expected<const IntelPTSingleBufferTrace &>
GetTracedThread(lldb::tid_t tid) const; GetTracedThread(lldb::tid_t tid) const;
llvm::Error TraceStart(lldb::tid_t tid, llvm::Error TraceStart(lldb::tid_t tid,
@ -156,8 +48,7 @@ public:
llvm::Error TraceStop(lldb::tid_t tid); llvm::Error TraceStop(lldb::tid_t tid);
private: private:
lldb::pid_t m_pid; llvm::DenseMap<lldb::tid_t, IntelPTSingleBufferTraceUP> m_thread_traces;
llvm::DenseMap<lldb::tid_t, IntelPTThreadTraceUP> m_thread_traces;
/// Total actual thread buffer size in bytes /// Total actual thread buffer size in bytes
size_t m_total_buffer_size = 0; size_t m_total_buffer_size = 0;
}; };
@ -165,8 +56,8 @@ private:
/// Manages a "process trace" instance. /// Manages a "process trace" instance.
class IntelPTProcessTrace { class IntelPTProcessTrace {
public: public:
IntelPTProcessTrace(lldb::pid_t pid, const TraceIntelPTStartRequest &request) IntelPTProcessTrace(const TraceIntelPTStartRequest &request)
: m_thread_traces(pid), m_tracing_params(request) {} : m_tracing_params(request) {}
bool TracesThread(lldb::tid_t tid) const; bool TracesThread(lldb::tid_t tid) const;
@ -185,7 +76,7 @@ private:
/// Main class that manages intel-pt process and thread tracing. /// Main class that manages intel-pt process and thread tracing.
class IntelPTCollector { class IntelPTCollector {
public: public:
IntelPTCollector(lldb::pid_t pid); IntelPTCollector();
static bool IsSupported(); static bool IsSupported();
@ -222,14 +113,13 @@ private:
llvm::Error TraceStart(lldb::tid_t tid, llvm::Error TraceStart(lldb::tid_t tid,
const TraceIntelPTStartRequest &request); const TraceIntelPTStartRequest &request);
llvm::Expected<const IntelPTThreadTrace &> llvm::Expected<const IntelPTSingleBufferTrace &>
GetTracedThread(lldb::tid_t tid) const; GetTracedThread(lldb::tid_t tid) const;
bool IsProcessTracingEnabled() const; bool IsProcessTracingEnabled() const;
void ClearProcessTracing(); void ClearProcessTracing();
lldb::pid_t m_pid;
/// Threads traced due to "thread tracing" /// Threads traced due to "thread tracing"
IntelPTThreadTraceCollection m_thread_traces; IntelPTThreadTraceCollection m_thread_traces;
/// Threads traced due to "process tracing". Only one active "process tracing" /// Threads traced due to "process tracing". Only one active "process tracing"

View File

@ -0,0 +1,302 @@
//===-- IntelPTSingleBufferTrace.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 "IntelPTSingleBufferTrace.h"
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include <sstream>
#include <linux/perf_event.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
using namespace lldb;
using namespace lldb_private;
using namespace process_linux;
using namespace llvm;
const char *kOSEventIntelPTTypeFile =
"/sys/bus/event_source/devices/intel_pt/type";
const char *kPSBPeriodCapFile =
"/sys/bus/event_source/devices/intel_pt/caps/psb_cyc";
const char *kPSBPeriodValidValuesFile =
"/sys/bus/event_source/devices/intel_pt/caps/psb_periods";
const char *kPSBPeriodBitOffsetFile =
"/sys/bus/event_source/devices/intel_pt/format/psb_period";
const char *kTSCBitOffsetFile =
"/sys/bus/event_source/devices/intel_pt/format/tsc";
enum IntelPTConfigFileType {
Hex = 0,
// 0 or 1
ZeroOne,
Decimal,
// a bit index file always starts with the prefix config: following by an int,
// which represents the offset of the perf_event_attr.config value where to
// store a given configuration.
BitOffset
};
static Expected<uint32_t> ReadIntelPTConfigFile(const char *file,
IntelPTConfigFileType type) {
ErrorOr<std::unique_ptr<MemoryBuffer>> stream =
MemoryBuffer::getFileAsStream(file);
if (!stream)
return createStringError(inconvertibleErrorCode(),
"Can't open the file '%s'", file);
uint32_t value = 0;
StringRef text_buffer = stream.get()->getBuffer();
if (type == BitOffset) {
const char *prefix = "config:";
if (!text_buffer.startswith(prefix))
return createStringError(inconvertibleErrorCode(),
"The file '%s' contents doesn't start with '%s'",
file, prefix);
text_buffer = text_buffer.substr(strlen(prefix));
}
auto getRadix = [&]() {
switch (type) {
case Hex:
return 16;
case ZeroOne:
case Decimal:
case BitOffset:
return 10;
}
llvm_unreachable("Fully covered switch above!");
};
auto createError = [&](const char *expected_value_message) {
return createStringError(
inconvertibleErrorCode(),
"The file '%s' has an invalid value. It should be %s.", file,
expected_value_message);
};
if (text_buffer.trim().consumeInteger(getRadix(), value) ||
(type == ZeroOne && value != 0 && value != 1)) {
switch (type) {
case Hex:
return createError("an unsigned hexadecimal int");
case ZeroOne:
return createError("0 or 1");
case Decimal:
case BitOffset:
return createError("an unsigned decimal int");
}
}
return value;
}
/// Return the Linux perf event type for Intel PT.
Expected<uint32_t> process_linux::GetIntelPTOSEventType() {
return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile,
IntelPTConfigFileType::Decimal);
}
static Error CheckPsbPeriod(size_t psb_period) {
Expected<uint32_t> cap =
ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne);
if (!cap)
return cap.takeError();
if (*cap == 0)
return createStringError(inconvertibleErrorCode(),
"psb_period is unsupported in the system.");
Expected<uint32_t> valid_values = ReadIntelPTConfigFile(
kPSBPeriodValidValuesFile, IntelPTConfigFileType::Hex);
if (!valid_values)
return valid_values.takeError();
if (valid_values.get() & (1 << psb_period))
return Error::success();
std::ostringstream error;
// 0 is always a valid value
error << "Invalid psb_period. Valid values are: 0";
uint32_t mask = valid_values.get();
while (mask) {
int index = __builtin_ctz(mask);
if (index > 0)
error << ", " << index;
// clear the lowest bit
mask &= mask - 1;
}
error << ".";
return createStringError(inconvertibleErrorCode(), error.str().c_str());
}
static Expected<uint64_t>
GeneratePerfEventConfigValue(bool enable_tsc, Optional<size_t> psb_period) {
uint64_t config = 0;
// tsc is always supported
if (enable_tsc) {
if (Expected<uint32_t> offset = ReadIntelPTConfigFile(
kTSCBitOffsetFile, IntelPTConfigFileType::BitOffset))
config |= 1 << *offset;
else
return offset.takeError();
}
if (psb_period) {
if (Error error = CheckPsbPeriod(*psb_period))
return std::move(error);
if (Expected<uint32_t> offset = ReadIntelPTConfigFile(
kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset))
config |= *psb_period << *offset;
else
return offset.takeError();
}
return config;
}
/// Create a \a perf_event_attr configured for
/// an IntelPT event.
///
/// \return
/// A \a perf_event_attr if successful,
/// or an \a llvm::Error otherwise.
static Expected<perf_event_attr>
CreateIntelPTPerfEventConfiguration(bool enable_tsc,
llvm::Optional<size_t> psb_period) {
#ifndef PERF_ATTR_SIZE_VER5
return llvm_unreachable("Intel PT Linux perf event not supported");
#else
perf_event_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.exclude_kernel = 1;
attr.sample_type = PERF_SAMPLE_TIME;
attr.sample_id_all = 1;
attr.exclude_hv = 1;
attr.exclude_idle = 1;
attr.mmap = 1;
if (Expected<uint64_t> config_value =
GeneratePerfEventConfigValue(enable_tsc, psb_period))
attr.config = *config_value;
else
return config_value.takeError();
if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType())
attr.type = *intel_pt_type;
else
return intel_pt_type.takeError();
return attr;
#endif
}
size_t IntelPTSingleBufferTrace::GetTraceBufferSize() const {
return m_perf_event.GetAuxBuffer().size();
}
Expected<std::vector<uint8_t>>
IntelPTSingleBufferTrace::GetTraceBuffer(size_t offset, size_t size) const {
auto fd = m_perf_event.GetFd();
perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
// Disable the perf event to force a flush out of the CPU's internal buffer.
// Besides, we can guarantee that the CPU won't override any data as we are
// reading the buffer.
//
// The Intel documentation says:
//
// Packets are first buffered internally and then written out asynchronously.
// To collect packet output for postprocessing, a collector needs first to
// ensure that all packet data has been flushed from internal buffers.
// Software can ensure this by stopping packet generation by clearing
// IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in
// Section 35.2.7.2).
//
// This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned
// in the man page of perf_event_open.
ioctl(fd, PERF_EVENT_IOC_DISABLE);
Log *log = GetLog(POSIXLog::Trace);
Status error;
uint64_t head = mmap_metadata.aux_head;
LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head);
/**
* When configured as ring buffer, the aux buffer keeps wrapping around
* the buffer and its not possible to detect how many times the buffer
* wrapped. Initially the buffer is filled with zeros,as shown below
* so in order to get complete buffer we first copy firstpartsize, followed
* by any left over part from beginning to aux_head
*
* aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size
* aux_head->||<- firstpartsize ->|
*
* */
std::vector<uint8_t> data(size, 0);
MutableArrayRef<uint8_t> buffer(data);
ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(),
static_cast<size_t>(head), offset);
// Reenable tracing now we have read the buffer
ioctl(fd, PERF_EVENT_IOC_ENABLE);
return data;
}
Expected<IntelPTSingleBufferTraceUP>
IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request,
lldb::tid_t tid) {
Log *log = GetLog(POSIXLog::Trace);
LLDB_LOG(log, "Will start tracing thread id {0}", tid);
if (__builtin_popcount(request.trace_buffer_size) != 1 ||
request.trace_buffer_size < 4096) {
return createStringError(
inconvertibleErrorCode(),
"The trace buffer size must be a power of 2 greater than or equal to "
"4096 (2^12) bytes. It was %" PRIu64 ".",
request.trace_buffer_size);
}
uint64_t page_size = getpagesize();
uint64_t buffer_numpages = static_cast<uint64_t>(llvm::PowerOf2Floor(
(request.trace_buffer_size + page_size - 1) / page_size));
Expected<perf_event_attr> attr = CreateIntelPTPerfEventConfiguration(
request.enable_tsc, request.psb_period.map([](int value) {
return static_cast<uint64_t>(value);
}));
if (!attr)
return attr.takeError();
LLDB_LOG(log, "Will create trace buffer of size {0}",
request.trace_buffer_size);
if (Expected<PerfEvent> perf_event = PerfEvent::Init(*attr, tid)) {
if (Error mmap_err = perf_event->MmapMetadataAndBuffers(buffer_numpages,
buffer_numpages)) {
return std::move(mmap_err);
}
return IntelPTSingleBufferTraceUP(
new IntelPTSingleBufferTrace(std::move(*perf_event), tid));
} else {
return perf_event.takeError();
}
}

View File

@ -0,0 +1,96 @@
//===-- IntelPTSingleBufferTrace.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 liblldb_IntelPTSingleBufferTrace_H_
#define liblldb_IntelPTSingleBufferTrace_H_
#include "Perf.h"
#include "lldb/Utility/TraceIntelPTGDBRemotePackets.h"
#include "lldb/lldb-types.h"
#include "llvm/Support/Error.h"
#include <memory>
namespace lldb_private {
namespace process_linux {
llvm::Expected<uint32_t> GetIntelPTOSEventType();
class IntelPTTrace;
class IntelPTSingleBufferTrace;
using IntelPTThreadTraceUP = std::unique_ptr<IntelPTTrace>;
using IntelPTSingleBufferTraceUP = std::unique_ptr<IntelPTSingleBufferTrace>;
/// This class wraps a single perf event collecting intel pt data in a single
/// buffer.
class IntelPTSingleBufferTrace {
public:
/// Start tracing using a single Intel PT trace buffer.
///
/// \param[in] request
/// Intel PT configuration parameters.
///
/// \param[in] tid
/// The tid of the thread to be traced.
///
/// \return
/// A \a IntelPTSingleBufferTrace instance if tracing was successful, or
/// an \a llvm::Error otherwise.
static llvm::Expected<IntelPTSingleBufferTraceUP>
Start(const TraceIntelPTStartRequest &request, lldb::tid_t tid);
/// \return
/// The bytes requested by a jLLDBTraceGetBinaryData packet that was routed
/// to this trace instace.
llvm::Expected<std::vector<uint8_t>>
GetBinaryData(const TraceGetBinaryDataRequest &request) const;
/// Read the trace buffer managed by this trace instance.
///
/// \param[in] offset
/// Offset of the data to read.
///
/// \param[in] size
/// Number of bytes to read.
///
/// \return
/// A vector with the requested binary data. The vector will have the
/// size of the requested \a size. Non-available positions will be
/// filled with zeroes.
llvm::Expected<std::vector<uint8_t>> GetTraceBuffer(size_t offset,
size_t size) const;
/// \return
/// The total the size in bytes used by the trace buffer managed by this
/// trace instance.
size_t GetTraceBufferSize() const;
private:
/// Construct new \a IntelPTSingleBufferThreadTrace. Users are supposed to
/// create instances of this class via the \a Start() method and not invoke
/// this one directly.
///
/// \param[in] perf_event
/// perf event configured for IntelPT.
///
/// \param[in] tid
/// The thread being traced.
IntelPTSingleBufferTrace(PerfEvent &&perf_event, lldb::tid_t tid)
: m_perf_event(std::move(perf_event)) {}
/// perf event configured for IntelPT.
PerfEvent m_perf_event;
};
} // namespace process_linux
} // namespace lldb_private
#endif // liblldb_IntelPTSingleBufferTrace_H_

View File

@ -312,7 +312,7 @@ NativeProcessLinux::NativeProcessLinux(::pid_t pid, int terminal_fd,
const ArchSpec &arch, MainLoop &mainloop, const ArchSpec &arch, MainLoop &mainloop,
llvm::ArrayRef<::pid_t> tids) llvm::ArrayRef<::pid_t> tids)
: NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch), : NativeProcessELF(pid, terminal_fd, delegate), m_arch(arch),
m_main_loop(mainloop), m_intel_pt_collector(pid) { m_main_loop(mainloop) {
if (m_terminal_fd != -1) { if (m_terminal_fd != -1) {
Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK);
assert(status.Success()); assert(status.Success());

View File

@ -8,6 +8,7 @@
#include "Perf.h" #include "Perf.h"
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
#include "lldb/Host/linux/Support.h" #include "lldb/Host/linux/Support.h"
#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/FormatVariadic.h"
@ -22,6 +23,54 @@ using namespace lldb_private;
using namespace process_linux; using namespace process_linux;
using namespace llvm; using namespace llvm;
void lldb_private::process_linux::ReadCyclicBuffer(
llvm::MutableArrayRef<uint8_t> &dst, llvm::ArrayRef<uint8_t> src,
size_t src_cyc_index, size_t offset) {
Log *log = GetLog(POSIXLog::Trace);
if (dst.empty() || src.empty()) {
dst = dst.drop_back(dst.size());
return;
}
if (dst.data() == nullptr || src.data() == nullptr) {
dst = dst.drop_back(dst.size());
return;
}
if (src_cyc_index > src.size()) {
dst = dst.drop_back(dst.size());
return;
}
if (offset >= src.size()) {
LLDB_LOG(log, "Too Big offset ");
dst = dst.drop_back(dst.size());
return;
}
llvm::SmallVector<ArrayRef<uint8_t>, 2> parts = {
src.slice(src_cyc_index), src.take_front(src_cyc_index)};
if (offset > parts[0].size()) {
parts[1] = parts[1].slice(offset - parts[0].size());
parts[0] = parts[0].drop_back(parts[0].size());
} else if (offset == parts[0].size()) {
parts[0] = parts[0].drop_back(parts[0].size());
} else {
parts[0] = parts[0].slice(offset);
}
auto next = dst.begin();
auto bytes_left = dst.size();
for (auto part : parts) {
size_t chunk_size = std::min(part.size(), bytes_left);
next = std::copy_n(part.begin(), chunk_size, next);
bytes_left -= chunk_size;
}
dst = dst.drop_back(bytes_left);
}
Expected<LinuxPerfZeroTscConversion> Expected<LinuxPerfZeroTscConversion>
lldb_private::process_linux::LoadPerfTscConversionParameters() { lldb_private::process_linux::LoadPerfTscConversionParameters() {
lldb::pid_t pid = getpid(); lldb::pid_t pid = getpid();

View File

@ -74,6 +74,24 @@ using MmapUP = std::unique_ptr<void, resource_handle::MmapDeleter>;
} // namespace resource_handle } // namespace resource_handle
/// Read data from a cyclic buffer
///
/// \param[in] [out] buf
/// Destination buffer, the buffer will be truncated to written size.
///
/// \param[in] src
/// Source buffer which must be a cyclic buffer.
///
/// \param[in] src_cyc_index
/// The index pointer (start of the valid data in the cyclic
/// buffer).
///
/// \param[in] offset
/// The offset to begin reading the data in the cyclic buffer.
void ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
llvm::ArrayRef<uint8_t> src, size_t src_cyc_index,
size_t offset);
/// Thin wrapper of the perf_event_open API. /// Thin wrapper of the perf_event_open API.
/// ///
/// Exposes the metadata page and data and aux buffers of a perf event. /// Exposes the metadata page and data and aux buffers of a perf event.

View File

@ -81,8 +81,7 @@ TraceIntelPT::TraceIntelPT(
for (const ThreadPostMortemTraceSP &thread : traced_threads) { for (const ThreadPostMortemTraceSP &thread : traced_threads) {
m_thread_decoders.emplace(thread->GetID(), m_thread_decoders.emplace(thread->GetID(),
std::make_unique<ThreadDecoder>(thread, *this)); std::make_unique<ThreadDecoder>(thread, *this));
SetPostMortemThreadDataFile(thread->GetID(), SetPostMortemThreadDataFile(thread->GetID(), IntelPTDataKinds::kTraceBuffer,
IntelPTDataKinds::kThreadTraceBuffer,
thread->GetTraceFile()); thread->GetTraceFile());
} }
} }
@ -373,8 +372,7 @@ Error TraceIntelPT::Start(llvm::ArrayRef<lldb::tid_t> tids,
Error TraceIntelPT::OnThreadBufferRead(lldb::tid_t tid, Error TraceIntelPT::OnThreadBufferRead(lldb::tid_t tid,
OnBinaryDataReadCallback callback) { OnBinaryDataReadCallback callback) {
return OnThreadBinaryDataRead(tid, IntelPTDataKinds::kThreadTraceBuffer, return OnThreadBinaryDataRead(tid, IntelPTDataKinds::kTraceBuffer, callback);
callback);
} }
TaskTimer &TraceIntelPT::GetTimer() { return m_task_timer; } TaskTimer &TraceIntelPT::GetTimer() { return m_task_timer; }

View File

@ -49,7 +49,7 @@ llvm::Error TraceIntelPTSessionSaver::SaveToDisk(TraceIntelPT &trace_ipt,
llvm::Expected<JSONTraceSessionBase> json_session_description = llvm::Expected<JSONTraceSessionBase> json_session_description =
TraceSessionSaver::BuildProcessesSection( TraceSessionSaver::BuildProcessesSection(
*live_process, IntelPTDataKinds::kThreadTraceBuffer, directory); *live_process, IntelPTDataKinds::kTraceBuffer, directory);
if (!json_session_description) if (!json_session_description)
return json_session_description.takeError(); return json_session_description.takeError();

View File

@ -14,7 +14,7 @@ using namespace llvm::json;
namespace lldb_private { namespace lldb_private {
const char *IntelPTDataKinds::kProcFsCpuInfo = "procfsCpuInfo"; const char *IntelPTDataKinds::kProcFsCpuInfo = "procfsCpuInfo";
const char *IntelPTDataKinds::kThreadTraceBuffer = "threadTraceBuffer"; const char *IntelPTDataKinds::kTraceBuffer = "traceBuffer";
bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet, bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet,
Path path) { Path path) {

View File

@ -1,5 +1,4 @@
add_lldb_unittest(ProcessLinuxTests add_lldb_unittest(ProcessLinuxTests
IntelPTCollectorTests.cpp
PerfTests.cpp PerfTests.cpp
ProcfsTests.cpp ProcfsTests.cpp

View File

@ -1,147 +0,0 @@
//===-- IntelPTCollectorTests.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 "gtest/gtest.h"
#include "IntelPTCollector.h"
#include "llvm/ADT/ArrayRef.h"
using namespace lldb_private;
using namespace process_linux;
size_t ReadCylicBufferWrapper(void *buf, size_t buf_size, void *cyc_buf,
size_t cyc_buf_size, size_t cyc_start,
size_t offset) {
llvm::MutableArrayRef<uint8_t> dst(reinterpret_cast<uint8_t *>(buf),
buf_size);
llvm::ArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(cyc_buf),
cyc_buf_size);
IntelPTThreadTrace::ReadCyclicBuffer(dst, src, cyc_start, offset);
return dst.size();
}
TEST(CyclicBuffer, EdgeCases) {
size_t bytes_read;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
// We will always leave the last bytes untouched
// so that string comparisons work.
char smaller_buffer[4] = {};
// empty buffer to read into
bytes_read = ReadCylicBufferWrapper(smaller_buffer, 0, cyclic_buffer,
sizeof(cyclic_buffer), 3, 0);
ASSERT_EQ(0u, bytes_read);
// empty cyclic buffer
bytes_read = ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, 0, 3, 0);
ASSERT_EQ(0u, bytes_read);
// bigger offset
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 6);
ASSERT_EQ(0u, bytes_read);
// wrong offset
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 7);
ASSERT_EQ(0u, bytes_read);
// wrong start
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 7);
ASSERT_EQ(0u, bytes_read);
}
TEST(CyclicBuffer, EqualSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
char cyclic[] = "cyclic";
for (size_t i = 0; i < sizeof(cyclic); i++) {
// We will always leave the last bytes untouched
// so that string comparisons work.
char equal_size_buffer[7] = {};
bytes_read =
ReadCylicBufferWrapper(equal_size_buffer, sizeof(cyclic_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, i);
ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read);
ASSERT_STREQ(equal_size_buffer, (cyclic + i));
}
}
TEST(CyclicBuffer, SmallerSizeBuffer) {
size_t bytes_read;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
// We will always leave the last bytes untouched
// so that string comparisons work.
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 0);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "cyc");
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 1);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "ycl");
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 2);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "cli");
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 3);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "lic");
{
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 4);
ASSERT_EQ(2u, bytes_read);
ASSERT_STREQ(smaller_buffer, "ic");
}
{
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 5);
ASSERT_EQ(1u, bytes_read);
ASSERT_STREQ(smaller_buffer, "c");
}
}
TEST(CyclicBuffer, BiggerSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
char cyclic[] = "cyclic";
for (size_t i = 0; i < sizeof(cyclic); i++) {
// We will always leave the last bytes untouched
// so that string comparisons work.
char bigger_buffer[10] = {};
bytes_read =
ReadCylicBufferWrapper(bigger_buffer, (sizeof(bigger_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, i);
ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read);
ASSERT_STREQ(bigger_buffer, (cyclic + i));
}
}

View File

@ -86,4 +86,135 @@ TEST(Perf, TscConversion) {
(SLEEP_NANOS + acceptable_overhead).count()); (SLEEP_NANOS + acceptable_overhead).count());
} }
size_t ReadCylicBufferWrapper(void *buf, size_t buf_size, void *cyc_buf,
size_t cyc_buf_size, size_t cyc_start,
size_t offset) {
llvm::MutableArrayRef<uint8_t> dst(reinterpret_cast<uint8_t *>(buf),
buf_size);
llvm::ArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(cyc_buf),
cyc_buf_size);
ReadCyclicBuffer(dst, src, cyc_start, offset);
return dst.size();
}
TEST(CyclicBuffer, EdgeCases) {
size_t bytes_read;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
// We will always leave the last bytes untouched
// so that string comparisons work.
char smaller_buffer[4] = {};
// empty buffer to read into
bytes_read = ReadCylicBufferWrapper(smaller_buffer, 0, cyclic_buffer,
sizeof(cyclic_buffer), 3, 0);
ASSERT_EQ(0u, bytes_read);
// empty cyclic buffer
bytes_read = ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, 0, 3, 0);
ASSERT_EQ(0u, bytes_read);
// bigger offset
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 6);
ASSERT_EQ(0u, bytes_read);
// wrong offset
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 7);
ASSERT_EQ(0u, bytes_read);
// wrong start
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, sizeof(smaller_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, 7);
ASSERT_EQ(0u, bytes_read);
}
TEST(CyclicBuffer, EqualSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
char cyclic[] = "cyclic";
for (size_t i = 0; i < sizeof(cyclic); i++) {
// We will always leave the last bytes untouched
// so that string comparisons work.
char equal_size_buffer[7] = {};
bytes_read =
ReadCylicBufferWrapper(equal_size_buffer, sizeof(cyclic_buffer),
cyclic_buffer, sizeof(cyclic_buffer), 3, i);
ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read);
ASSERT_STREQ(equal_size_buffer, (cyclic + i));
}
}
TEST(CyclicBuffer, SmallerSizeBuffer) {
size_t bytes_read;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
// We will always leave the last bytes untouched
// so that string comparisons work.
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 0);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "cyc");
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 1);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "ycl");
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 2);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "cli");
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 3);
ASSERT_EQ(3u, bytes_read);
ASSERT_STREQ(smaller_buffer, "lic");
{
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 4);
ASSERT_EQ(2u, bytes_read);
ASSERT_STREQ(smaller_buffer, "ic");
}
{
char smaller_buffer[4] = {};
bytes_read =
ReadCylicBufferWrapper(smaller_buffer, (sizeof(smaller_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, 5);
ASSERT_EQ(1u, bytes_read);
ASSERT_STREQ(smaller_buffer, "c");
}
}
TEST(CyclicBuffer, BiggerSizeBuffer) {
size_t bytes_read = 0;
uint8_t cyclic_buffer[6] = {'l', 'i', 'c', 'c', 'y', 'c'};
char cyclic[] = "cyclic";
for (size_t i = 0; i < sizeof(cyclic); i++) {
// We will always leave the last bytes untouched
// so that string comparisons work.
char bigger_buffer[10] = {};
bytes_read =
ReadCylicBufferWrapper(bigger_buffer, (sizeof(bigger_buffer) - 1),
cyclic_buffer, sizeof(cyclic_buffer), 3, i);
ASSERT_EQ((sizeof(cyclic) - i - 1), bytes_read);
ASSERT_STREQ(bigger_buffer, (cyclic + i));
}
}
#endif // __x86_64__ #endif // __x86_64__