xref: /llvm-project/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp (revision 4bac5f8344ea6405e3964141c8f591c68eefd373)
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