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
28 using namespace lldb;
29 using namespace lldb_private;
30 using namespace process_linux;
31 using namespace llvm;
32
IntelPTCollector(NativeProcessProtocol & process)33 IntelPTCollector::IntelPTCollector(NativeProcessProtocol &process)
34 : m_process(process) {}
35
36 llvm::Expected<LinuxPerfZeroTscConversion &>
FetchPerfTscConversionParameters()37 IntelPTCollector::FetchPerfTscConversionParameters() {
38 if (Expected<LinuxPerfZeroTscConversion> tsc_conversion =
39 LoadPerfTscConversionParameters())
40 return *tsc_conversion;
41 else
42 return createStringError(inconvertibleErrorCode(),
43 "Unable to load TSC to wall time conversion: %s",
44 toString(tsc_conversion.takeError()).c_str());
45 }
46
TraceStop(lldb::tid_t tid)47 Error 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
TraceStop(const TraceStopRequest & request)53 Error 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(std::move(error),
61 TraceStop(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.
GetCGroupFileDescriptor(lldb::pid_t pid)69 static 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(formatv("/proc/{0}/cgroup", pid));
76 if (!ifile)
77 return std::nullopt;
78
79 std::string line;
80 while (std::getline(ifile, line)) {
81 if (line.find("0:") != 0)
82 continue;
83
84 std::string slice = line.substr(line.find_first_of('/'));
85 if (slice.empty())
86 return std::nullopt;
87 std::string cgroup_file = formatv("/sys/fs/cgroup/{0}", 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(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
TraceStart(const TraceIntelPTStartRequest & request)99 Error IntelPTCollector::TraceStart(const TraceIntelPTStartRequest &request) {
100 if (request.IsProcessTracing()) {
101 if (m_process_trace_up) {
102 return createStringError(
103 inconvertibleErrorCode(),
104 "Process currently traced. Stop process tracing first");
105 }
106 if (request.IsPerCpuTracing()) {
107 if (m_thread_traces.GetTracedThreadsCount() > 0)
108 return createStringError(
109 inconvertibleErrorCode(),
110 "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(false))
125 cgroup_fd = GetCGroupFileDescriptor(m_process.GetID());
126
127 if (Expected<IntelPTProcessTraceUP> trace =
128 IntelPTMultiCoreTrace::StartOnAllCores(effective_request,
129 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(thread.GetID());
139
140 // per-thread process tracing
141 if (Expected<IntelPTProcessTraceUP> trace =
142 IntelPTPerThreadProcessTrace::Start(request, 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 std::move(error),
156 createStringError(inconvertibleErrorCode(),
157 formatv("Thread with tid {0} is currently "
158 "traced. Stop tracing it first.",
159 tid)
160 .str()
161 .c_str()));
162 else
163 error = joinErrors(std::move(error),
164 m_thread_traces.TraceStart(tid, request));
165 }
166 return error;
167 }
168 }
169
ProcessWillResume()170 void IntelPTCollector::ProcessWillResume() {
171 if (m_process_trace_up)
172 m_process_trace_up->ProcessWillResume();
173 }
174
ProcessDidStop()175 void IntelPTCollector::ProcessDidStop() {
176 if (m_process_trace_up)
177 m_process_trace_up->ProcessDidStop();
178 }
179
OnThreadCreated(lldb::tid_t tid)180 Error 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
OnThreadDestroyed(lldb::tid_t tid)187 Error 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
GetState()195 Expected<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 {IntelPTDataKinds::kProcFsCpuInfo, cpu_info->size()});
206
207 m_thread_traces.ForEachThread(
208 [&](lldb::tid_t tid, const IntelPTSingleBufferTrace &thread_trace) {
209 state.traced_threads.push_back(
210 {tid,
211 {{IntelPTDataKinds::kIptTrace, 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(toString(tsc_conversion.takeError()));
219 return toJSON(state);
220 }
221
222 Expected<std::vector<uint8_t>>
GetBinaryData(const TraceGetBinaryDataRequest & request)223 IntelPTCollector::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 inconvertibleErrorCode(),
247 formatv("Can't fetch data kind {0} for cpu_id {1}, tid {2} and "
248 "\"process tracing\" mode {3}",
249 request.kind, request.cpu_id, request.tid,
250 m_process_trace_up ? "enabled" : "not enabled"));
251 }
252
IsSupported()253 bool IntelPTCollector::IsSupported() {
254 if (Expected<uint32_t> intel_pt_type = GetIntelPTOSEventType()) {
255 return true;
256 } else {
257 llvm::consumeError(intel_pt_type.takeError());
258 return false;
259 }
260 }
261
Clear()262 void IntelPTCollector::Clear() {
263 m_process_trace_up.reset();
264 m_thread_traces.Clear();
265 }
266