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