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