| //===-- ProcessorTrace.cpp ------------------------------------ -*- 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include <algorithm> | |
| #include <fstream> | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/MathExtras.h" | |
| #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" | |
| #include "ProcessorTrace.h" | |
| #include "lldb/Host/linux/Support.h" | |
| #include <sys/syscall.h> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace process_linux; | |
| using namespace llvm; | |
| lldb::user_id_t ProcessorTraceMonitor::m_trace_num = 1; | |
| Status ProcessorTraceMonitor::GetTraceConfig(TraceOptions &config) const { | |
| #ifndef PERF_ATTR_SIZE_VER5 | |
| llvm_unreachable("perf event not supported"); | |
| #else | |
| Status error; | |
| config.setType(lldb::TraceType::eTraceTypeProcessorTrace); | |
| config.setMetaDataBufferSize(m_mmap_meta->data_size); | |
| config.setTraceBufferSize(m_mmap_meta->aux_size); | |
| error = GetCPUType(config); | |
| return error; | |
| #endif | |
| } | |
| Status ProcessorTraceMonitor::StartTrace(lldb::pid_t pid, lldb::tid_t tid, | |
| const TraceOptions &config) { | |
| #ifndef PERF_ATTR_SIZE_VER5 | |
| llvm_unreachable("perf event not supported"); | |
| #else | |
| Status error; | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); | |
| LLDB_LOG(log, "called thread id {0}", tid); | |
| uint64_t page_size = getpagesize(); | |
| uint64_t bufsize = config.getTraceBufferSize(); | |
| uint64_t metabufsize = config.getMetaDataBufferSize(); | |
| uint64_t numpages = static_cast<uint64_t>( | |
| llvm::PowerOf2Floor((bufsize + page_size - 1) / page_size)); | |
| numpages = std::max<uint64_t>(1, numpages); | |
| bufsize = page_size * numpages; | |
| numpages = static_cast<uint64_t>( | |
| llvm::PowerOf2Floor((metabufsize + page_size - 1) / page_size)); | |
| metabufsize = page_size * numpages; | |
| perf_event_attr attr; | |
| memset(&attr, 0, sizeof(attr)); | |
| attr.size = sizeof(attr); | |
| attr.exclude_kernel = 1; | |
| attr.sample_type = PERF_SAMPLE_TIME; | |
| attr.sample_id_all = 1; | |
| attr.exclude_hv = 1; | |
| attr.exclude_idle = 1; | |
| attr.mmap = 1; | |
| int intel_pt_type = 0; | |
| auto ret = llvm::MemoryBuffer::getFileAsStream( | |
| "/sys/bus/event_source/devices/intel_pt/type"); | |
| if (!ret) { | |
| LLDB_LOG(log, "failed to open Config file"); | |
| return ret.getError(); | |
| } | |
| StringRef rest = ret.get()->getBuffer(); | |
| if (rest.empty() || rest.trim().getAsInteger(10, intel_pt_type)) { | |
| LLDB_LOG(log, "failed to read Config file"); | |
| error.SetErrorString("invalid file"); | |
| return error; | |
| } | |
| rest.trim().getAsInteger(10, intel_pt_type); | |
| LLDB_LOG(log, "intel pt type {0}", intel_pt_type); | |
| attr.type = intel_pt_type; | |
| LLDB_LOG(log, "meta buffer size {0}", metabufsize); | |
| LLDB_LOG(log, "buffer size {0} ", bufsize); | |
| if (error.Fail()) { | |
| LLDB_LOG(log, "Status in custom config"); | |
| return error; | |
| } | |
| 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); | |
| error.SetErrorString("perf event syscall Failed"); | |
| return error; | |
| } | |
| m_fd = std::unique_ptr<int, file_close>(new int(fd), file_close()); | |
| errno = 0; | |
| auto base = | |
| mmap(nullptr, (metabufsize + page_size), PROT_WRITE, MAP_SHARED, fd, 0); | |
| if (base == MAP_FAILED) { | |
| LLDB_LOG(log, "mmap base error {0}", errno); | |
| error.SetErrorString("Meta buffer allocation failed"); | |
| return error; | |
| } | |
| m_mmap_meta = std::unique_ptr<perf_event_mmap_page, munmap_delete>( | |
| reinterpret_cast<perf_event_mmap_page *>(base), | |
| munmap_delete(metabufsize + page_size)); | |
| m_mmap_meta->aux_offset = m_mmap_meta->data_offset + m_mmap_meta->data_size; | |
| m_mmap_meta->aux_size = bufsize; | |
| errno = 0; | |
| auto mmap_aux = mmap(nullptr, bufsize, PROT_READ, MAP_SHARED, fd, | |
| static_cast<long int>(m_mmap_meta->aux_offset)); | |
| if (mmap_aux == MAP_FAILED) { | |
| LLDB_LOG(log, "second mmap done {0}", errno); | |
| error.SetErrorString("Trace buffer allocation failed"); | |
| return error; | |
| } | |
| m_mmap_aux = std::unique_ptr<uint8_t, munmap_delete>( | |
| reinterpret_cast<uint8_t *>(mmap_aux), munmap_delete(bufsize)); | |
| return error; | |
| #endif | |
| } | |
| llvm::MutableArrayRef<uint8_t> ProcessorTraceMonitor::GetDataBuffer() { | |
| #ifndef PERF_ATTR_SIZE_VER5 | |
| llvm_unreachable("perf event not supported"); | |
| #else | |
| return MutableArrayRef<uint8_t>( | |
| (reinterpret_cast<uint8_t *>(m_mmap_meta.get()) + | |
| m_mmap_meta->data_offset), | |
| m_mmap_meta->data_size); | |
| #endif | |
| } | |
| llvm::MutableArrayRef<uint8_t> ProcessorTraceMonitor::GetAuxBuffer() { | |
| #ifndef PERF_ATTR_SIZE_VER5 | |
| llvm_unreachable("perf event not supported"); | |
| #else | |
| return MutableArrayRef<uint8_t>(m_mmap_aux.get(), m_mmap_meta->aux_size); | |
| #endif | |
| } | |
| Status ProcessorTraceMonitor::GetCPUType(TraceOptions &config) { | |
| Status error; | |
| uint64_t cpu_family = -1; | |
| uint64_t model = -1; | |
| uint64_t stepping = -1; | |
| std::string vendor_id; | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); | |
| auto BufferOrError = getProcFile("cpuinfo"); | |
| if (!BufferOrError) | |
| return BufferOrError.getError(); | |
| LLDB_LOG(log, "GetCPUType Function"); | |
| StringRef Rest = BufferOrError.get()->getBuffer(); | |
| while (!Rest.empty()) { | |
| StringRef Line; | |
| std::tie(Line, Rest) = Rest.split('\n'); | |
| SmallVector<StringRef, 2> columns; | |
| Line.split(columns, StringRef(":"), -1, false); | |
| if (columns.size() < 2) | |
| continue; // continue searching | |
| columns[1] = columns[1].trim(" "); | |
| if (columns[0].contains("cpu family") && | |
| columns[1].getAsInteger(10, cpu_family)) | |
| continue; | |
| else if (columns[0].contains("model") && columns[1].getAsInteger(10, model)) | |
| continue; | |
| else if (columns[0].contains("stepping") && | |
| columns[1].getAsInteger(10, stepping)) | |
| continue; | |
| else if (columns[0].contains("vendor_id")) { | |
| vendor_id = columns[1].str(); | |
| if (!vendor_id.empty()) | |
| continue; | |
| } | |
| LLDB_LOG(log, "{0}:{1}:{2}:{3}", cpu_family, model, stepping, vendor_id); | |
| if ((cpu_family != static_cast<uint64_t>(-1)) && | |
| (model != static_cast<uint64_t>(-1)) && | |
| (stepping != static_cast<uint64_t>(-1)) && (!vendor_id.empty())) { | |
| auto params_dict = std::make_shared<StructuredData::Dictionary>(); | |
| params_dict->AddIntegerItem("cpu_family", cpu_family); | |
| params_dict->AddIntegerItem("cpu_model", model); | |
| params_dict->AddIntegerItem("cpu_stepping", stepping); | |
| params_dict->AddStringItem("cpu_vendor", vendor_id); | |
| llvm::StringRef intel_custom_params_key("intel-pt"); | |
| auto intel_custom_params = std::make_shared<StructuredData::Dictionary>(); | |
| intel_custom_params->AddItem( | |
| intel_custom_params_key, | |
| StructuredData::ObjectSP(std::move(params_dict))); | |
| config.setTraceParams(intel_custom_params); | |
| return error; // we are done | |
| } | |
| } | |
| error.SetErrorString("cpu info not found"); | |
| return error; | |
| } | |
| llvm::Expected<ProcessorTraceMonitorUP> | |
| ProcessorTraceMonitor::Create(lldb::pid_t pid, lldb::tid_t tid, | |
| const TraceOptions &config, | |
| bool useProcessSettings) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); | |
| Status error; | |
| if (tid == LLDB_INVALID_THREAD_ID) { | |
| error.SetErrorString("thread not specified"); | |
| return error.ToError(); | |
| } | |
| ProcessorTraceMonitorUP pt_monitor_up(new ProcessorTraceMonitor); | |
| error = pt_monitor_up->StartTrace(pid, tid, config); | |
| if (error.Fail()) | |
| return error.ToError(); | |
| pt_monitor_up->SetThreadID(tid); | |
| if (useProcessSettings) { | |
| pt_monitor_up->SetTraceID(0); | |
| } else { | |
| pt_monitor_up->SetTraceID(m_trace_num++); | |
| LLDB_LOG(log, "Trace ID {0}", m_trace_num); | |
| } | |
| return std::move(pt_monitor_up); | |
| } | |
| Status | |
| ProcessorTraceMonitor::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer, | |
| size_t offset) { | |
| #ifndef PERF_ATTR_SIZE_VER5 | |
| llvm_unreachable("perf event not supported"); | |
| #else | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); | |
| Status error; | |
| uint64_t head = m_mmap_meta->aux_head; | |
| LLDB_LOG(log, "Aux size -{0} , Head - {1}", m_mmap_meta->aux_size, head); | |
| /** | |
| * When configured as ring buffer, the aux buffer keeps wrapping around | |
| * the buffer and its not possible to detect how many times the buffer | |
| * wrapped. Initially the buffer is filled with zeros,as shown below | |
| * so in order to get complete buffer we first copy firstpartsize, followed | |
| * by any left over part from beginning to aux_head | |
| * | |
| * aux_offset [d,d,d,d,d,d,d,d,0,0,0,0,0,0,0,0,0,0,0] aux_size | |
| * aux_head->||<- firstpartsize ->| | |
| * | |
| * */ | |
| ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast<size_t>(head), offset); | |
| LLDB_LOG(log, "ReadCyclic BUffer Done"); | |
| return error; | |
| #endif | |
| } | |
| Status | |
| ProcessorTraceMonitor::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer, | |
| size_t offset) { | |
| #ifndef PERF_ATTR_SIZE_VER5 | |
| llvm_unreachable("perf event not supported"); | |
| #else | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); | |
| uint64_t bytes_remaining = buffer.size(); | |
| Status error; | |
| uint64_t head = m_mmap_meta->data_head; | |
| /* | |
| * The data buffer and aux buffer have different implementations | |
| * with respect to their definition of head pointer. In the case | |
| * of Aux data buffer the head always wraps around the aux buffer | |
| * and we don't need to care about it, whereas the data_head keeps | |
| * increasing and needs to be wrapped by modulus operator | |
| */ | |
| LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining); | |
| auto data_buffer = 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); | |
| ReadCyclicBuffer(buffer, data_buffer, static_cast<size_t>(head), offset); | |
| bytes_remaining -= buffer.size(); | |
| } else { | |
| LLDB_LOG(log, "Head - {0}", head); | |
| if (offset >= head) { | |
| LLDB_LOG(log, "Invalid Offset "); | |
| error.SetErrorString("invalid offset"); | |
| buffer = buffer.slice(buffer.size()); | |
| return error; | |
| } | |
| auto data = data_buffer.slice(offset, (head - offset)); | |
| auto remaining = std::copy(data.begin(), data.end(), buffer.begin()); | |
| bytes_remaining -= (remaining - buffer.begin()); | |
| } | |
| buffer = buffer.drop_back(bytes_remaining); | |
| return error; | |
| #endif | |
| } | |
| void ProcessorTraceMonitor::ReadCyclicBuffer( | |
| llvm::MutableArrayRef<uint8_t> &dst, llvm::MutableArrayRef<uint8_t> src, | |
| size_t src_cyc_index, size_t offset) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_PTRACE)); | |
| if (dst.empty() || src.empty()) { | |
| dst = dst.drop_back(dst.size()); | |
| return; | |
| } | |
| if (dst.data() == nullptr || src.data() == nullptr) { | |
| dst = dst.drop_back(dst.size()); | |
| return; | |
| } | |
| if (src_cyc_index > src.size()) { | |
| dst = dst.drop_back(dst.size()); | |
| return; | |
| } | |
| if (offset >= src.size()) { | |
| LLDB_LOG(log, "Too Big offset "); | |
| dst = dst.drop_back(dst.size()); | |
| return; | |
| } | |
| llvm::SmallVector<MutableArrayRef<uint8_t>, 2> parts = { | |
| src.slice(src_cyc_index), src.take_front(src_cyc_index)}; | |
| if (offset > parts[0].size()) { | |
| parts[1] = parts[1].slice(offset - parts[0].size()); | |
| parts[0] = parts[0].drop_back(parts[0].size()); | |
| } else if (offset == parts[0].size()) { | |
| parts[0] = parts[0].drop_back(parts[0].size()); | |
| } else { | |
| parts[0] = parts[0].slice(offset); | |
| } | |
| auto next = dst.begin(); | |
| auto bytes_left = dst.size(); | |
| for (auto part : parts) { | |
| size_t chunk_size = std::min(part.size(), bytes_left); | |
| next = std::copy_n(part.begin(), chunk_size, next); | |
| bytes_left -= chunk_size; | |
| } | |
| dst = dst.drop_back(bytes_left); | |
| } |