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