| //===-- IntelPTSingleBufferTrace.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 "IntelPTSingleBufferTrace.h" |
| |
| #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" |
| #include "lldb/Utility/Status.h" |
| #include "lldb/Utility/StreamString.h" |
| |
| #include "llvm/Support/Host.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| |
| #include <sstream> |
| |
| #include <linux/perf_event.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace process_linux; |
| using namespace llvm; |
| |
| const char *kOSEventIntelPTTypeFile = |
| "/sys/bus/event_source/devices/intel_pt/type"; |
| |
| const char *kPSBPeriodCapFile = |
| "/sys/bus/event_source/devices/intel_pt/caps/psb_cyc"; |
| |
| const char *kPSBPeriodValidValuesFile = |
| "/sys/bus/event_source/devices/intel_pt/caps/psb_periods"; |
| |
| const char *kPSBPeriodBitOffsetFile = |
| "/sys/bus/event_source/devices/intel_pt/format/psb_period"; |
| |
| const char *kTSCBitOffsetFile = |
| "/sys/bus/event_source/devices/intel_pt/format/tsc"; |
| |
| enum IntelPTConfigFileType { |
| Hex = 0, |
| // 0 or 1 |
| ZeroOne, |
| Decimal, |
| // a bit index file always starts with the prefix config: following by an int, |
| // which represents the offset of the perf_event_attr.config value where to |
| // store a given configuration. |
| BitOffset |
| }; |
| |
| static Expected<uint32_t> ReadIntelPTConfigFile(const char *file, |
| IntelPTConfigFileType type) { |
| ErrorOr<std::unique_ptr<MemoryBuffer>> stream = |
| MemoryBuffer::getFileAsStream(file); |
| |
| if (!stream) |
| return createStringError(inconvertibleErrorCode(), |
| "Can't open the file '%s'", file); |
| |
| uint32_t value = 0; |
| StringRef text_buffer = stream.get()->getBuffer(); |
| |
| if (type == BitOffset) { |
| const char *prefix = "config:"; |
| if (!text_buffer.startswith(prefix)) |
| return createStringError(inconvertibleErrorCode(), |
| "The file '%s' contents doesn't start with '%s'", |
| file, prefix); |
| text_buffer = text_buffer.substr(strlen(prefix)); |
| } |
| |
| auto getRadix = [&]() { |
| switch (type) { |
| case Hex: |
| return 16; |
| case ZeroOne: |
| case Decimal: |
| case BitOffset: |
| return 10; |
| } |
| llvm_unreachable("Fully covered switch above!"); |
| }; |
| |
| auto createError = [&](const char *expected_value_message) { |
| return createStringError( |
| inconvertibleErrorCode(), |
| "The file '%s' has an invalid value. It should be %s.", file, |
| expected_value_message); |
| }; |
| |
| if (text_buffer.trim().consumeInteger(getRadix(), value) || |
| (type == ZeroOne && value != 0 && value != 1)) { |
| switch (type) { |
| case Hex: |
| return createError("an unsigned hexadecimal int"); |
| case ZeroOne: |
| return createError("0 or 1"); |
| case Decimal: |
| case BitOffset: |
| return createError("an unsigned decimal int"); |
| } |
| } |
| return value; |
| } |
| |
| /// Return the Linux perf event type for Intel PT. |
| Expected<uint32_t> process_linux::GetIntelPTOSEventType() { |
| return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile, |
| IntelPTConfigFileType::Decimal); |
| } |
| |
| static Error CheckPsbPeriod(size_t psb_period) { |
| Expected<uint32_t> cap = |
| ReadIntelPTConfigFile(kPSBPeriodCapFile, IntelPTConfigFileType::ZeroOne); |
| if (!cap) |
| return cap.takeError(); |
| if (*cap == 0) |
| return createStringError(inconvertibleErrorCode(), |
| "psb_period is unsupported in the system."); |
| |
| Expected<uint32_t> valid_values = ReadIntelPTConfigFile( |
| kPSBPeriodValidValuesFile, IntelPTConfigFileType::Hex); |
| if (!valid_values) |
| return valid_values.takeError(); |
| |
| if (valid_values.get() & (1 << psb_period)) |
| return Error::success(); |
| |
| std::ostringstream error; |
| // 0 is always a valid value |
| error << "Invalid psb_period. Valid values are: 0"; |
| uint32_t mask = valid_values.get(); |
| while (mask) { |
| int index = __builtin_ctz(mask); |
| if (index > 0) |
| error << ", " << index; |
| // clear the lowest bit |
| mask &= mask - 1; |
| } |
| error << "."; |
| return createStringError(inconvertibleErrorCode(), error.str().c_str()); |
| } |
| |
| #ifdef PERF_ATTR_SIZE_VER5 |
| static Expected<uint64_t> |
| GeneratePerfEventConfigValue(bool enable_tsc, Optional<uint64_t> psb_period) { |
| uint64_t config = 0; |
| // tsc is always supported |
| if (enable_tsc) { |
| if (Expected<uint32_t> offset = ReadIntelPTConfigFile( |
| kTSCBitOffsetFile, IntelPTConfigFileType::BitOffset)) |
| config |= 1 << *offset; |
| else |
| return offset.takeError(); |
| } |
| if (psb_period) { |
| if (Error error = CheckPsbPeriod(*psb_period)) |
| return std::move(error); |
| |
| if (Expected<uint32_t> offset = ReadIntelPTConfigFile( |
| kPSBPeriodBitOffsetFile, IntelPTConfigFileType::BitOffset)) |
| config |= *psb_period << *offset; |
| else |
| return offset.takeError(); |
| } |
| return config; |
| } |
| |
| /// 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 Expected<perf_event_attr> |
| CreateIntelPTPerfEventConfiguration(bool enable_tsc, |
| llvm::Optional<uint64_t> psb_period) { |
| perf_event_attr attr; |
| memset(&attr, 0, sizeof(attr)); |
| attr.size = sizeof(attr); |
| attr.exclude_kernel = 1; |
| attr.exclude_hv = 1; |
| attr.exclude_idle = 1; |
| |
| if (Expected<uint64_t> config_value = |
| GeneratePerfEventConfigValue(enable_tsc, psb_period)) |
| attr.config = *config_value; |
| else |
| return config_value.takeError(); |
| |
| if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType()) |
| attr.type = *intel_pt_type; |
| else |
| return intel_pt_type.takeError(); |
| |
| return attr; |
| } |
| #endif |
| |
| size_t IntelPTSingleBufferTrace::GetIptTraceSize() const { |
| return m_perf_event.GetAuxBuffer().size(); |
| } |
| |
| Error IntelPTSingleBufferTrace::Pause() { |
| return m_perf_event.DisableWithIoctl(); |
| } |
| |
| Error IntelPTSingleBufferTrace::Resume() { |
| return m_perf_event.EnableWithIoctl(); |
| } |
| |
| Expected<std::vector<uint8_t>> IntelPTSingleBufferTrace::GetIptTrace() { |
| // 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. |
| // The Intel documentation says: |
| // |
| // Packets are first buffered internally and then written out |
| // asynchronously. To collect packet output for postprocessing, a collector |
| // needs first to ensure that all packet data has been flushed from internal |
| // buffers. Software can ensure this by stopping packet generation by |
| // clearing IA32_RTIT_CTL.TraceEn (see “Disabling Packet Generation” in |
| // Section 35.2.7.2). |
| // |
| // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as |
| // mentioned in the man page of perf_event_open. |
| return m_perf_event.GetReadOnlyAuxBuffer(); |
| } |
| |
| Expected<IntelPTSingleBufferTrace> IntelPTSingleBufferTrace::Start( |
| const TraceIntelPTStartRequest &request, Optional<lldb::tid_t> tid, |
| Optional<cpu_id_t> cpu_id, bool disabled, Optional<int> cgroup_fd) { |
| #ifndef PERF_ATTR_SIZE_VER5 |
| return createStringError(inconvertibleErrorCode(), |
| "Intel PT Linux perf event not supported"); |
| #else |
| Log *log = GetLog(POSIXLog::Trace); |
| |
| LLDB_LOG(log, "Will start tracing thread id {0} and cpu id {1}", tid, cpu_id); |
| |
| if (__builtin_popcount(request.ipt_trace_size) != 1 || |
| request.ipt_trace_size < 4096) { |
| return createStringError( |
| inconvertibleErrorCode(), |
| "The intel pt trace size must be a power of 2 greater than or equal to " |
| "4096 (2^12) bytes. It was %" PRIu64 ".", |
| request.ipt_trace_size); |
| } |
| uint64_t page_size = getpagesize(); |
| uint64_t aux_buffer_numpages = static_cast<uint64_t>(llvm::PowerOf2Floor( |
| (request.ipt_trace_size + page_size - 1) / page_size)); |
| |
| Expected<perf_event_attr> attr = CreateIntelPTPerfEventConfiguration( |
| request.enable_tsc, request.psb_period.map([](int value) { |
| return static_cast<uint64_t>(value); |
| })); |
| if (!attr) |
| return attr.takeError(); |
| attr->disabled = disabled; |
| |
| LLDB_LOG(log, "Will create intel pt trace buffer of size {0}", |
| request.ipt_trace_size); |
| unsigned long flags = 0; |
| if (cgroup_fd) { |
| tid = *cgroup_fd; |
| flags |= PERF_FLAG_PID_CGROUP; |
| } |
| |
| if (Expected<PerfEvent> perf_event = |
| PerfEvent::Init(*attr, tid, cpu_id, -1, flags)) { |
| if (Error mmap_err = perf_event->MmapMetadataAndBuffers( |
| /*num_data_pages=*/0, aux_buffer_numpages, |
| /*data_buffer_write=*/true)) { |
| return std::move(mmap_err); |
| } |
| return IntelPTSingleBufferTrace(std::move(*perf_event)); |
| } else { |
| return perf_event.takeError(); |
| } |
| #endif |
| } |
| |
| const PerfEvent &IntelPTSingleBufferTrace::GetPerfEvent() const { |
| return m_perf_event; |
| } |