diff --git a/lldb/docs/lldb-gdb-remote.txt b/lldb/docs/lldb-gdb-remote.txt --- a/lldb/docs/lldb-gdb-remote.txt +++ b/lldb/docs/lldb-gdb-remote.txt @@ -495,7 +495,7 @@ // INTEL PT // // 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. // // Counter info kinds: @@ -550,7 +550,7 @@ // INTEL PT // // 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. //---------------------------------------------------------------------- diff --git a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h --- a/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h +++ b/lldb/include/lldb/Utility/TraceIntelPTGDBRemotePackets.h @@ -21,7 +21,7 @@ // List of data kinds used by jLLDBGetState and jLLDBGetBinaryData. struct IntelPTDataKinds { static const char *kProcFsCpuInfo; - static const char *kThreadTraceBuffer; + static const char *kTraceBuffer; }; /// jLLDBTraceStart gdb-remote packet diff --git a/lldb/source/Plugins/Process/Linux/CMakeLists.txt b/lldb/source/Plugins/Process/Linux/CMakeLists.txt --- a/lldb/source/Plugins/Process/Linux/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Linux/CMakeLists.txt @@ -1,5 +1,6 @@ add_lldb_library(lldbPluginProcessLinux IntelPTCollector.cpp + IntelPTSingleBufferTrace.cpp NativeProcessLinux.cpp NativeRegisterContextLinux.cpp NativeRegisterContextLinux_arm.cpp diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h @@ -11,6 +11,8 @@ #include "Perf.h" +#include "IntelPTSingleBufferTrace.h" + #include "lldb/Utility/Status.h" #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "lldb/lldb-types.h" @@ -23,120 +25,10 @@ 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 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 - Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, bool enable_tsc, - llvm::Optional 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 - CreateIntelPTPerfEventConfiguration(bool enable_tsc, - llvm::Optional 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> GetIntelPTBuffer(size_t offset, - size_t size) const; - - Status ReadPerfTraceAux(llvm::MutableArrayRef &buffer, - size_t offset = 0) const; - - Status ReadPerfTraceData(llvm::MutableArrayRef &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 &dst, - llvm::ArrayRef 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. class IntelPTThreadTraceCollection { public: - IntelPTThreadTraceCollection(lldb::pid_t pid) : m_pid(pid) {} + IntelPTThreadTraceCollection() {} /// Dispose of all traces void Clear(); @@ -147,7 +39,7 @@ std::vector GetThreadStates() const; - llvm::Expected + llvm::Expected GetTracedThread(lldb::tid_t tid) const; llvm::Error TraceStart(lldb::tid_t tid, @@ -156,8 +48,7 @@ llvm::Error TraceStop(lldb::tid_t tid); private: - lldb::pid_t m_pid; - llvm::DenseMap m_thread_traces; + llvm::DenseMap m_thread_traces; /// Total actual thread buffer size in bytes size_t m_total_buffer_size = 0; }; @@ -165,8 +56,8 @@ /// Manages a "process trace" instance. class IntelPTProcessTrace { public: - IntelPTProcessTrace(lldb::pid_t pid, const TraceIntelPTStartRequest &request) - : m_thread_traces(pid), m_tracing_params(request) {} + IntelPTProcessTrace(const TraceIntelPTStartRequest &request) + : m_tracing_params(request) {} bool TracesThread(lldb::tid_t tid) const; @@ -185,7 +76,7 @@ /// Main class that manages intel-pt process and thread tracing. class IntelPTCollector { public: - IntelPTCollector(lldb::pid_t pid); + IntelPTCollector(); static bool IsSupported(); @@ -222,14 +113,13 @@ llvm::Error TraceStart(lldb::tid_t tid, const TraceIntelPTStartRequest &request); - llvm::Expected + llvm::Expected GetTracedThread(lldb::tid_t tid) const; bool IsProcessTracingEnabled() const; void ClearProcessTracing(); - lldb::pid_t m_pid; /// Threads traced due to "thread tracing" IntelPTThreadTraceCollection m_thread_traces; /// Threads traced due to "process tracing". Only one active "process tracing" diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -32,394 +32,6 @@ 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 *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 ReadIntelPTConfigFile(const char *file, - IntelPTConfigFileType type) { - ErrorOr> 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 GetOSEventType() { - return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, - IntelPTConfigFileType::Decimal); -} - -static Error CheckPsbPeriod(size_t psb_period) { - Expected cap = - ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); - if (!cap) - return cap.takeError(); - if (*cap == 0) - return createStringError(inconvertibleErrorCode(), - "psb_period is unsupported in the system."); - - Expected 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 -GeneratePerfEventConfigValue(bool enable_tsc, Optional psb_period) { - uint64_t config = 0; - // tsc is always supported - if (enable_tsc) { - if (Expected 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 offset = ReadIntelPTConfigFile( - kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset)) - config |= *psb_period << *offset; - else - return offset.takeError(); - } - return config; -} - -llvm::Expected -IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration( - bool enable_tsc, Optional 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 config_value = - GeneratePerfEventConfigValue(enable_tsc, psb_period)) { - attr.config = *config_value; - } else { - return config_value.takeError(); - } - - if (Expected intel_pt_type = GetOSEventType()) { - attr.type = *intel_pt_type; - } else { - return intel_pt_type.takeError(); - } - - return attr; -} - -llvm::Expected -IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, - bool enable_tsc, Optional 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( - llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); - - Expected attr = - IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc, - psb_period); - if (!attr) - return attr.takeError(); - - LLDB_LOG(log, "buffer size {0} ", buffer_size); - - if (Expected 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> -IntelPTThreadTrace::GetIntelPTBuffer(size_t offset, size_t size) const { - std::vector data(size, 0); - MutableArrayRef buffer_ref(data); - Status error = ReadPerfTraceAux(buffer_ref, 0); - if (error.Fail()) - return error.ToError(); - return data; -} - -Status -IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef &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(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 &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(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 &dst, - llvm::ArrayRef 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, 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(m_tid), - {TraceBinaryData{IntelPTDataKinds::kThreadTraceBuffer, - static_cast(GetTraceBufferSize())}}}; -} - /// IntelPTThreadTraceCollection bool IntelPTThreadTraceCollection::TracesThread(lldb::tid_t tid) const { @@ -442,9 +54,8 @@ return createStringError(inconvertibleErrorCode(), "Thread %" PRIu64 " already traced", tid); - Expected trace_up = IntelPTThreadTrace::Create( - m_pid, tid, request.trace_buffer_size, request.enable_tsc, - request.psb_period.map([](int64_t period) { return (size_t)period; })); + Expected trace_up = + IntelPTSingleBufferTrace::Start(request, tid); if (!trace_up) return trace_up.takeError(); @@ -461,11 +72,14 @@ IntelPTThreadTraceCollection::GetThreadStates() const { std::vector states; for (const auto &it : m_thread_traces) - states.push_back(it.second->GetState()); + states.push_back({static_cast(it.first), + {TraceBinaryData{IntelPTDataKinds::kTraceBuffer, + static_cast( + it.second->GetTraceBufferSize())}}}); return states; } -Expected +Expected IntelPTThreadTraceCollection::GetTracedThread(lldb::tid_t tid) const { auto it = m_thread_traces.find(tid); if (it == m_thread_traces.end()) @@ -510,8 +124,7 @@ /// IntelPTCollector -IntelPTCollector::IntelPTCollector(lldb::pid_t pid) - : m_pid(pid), m_thread_traces(pid) { +IntelPTCollector::IntelPTCollector() { if (Expected tsc_conversion = LoadPerfTscConversionParameters()) m_tsc_conversion = @@ -553,7 +166,7 @@ return createStringError(inconvertibleErrorCode(), "Per-core tracing is not supported."); } - m_process_trace = IntelPTProcessTrace(m_pid, request); + m_process_trace = IntelPTProcessTrace(request); Error error = Error::success(); for (lldb::tid_t tid : process_threads) @@ -604,7 +217,7 @@ return toJSON(state); } -Expected +Expected IntelPTCollector::GetTracedThread(lldb::tid_t tid) const { if (IsProcessTracingEnabled() && m_process_trace->TracesThread(tid)) return m_process_trace->GetThreadTraces().GetTracedThread(tid); @@ -613,10 +226,10 @@ Expected> IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const { - if (request.kind == IntelPTDataKinds::kThreadTraceBuffer) { - if (Expected trace = + if (request.kind == IntelPTDataKinds::kTraceBuffer) { + if (Expected trace = GetTracedThread(*request.tid)) - return trace->GetIntelPTBuffer(request.offset, request.size); + return trace->GetTraceBuffer(request.offset, request.size); else return trace.takeError(); } else if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) { @@ -630,12 +243,12 @@ void IntelPTCollector::ClearProcessTracing() { m_process_trace = None; } bool IntelPTCollector::IsSupported() { - Expected intel_pt_type = GetOSEventType(); - if (!intel_pt_type) { + if (Expected intel_pt_type = GetIntelPTOSEventType()) { + return true; + } else { llvm::consumeError(intel_pt_type.takeError()); return false; } - return true; } bool IntelPTCollector::IsProcessTracingEnabled() const { diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.h @@ -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 + +namespace lldb_private { +namespace process_linux { + +llvm::Expected GetIntelPTOSEventType(); + +class IntelPTTrace; +class IntelPTSingleBufferTrace; + +using IntelPTThreadTraceUP = std::unique_ptr; +using IntelPTSingleBufferTraceUP = std::unique_ptr; + +/// 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 + 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> + 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> 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_ diff --git a/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/IntelPTSingleBufferTrace.cpp @@ -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 + +#include +#include +#include +#include + +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 ReadIntelPTConfigFile(const char *file, + IntelPTConfigFileType type) { + ErrorOr> 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 process_linux::GetIntelPTOSEventType() { + return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, + IntelPTConfigFileType::Decimal); +} + +static Error CheckPsbPeriod(size_t psb_period) { + Expected cap = + ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); + if (!cap) + return cap.takeError(); + if (*cap == 0) + return createStringError(inconvertibleErrorCode(), + "psb_period is unsupported in the system."); + + Expected 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 +GeneratePerfEventConfigValue(bool enable_tsc, Optional psb_period) { + uint64_t config = 0; + // tsc is always supported + if (enable_tsc) { + if (Expected 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 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 +CreateIntelPTPerfEventConfiguration(bool enable_tsc, + llvm::Optional 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 config_value = + GeneratePerfEventConfigValue(enable_tsc, psb_period)) + attr.config = *config_value; + else + return config_value.takeError(); + + if (Expected 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> +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 data(size, 0); + MutableArrayRef buffer(data); + ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(), + static_cast(head), offset); + + // Reenable tracing now we have read the buffer + ioctl(fd, PERF_EVENT_IOC_ENABLE); + return data; +} + +Expected +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(llvm::PowerOf2Floor( + (request.trace_buffer_size + page_size - 1) / page_size)); + + Expected attr = CreateIntelPTPerfEventConfiguration( + request.enable_tsc, request.psb_period.map([](int value) { + return static_cast(value); + })); + if (!attr) + return attr.takeError(); + + LLDB_LOG(log, "Will create trace buffer of size {0}", + request.trace_buffer_size); + + if (Expected 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(); + } +} diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -312,7 +312,7 @@ const ArchSpec &arch, MainLoop &mainloop, llvm::ArrayRef<::pid_t> tids) : 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) { Status status = EnsureFDFlags(m_terminal_fd, O_NONBLOCK); assert(status.Success()); diff --git a/lldb/source/Plugins/Process/Linux/Perf.h b/lldb/source/Plugins/Process/Linux/Perf.h --- a/lldb/source/Plugins/Process/Linux/Perf.h +++ b/lldb/source/Plugins/Process/Linux/Perf.h @@ -74,6 +74,24 @@ } // 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 &dst, + llvm::ArrayRef src, size_t src_cyc_index, + size_t offset); + /// Thin wrapper of the perf_event_open API. /// /// Exposes the metadata page and data and aux buffers of a perf event. diff --git a/lldb/source/Plugins/Process/Linux/Perf.cpp b/lldb/source/Plugins/Process/Linux/Perf.cpp --- a/lldb/source/Plugins/Process/Linux/Perf.cpp +++ b/lldb/source/Plugins/Process/Linux/Perf.cpp @@ -8,6 +8,7 @@ #include "Perf.h" +#include "Plugins/Process/POSIX/ProcessPOSIXLog.h" #include "lldb/Host/linux/Support.h" #include "llvm/Support/FormatVariadic.h" @@ -22,6 +23,54 @@ using namespace process_linux; using namespace llvm; +void lldb_private::process_linux::ReadCyclicBuffer( + llvm::MutableArrayRef &dst, llvm::ArrayRef 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, 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 lldb_private::process_linux::LoadPerfTscConversionParameters() { lldb::pid_t pid = getpid(); diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPT.cpp @@ -81,8 +81,7 @@ for (const ThreadPostMortemTraceSP &thread : traced_threads) { m_thread_decoders.emplace(thread->GetID(), std::make_unique(thread, *this)); - SetPostMortemThreadDataFile(thread->GetID(), - IntelPTDataKinds::kThreadTraceBuffer, + SetPostMortemThreadDataFile(thread->GetID(), IntelPTDataKinds::kTraceBuffer, thread->GetTraceFile()); } } @@ -373,8 +372,7 @@ Error TraceIntelPT::OnThreadBufferRead(lldb::tid_t tid, OnBinaryDataReadCallback callback) { - return OnThreadBinaryDataRead(tid, IntelPTDataKinds::kThreadTraceBuffer, - callback); + return OnThreadBinaryDataRead(tid, IntelPTDataKinds::kTraceBuffer, callback); } TaskTimer &TraceIntelPT::GetTimer() { return m_task_timer; } diff --git a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp --- a/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp +++ b/lldb/source/Plugins/Trace/intel-pt/TraceIntelPTSessionSaver.cpp @@ -49,7 +49,7 @@ llvm::Expected json_session_description = TraceSessionSaver::BuildProcessesSection( - *live_process, IntelPTDataKinds::kThreadTraceBuffer, directory); + *live_process, IntelPTDataKinds::kTraceBuffer, directory); if (!json_session_description) return json_session_description.takeError(); diff --git a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp --- a/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp +++ b/lldb/source/Utility/TraceIntelPTGDBRemotePackets.cpp @@ -14,7 +14,7 @@ namespace lldb_private { const char *IntelPTDataKinds::kProcFsCpuInfo = "procfsCpuInfo"; -const char *IntelPTDataKinds::kThreadTraceBuffer = "threadTraceBuffer"; +const char *IntelPTDataKinds::kTraceBuffer = "traceBuffer"; bool fromJSON(const json::Value &value, TraceIntelPTStartRequest &packet, Path path) { diff --git a/lldb/unittests/Process/Linux/CMakeLists.txt b/lldb/unittests/Process/Linux/CMakeLists.txt --- a/lldb/unittests/Process/Linux/CMakeLists.txt +++ b/lldb/unittests/Process/Linux/CMakeLists.txt @@ -1,5 +1,4 @@ add_lldb_unittest(ProcessLinuxTests - IntelPTCollectorTests.cpp PerfTests.cpp ProcfsTests.cpp diff --git a/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp b/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp deleted file mode 100644 --- a/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp +++ /dev/null @@ -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 dst(reinterpret_cast(buf), - buf_size); - llvm::ArrayRef src(reinterpret_cast(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)); - } -} diff --git a/lldb/unittests/Process/Linux/PerfTests.cpp b/lldb/unittests/Process/Linux/PerfTests.cpp --- a/lldb/unittests/Process/Linux/PerfTests.cpp +++ b/lldb/unittests/Process/Linux/PerfTests.cpp @@ -86,4 +86,135 @@ (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 dst(reinterpret_cast(buf), + buf_size); + llvm::ArrayRef src(reinterpret_cast(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__