1 | //===-- source/Host/linux/Host.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 <cerrno> |
10 | #include <cstdio> |
11 | #include <cstring> |
12 | #include <dirent.h> |
13 | #include <fcntl.h> |
14 | #include <optional> |
15 | #include <sys/stat.h> |
16 | #include <sys/types.h> |
17 | #include <sys/utsname.h> |
18 | #include <unistd.h> |
19 | |
20 | #include "llvm/ADT/StringSwitch.h" |
21 | #include "llvm/Object/ELF.h" |
22 | #include "llvm/Support/ScopedPrinter.h" |
23 | |
24 | #include "lldb/Utility/LLDBLog.h" |
25 | #include "lldb/Utility/Log.h" |
26 | #include "lldb/Utility/ProcessInfo.h" |
27 | #include "lldb/Utility/Status.h" |
28 | |
29 | #include "lldb/Host/FileSystem.h" |
30 | #include "lldb/Host/Host.h" |
31 | #include "lldb/Host/HostInfo.h" |
32 | #include "lldb/Host/linux/Host.h" |
33 | #include "lldb/Host/linux/Support.h" |
34 | #include "lldb/Utility/DataExtractor.h" |
35 | |
36 | using namespace lldb; |
37 | using namespace lldb_private; |
38 | |
39 | namespace { |
40 | enum class ProcessState { |
41 | Unknown, |
42 | Dead, |
43 | DiskSleep, |
44 | Idle, |
45 | Paging, |
46 | Parked, |
47 | Running, |
48 | Sleeping, |
49 | TracedOrStopped, |
50 | Zombie, |
51 | }; |
52 | |
53 | constexpr int task_comm_len = 16; |
54 | |
55 | struct StatFields { |
56 | ::pid_t pid = LLDB_INVALID_PROCESS_ID; |
57 | char comm[task_comm_len]; |
58 | char state; |
59 | ::pid_t ppid = LLDB_INVALID_PROCESS_ID; |
60 | ::pid_t pgrp = LLDB_INVALID_PROCESS_ID; |
61 | ::pid_t session = LLDB_INVALID_PROCESS_ID; |
62 | int tty_nr; |
63 | int tpgid; |
64 | unsigned flags; |
65 | long unsigned minflt; |
66 | long unsigned cminflt; |
67 | long unsigned majflt; |
68 | long unsigned cmajflt; |
69 | long unsigned utime; |
70 | long unsigned stime; |
71 | long cutime; |
72 | long cstime; |
73 | // .... other things. We don't need them below |
74 | }; |
75 | } |
76 | |
77 | namespace lldb_private { |
78 | class ProcessLaunchInfo; |
79 | } |
80 | |
81 | static bool GetStatusInfo(::pid_t Pid, ProcessInstanceInfo &ProcessInfo, |
82 | ProcessState &State, ::pid_t &TracerPid, |
83 | ::pid_t &Tgid) { |
84 | Log *log = GetLog(mask: LLDBLog::Host); |
85 | |
86 | auto BufferOrError = getProcFile(pid: Pid, file: "stat" ); |
87 | if (!BufferOrError) |
88 | return false; |
89 | |
90 | llvm::StringRef Rest = BufferOrError.get()->getBuffer(); |
91 | if (Rest.empty()) |
92 | return false; |
93 | StatFields stat_fields; |
94 | if (sscanf(s: Rest.data(), |
95 | format: "%d %s %c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu %ld %ld" , |
96 | &stat_fields.pid, stat_fields.comm, &stat_fields.state, |
97 | &stat_fields.ppid, &stat_fields.pgrp, &stat_fields.session, |
98 | &stat_fields.tty_nr, &stat_fields.tpgid, &stat_fields.flags, |
99 | &stat_fields.minflt, &stat_fields.cminflt, &stat_fields.majflt, |
100 | &stat_fields.cmajflt, &stat_fields.utime, &stat_fields.stime, |
101 | &stat_fields.cutime, &stat_fields.cstime) < 0) { |
102 | return false; |
103 | } |
104 | |
105 | auto convert = [sc_clk_ticks = sysconf(_SC_CLK_TCK)](auto time_in_ticks) { |
106 | ProcessInstanceInfo::timespec ts; |
107 | if (sc_clk_ticks <= 0) { |
108 | return ts; |
109 | } |
110 | ts.tv_sec = time_in_ticks / sc_clk_ticks; |
111 | double remainder = |
112 | (static_cast<double>(time_in_ticks) / sc_clk_ticks) - ts.tv_sec; |
113 | ts.tv_usec = |
114 | std::chrono::microseconds{std::lround(x: 1e+6 * remainder)}.count(); |
115 | return ts; |
116 | }; |
117 | |
118 | ProcessInfo.SetParentProcessID(stat_fields.ppid); |
119 | ProcessInfo.SetProcessGroupID(stat_fields.pgrp); |
120 | ProcessInfo.SetProcessSessionID(stat_fields.session); |
121 | ProcessInfo.SetUserTime(convert(stat_fields.utime)); |
122 | ProcessInfo.SetSystemTime(convert(stat_fields.stime)); |
123 | ProcessInfo.SetCumulativeUserTime(convert(stat_fields.cutime)); |
124 | ProcessInfo.SetCumulativeSystemTime(convert(stat_fields.cstime)); |
125 | switch (stat_fields.state) { |
126 | case 'R': |
127 | State = ProcessState::Running; |
128 | break; |
129 | case 'S': |
130 | State = ProcessState::Sleeping; |
131 | break; |
132 | case 'D': |
133 | State = ProcessState::DiskSleep; |
134 | break; |
135 | case 'Z': |
136 | State = ProcessState::Zombie; |
137 | break; |
138 | case 'X': |
139 | State = ProcessState::Dead; |
140 | break; |
141 | case 'P': |
142 | State = ProcessState::Parked; |
143 | break; |
144 | case 'W': |
145 | State = ProcessState::Paging; |
146 | break; |
147 | case 'I': |
148 | State = ProcessState::Idle; |
149 | break; |
150 | case 'T': // Stopped on a signal or (before Linux 2.6.33) trace stopped |
151 | [[fallthrough]]; |
152 | case 't': |
153 | State = ProcessState::TracedOrStopped; |
154 | break; |
155 | default: |
156 | State = ProcessState::Unknown; |
157 | break; |
158 | } |
159 | |
160 | if (State == ProcessState::Unknown) { |
161 | LLDB_LOG(log, "Unknown process state {0}" , stat_fields.state); |
162 | } |
163 | |
164 | BufferOrError = getProcFile(pid: Pid, file: "status" ); |
165 | if (!BufferOrError) |
166 | return false; |
167 | |
168 | Rest = BufferOrError.get()->getBuffer(); |
169 | if (Rest.empty()) |
170 | return false; |
171 | |
172 | while (!Rest.empty()) { |
173 | llvm::StringRef Line; |
174 | std::tie(args&: Line, args&: Rest) = Rest.split(Separator: '\n'); |
175 | |
176 | if (Line.consume_front(Prefix: "Gid:" )) { |
177 | // Real, effective, saved set, and file system GIDs. Read the first two. |
178 | Line = Line.ltrim(); |
179 | uint32_t RGid, EGid; |
180 | Line.consumeInteger(Radix: 10, Result&: RGid); |
181 | Line = Line.ltrim(); |
182 | Line.consumeInteger(Radix: 10, Result&: EGid); |
183 | |
184 | ProcessInfo.SetGroupID(RGid); |
185 | ProcessInfo.SetEffectiveGroupID(EGid); |
186 | } else if (Line.consume_front(Prefix: "Uid:" )) { |
187 | // Real, effective, saved set, and file system UIDs. Read the first two. |
188 | Line = Line.ltrim(); |
189 | uint32_t RUid, EUid; |
190 | Line.consumeInteger(Radix: 10, Result&: RUid); |
191 | Line = Line.ltrim(); |
192 | Line.consumeInteger(Radix: 10, Result&: EUid); |
193 | |
194 | ProcessInfo.SetUserID(RUid); |
195 | ProcessInfo.SetEffectiveUserID(EUid); |
196 | } else if (Line.consume_front(Prefix: "TracerPid:" )) { |
197 | Line = Line.ltrim(); |
198 | Line.consumeInteger(Radix: 10, Result&: TracerPid); |
199 | } else if (Line.consume_front(Prefix: "Tgid:" )) { |
200 | Line = Line.ltrim(); |
201 | Line.consumeInteger(Radix: 10, Result&: Tgid); |
202 | } |
203 | } |
204 | return true; |
205 | } |
206 | |
207 | static bool IsDirNumeric(const char *dname) { |
208 | for (; *dname; dname++) { |
209 | if (!isdigit(*dname)) |
210 | return false; |
211 | } |
212 | return true; |
213 | } |
214 | |
215 | static ArchSpec GetELFProcessCPUType(llvm::StringRef exe_path) { |
216 | Log *log = GetLog(mask: LLDBLog::Host); |
217 | |
218 | auto buffer_sp = FileSystem::Instance().CreateDataBuffer(path: exe_path, size: 0x20, offset: 0); |
219 | if (!buffer_sp) |
220 | return ArchSpec(); |
221 | |
222 | uint8_t exe_class = |
223 | llvm::object::getElfArchType( |
224 | Object: {reinterpret_cast<const char *>(buffer_sp->GetBytes()), |
225 | size_t(buffer_sp->GetByteSize())}) |
226 | .first; |
227 | |
228 | switch (exe_class) { |
229 | case llvm::ELF::ELFCLASS32: |
230 | return HostInfo::GetArchitecture(arch_kind: HostInfo::eArchKind32); |
231 | case llvm::ELF::ELFCLASS64: |
232 | return HostInfo::GetArchitecture(arch_kind: HostInfo::eArchKind64); |
233 | default: |
234 | LLDB_LOG(log, "Unknown elf class ({0}) in file {1}" , exe_class, exe_path); |
235 | return ArchSpec(); |
236 | } |
237 | } |
238 | |
239 | static void GetProcessArgs(::pid_t pid, ProcessInstanceInfo &process_info) { |
240 | auto BufferOrError = getProcFile(pid, file: "cmdline" ); |
241 | if (!BufferOrError) |
242 | return; |
243 | std::unique_ptr<llvm::MemoryBuffer> Cmdline = std::move(*BufferOrError); |
244 | |
245 | llvm::StringRef Arg0, Rest; |
246 | std::tie(args&: Arg0, args&: Rest) = Cmdline->getBuffer().split(Separator: '\0'); |
247 | process_info.SetArg0(Arg0); |
248 | while (!Rest.empty()) { |
249 | llvm::StringRef Arg; |
250 | std::tie(args&: Arg, args&: Rest) = Rest.split(Separator: '\0'); |
251 | process_info.GetArguments().AppendArgument(arg_str: Arg); |
252 | } |
253 | } |
254 | |
255 | static void GetExePathAndArch(::pid_t pid, ProcessInstanceInfo &process_info) { |
256 | Log *log = GetLog(mask: LLDBLog::Process); |
257 | std::string ExePath(PATH_MAX, '\0'); |
258 | |
259 | // We can't use getProcFile here because proc/[pid]/exe is a symbolic link. |
260 | llvm::SmallString<64> ProcExe; |
261 | (llvm::Twine("/proc/" ) + llvm::Twine(pid) + "/exe" ).toVector(Out&: ProcExe); |
262 | |
263 | ssize_t len = readlink(path: ProcExe.c_str(), buf: &ExePath[0], PATH_MAX); |
264 | if (len > 0) { |
265 | ExePath.resize(n: len); |
266 | } else { |
267 | LLDB_LOG(log, "failed to read link exe link for {0}: {1}" , pid, |
268 | Status(errno, eErrorTypePOSIX)); |
269 | ExePath.resize(n: 0); |
270 | } |
271 | // If the binary has been deleted, the link name has " (deleted)" appended. |
272 | // Remove if there. |
273 | llvm::StringRef PathRef = ExePath; |
274 | PathRef.consume_back(Suffix: " (deleted)" ); |
275 | |
276 | if (!PathRef.empty()) { |
277 | process_info.GetExecutableFile().SetFile(path: PathRef, style: FileSpec::Style::native); |
278 | process_info.SetArchitecture(GetELFProcessCPUType(exe_path: PathRef)); |
279 | } |
280 | } |
281 | |
282 | static void GetProcessEnviron(::pid_t pid, ProcessInstanceInfo &process_info) { |
283 | // Get the process environment. |
284 | auto BufferOrError = getProcFile(pid, file: "environ" ); |
285 | if (!BufferOrError) |
286 | return; |
287 | |
288 | std::unique_ptr<llvm::MemoryBuffer> Environ = std::move(*BufferOrError); |
289 | llvm::StringRef Rest = Environ->getBuffer(); |
290 | while (!Rest.empty()) { |
291 | llvm::StringRef Var; |
292 | std::tie(args&: Var, args&: Rest) = Rest.split(Separator: '\0'); |
293 | process_info.GetEnvironment().insert(KeyEqValue: Var); |
294 | } |
295 | } |
296 | |
297 | static bool GetProcessAndStatInfo(::pid_t pid, |
298 | ProcessInstanceInfo &process_info, |
299 | ProcessState &State, ::pid_t &tracerpid) { |
300 | ::pid_t tgid; |
301 | tracerpid = 0; |
302 | process_info.Clear(); |
303 | |
304 | process_info.SetProcessID(pid); |
305 | |
306 | GetExePathAndArch(pid, process_info); |
307 | GetProcessArgs(pid, process_info); |
308 | GetProcessEnviron(pid, process_info); |
309 | |
310 | // Get User and Group IDs and get tracer pid. |
311 | if (!GetStatusInfo(Pid: pid, ProcessInfo&: process_info, State, TracerPid&: tracerpid, Tgid&: tgid)) |
312 | return false; |
313 | |
314 | return true; |
315 | } |
316 | |
317 | uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info, |
318 | ProcessInstanceInfoList &process_infos) { |
319 | static const char procdir[] = "/proc/" ; |
320 | |
321 | DIR *dirproc = opendir(name: procdir); |
322 | if (dirproc) { |
323 | struct dirent *direntry = nullptr; |
324 | const uid_t our_uid = getuid(); |
325 | const lldb::pid_t our_pid = getpid(); |
326 | bool all_users = match_info.GetMatchAllUsers(); |
327 | |
328 | while ((direntry = readdir(dirp: dirproc)) != nullptr) { |
329 | if (direntry->d_type != DT_DIR || !IsDirNumeric(dname: direntry->d_name)) |
330 | continue; |
331 | |
332 | lldb::pid_t pid = atoi(nptr: direntry->d_name); |
333 | |
334 | // Skip this process. |
335 | if (pid == our_pid) |
336 | continue; |
337 | |
338 | ::pid_t tracerpid; |
339 | ProcessState State; |
340 | ProcessInstanceInfo process_info; |
341 | |
342 | if (!GetProcessAndStatInfo(pid, process_info, State, tracerpid)) |
343 | continue; |
344 | |
345 | // Skip if process is being debugged. |
346 | if (tracerpid != 0) |
347 | continue; |
348 | |
349 | if (State == ProcessState::Zombie) |
350 | continue; |
351 | |
352 | // Check for user match if we're not matching all users and not running |
353 | // as root. |
354 | if (!all_users && (our_uid != 0) && (process_info.GetUserID() != our_uid)) |
355 | continue; |
356 | |
357 | if (match_info.Matches(proc_info: process_info)) { |
358 | process_infos.push_back(x: process_info); |
359 | } |
360 | } |
361 | |
362 | closedir(dirp: dirproc); |
363 | } |
364 | |
365 | return process_infos.size(); |
366 | } |
367 | |
368 | bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) { |
369 | bool tids_changed = false; |
370 | static const char procdir[] = "/proc/" ; |
371 | static const char taskdir[] = "/task/" ; |
372 | std::string process_task_dir = procdir + llvm::to_string(Value: pid) + taskdir; |
373 | DIR *dirproc = opendir(name: process_task_dir.c_str()); |
374 | |
375 | if (dirproc) { |
376 | struct dirent *direntry = nullptr; |
377 | while ((direntry = readdir(dirp: dirproc)) != nullptr) { |
378 | if (direntry->d_type != DT_DIR || !IsDirNumeric(dname: direntry->d_name)) |
379 | continue; |
380 | |
381 | lldb::tid_t tid = atoi(nptr: direntry->d_name); |
382 | TidMap::iterator it = tids_to_attach.find(x: tid); |
383 | if (it == tids_to_attach.end()) { |
384 | tids_to_attach.insert(x: TidPair(tid, false)); |
385 | tids_changed = true; |
386 | } |
387 | } |
388 | closedir(dirp: dirproc); |
389 | } |
390 | |
391 | return tids_changed; |
392 | } |
393 | |
394 | bool Host::GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &process_info) { |
395 | ::pid_t tracerpid; |
396 | ProcessState State; |
397 | return GetProcessAndStatInfo(pid, process_info, State, tracerpid); |
398 | } |
399 | |
400 | Environment Host::GetEnvironment() { return Environment(environ); } |
401 | |
402 | Status Host::ShellExpandArguments(ProcessLaunchInfo &launch_info) { |
403 | return Status("unimplemented" ); |
404 | } |
405 | |
406 | std::optional<lldb::pid_t> lldb_private::getPIDForTID(lldb::pid_t tid) { |
407 | ::pid_t tracerpid, tgid = LLDB_INVALID_PROCESS_ID; |
408 | ProcessInstanceInfo process_info; |
409 | ProcessState state; |
410 | |
411 | if (!GetStatusInfo(Pid: tid, ProcessInfo&: process_info, State&: state, TracerPid&: tracerpid, Tgid&: tgid) || |
412 | tgid == LLDB_INVALID_PROCESS_ID) |
413 | return std::nullopt; |
414 | return tgid; |
415 | } |
416 | |