Index: lldb/source/Plugins/Process/Linux/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Process/Linux/CMakeLists.txt +++ lldb/source/Plugins/Process/Linux/CMakeLists.txt @@ -8,6 +8,7 @@ NativeRegisterContextLinux_s390x.cpp NativeRegisterContextLinux_x86_64.cpp NativeThreadLinux.cpp + Perf.cpp SingleStepCheck.cpp LINK_LIBS Index: lldb/source/Plugins/Process/Linux/IntelPTCollector.h =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTCollector.h +++ 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 #include @@ -31,42 +31,15 @@ typedef std::unique_ptr 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()(ptr); - } - }; - - std::unique_ptr m_mmap_meta; - std::unique_ptr m_mmap_aux; - std::unique_ptr 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 @@ /// 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 psb_period); - - llvm::MutableArrayRef GetAuxBuffer() const; - llvm::MutableArrayRef 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> 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 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 @@ -146,11 +108,29 @@ /// \param[in] offset /// The offset to begin reading the data in the cyclic buffer. static void ReadCyclicBuffer(llvm::MutableArrayRef &dst, - llvm::MutableArrayRef src, + 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. Index: lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -6,19 +6,23 @@ // //===----------------------------------------------------------------------===// -#include -#include -#include +#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 +#include +#include +#include +#include #include #include @@ -53,6 +57,21 @@ BitOffset }; +/// Get the content of /proc/cpuinfo that can be later used to decode traces. +static Expected> GetCPUInfo() { + static llvm::Optional> 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( + reinterpret_cast(buffer.getBufferStart()), + reinterpret_cast(buffer.getBufferEnd())); + } + return *cpu_info; +} + static Expected ReadIntelPTConfigFile(const char *file, IntelPTConfigFileType type) { ErrorOr> stream = @@ -106,6 +125,7 @@ } return value; } + /// Return the Linux perf event type for Intel PT. static Expected GetOSEventType() { return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, @@ -148,7 +168,7 @@ #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 @@ return config; } -Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_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); - - 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( - llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); - numpages = std::max(1, numpages); - buffer_size = page_size * numpages; - +llvm::Expected +IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration( + bool enable_tsc, Optional psb_period) { perf_event_attr attr; memset(&attr, 0, sizeof(attr)); attr.size = sizeof(attr); @@ -213,106 +212,59 @@ if (Expected 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 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(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( - reinterpret_cast(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(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( - reinterpret_cast(mmap_aux), munmap_delete(buffer_size)); - return Error::success(); -#endif + return attr; } -llvm::MutableArrayRef IntelPTThreadTrace::GetDataBuffer() const { +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 - return MutableArrayRef( - (reinterpret_cast(m_mmap_meta.get()) + - m_mmap_meta->data_offset), - m_mmap_meta->data_size); -#endif -} + Log *log = GetLog(POSIXLog::Ptrace); -llvm::MutableArrayRef IntelPTThreadTrace::GetAuxBuffer() const { -#ifndef PERF_ATTR_SIZE_VER5 - llvm_unreachable("Intel PT Linux perf event not supported"); -#else - return MutableArrayRef(m_mmap_aux.get(), m_mmap_meta->aux_size); -#endif -} + LLDB_LOG(log, "called thread id {0}", tid); -Expected> IntelPTThreadTrace::GetCPUInfo() { - static llvm::Optional> 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( - reinterpret_cast(buffer.getBufferStart()), - reinterpret_cast(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( + llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); -llvm::Expected -IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, - bool enable_tsc, Optional psb_period) { - IntelPTThreadTraceUP thread_trace_up(new IntelPTThreadTrace()); + Expected 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 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> @@ -331,6 +283,8 @@ #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 @@ // // 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 @@ * * */ - ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast(head), offset); - LLDB_LOG(log, "ReadCyclic BUffer Done"); + 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(*m_fd, PERF_EVENT_IOC_ENABLE); + ioctl(fd, PERF_EVENT_IOC_ENABLE); return error; #endif } @@ -385,7 +340,8 @@ 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 different implementations @@ -397,11 +353,11 @@ 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(head), offset); bytes_remaining -= buffer.size(); @@ -424,7 +380,7 @@ } void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef &dst, - llvm::MutableArrayRef src, + llvm::ArrayRef src, size_t src_cyc_index, size_t offset) { Log *log = GetLog(POSIXLog::Ptrace); @@ -450,7 +406,7 @@ return; } - llvm::SmallVector, 2> parts = { + llvm::SmallVector, 2> parts = { src.slice(src_cyc_index), src.take_front(src_cyc_index)}; if (offset > parts[0].size()) { @@ -624,7 +580,7 @@ } Expected IntelPTCollector::GetState() const { - Expected> cpu_info = IntelPTThreadTrace::GetCPUInfo(); + Expected> cpu_info = GetCPUInfo(); if (!cpu_info) return cpu_info.takeError(); @@ -661,7 +617,7 @@ else return trace.takeError(); } else if (request.kind == "cpuInfo") { - return IntelPTThreadTrace::GetCPUInfo(); + return GetCPUInfo(); } return createStringError(inconvertibleErrorCode(), "Unsuported trace binary data kind: %s", Index: lldb/source/Plugins/Process/Linux/Perf.h =================================================================== --- /dev/null +++ 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 +#include +#include + +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; +using MmapUP = std::unique_ptr; + +} // 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 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 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 extending \a data_size bytes from \a data_offset. + llvm::ArrayRef 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 extending \a aux_size bytes from \a aux_offset. + llvm::ArrayRef 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 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 FetchPerfTscConversionParameters(); + +} // namespace process_linux +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H Index: lldb/source/Plugins/Process/Linux/Perf.cpp =================================================================== --- /dev/null +++ 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 +#include +#include +#include +#include +#include + +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +Expected +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 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()(ptr); +} + +llvm::Expected 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::Init(perf_event_attr &attr, + lldb::pid_t pid) { + return Init(attr, pid, -1, -1, 0); +} + +llvm::Expected +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 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 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(m_metadata_data_base.get()); +} + +ArrayRef PerfEvent::GetDataBuffer() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + return {reinterpret_cast(m_metadata_data_base.get()) + + mmap_metadata.data_offset, + mmap_metadata.data_size}; +} + +ArrayRef PerfEvent::GetAuxBuffer() const { + perf_event_mmap_page &mmap_metadata = GetMetadataPage(); + return {reinterpret_cast(m_aux_base.get()), + mmap_metadata.aux_size}; +} Index: lldb/unittests/Process/Linux/CMakeLists.txt =================================================================== --- lldb/unittests/Process/Linux/CMakeLists.txt +++ 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) Index: lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp =================================================================== --- lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp +++ lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp @@ -20,8 +20,8 @@ size_t offset) { llvm::MutableArrayRef dst(reinterpret_cast(buf), buf_size); - llvm::MutableArrayRef src(reinterpret_cast(cyc_buf), - cyc_buf_size); + llvm::ArrayRef src(reinterpret_cast(cyc_buf), + cyc_buf_size); IntelPTThreadTrace::ReadCyclicBuffer(dst, src, cyc_start, offset); return dst.size(); } Index: lldb/unittests/Process/Linux/PerfTests.cpp =================================================================== --- /dev/null +++ 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 +#include + +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 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 difference 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 difference to be exactly equal to the sleep time. + + const int SLEEP_SECS = 1; + std::chrono::nanoseconds SLEEP_NANOS{std::chrono::seconds(SLEEP_SECS)}; + + Expected params = + FetchPerfTscConversionParameters(); + + // Skip the test if the conversion parameters aren't available. + if (!params) + GTEST_SKIP() << params.takeError(); + + Expected tsc_before_sleep = readTsc(); + sleep(SLEEP_SECS); + Expected 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()); +}