1//===-- IntelPTCollector.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 "IntelPTCollector.h"
10#include "Perf.h"
11#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
12#include "Procfs.h"
13#include "lldb/Host/linux/Support.h"
14#include "lldb/Utility/StreamString.h"
15#include "llvm/ADT/StringRef.h"
16#include "llvm/Support/Error.h"
17#include "llvm/Support/MathExtras.h"
18#include <algorithm>
19#include <cstddef>
20#include <fcntl.h>
21#include <fstream>
22#include <linux/perf_event.h>
23#include <optional>
24#include <sstream>
25#include <sys/ioctl.h>
26#include <sys/syscall.h>
27
28using namespace lldb;
29using namespace lldb_private;
30using namespace process_linux;
31using namespace llvm;
32
33IntelPTCollector::IntelPTCollector(NativeProcessProtocol &process)
34 : m_process(process) {}
35
36llvm::Expected<LinuxPerfZeroTscConversion &>
37IntelPTCollector::FetchPerfTscConversionParameters() {
38 if (Expected<LinuxPerfZeroTscConversion> tsc_conversion =
39 LoadPerfTscConversionParameters())
40 return *tsc_conversion;
41 else
42 return createStringError(EC: inconvertibleErrorCode(),
43 Fmt: "Unable to load TSC to wall time conversion: %s",
44 Vals: toString(E: tsc_conversion.takeError()).c_str());
45}
46
47Error IntelPTCollector::TraceStop(lldb::tid_t tid) {
48 if (m_process_trace_up && m_process_trace_up->TracesThread(tid))
49 return m_process_trace_up->TraceStop(tid);
50 return m_thread_traces.TraceStop(tid);
51}
52
53Error IntelPTCollector::TraceStop(const TraceStopRequest &request) {
54 if (request.IsProcessTracing()) {
55 Clear();
56 return Error::success();
57 } else {
58 Error error = Error::success();
59 for (int64_t tid : *request.tids)
60 error = joinErrors(E1: std::move(error),
61 E2: TraceStop(tid: static_cast<lldb::tid_t>(tid)));
62 return error;
63 }
64}
65
66/// \return
67/// some file descriptor in /sys/fs/ associated with the cgroup of the given
68/// pid, or \a std::nullopt if the pid is not part of a cgroup.
69static std::optional<int> GetCGroupFileDescriptor(lldb::pid_t pid) {
70 static std::optional<int> fd;
71 if (fd)
72 return fd;
73
74 std::ifstream ifile;
75 ifile.open(s: formatv(Fmt: "/proc/{0}/cgroup", Vals&: pid));
76 if (!ifile)
77 return std::nullopt;
78
79 std::string line;
80 while (std::getline(is&: ifile, str&: line)) {
81 if (line.find(s: "0:") != 0)
82 continue;
83
84 std::string slice = line.substr(pos: line.find_first_of(c: '/'));
85 if (slice.empty())
86 return std::nullopt;
87 std::string cgroup_file = formatv(Fmt: "/sys/fs/cgroup/{0}", Vals&: slice);
88 // This cgroup should for the duration of the target, so we don't need to
89 // invoke close ourselves.
90 int maybe_fd = open(file: cgroup_file.c_str(), O_RDONLY);
91 if (maybe_fd != -1) {
92 fd = maybe_fd;
93 return fd;
94 }
95 }
96 return std::nullopt;
97}
98
99Error IntelPTCollector::TraceStart(const TraceIntelPTStartRequest &request) {
100 if (request.IsProcessTracing()) {
101 if (m_process_trace_up) {
102 return createStringError(
103 EC: inconvertibleErrorCode(),
104 Msg: "Process currently traced. Stop process tracing first");
105 }
106 if (request.IsPerCpuTracing()) {
107 if (m_thread_traces.GetTracedThreadsCount() > 0)
108 return createStringError(
109 EC: inconvertibleErrorCode(),
110 Msg: "Threads currently traced. Stop tracing them first.");
111 // CPU tracing is useless if we can't convert tsc to nanos.
112 Expected<LinuxPerfZeroTscConversion &> tsc_conversion =
113 FetchPerfTscConversionParameters();
114 if (!tsc_conversion)
115 return tsc_conversion.takeError();
116
117 // We force the enablement of TSCs, which is needed for correlating the
118 // cpu traces.
119 TraceIntelPTStartRequest effective_request = request;
120 effective_request.enable_tsc = true;
121
122 // We try to use cgroup filtering whenever possible
123 std::optional<int> cgroup_fd;
124 if (!request.disable_cgroup_filtering.value_or(u: false))
125 cgroup_fd = GetCGroupFileDescriptor(pid: m_process.GetID());
126
127 if (Expected<IntelPTProcessTraceUP> trace =
128 IntelPTMultiCoreTrace::StartOnAllCores(request: effective_request,
129 process&: m_process, cgroup_fd)) {
130 m_process_trace_up = std::move(*trace);
131 return Error::success();
132 } else {
133 return trace.takeError();
134 }
135 } else {
136 std::vector<lldb::tid_t> process_threads;
137 for (NativeThreadProtocol &thread : m_process.Threads())
138 process_threads.push_back(x: thread.GetID());
139
140 // per-thread process tracing
141 if (Expected<IntelPTProcessTraceUP> trace =
142 IntelPTPerThreadProcessTrace::Start(request, current_tids: process_threads)) {
143 m_process_trace_up = std::move(trace.get());
144 return Error::success();
145 } else {
146 return trace.takeError();
147 }
148 }
149 } else {
150 // individual thread tracing
151 Error error = Error::success();
152 for (int64_t tid : *request.tids) {
153 if (m_process_trace_up && m_process_trace_up->TracesThread(tid))
154 error = joinErrors(
155 E1: std::move(error),
156 E2: createStringError(EC: inconvertibleErrorCode(),
157 Msg: formatv(Fmt: "Thread with tid {0} is currently "
158 "traced. Stop tracing it first.",
159 Vals&: tid)
160 .str()
161 .c_str()));
162 else
163 error = joinErrors(E1: std::move(error),
164 E2: m_thread_traces.TraceStart(tid, request));
165 }
166 return error;
167 }
168}
169
170void IntelPTCollector::ProcessWillResume() {
171 if (m_process_trace_up)
172 m_process_trace_up->ProcessWillResume();
173}
174
175void IntelPTCollector::ProcessDidStop() {
176 if (m_process_trace_up)
177 m_process_trace_up->ProcessDidStop();
178}
179
180Error IntelPTCollector::OnThreadCreated(lldb::tid_t tid) {
181 if (m_process_trace_up)
182 return m_process_trace_up->TraceStart(tid);
183
184 return Error::success();
185}
186
187Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) {
188 if (m_process_trace_up && m_process_trace_up->TracesThread(tid))
189 return m_process_trace_up->TraceStop(tid);
190 else if (m_thread_traces.TracesThread(tid))
191 return m_thread_traces.TraceStop(tid);
192 return Error::success();
193}
194
195Expected<json::Value> IntelPTCollector::GetState() {
196 Expected<ArrayRef<uint8_t>> cpu_info = GetProcfsCpuInfo();
197 if (!cpu_info)
198 return cpu_info.takeError();
199
200 TraceIntelPTGetStateResponse state;
201 if (m_process_trace_up)
202 state = m_process_trace_up->GetState();
203
204 state.process_binary_data.push_back(
205 x: {.kind: IntelPTDataKinds::kProcFsCpuInfo, .size: cpu_info->size()});
206
207 m_thread_traces.ForEachThread(
208 callback: [&](lldb::tid_t tid, const IntelPTSingleBufferTrace &thread_trace) {
209 state.traced_threads.push_back(
210 x: {.tid: tid,
211 .binary_data: {{.kind: IntelPTDataKinds::kIptTrace, .size: thread_trace.GetIptTraceSize()}}});
212 });
213
214 if (Expected<LinuxPerfZeroTscConversion &> tsc_conversion =
215 FetchPerfTscConversionParameters())
216 state.tsc_perf_zero_conversion = *tsc_conversion;
217 else
218 state.AddWarning(warning: toString(E: tsc_conversion.takeError()));
219 return toJSON(packet: state);
220}
221
222Expected<std::vector<uint8_t>>
223IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) {
224 if (request.kind == IntelPTDataKinds::kProcFsCpuInfo)
225 return GetProcfsCpuInfo();
226
227 if (m_process_trace_up) {
228 Expected<std::optional<std::vector<uint8_t>>> data =
229 m_process_trace_up->TryGetBinaryData(request);
230 if (!data)
231 return data.takeError();
232 if (*data)
233 return **data;
234 }
235
236 {
237 Expected<std::optional<std::vector<uint8_t>>> data =
238 m_thread_traces.TryGetBinaryData(request);
239 if (!data)
240 return data.takeError();
241 if (*data)
242 return **data;
243 }
244
245 return createStringError(
246 EC: inconvertibleErrorCode(),
247 S: formatv(Fmt: "Can't fetch data kind {0} for cpu_id {1}, tid {2} and "
248 "\"process tracing\" mode {3}",
249 Vals: request.kind, Vals: request.cpu_id, Vals: request.tid,
250 Vals: m_process_trace_up ? "enabled" : "not enabled"));
251}
252
253bool IntelPTCollector::IsSupported() {
254 if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType()) {
255 return true;
256 } else {
257 llvm::consumeError(Err: intel_pt_type.takeError());
258 return false;
259 }
260}
261
262void IntelPTCollector::Clear() {
263 m_process_trace_up.reset();
264 m_thread_traces.Clear();
265}
266

source code of lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp