| //===-- IntelPTCollector.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 "IntelPTCollector.h" | |
| #include "Plugins/Process/Linux/Procfs.h" | |
| #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/MathExtras.h" | |
| #include <fcntl.h> | |
| #include <fstream> | |
| #include <linux/perf_event.h> | |
| #include <optional> | |
| #include <sys/ioctl.h> | |
| #include <sys/syscall.h> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace process_linux; | |
| using namespace llvm; | |
| IntelPTCollector::IntelPTCollector(NativeProcessProtocol &process) | |
| : m_process(process) {} | |
| llvm::Expected<LinuxPerfZeroTscConversion &> | |
| IntelPTCollector::FetchPerfTscConversionParameters() { | |
| if (Expected<LinuxPerfZeroTscConversion> tsc_conversion = | |
| LoadPerfTscConversionParameters()) | |
| return *tsc_conversion; | |
| else | |
| return createStringError(inconvertibleErrorCode(), | |
| "Unable to load TSC to wall time conversion: %s", | |
| toString(tsc_conversion.takeError()).c_str()); | |
| } | |
| Error IntelPTCollector::TraceStop(lldb::tid_t tid) { | |
| if (m_process_trace_up && m_process_trace_up->TracesThread(tid)) | |
| return m_process_trace_up->TraceStop(tid); | |
| return m_thread_traces.TraceStop(tid); | |
| } | |
| Error IntelPTCollector::TraceStop(const TraceStopRequest &request) { | |
| if (request.IsProcessTracing()) { | |
| Clear(); | |
| return Error::success(); | |
| } else { | |
| Error error = Error::success(); | |
| for (int64_t tid : *request.tids) | |
| error = joinErrors(std::move(error), | |
| TraceStop(static_cast<lldb::tid_t>(tid))); | |
| return error; | |
| } | |
| } | |
| /// \return | |
| /// some file descriptor in /sys/fs/ associated with the cgroup of the given | |
| /// pid, or \a std::nullopt if the pid is not part of a cgroup. | |
| static std::optional<int> GetCGroupFileDescriptor(lldb::pid_t pid) { | |
| static std::optional<int> fd; | |
| if (fd) | |
| return fd; | |
| std::ifstream ifile; | |
| ifile.open(formatv("/proc/{0}/cgroup", pid)); | |
| if (!ifile) | |
| return std::nullopt; | |
| std::string line; | |
| while (std::getline(ifile, line)) { | |
| if (line.find("0:") != 0) | |
| continue; | |
| std::string slice = line.substr(line.find_first_of('/')); | |
| if (slice.empty()) | |
| return std::nullopt; | |
| std::string cgroup_file = formatv("/sys/fs/cgroup/{0}", slice); | |
| // This cgroup should for the duration of the target, so we don't need to | |
| // invoke close ourselves. | |
| int maybe_fd = open(cgroup_file.c_str(), O_RDONLY); | |
| if (maybe_fd != -1) { | |
| fd = maybe_fd; | |
| return fd; | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| Error IntelPTCollector::TraceStart(const TraceIntelPTStartRequest &request) { | |
| if (request.IsProcessTracing()) { | |
| if (m_process_trace_up) { | |
| return createStringError( | |
| inconvertibleErrorCode(), | |
| "Process currently traced. Stop process tracing first"); | |
| } | |
| if (request.IsPerCpuTracing()) { | |
| if (m_thread_traces.GetTracedThreadsCount() > 0) | |
| return createStringError( | |
| inconvertibleErrorCode(), | |
| "Threads currently traced. Stop tracing them first."); | |
| // CPU tracing is useless if we can't convert tsc to nanos. | |
| Expected<LinuxPerfZeroTscConversion &> tsc_conversion = | |
| FetchPerfTscConversionParameters(); | |
| if (!tsc_conversion) | |
| return tsc_conversion.takeError(); | |
| // We force the enablement of TSCs, which is needed for correlating the | |
| // cpu traces. | |
| TraceIntelPTStartRequest effective_request = request; | |
| effective_request.enable_tsc = true; | |
| // We try to use cgroup filtering whenever possible | |
| std::optional<int> cgroup_fd; | |
| if (!request.disable_cgroup_filtering.value_or(false)) | |
| cgroup_fd = GetCGroupFileDescriptor(m_process.GetID()); | |
| if (Expected<IntelPTProcessTraceUP> trace = | |
| IntelPTMultiCoreTrace::StartOnAllCores(effective_request, | |
| m_process, cgroup_fd)) { | |
| m_process_trace_up = std::move(*trace); | |
| return Error::success(); | |
| } else { | |
| return trace.takeError(); | |
| } | |
| } else { | |
| std::vector<lldb::tid_t> process_threads; | |
| for (NativeThreadProtocol &thread : m_process.Threads()) | |
| process_threads.push_back(thread.GetID()); | |
| // per-thread process tracing | |
| if (Expected<IntelPTProcessTraceUP> trace = | |
| IntelPTPerThreadProcessTrace::Start(request, process_threads)) { | |
| m_process_trace_up = std::move(trace.get()); | |
| return Error::success(); | |
| } else { | |
| return trace.takeError(); | |
| } | |
| } | |
| } else { | |
| // individual thread tracing | |
| Error error = Error::success(); | |
| for (int64_t tid : *request.tids) { | |
| if (m_process_trace_up && m_process_trace_up->TracesThread(tid)) | |
| error = joinErrors( | |
| std::move(error), | |
| createStringError(inconvertibleErrorCode(), | |
| formatv("Thread with tid {0} is currently " | |
| "traced. Stop tracing it first.", | |
| tid) | |
| .str() | |
| .c_str())); | |
| else | |
| error = joinErrors(std::move(error), | |
| m_thread_traces.TraceStart(tid, request)); | |
| } | |
| return error; | |
| } | |
| } | |
| void IntelPTCollector::ProcessWillResume() { | |
| if (m_process_trace_up) | |
| m_process_trace_up->ProcessWillResume(); | |
| } | |
| void IntelPTCollector::ProcessDidStop() { | |
| if (m_process_trace_up) | |
| m_process_trace_up->ProcessDidStop(); | |
| } | |
| Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) { | |
| if (m_process_trace_up) | |
| return m_process_trace_up->TraceStart(tid); | |
| return Error::success(); | |
| } | |
| Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) { | |
| if (m_process_trace_up && m_process_trace_up->TracesThread(tid)) | |
| return m_process_trace_up->TraceStop(tid); | |
| else if (m_thread_traces.TracesThread(tid)) | |
| return m_thread_traces.TraceStop(tid); | |
| return Error::success(); | |
| } | |
| Expected<json::Value> IntelPTCollector::GetState() { | |
| Expected<ArrayRef<uint8_t>> cpu_info = GetProcfsCpuInfo(); | |
| if (!cpu_info) | |
| return cpu_info.takeError(); | |
| TraceIntelPTGetStateResponse state; | |
| if (m_process_trace_up) | |
| state = m_process_trace_up->GetState(); | |
| state.process_binary_data.push_back( | |
| {IntelPTDataKinds::kProcFsCpuInfo, cpu_info->size()}); | |
| m_thread_traces.ForEachThread( | |
| [&](lldb::tid_t tid, const IntelPTSingleBufferTrace &thread_trace) { | |
| state.traced_threads.push_back( | |
| {tid, | |
| {{IntelPTDataKinds::kIptTrace, thread_trace.GetIptTraceSize()}}}); | |
| }); | |
| if (Expected<LinuxPerfZeroTscConversion &> tsc_conversion = | |
| FetchPerfTscConversionParameters()) | |
| state.tsc_perf_zero_conversion = *tsc_conversion; | |
| else | |
| state.AddWarning(toString(tsc_conversion.takeError())); | |
| return toJSON(state); | |
| } | |
| Expected<std::vector<uint8_t>> | |
| IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) { | |
| if (request.kind == IntelPTDataKinds::kProcFsCpuInfo) | |
| return GetProcfsCpuInfo(); | |
| if (m_process_trace_up) { | |
| Expected<std::optional<std::vector<uint8_t>>> data = | |
| m_process_trace_up->TryGetBinaryData(request); | |
| if (!data) | |
| return data.takeError(); | |
| if (*data) | |
| return **data; | |
| } | |
| { | |
| Expected<std::optional<std::vector<uint8_t>>> data = | |
| m_thread_traces.TryGetBinaryData(request); | |
| if (!data) | |
| return data.takeError(); | |
| if (*data) | |
| return **data; | |
| } | |
| return createStringError( | |
| inconvertibleErrorCode(), | |
| formatv("Can't fetch data kind {0} for cpu_id {1}, tid {2} and " | |
| "\"process tracing\" mode {3}", | |
| request.kind, request.cpu_id, request.tid, | |
| m_process_trace_up ? "enabled" : "not enabled")); | |
| } | |
| bool IntelPTCollector::IsSupported() { | |
| if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType()) { | |
| return true; | |
| } else { | |
| llvm::consumeError(intel_pt_type.takeError()); | |
| return false; | |
| } | |
| } | |
| void IntelPTCollector::Clear() { | |
| m_process_trace_up.reset(); | |
| m_thread_traces.Clear(); | |
| } |