1 //===-- Perf.cpp ----------------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "Perf.h" 10 11 #include "lldb/Host/linux/Support.h" 12 13 #include "llvm/Support/FormatVariadic.h" 14 #include "llvm/Support/MathExtras.h" 15 #include "llvm/Support/MemoryBuffer.h" 16 17 #include <sys/mman.h> 18 #include <sys/syscall.h> 19 #include <unistd.h> 20 21 using namespace lldb_private; 22 using namespace process_linux; 23 using namespace llvm; 24 25 Expected<LinuxPerfZeroTscConversion> 26 lldb_private::process_linux::LoadPerfTscConversionParameters() { 27 lldb::pid_t pid = getpid(); 28 perf_event_attr attr; 29 memset(&attr, 0, sizeof(attr)); 30 attr.size = sizeof(attr); 31 attr.type = PERF_TYPE_SOFTWARE; 32 attr.config = PERF_COUNT_SW_DUMMY; 33 34 Expected<PerfEvent> perf_event = PerfEvent::Init(attr, pid); 35 if (!perf_event) 36 return perf_event.takeError(); 37 if (Error mmap_err = perf_event->MmapMetadataAndBuffers(/*num_data_pages*/ 0, 38 /*num_aux_pages*/ 0)) 39 return std::move(mmap_err); 40 41 perf_event_mmap_page &mmap_metada = perf_event->GetMetadataPage(); 42 if (mmap_metada.cap_user_time && mmap_metada.cap_user_time_zero) { 43 return LinuxPerfZeroTscConversion{ 44 mmap_metada.time_mult, mmap_metada.time_shift, mmap_metada.time_zero}; 45 } else { 46 auto err_cap = 47 !mmap_metada.cap_user_time ? "cap_user_time" : "cap_user_time_zero"; 48 std::string err_msg = 49 llvm::formatv("Can't get TSC to real time conversion values. " 50 "perf_event capability '{0}' not supported.", 51 err_cap); 52 return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); 53 } 54 } 55 56 void resource_handle::MmapDeleter::operator()(void *ptr) { 57 if (m_bytes && ptr != nullptr) 58 munmap(ptr, m_bytes); 59 } 60 61 void resource_handle::FileDescriptorDeleter::operator()(long *ptr) { 62 if (ptr == nullptr) 63 return; 64 if (*ptr == -1) 65 return; 66 close(*ptr); 67 std::default_delete<long>()(ptr); 68 } 69 70 llvm::Expected<PerfEvent> PerfEvent::Init(perf_event_attr &attr, 71 lldb::pid_t pid, int cpu, 72 int group_fd, unsigned long flags) { 73 errno = 0; 74 long fd = syscall(SYS_perf_event_open, &attr, pid, cpu, group_fd, flags); 75 if (fd == -1) { 76 std::string err_msg = 77 llvm::formatv("perf event syscall failed: {0}", std::strerror(errno)); 78 return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg); 79 } 80 return PerfEvent{fd}; 81 } 82 83 llvm::Expected<PerfEvent> PerfEvent::Init(perf_event_attr &attr, 84 lldb::pid_t pid) { 85 return Init(attr, pid, -1, -1, 0); 86 } 87 88 llvm::Expected<resource_handle::MmapUP> 89 PerfEvent::DoMmap(void *addr, size_t length, int prot, int flags, 90 long int offset, llvm::StringRef buffer_name) { 91 errno = 0; 92 auto mmap_result = ::mmap(nullptr, length, prot, flags, GetFd(), offset); 93 94 if (mmap_result == MAP_FAILED) { 95 std::string err_msg = 96 llvm::formatv("perf event mmap allocation failed for {0}: {1}", 97 buffer_name, std::strerror(errno)); 98 return createStringError(inconvertibleErrorCode(), err_msg); 99 } 100 return resource_handle::MmapUP(mmap_result, length); 101 } 102 103 llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages) { 104 size_t mmap_size = (num_data_pages + 1) * getpagesize(); 105 if (Expected<resource_handle::MmapUP> mmap_metadata_data = 106 DoMmap(nullptr, mmap_size, PROT_WRITE, MAP_SHARED, 0, 107 "metadata and data buffer")) { 108 m_metadata_data_base = std::move(mmap_metadata_data.get()); 109 return Error::success(); 110 } else 111 return mmap_metadata_data.takeError(); 112 } 113 114 llvm::Error PerfEvent::MmapAuxBuffer(size_t num_aux_pages) { 115 if (num_aux_pages == 0) 116 return Error::success(); 117 118 perf_event_mmap_page &metadata_page = GetMetadataPage(); 119 metadata_page.aux_offset = 120 metadata_page.data_offset + metadata_page.data_size; 121 metadata_page.aux_size = num_aux_pages * getpagesize(); 122 123 if (Expected<resource_handle::MmapUP> mmap_aux = 124 DoMmap(nullptr, metadata_page.aux_size, PROT_READ, MAP_SHARED, 125 metadata_page.aux_offset, "aux buffer")) { 126 m_aux_base = std::move(mmap_aux.get()); 127 return Error::success(); 128 } else 129 return mmap_aux.takeError(); 130 } 131 132 llvm::Error PerfEvent::MmapMetadataAndBuffers(size_t num_data_pages, 133 size_t num_aux_pages) { 134 if (num_data_pages != 0 && !isPowerOf2_64(num_data_pages)) 135 return llvm::createStringError( 136 llvm::inconvertibleErrorCode(), 137 llvm::formatv("Number of data pages must be a power of 2, got: {0}", 138 num_data_pages)); 139 if (num_aux_pages != 0 && !isPowerOf2_64(num_aux_pages)) 140 return llvm::createStringError( 141 llvm::inconvertibleErrorCode(), 142 llvm::formatv("Number of aux pages must be a power of 2, got: {0}", 143 num_aux_pages)); 144 if (Error err = MmapMetadataAndDataBuffer(num_data_pages)) 145 return err; 146 if (Error err = MmapAuxBuffer(num_aux_pages)) 147 return err; 148 return Error::success(); 149 } 150 151 long PerfEvent::GetFd() const { return *(m_fd.get()); } 152 153 perf_event_mmap_page &PerfEvent::GetMetadataPage() const { 154 return *reinterpret_cast<perf_event_mmap_page *>(m_metadata_data_base.get()); 155 } 156 157 ArrayRef<uint8_t> PerfEvent::GetDataBuffer() const { 158 perf_event_mmap_page &mmap_metadata = GetMetadataPage(); 159 return {reinterpret_cast<uint8_t *>(m_metadata_data_base.get()) + 160 mmap_metadata.data_offset, 161 static_cast<size_t>(mmap_metadata.data_size)}; 162 } 163 164 ArrayRef<uint8_t> PerfEvent::GetAuxBuffer() const { 165 perf_event_mmap_page &mmap_metadata = GetMetadataPage(); 166 return {reinterpret_cast<uint8_t *>(m_aux_base.get()), 167 static_cast<size_t>(mmap_metadata.aux_size)}; 168 } 169