| 1 | //===-- IntelPTSingleBufferTrace.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 "IntelPTSingleBufferTrace.h" |
| 10 | #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" |
| 11 | #include "lldb/Utility/Status.h" |
| 12 | #include "lldb/Utility/StreamString.h" |
| 13 | #include "llvm/Support/MemoryBuffer.h" |
| 14 | #include "llvm/TargetParser/Host.h" |
| 15 | #include <linux/perf_event.h> |
| 16 | #include <sstream> |
| 17 | #include <sys/syscall.h> |
| 18 | #include <unistd.h> |
| 19 | |
| 20 | using namespace lldb; |
| 21 | using namespace lldb_private; |
| 22 | using namespace process_linux; |
| 23 | using namespace llvm; |
| 24 | |
| 25 | const char kOSEventIntelPTTypeFile[] = |
| 26 | "/sys/bus/event_source/devices/intel_pt/type" ; |
| 27 | |
| 28 | const char kPSBPeriodCapFile[] = |
| 29 | "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc" ; |
| 30 | |
| 31 | const char kPSBPeriodValidValuesFile[] = |
| 32 | "/sys/bus/event_source/devices/intel_pt/caps/psb_periods" ; |
| 33 | |
| 34 | const char kPSBPeriodBitOffsetFile[] = |
| 35 | "/sys/bus/event_source/devices/intel_pt/format/psb_period" ; |
| 36 | |
| 37 | const char kTSCBitOffsetFile[] = |
| 38 | "/sys/bus/event_source/devices/intel_pt/format/tsc" ; |
| 39 | |
| 40 | enum IntelPTConfigFileType { |
| 41 | Hex = 0, |
| 42 | // 0 or 1 |
| 43 | ZeroOne, |
| 44 | Decimal, |
| 45 | // a bit index file always starts with the prefix config: following by an int, |
| 46 | // which represents the offset of the perf_event_attr.config value where to |
| 47 | // store a given configuration. |
| 48 | BitOffset |
| 49 | }; |
| 50 | |
| 51 | static Expected<uint32_t> ReadIntelPTConfigFile(const char *file, |
| 52 | IntelPTConfigFileType type) { |
| 53 | ErrorOr<std::unique_ptr<MemoryBuffer>> stream = |
| 54 | MemoryBuffer::getFileAsStream(Filename: file); |
| 55 | |
| 56 | if (!stream) |
| 57 | return createStringError(EC: inconvertibleErrorCode(), |
| 58 | Fmt: "Can't open the file '%s'" , Vals: file); |
| 59 | |
| 60 | uint32_t value = 0; |
| 61 | StringRef text_buffer = stream.get()->getBuffer(); |
| 62 | |
| 63 | if (type == BitOffset) { |
| 64 | const char *prefix = "config:" ; |
| 65 | if (!text_buffer.starts_with(Prefix: prefix)) |
| 66 | return createStringError(EC: inconvertibleErrorCode(), |
| 67 | Fmt: "The file '%s' contents doesn't start with '%s'" , |
| 68 | Vals: file, Vals: prefix); |
| 69 | text_buffer = text_buffer.substr(Start: strlen(s: prefix)); |
| 70 | } |
| 71 | |
| 72 | auto getRadix = [&]() { |
| 73 | switch (type) { |
| 74 | case Hex: |
| 75 | return 16; |
| 76 | case ZeroOne: |
| 77 | case Decimal: |
| 78 | case BitOffset: |
| 79 | return 10; |
| 80 | } |
| 81 | llvm_unreachable("Fully covered switch above!" ); |
| 82 | }; |
| 83 | |
| 84 | auto createError = [&](const char *expected_value_message) { |
| 85 | return createStringError( |
| 86 | EC: inconvertibleErrorCode(), |
| 87 | Fmt: "The file '%s' has an invalid value. It should be %s." , Vals: file, |
| 88 | Vals: expected_value_message); |
| 89 | }; |
| 90 | |
| 91 | if (text_buffer.trim().consumeInteger(Radix: getRadix(), Result&: value) || |
| 92 | (type == ZeroOne && value != 0 && value != 1)) { |
| 93 | switch (type) { |
| 94 | case Hex: |
| 95 | return createError("an unsigned hexadecimal int" ); |
| 96 | case ZeroOne: |
| 97 | return createError("0 or 1" ); |
| 98 | case Decimal: |
| 99 | case BitOffset: |
| 100 | return createError("an unsigned decimal int" ); |
| 101 | } |
| 102 | } |
| 103 | return value; |
| 104 | } |
| 105 | |
| 106 | /// Return the Linux perf event type for Intel PT. |
| 107 | Expected<uint32_t> process_linux::GetIntelPTOSEventType() { |
| 108 | return ReadIntelPTConfigFile(file: kOSEventIntelPTTypeFile, |
| 109 | type: IntelPTConfigFileType::Decimal); |
| 110 | } |
| 111 | |
| 112 | static Error CheckPsbPeriod(size_t psb_period) { |
| 113 | Expected<uint32_t> cap = |
| 114 | ReadIntelPTConfigFile(file: kPSBPeriodCapFile, type: IntelPTConfigFileType::ZeroOne); |
| 115 | if (!cap) |
| 116 | return cap.takeError(); |
| 117 | if (*cap == 0) |
| 118 | return createStringError(EC: inconvertibleErrorCode(), |
| 119 | S: "psb_period is unsupported in the system." ); |
| 120 | |
| 121 | Expected<uint32_t> valid_values = ReadIntelPTConfigFile( |
| 122 | file: kPSBPeriodValidValuesFile, type: IntelPTConfigFileType::Hex); |
| 123 | if (!valid_values) |
| 124 | return valid_values.takeError(); |
| 125 | |
| 126 | if (valid_values.get() & (1 << psb_period)) |
| 127 | return Error::success(); |
| 128 | |
| 129 | std::ostringstream error; |
| 130 | // 0 is always a valid value |
| 131 | error << "Invalid psb_period. Valid values are: 0" ; |
| 132 | uint32_t mask = valid_values.get(); |
| 133 | while (mask) { |
| 134 | int index = __builtin_ctz(mask); |
| 135 | if (index > 0) |
| 136 | error << ", " << index; |
| 137 | // clear the lowest bit |
| 138 | mask &= mask - 1; |
| 139 | } |
| 140 | error << "." ; |
| 141 | return createStringError(EC: inconvertibleErrorCode(), S: error.str().c_str()); |
| 142 | } |
| 143 | |
| 144 | #ifdef PERF_ATTR_SIZE_VER5 |
| 145 | static Expected<uint64_t> |
| 146 | GeneratePerfEventConfigValue(bool enable_tsc, |
| 147 | std::optional<uint64_t> psb_period) { |
| 148 | uint64_t config = 0; |
| 149 | // tsc is always supported |
| 150 | if (enable_tsc) { |
| 151 | if (Expected<uint32_t> offset = ReadIntelPTConfigFile( |
| 152 | file: kTSCBitOffsetFile, type: IntelPTConfigFileType::BitOffset)) |
| 153 | config |= 1 << *offset; |
| 154 | else |
| 155 | return offset.takeError(); |
| 156 | } |
| 157 | if (psb_period) { |
| 158 | if (Error error = CheckPsbPeriod(psb_period: *psb_period)) |
| 159 | return std::move(error); |
| 160 | |
| 161 | if (Expected<uint32_t> offset = ReadIntelPTConfigFile( |
| 162 | file: kPSBPeriodBitOffsetFile, type: IntelPTConfigFileType::BitOffset)) |
| 163 | config |= *psb_period << *offset; |
| 164 | else |
| 165 | return offset.takeError(); |
| 166 | } |
| 167 | return config; |
| 168 | } |
| 169 | |
| 170 | /// Create a \a perf_event_attr configured for |
| 171 | /// an IntelPT event. |
| 172 | /// |
| 173 | /// \return |
| 174 | /// A \a perf_event_attr if successful, |
| 175 | /// or an \a llvm::Error otherwise. |
| 176 | static Expected<perf_event_attr> |
| 177 | CreateIntelPTPerfEventConfiguration(bool enable_tsc, |
| 178 | std::optional<uint64_t> psb_period) { |
| 179 | perf_event_attr attr; |
| 180 | memset(s: &attr, c: 0, n: sizeof(attr)); |
| 181 | attr.size = sizeof(attr); |
| 182 | attr.exclude_kernel = 1; |
| 183 | attr.exclude_hv = 1; |
| 184 | attr.exclude_idle = 1; |
| 185 | |
| 186 | if (Expected<uint64_t> config_value = |
| 187 | GeneratePerfEventConfigValue(enable_tsc, psb_period)) |
| 188 | attr.config = *config_value; |
| 189 | else |
| 190 | return config_value.takeError(); |
| 191 | |
| 192 | if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType()) |
| 193 | attr.type = *intel_pt_type; |
| 194 | else |
| 195 | return intel_pt_type.takeError(); |
| 196 | |
| 197 | return attr; |
| 198 | } |
| 199 | #endif |
| 200 | |
| 201 | size_t IntelPTSingleBufferTrace::GetIptTraceSize() const { |
| 202 | return m_perf_event.GetAuxBuffer().size(); |
| 203 | } |
| 204 | |
| 205 | Error IntelPTSingleBufferTrace::Pause() { |
| 206 | return m_perf_event.DisableWithIoctl(); |
| 207 | } |
| 208 | |
| 209 | Error IntelPTSingleBufferTrace::Resume() { |
| 210 | return m_perf_event.EnableWithIoctl(); |
| 211 | } |
| 212 | |
| 213 | Expected<std::vector<uint8_t>> IntelPTSingleBufferTrace::GetIptTrace() { |
| 214 | // Disable the perf event to force a flush out of the CPU's internal buffer. |
| 215 | // Besides, we can guarantee that the CPU won't override any data as we are |
| 216 | // reading the buffer. |
| 217 | // The Intel documentation says: |
| 218 | // |
| 219 | // Packets are first buffered internally and then written out |
| 220 | // asynchronously. To collect packet output for postprocessing, a collector |
| 221 | // needs first to ensure that all packet data has been flushed from internal |
| 222 | // buffers. Software can ensure this by stopping packet generation by |
| 223 | // clearing IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in |
| 224 | // Section 35.2.7.2). |
| 225 | // |
| 226 | // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as |
| 227 | // mentioned in the man page of perf_event_open. |
| 228 | return m_perf_event.GetReadOnlyAuxBuffer(); |
| 229 | } |
| 230 | |
| 231 | Expected<IntelPTSingleBufferTrace> |
| 232 | IntelPTSingleBufferTrace::Start(const TraceIntelPTStartRequest &request, |
| 233 | std::optional<lldb::tid_t> tid, |
| 234 | std::optional<cpu_id_t> cpu_id, bool disabled, |
| 235 | std::optional<int> cgroup_fd) { |
| 236 | #ifndef PERF_ATTR_SIZE_VER5 |
| 237 | return createStringError(inconvertibleErrorCode(), |
| 238 | "Intel PT Linux perf event not supported" ); |
| 239 | #else |
| 240 | Log *log = GetLog(mask: POSIXLog::Trace); |
| 241 | |
| 242 | LLDB_LOG(log, "Will start tracing thread id {0} and cpu id {1}" , tid, cpu_id); |
| 243 | |
| 244 | if (__builtin_popcount(request.ipt_trace_size) != 1 || |
| 245 | request.ipt_trace_size < 4096) { |
| 246 | return createStringError( |
| 247 | EC: inconvertibleErrorCode(), |
| 248 | Fmt: "The intel pt trace size must be a power of 2 greater than or equal to " |
| 249 | "4096 (2^12) bytes. It was %" PRIu64 "." , |
| 250 | Vals: request.ipt_trace_size); |
| 251 | } |
| 252 | uint64_t page_size = getpagesize(); |
| 253 | uint64_t aux_buffer_numpages = static_cast<uint64_t>(llvm::bit_floor( |
| 254 | Value: (request.ipt_trace_size + page_size - 1) / page_size)); |
| 255 | |
| 256 | Expected<perf_event_attr> attr = CreateIntelPTPerfEventConfiguration( |
| 257 | enable_tsc: request.enable_tsc, |
| 258 | psb_period: llvm::transformOptional(O: request.psb_period, F: [](int value) { |
| 259 | return static_cast<uint64_t>(value); |
| 260 | })); |
| 261 | if (!attr) |
| 262 | return attr.takeError(); |
| 263 | attr->disabled = disabled; |
| 264 | |
| 265 | LLDB_LOG(log, "Will create intel pt trace buffer of size {0}" , |
| 266 | request.ipt_trace_size); |
| 267 | unsigned long flags = 0; |
| 268 | if (cgroup_fd) { |
| 269 | tid = *cgroup_fd; |
| 270 | flags |= PERF_FLAG_PID_CGROUP; |
| 271 | } |
| 272 | |
| 273 | if (Expected<PerfEvent> perf_event = |
| 274 | PerfEvent::Init(attr&: *attr, pid: tid, cpu: cpu_id, group_fd: -1, flags)) { |
| 275 | if (Error mmap_err = perf_event->MmapMetadataAndBuffers( |
| 276 | /*num_data_pages=*/0, num_aux_pages: aux_buffer_numpages, |
| 277 | /*data_buffer_write=*/true)) { |
| 278 | return std::move(mmap_err); |
| 279 | } |
| 280 | return IntelPTSingleBufferTrace(std::move(*perf_event)); |
| 281 | } else { |
| 282 | return perf_event.takeError(); |
| 283 | } |
| 284 | #endif |
| 285 | } |
| 286 | |
| 287 | const PerfEvent &IntelPTSingleBufferTrace::GetPerfEvent() const { |
| 288 | return m_perf_event; |
| 289 | } |
| 290 | |