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,6 +9,7 @@ #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" @@ -32,34 +33,7 @@ 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; + PerfEvent m_perf_event; lldb::tid_t m_tid; /// Start tracing a thread @@ -84,12 +58,8 @@ 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()) {} + IntelPTThreadTrace(PerfEvent &&perf_event, lldb::tid_t tid) + : m_perf_event(std::move(perf_event)), m_tid(tid) {} public: /// Get the content of /proc/cpuinfo that can be later used to decode traces. @@ -146,7 +116,7 @@ /// \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. Index: lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp +++ lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include "llvm/ADT/StringRef.h" @@ -15,6 +17,7 @@ #include "llvm/Support/MathExtras.h" #include "IntelPTCollector.h" +#include "Perf.h" #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" #include "lldb/Host/linux/Support.h" #include "lldb/Utility/StreamString.h" @@ -106,6 +109,7 @@ } return value; } + /// Return the Linux perf event type for Intel PT. static Expected GetOSEventType() { return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, @@ -148,7 +152,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,17 +180,29 @@ return config; } -Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid, - uint64_t buffer_size, bool enable_tsc, - Optional psb_period) { +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())); + } + return *cpu_info; +} + +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); - 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( @@ -195,10 +211,11 @@ "4096 (2^12) bytes. It was %" PRIu64 ".", buffer_size); } - uint64_t numpages = static_cast( + // TODO: change the buffer_size argument to be a number of pages instead of + // bytes since the perf wrapper APIs operate on number of pages. + uint64_t page_size = getpagesize(); + uint64_t buffer_numpages = static_cast( llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size)); - numpages = std::max(1, numpages); - buffer_size = page_size * numpages; perf_event_attr attr; memset(&attr, 0, sizeof(attr)); @@ -227,94 +244,19 @@ 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"); + if (auto perf_event_expected = PerfEvent::InitPerfEvent(attr, tid)) { + auto perf_event = std::move(perf_event_expected.get()); + if (Error mmap_err = perf_event.mmap(buffer_numpages, buffer_numpages)) { + return std::move(mmap_err); + } + return IntelPTThreadTraceUP( + new IntelPTThreadTrace(std::move(perf_event), tid)); + } else { + return perf_event_expected.takeError(); } - m_mmap_aux = std::unique_ptr( - reinterpret_cast(mmap_aux), munmap_delete(buffer_size)); - return Error::success(); #endif } -llvm::MutableArrayRef IntelPTThreadTrace::GetDataBuffer() const { -#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 -} - -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 -} - -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())); - } - return *cpu_info; -} - -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()); - - if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size, - enable_tsc, psb_period)) - return std::move(err); - - return std::move(thread_trace_up); -} - Expected> IntelPTThreadTrace::GetIntelPTBuffer(size_t offset, size_t size) const { std::vector data(size, 0); @@ -331,6 +273,8 @@ #ifndef PERF_ATTR_SIZE_VER5 llvm_unreachable("perf event not supported"); #else + auto fd = m_perf_event.GetFd(); + auto mmap_meta = 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 +290,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_meta->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_meta->aux_size, head); /** * When configured as ring buffer, the aux buffer keeps wrapping around @@ -366,11 +310,12 @@ * * */ - ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast(head), offset); + 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 +330,8 @@ uint64_t bytes_remaining = buffer.size(); Status error; - uint64_t head = m_mmap_meta->data_head; + auto mmap_meta = m_perf_event.GetMetadataPage(); + uint64_t head = mmap_meta->data_head; /* * The data buffer and aux buffer have different implementations @@ -397,11 +343,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_meta->data_size, head); ReadCyclicBuffer(buffer, data_buffer, static_cast(head), offset); bytes_remaining -= buffer.size(); @@ -424,7 +370,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 +396,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()) { Index: lldb/source/Plugins/Process/Linux/Perf.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Linux/Perf.h @@ -0,0 +1,158 @@ +//===-- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef lldb_Perf_H_ +#define lldb_Perf_H_ + +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" + +#include +#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: + MmapDeleter(size_t length) : m_length(length) {} + void operator()(void *ptr) { + if (m_length) + munmap(ptr, m_length); + } + +private: + size_t m_length; +}; + +/// 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. +// TODO: rename this to fd deleter +class FileDescriptorDeleter { +public: + FileDescriptorDeleter() = default; + void operator()(long *ptr) { + if (ptr == nullptr) + return; + if (*ptr == -1) + return; + close(*ptr); + std::default_delete()(ptr); + } +}; + +using FileDescriptorUP = + std::unique_ptr; +template +using MmapUP = std::unique_ptr; + +} // namespace resource_handle + +/// Thin wrapper of the perf_event_open API. +/// +/// Exposes the data/aux buffers and handles the management of the +/// resources associated with the event (fd, mmap). +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, + /// so 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 \a PerfEvent + /// with \a m_fd set to the syscall's result and its mmap pointers set to + /// \a nullptr is returned, or an \a llvm::Error otherwise. Note: + /// PerfEvent::mmap() must be called before accessing \a m_mmap_meta or \a + /// m_aux_buffers. + static llvm::Expected InitPerfEvent(perf_event_attr &attr, + lldb::pid_t pid, int cpu, + int group_fd, + unsigned long flags); + static llvm::Expected InitPerfEvent(perf_event_attr &attr, + lldb::pid_t pid) { + return InitPerfEvent(attr, pid, -1, -1, 0); + }; + + /// Create ring buffers via mmap. + /// + /// \param[in] data_pages + /// Number of pages in the data buffer. + /// This value *should not* include the one additional page for the \a + /// struct perf_event_mmap_page. + /// + /// \param[in] aux_pages + /// Number of pages in the aux buffer. + /// + /// \return + /// \a llvm::Error::success if the mmap pointers are correctly set, + /// or an \a llvm::Error otherwise. + llvm::Error mmap(size_t data_pages, size_t aux_pages); + long GetFd() const; + perf_event_mmap_page *GetMetadataPage() const; + llvm::ArrayRef GetDataBuffer() const; + llvm::ArrayRef GetAuxBuffer() const; + +private: + resource_handle::FileDescriptorUP m_fd; + resource_handle::MmapUP m_mmap_meta; + resource_handle::MmapUP m_aux_buffer; + PerfEvent(long fd) + : m_fd(new long(fd), resource_handle::FileDescriptorDeleter()), + m_mmap_meta(nullptr, resource_handle::MmapDeleter(0)), + m_aux_buffer(nullptr, resource_handle::MmapDeleter(0)) {} +}; + +/// 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; +}; + +/// Fetch \a PerfTscConversionParameters from \a perf_event_mmap_page, if +/// available. +/// +/// \param[in] pid +/// pid used to make the perf_event_open syscall. +llvm::Expected +FetchPerfTscConversionParameters(lldb::pid_t pid); + +} // namespace process_linux +} // namespace lldb_private + +#endif // #ifndef lldb_Perf_H_ Index: lldb/source/Plugins/Process/Linux/Perf.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Linux/Perf.cpp @@ -0,0 +1,113 @@ +//===-- 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 "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" + +#include +#include +#include + +using namespace lldb_private; +using namespace process_linux; +using namespace llvm; + +Expected +FetchPerfTscConversionParameters(lldb::pid_t pid) { + perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.type = PERF_TYPE_SOFTWARE; + attr.config = PERF_COUNT_SW_DUMMY; + + auto perf_event = PerfEvent::InitPerfEvent(attr, pid); + if (!perf_event) + return perf_event.takeError(); + if (Error mmap_err = perf_event->mmap(1, 0)) + return std::move(mmap_err); + + // Successful call to PerfEvent::mmap gurantees this is not nullptr + perf_event_mmap_page *perf_mmap_meta = perf_event->GetMetadataPage(); + if (perf_mmap_meta->cap_user_time && perf_mmap_meta->cap_user_time_zero) { + return PerfTscConversionParameters{perf_mmap_meta->time_mult, + perf_mmap_meta->time_shift, + perf_mmap_meta->time_zero}; + } else { + auto err_cap = + !perf_mmap_meta->cap_user_time ? "cap_user_time" : "cap_user_time_zero"; + std::string err_msg = llvm::formatv( + "{0} not supported. TSC cannot be converted to time unit", err_cap); + return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); + } +} + +llvm::Expected PerfEvent::InitPerfEvent(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}; +} + +Error PerfEvent::mmap(size_t data_pages, size_t aux_pages) { + const int page_size = getpagesize(); + const size_t data_mmap_size = (1 + data_pages) * page_size; + const size_t aux_mmap_size = aux_pages * page_size; + const long fd = GetFd(); + + errno = 0; + auto base = ::mmap(nullptr, data_mmap_size, PROT_WRITE, MAP_SHARED, fd, 0); + + if (base == MAP_FAILED) { + std::string err_msg = llvm::formatv("perf data mmap allocation failed: {0}", + std::strerror(errno)); + return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); + } + m_mmap_meta = resource_handle::MmapUP( + reinterpret_cast(base), data_mmap_size); + + m_mmap_meta->aux_offset = m_mmap_meta->data_offset + m_mmap_meta->data_size; + m_mmap_meta->aux_size = aux_mmap_size; + + errno = 0; + auto mmap_aux = ::mmap(nullptr, aux_mmap_size, PROT_READ, MAP_SHARED, fd, + static_cast(m_mmap_meta->aux_offset)); + + if (mmap_aux == MAP_FAILED) { + std::string err_msg = llvm::formatv("perf aux mmap allocation failed: {0}", + std::strerror(errno)); + return createStringError(inconvertibleErrorCode(), err_msg); + } + m_aux_buffer = resource_handle::MmapUP( + reinterpret_cast(mmap_aux), aux_mmap_size); + + return Error::success(); +} +long PerfEvent::GetFd() const { return *(m_fd.get()); } + +perf_event_mmap_page *PerfEvent::GetMetadataPage() const { + return m_mmap_meta ? m_mmap_meta.get() : nullptr; +} + +ArrayRef PerfEvent::GetDataBuffer() const { + return {reinterpret_cast(m_mmap_meta.get()) + + m_mmap_meta->data_offset, + m_mmap_meta->data_size}; +} + +ArrayRef PerfEvent::GetAuxBuffer() const { + return {m_aux_buffer.get(), m_mmap_meta->aux_size}; +} 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(); }