Author: Jakob Johnson Date: 2022-03-21T13:38:52-07:00 New Revision: e6c84f82b87576a57d1fa1c7e8c289d3d4fa7ab1
URL: https://github.com/llvm/llvm-project/commit/e6c84f82b87576a57d1fa1c7e8c289d3d4fa7ab1 DIFF: https://github.com/llvm/llvm-project/commit/e6c84f82b87576a57d1fa1c7e8c289d3d4fa7ab1.diff LOG: Add thin wrapper for perf_event_open API - Add PerfEvent class to handle creating ring buffers and handle the resources associated with a perf_event - Refactor IntelPT collection code to use this new API - Add TSC to timestamp conversion logic with unittest Differential Revision: https://reviews.llvm.org/D121734 Added: lldb/source/Plugins/Process/Linux/Perf.cpp lldb/source/Plugins/Process/Linux/Perf.h lldb/unittests/Process/Linux/PerfTests.cpp Modified: lldb/source/Plugins/Process/Linux/CMakeLists.txt lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp lldb/source/Plugins/Process/Linux/IntelPTCollector.h lldb/unittests/Process/Linux/CMakeLists.txt lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp Removed: ################################################################################ diff --git a/lldb/source/Plugins/Process/Linux/CMakeLists.txt b/lldb/source/Plugins/Process/Linux/CMakeLists.txt index 60958bb913960..cc70edba3483e 100644 --- a/lldb/source/Plugins/Process/Linux/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Linux/CMakeLists.txt @@ -8,6 +8,7 @@ add_lldb_library(lldbPluginProcessLinux NativeRegisterContextLinux_s390x.cpp NativeRegisterContextLinux_x86_64.cpp NativeThreadLinux.cpp + Perf.cpp SingleStepCheck.cpp LINK_LIBS diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp index 0e65c88a1f765..5f31c10f8b88b 100644 --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -6,19 +6,23 @@ // //===----------------------------------------------------------------------===// -#include <algorithm> -#include <fstream> -#include <sstream> +#include "IntelPTCollector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/MathExtras.h" +#include "Perf.h" -#include "IntelPTCollector.h" #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" #include "lldb/Host/linux/Support.h" #include "lldb/Utility/StreamString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MathExtras.h" + +#include <algorithm> +#include <cstddef> +#include <fstream> +#include <linux/perf_event.h> +#include <sstream> #include <sys/ioctl.h> #include <sys/syscall.h> @@ -53,6 +57,21 @@ enum IntelPTConfigFileType { BitOffset }; +/// Get the content of /proc/cpuinfo that can be later used to decode traces. +static Expected<ArrayRef<uint8_t>> GetCPUInfo() { + static llvm::Optional<std::vector<uint8_t>> cpu_info; + if (!cpu_info) { + auto buffer_or_error = errorOrToExpected(getProcFile("cpuinfo")); + if (!buffer_or_error) + return buffer_or_error.takeError(); + MemoryBuffer &buffer = **buffer_or_error; + cpu_info = std::vector<uint8_t>( + reinterpret_cast<const uint8_t *>(buffer.getBufferStart()), + reinterpret_cast<const uint8_t *>(buffer.getBufferEnd())); + } + return *cpu_info; +} + static Expected<uint32_t> ReadIntelPTConfigFile(const char *file, IntelPTConfigFileType type) { ErrorOr<std::unique_ptr<MemoryBuffer>> stream = @@ -106,6 +125,7 @@ static Expected<uint32_t> ReadIntelPTConfigFile(const char *file, } return value; } + /// Return the Linux perf event type for Intel PT. static Expected<uint32_t> GetOSEventType() { return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, @@ -148,7 +168,7 @@ size_t IntelPTThreadTrace::GetTraceBufferSize() const { #ifndef PERF_ATTR_SIZE_VER5 llvm_unreachable("Intel PT Linux perf event not supported"); #else - return m_mmap_meta->aux_size; + return m_perf_event.GetAuxBuffer().size(); #endif } @@ -176,30 +196,9 @@ GeneratePerfEventConfigValue(bool enable_tsc, Optional<size_t> psb_period) { return config; } -Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_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); - - m_tid = tid; - LLDB_LOG(log, "called thread id {0}", tid); - uint64_t page_size = getpagesize(); - - 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 numpages = static_cast<uint64_t>( - llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); - numpages = std::max<uint64_t>(1, numpages); - buffer_size = page_size * numpages; - +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); @@ -213,106 +212,59 @@ Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, if (Expected<uint64_t> config_value = GeneratePerfEventConfigValue(enable_tsc, psb_period)) { attr.config = *config_value; - LLDB_LOG(log, "intel pt config {0}", attr.config); } else { return config_value.takeError(); } if (Expected<uint32_t> intel_pt_type = GetOSEventType()) { attr.type = *intel_pt_type; - LLDB_LOG(log, "intel pt type {0}", attr.type); } else { return intel_pt_type.takeError(); } - LLDB_LOG(log, "buffer size {0} ", buffer_size); - - errno = 0; - auto fd = - syscall(SYS_perf_event_open, &attr, static_cast<::tid_t>(tid), -1, -1, 0); - if (fd == -1) { - LLDB_LOG(log, "syscall error {0}", errno); - return createStringError(inconvertibleErrorCode(), - "perf event syscall failed"); - } - - m_fd = std::unique_ptr<int, file_close>(new int(fd), file_close()); - - errno = 0; - auto base = - mmap(nullptr, (buffer_size + page_size), PROT_WRITE, MAP_SHARED, fd, 0); - - if (base == MAP_FAILED) { - LLDB_LOG(log, "mmap base error {0}", errno); - return createStringError(inconvertibleErrorCode(), - "Meta buffer allocation failed"); - } - - m_mmap_meta = std::unique_ptr<perf_event_mmap_page, munmap_delete>( - reinterpret_cast<perf_event_mmap_page *>(base), - munmap_delete(buffer_size + page_size)); - - m_mmap_meta->aux_offset = m_mmap_meta->data_offset + m_mmap_meta->data_size; - m_mmap_meta->aux_size = buffer_size; - - errno = 0; - auto mmap_aux = mmap(nullptr, buffer_size, PROT_READ, MAP_SHARED, fd, - static_cast<long int>(m_mmap_meta->aux_offset)); - - if (mmap_aux == MAP_FAILED) { - LLDB_LOG(log, "second mmap done {0}", errno); - return createStringError(inconvertibleErrorCode(), - "Trace buffer allocation failed"); - } - m_mmap_aux = std::unique_ptr<uint8_t, munmap_delete>( - reinterpret_cast<uint8_t *>(mmap_aux), munmap_delete(buffer_size)); - return Error::success(); -#endif + return attr; } -llvm::MutableArrayRef<uint8_t> IntelPTThreadTrace::GetDataBuffer() const { +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 - return MutableArrayRef<uint8_t>( - (reinterpret_cast<uint8_t *>(m_mmap_meta.get()) + - m_mmap_meta->data_offset), - m_mmap_meta->data_size); -#endif -} + Log *log = GetLog(POSIXLog::Ptrace); -llvm::MutableArrayRef<uint8_t> IntelPTThreadTrace::GetAuxBuffer() const { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("Intel PT Linux perf event not supported"); -#else - return MutableArrayRef<uint8_t>(m_mmap_aux.get(), m_mmap_meta->aux_size); -#endif -} + LLDB_LOG(log, "called thread id {0}", tid); -Expected<ArrayRef<uint8_t>> IntelPTThreadTrace::GetCPUInfo() { - static llvm::Optional<std::vector<uint8_t>> cpu_info; - if (!cpu_info) { - auto buffer_or_error = getProcFile("cpuinfo"); - if (!buffer_or_error) - return Status(buffer_or_error.getError()).ToError(); - MemoryBuffer &buffer = **buffer_or_error; - cpu_info = std::vector<uint8_t>( - reinterpret_cast<const uint8_t *>(buffer.getBufferStart()), - reinterpret_cast<const uint8_t *>(buffer.getBufferEnd())); + 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); } - return *cpu_info; -} + uint64_t page_size = getpagesize(); + uint64_t buffer_numpages = static_cast<uint64_t>( + llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); -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) { - IntelPTThreadTraceUP thread_trace_up(new IntelPTThreadTrace()); + Expected<perf_event_attr> attr = + IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc, + psb_period); + if (!attr) + return attr.takeError(); - if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size, - enable_tsc, psb_period)) - return std::move(err); + LLDB_LOG(log, "buffer size {0} ", buffer_size); - return std::move(thread_trace_up); + 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>> @@ -331,6 +283,8 @@ IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer, #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. @@ -346,13 +300,13 @@ IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer, // // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned // in the man page of perf_event_open. - ioctl(*m_fd, PERF_EVENT_IOC_DISABLE); + ioctl(fd, PERF_EVENT_IOC_DISABLE); Log *log = GetLog(POSIXLog::Ptrace); Status error; - uint64_t head = m_mmap_meta->aux_head; + uint64_t head = mmap_metadata.aux_head; - LLDB_LOG(log, "Aux size -{0} , Head - {1}", m_mmap_meta->aux_size, 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 @@ -366,11 +320,12 @@ IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer, * * */ - ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast<size_t>(head), offset); - LLDB_LOG(log, "ReadCyclic BUffer Done"); + 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(*m_fd, PERF_EVENT_IOC_ENABLE); + ioctl(fd, PERF_EVENT_IOC_ENABLE); return error; #endif } @@ -385,7 +340,8 @@ IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer, uint64_t bytes_remaining = buffer.size(); Status error; - uint64_t head = m_mmap_meta->data_head; + perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage(); + uint64_t head = mmap_metadata.data_head; /* * The data buffer and aux buffer have diff erent implementations @@ -397,11 +353,11 @@ IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer, LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining); - auto data_buffer = GetDataBuffer(); + 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}", m_mmap_meta->data_size, head); + 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(); @@ -424,7 +380,7 @@ IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer, } void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst, - llvm::MutableArrayRef<uint8_t> src, + llvm::ArrayRef<uint8_t> src, size_t src_cyc_index, size_t offset) { Log *log = GetLog(POSIXLog::Ptrace); @@ -450,7 +406,7 @@ void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst, return; } - llvm::SmallVector<MutableArrayRef<uint8_t>, 2> parts = { + llvm::SmallVector<ArrayRef<uint8_t>, 2> parts = { src.slice(src_cyc_index), src.take_front(src_cyc_index)}; if (offset > parts[0].size()) { @@ -624,7 +580,7 @@ Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) { } Expected<json::Value> IntelPTCollector::GetState() const { - Expected<ArrayRef<uint8_t>> cpu_info = IntelPTThreadTrace::GetCPUInfo(); + Expected<ArrayRef<uint8_t>> cpu_info = GetCPUInfo(); if (!cpu_info) return cpu_info.takeError(); @@ -661,7 +617,7 @@ IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const else return trace.takeError(); } else if (request.kind == "cpuInfo") { - return IntelPTThreadTrace::GetCPUInfo(); + return GetCPUInfo(); } return createStringError(inconvertibleErrorCode(), "Unsuported trace binary data kind: %s", diff --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h index 051b1ad285336..12fa12dc299d8 100644 --- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h +++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h @@ -9,11 +9,11 @@ #ifndef liblldb_IntelPTCollector_H_ #define liblldb_IntelPTCollector_H_ +#include "Perf.h" + #include "lldb/Utility/Status.h" #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h" #include "lldb/lldb-types.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" #include <linux/perf_event.h> #include <sys/mman.h> @@ -31,42 +31,15 @@ class IntelPTThreadTrace; typedef std::unique_ptr<IntelPTThreadTrace> IntelPTThreadTraceUP; class IntelPTThreadTrace { - - class munmap_delete { - size_t m_length; - - public: - munmap_delete(size_t length) : m_length(length) {} - void operator()(void *ptr) { - if (m_length) - munmap(ptr, m_length); - } - }; - - class file_close { - - public: - file_close() = default; - void operator()(int *ptr) { - if (ptr == nullptr) - return; - if (*ptr == -1) - return; - close(*ptr); - std::default_delete<int>()(ptr); - } - }; - - std::unique_ptr<perf_event_mmap_page, munmap_delete> m_mmap_meta; - std::unique_ptr<uint8_t, munmap_delete> m_mmap_aux; - std::unique_ptr<int, file_close> m_fd; - lldb::tid_t m_tid; - - /// Start tracing a thread +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. /// @@ -79,33 +52,22 @@ class IntelPTThreadTrace { /// More information in TraceIntelPT::GetStartConfigurationHelp(). /// /// \return - /// \a llvm::Error::success if tracing was successful, or an - /// \a llvm::Error otherwise. - llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, uint64_t buffer_size, - bool enable_tsc, llvm::Optional<size_t> psb_period); - - llvm::MutableArrayRef<uint8_t> GetAuxBuffer() const; - llvm::MutableArrayRef<uint8_t> GetDataBuffer() const; - - IntelPTThreadTrace() - : m_mmap_meta(nullptr, munmap_delete(0)), - m_mmap_aux(nullptr, munmap_delete(0)), m_fd(nullptr, file_close()) {} - -public: - /// Get the content of /proc/cpuinfo that can be later used to decode traces. - static llvm::Expected<llvm::ArrayRef<uint8_t>> GetCPUInfo(); - - /// Start tracing a thread. - /// - /// See \a StartTrace. - /// - /// \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 @@ -146,11 +108,29 @@ class IntelPTThreadTrace { /// \param[in] offset /// The offset to begin reading the data in the cyclic buffer. static void ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst, - llvm::MutableArrayRef<uint8_t> src, + 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. diff --git a/lldb/source/Plugins/Process/Linux/Perf.cpp b/lldb/source/Plugins/Process/Linux/Perf.cpp new file mode 100644 index 0000000000000..944076784b36d --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/Perf.cpp @@ -0,0 +1,181 @@ +//===-- Perf.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 "Perf.h" + +#include "lldb/lldb-types.h" + +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/MathExtras.h" + +#include <chrono> +#include <cstdint> +#include <linux/perf_event.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <unistd.h> + +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +Expected<PerfTscConversionParameters> +lldb_private::process_linux::FetchPerfTscConversionParameters() { + lldb::pid_t pid = getpid(); + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_DUMMY; + + Expected<PerfEvent> perf_event = PerfEvent::Init(attr, pid); + if (!perf_event) + return perf_event.takeError(); + if (Error mmap_err = perf_event->MmapMetadataAndBuffers(/*num_data_pages*/ 0, + /*num_aux_pages*/ 0)) + return std::move(mmap_err); + + perf_event_mmap_page &mmap_metada = perf_event->GetMetadataPage(); + if (mmap_metada.cap_user_time && mmap_metada.cap_user_time_zero) { + return PerfTscConversionParameters{ + mmap_metada.time_mult, mmap_metada.time_shift, mmap_metada.time_zero}; + } else { + auto err_cap = + !mmap_metada.cap_user_time ? "cap_user_time" : "cap_user_time_zero"; + std::string err_msg = + llvm::formatv("Can't get TSC to real time conversion values. " + "perf_event capability '{0}' not supported.", + err_cap); + return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); + } +} + +std::chrono::nanoseconds PerfTscConversionParameters::ToWallTime(uint64_t tsc) { + // See 'time_zero' section of + // https://man7.org/linux/man-pages/man2/perf_event_open.2.html + uint64_t quot = tsc >> m_time_shift; + uint64_t rem_flag = (((uint64_t)1 << m_time_shift) - 1); + uint64_t rem = tsc & rem_flag; + return std::chrono::nanoseconds{m_time_zero + quot * m_time_mult + + ((rem * m_time_mult) >> m_time_shift)}; +} + +void resource_handle::MmapDeleter::operator()(void *ptr) { + if (m_bytes && ptr != nullptr) + munmap(ptr, m_bytes); +} + +void resource_handle::FileDescriptorDeleter::operator()(long *ptr) { + if (ptr == nullptr) + return; + if (*ptr == -1) + return; + close(*ptr); + std::default_delete<long>()(ptr); +} + +llvm::Expected<PerfEvent> PerfEvent::Init(perf_event_attr &attr, + lldb::pid_t pid, int cpu, + int group_fd, unsigned long flags) { + errno = 0; + long fd = syscall(SYS_perf_event_open, &attr, pid, cpu, group_fd, flags); + if (fd == -1) { + std::string err_msg = + llvm::formatv("perf event syscall failed: {0}", std::strerror(errno)); + return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); + } + return PerfEvent{fd}; +} + +llvm::Expected<PerfEvent> PerfEvent::Init(perf_event_attr &attr, + lldb::pid_t pid) { + return Init(attr, pid, -1, -1, 0); +} + +llvm::Expected<resource_handle::MmapUP> +PerfEvent::DoMmap(void *addr, size_t length, int prot, int flags, + long int offset, llvm::StringRef buffer_name) { + errno = 0; + auto mmap_result = ::mmap(nullptr, length, prot, flags, GetFd(), offset); + + if (mmap_result == MAP_FAILED) { + std::string err_msg = + llvm::formatv("perf event mmap allocation failed for {0}: {1}", + buffer_name, std::strerror(errno)); + return createStringError(inconvertibleErrorCode(), err_msg); + } + return resource_handle::MmapUP(mmap_result, length); +} + +llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages) { + size_t mmap_size = (num_data_pages + 1) * getpagesize(); + if (Expected<resource_handle::MmapUP> mmap_metadata_data = + DoMmap(nullptr, mmap_size, PROT_WRITE, MAP_SHARED, 0, + "metadata and data buffer")) { + m_metadata_data_base = std::move(mmap_metadata_data.get()); + return Error::success(); + } else + return mmap_metadata_data.takeError(); +} + +llvm::Error PerfEvent::MmapAuxBuffer(size_t num_aux_pages) { + if (num_aux_pages == 0) + return Error::success(); + + perf_event_mmap_page &metadata_page = GetMetadataPage(); + metadata_page.aux_offset = + metadata_page.data_offset + metadata_page.data_size; + metadata_page.aux_size = num_aux_pages * getpagesize(); + + if (Expected<resource_handle::MmapUP> mmap_aux = + DoMmap(nullptr, metadata_page.aux_size, PROT_READ, MAP_SHARED, + metadata_page.aux_offset, "aux buffer")) { + m_aux_base = std::move(mmap_aux.get()); + return Error::success(); + } else + return mmap_aux.takeError(); +} + +llvm::Error PerfEvent::MmapMetadataAndBuffers(size_t num_data_pages, + size_t num_aux_pages) { + if (num_data_pages != 0 && !isPowerOf2_64(num_data_pages)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("Number of data pages must be a power of 2, got: {0}", + num_data_pages)); + if (num_aux_pages != 0 && !isPowerOf2_64(num_aux_pages)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("Number of aux pages must be a power of 2, got: {0}", + num_aux_pages)); + if (Error err = MmapMetadataAndDataBuffer(num_data_pages)) + return err; + if (Error err = MmapAuxBuffer(num_aux_pages)) + return err; + return Error::success(); +} + +long PerfEvent::GetFd() const { return *(m_fd.get()); } + +perf_event_mmap_page &PerfEvent::GetMetadataPage() const { + return *reinterpret_cast<perf_event_mmap_page *>(m_metadata_data_base.get()); +} + +ArrayRef<uint8_t> PerfEvent::GetDataBuffer() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + return {reinterpret_cast<uint8_t *>(m_metadata_data_base.get()) + + mmap_metadata.data_offset, + mmap_metadata.data_size}; +} + +ArrayRef<uint8_t> PerfEvent::GetAuxBuffer() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + return {reinterpret_cast<uint8_t *>(m_aux_base.get()), + mmap_metadata.aux_size}; +} diff --git a/lldb/source/Plugins/Process/Linux/Perf.h b/lldb/source/Plugins/Process/Linux/Perf.h new file mode 100644 index 0000000000000..6853b58666b1d --- /dev/null +++ b/lldb/source/Plugins/Process/Linux/Perf.h @@ -0,0 +1,262 @@ +//===-- Perf.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 +// +//===----------------------------------------------------------------------===// +/// \file +/// This file contains a thin wrapper of the perf_event_open API +/// and classes to handle the destruction of file descriptors +/// and mmap pointers. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H +#define LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H + +#include "lldb/lldb-types.h" + +#include "llvm/Support/Error.h" + +#include <chrono> +#include <cstdint> +#include <linux/perf_event.h> + +namespace lldb_private { +namespace process_linux { +namespace resource_handle { + +/// Custom deleter for the pointer returned by \a mmap. +/// +/// This functor type is provided to \a unique_ptr to properly +/// unmap the region at destruction time. +class MmapDeleter { +public: + /// Construct new \a MmapDeleter. + /// + /// \param[in] bytes + /// Size of the mmap'ed region in bytes. + MmapDeleter(size_t bytes = 0) : m_bytes(bytes) {} + + /// Unmap the mmap'ed region. + /// + /// If \a m_bytes==0 or \a ptr==nullptr, nothing is unmmapped. + /// + /// \param[in] ptr + /// pointer to the region to be unmmapped. + void operator()(void *ptr); + +private: + /// Size of the mmap'ed region, in bytes, to be unmapped. + size_t m_bytes; +}; + +/// Custom deleter for a file descriptor. +/// +/// This functor type is provided to \a unique_ptr to properly release +/// the resources associated with the file descriptor at destruction time. +class FileDescriptorDeleter { +public: + /// Close and free the memory associated with the file descriptor pointer. + /// + /// Effectively a no-op if \a ptr==nullptr or \a*ptr==-1. + /// + /// \param[in] ptr + /// Pointer to the file descriptor. + void operator()(long *ptr); +}; + +using FileDescriptorUP = + std::unique_ptr<long, resource_handle::FileDescriptorDeleter>; +using MmapUP = std::unique_ptr<void, resource_handle::MmapDeleter>; + +} // namespace resource_handle + +/// Thin wrapper of the perf_event_open API. +/// +/// Exposes the metadata page and data and aux buffers of a perf event. +/// Handles the management of the event's file descriptor and mmap'ed +/// regions. +class PerfEvent { +public: + /// Create a new performance monitoring event via the perf_event_open syscall. + /// + /// The parameters are directly forwarded to a perf_event_open syscall, + /// for additional information on the parameters visit + /// https://man7.org/linux/man-pages/man2/perf_event_open.2.html. + /// + /// \param[in] attr + /// Configuration information for the event. + /// + /// \param[in] pid + /// The process to be monitored by the event. + /// + /// \param[in] cpu + /// The cpu to be monitored by the event. + /// + /// \param[in] group_fd + /// File descriptor of the group leader. + /// + /// \param[in] flags + /// Bitmask of additional configuration flags. + /// + /// \return + /// If the perf_event_open syscall was successful, a minimal \a PerfEvent + /// instance, or an \a llvm::Error otherwise. + static llvm::Expected<PerfEvent> Init(perf_event_attr &attr, lldb::pid_t pid, + int cpu, int group_fd, + unsigned long flags); + + /// Create a new performance monitoring event via the perf_event_open syscall + /// with "default" values for the cpu, group_fd and flags arguments. + /// + /// Convenience method to be used when the perf event requires minimal + /// configuration. It handles the default values of all other arguments. + /// + /// \param[in] attr + /// Configuration information for the event. + /// + /// \param[in] pid + /// The process to be monitored by the event. + static llvm::Expected<PerfEvent> Init(perf_event_attr &attr, lldb::pid_t pid); + + /// Mmap the metadata page and the data and aux buffers of the perf event and + /// expose them through \a PerfEvent::GetMetadataPage() , \a + /// PerfEvent::GetDataBuffer() and \a PerfEvent::GetAuxBuffer(). + /// + /// This uses mmap underneath, which means that the number of pages mmap'ed + /// must be less than the actual data available by the kernel. The metadata + /// page is always mmap'ed. + /// + /// Mmap is needed because the underlying data might be changed by the kernel + /// dynamically. + /// + /// \param[in] num_data_pages + /// Number of pages in the data buffer to mmap, must be a power of 2. + /// A value of 0 is useful for "dummy" events that only want to access + /// the metadata, \a perf_event_mmap_page, or the aux buffer. + /// + /// \param[in] num_aux_pages + /// Number of pages in the aux buffer to mmap, must be a power of 2. + /// A value of 0 effectively is a no-op and no data is mmap'ed for this + /// buffer. + /// + /// \return + /// \a llvm::Error::success if the mmap operations succeeded, + /// or an \a llvm::Error otherwise. + llvm::Error MmapMetadataAndBuffers(size_t num_data_pages, + size_t num_aux_pages); + + /// Get the file descriptor associated with the perf event. + long GetFd() const; + + /// Get the metadata page from the data section's mmap buffer. + /// + /// The metadata page is always mmap'ed, even when \a num_data_pages is 0. + /// + /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers, + /// otherwise a failure might happen. + /// + /// \return + /// The data section's \a perf_event_mmap_page. + perf_event_mmap_page &GetMetadataPage() const; + + /// Get the data buffer from the data section's mmap buffer. + /// + /// The data buffer is the region of the data section's mmap buffer where + /// perf sample data is located. + /// + /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers, + /// otherwise a failure might happen. + /// + /// \return + /// \a ArrayRef<uint8_t> extending \a data_size bytes from \a data_offset. + llvm::ArrayRef<uint8_t> GetDataBuffer() const; + + /// Get the AUX buffer. + /// + /// AUX buffer is a region for high-bandwidth data streams + /// such as IntelPT. This is separate from the metadata and data buffer. + /// + /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers, + /// otherwise a failure might happen. + /// + /// \return + /// \a ArrayRef<uint8_t> extending \a aux_size bytes from \a aux_offset. + llvm::ArrayRef<uint8_t> GetAuxBuffer() const; + +private: + /// Create new \a PerfEvent. + /// + /// \param[in] fd + /// File descriptor of the perf event. + PerfEvent(long fd) + : m_fd(new long(fd), resource_handle::FileDescriptorDeleter()), + m_metadata_data_base(), m_aux_base() {} + + /// Wrapper for \a mmap to provide custom error messages. + /// + /// The parameters are directly forwarded to a \a mmap syscall, + /// for information on the parameters visit + /// https://man7.org/linux/man-pages/man2/mmap.2.html. + /// + /// The value of \a GetFd() is passed as the \a fd argument to \a mmap. + llvm::Expected<resource_handle::MmapUP> DoMmap(void *addr, size_t length, + int prot, int flags, + long int offset, + llvm::StringRef buffer_name); + + /// Mmap the data buffer of the perf event. + /// + /// \param[in] num_data_pages + /// Number of pages in the data buffer to mmap, must be a power of 2. + /// A value of 0 is useful for "dummy" events that only want to access + /// the metadata, \a perf_event_mmap_page, or the aux buffer. + llvm::Error MmapMetadataAndDataBuffer(size_t num_data_pages); + + /// Mmap the aux buffer of the perf event. + /// + /// \param[in] num_aux_pages + /// Number of pages in the aux buffer to mmap, must be a power of 2. + /// A value of 0 effectively is a no-op and no data is mmap'ed for this + /// buffer. + llvm::Error MmapAuxBuffer(size_t num_aux_pages); + + /// The file descriptor representing the perf event. + resource_handle::FileDescriptorUP m_fd; + /// Metadata page and data section where perf samples are stored. + resource_handle::MmapUP m_metadata_data_base; + /// AUX buffer is a separate region for high-bandwidth data streams + /// such as IntelPT. + resource_handle::MmapUP m_aux_base; +}; + +/// TSC to nanoseconds conversion values defined by the Linux perf_event API +/// when the capibilities cap_user_time and cap_user_time_zero are set. See the +/// documentation of `time_zero` in +/// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more +/// information. +struct PerfTscConversionParameters { + uint32_t m_time_mult; + uint16_t m_time_shift; + uint64_t m_time_zero; + + /// Convert TSC value to nanosecond wall time. + /// + /// \a param[in] tsc + /// The TSC value to be converted. + /// + /// \return + /// Nanosecond wall time. + std::chrono::nanoseconds ToWallTime(uint64_t tsc); +}; + +/// Fetch \a PerfTscConversionParameters from \a perf_event_mmap_page, if +/// available. +llvm::Expected<PerfTscConversionParameters> FetchPerfTscConversionParameters(); + +} // namespace process_linux +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H diff --git a/lldb/unittests/Process/Linux/CMakeLists.txt b/lldb/unittests/Process/Linux/CMakeLists.txt index 81f1e4bc5e7e6..30fa7e4ea57d9 100644 --- a/lldb/unittests/Process/Linux/CMakeLists.txt +++ b/lldb/unittests/Process/Linux/CMakeLists.txt @@ -1,9 +1,10 @@ -add_lldb_unittest(TraceIntelPTTests +add_lldb_unittest(ProcessLinuxTests IntelPTCollectorTests.cpp + PerfTests.cpp LINK_LIBS lldbPluginProcessLinux ) -target_include_directories(TraceIntelPTTests PRIVATE +target_include_directories(ProcessLinuxTests PRIVATE ${LLDB_SOURCE_DIR}/source/Plugins/Process/Linux) diff --git a/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp b/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp index 392961b222bf5..2754b958a9d49 100644 --- a/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp +++ b/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp @@ -20,8 +20,8 @@ size_t ReadCylicBufferWrapper(void *buf, size_t buf_size, void *cyc_buf, size_t offset) { llvm::MutableArrayRef<uint8_t> dst(reinterpret_cast<uint8_t *>(buf), buf_size); - llvm::MutableArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(cyc_buf), - cyc_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(); } diff --git a/lldb/unittests/Process/Linux/PerfTests.cpp b/lldb/unittests/Process/Linux/PerfTests.cpp new file mode 100644 index 0000000000000..f8467d7042ef7 --- /dev/null +++ b/lldb/unittests/Process/Linux/PerfTests.cpp @@ -0,0 +1,85 @@ +//===-- PerfTests.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 "Perf.h" + +#include "llvm/Support/Error.h" + +#include "gtest/gtest.h" +#include <chrono> +#include <cstdint> + +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +/// Helper function to read current TSC value. +/// +/// This code is based on llvm/xray. +static Expected<uint64_t> readTsc() { + + unsigned int eax, ebx, ecx, edx; + + // We check whether rdtscp support is enabled. According to the x86_64 manual, + // level should be set at 0x80000001, and we should have a look at bit 27 in + // EDX. That's 0x8000000 (or 1u << 27). + __asm__ __volatile__("cpuid" + : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) + : "0"(0x80000001)); + if (!(edx & (1u << 27))) { + return createStringError(inconvertibleErrorCode(), + "Missing rdtscp support."); + } + + unsigned cpu; + unsigned long rax, rdx; + + __asm__ __volatile__("rdtscp\n" : "=a"(rax), "=d"(rdx), "=c"(cpu)::); + + return (rdx << 32) + rax; +} + +// Test TSC to walltime conversion based on perf conversion values. +TEST(Perf, TscConversion) { + // This test works by first reading the TSC value directly before + // and after sleeping, then converting these values to nanoseconds, and + // finally ensuring the diff erence is approximately equal to the sleep time. + // + // There will be slight overhead associated with the sleep call, so it isn't + // reasonable to expect the diff erence to be exactly equal to the sleep time. + + const int SLEEP_SECS = 1; + std::chrono::nanoseconds SLEEP_NANOS{std::chrono::seconds(SLEEP_SECS)}; + + Expected<PerfTscConversionParameters> params = + FetchPerfTscConversionParameters(); + + // Skip the test if the conversion parameters aren't available. + if (!params) + GTEST_SKIP() << params.takeError(); + + Expected<uint64_t> tsc_before_sleep = readTsc(); + sleep(SLEEP_SECS); + Expected<uint64_t> tsc_after_sleep = readTsc(); + + // Skip the test if we are unable to read the TSC value. + if (!tsc_before_sleep) + GTEST_SKIP() << tsc_before_sleep.takeError(); + if (!tsc_after_sleep) + GTEST_SKIP() << tsc_after_sleep.takeError(); + + std::chrono::nanoseconds converted_tsc_ diff = + params->ToWallTime(*tsc_after_sleep) - + params->ToWallTime(*tsc_before_sleep); + + std::chrono::microseconds acceptable_overhead(500); + + ASSERT_GE(converted_tsc_ diff .count(), SLEEP_NANOS.count()); + ASSERT_LT(converted_tsc_ diff .count(), + (SLEEP_NANOS + acceptable_overhead).count()); +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits