| //===-- source/Host/linux/Host.cpp ----------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include <cerrno> | |
| #include <cstdio> | |
| #include <cstring> | |
| #include <dirent.h> | |
| #include <fcntl.h> | |
| #include <optional> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <sys/utsname.h> | |
| #include <unistd.h> | |
| #include "llvm/ADT/StringSwitch.h" | |
| #include "llvm/Object/ELF.h" | |
| #include "llvm/Support/ScopedPrinter.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/ProcessInfo.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Host/FileSystem.h" | |
| #include "lldb/Host/Host.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Host/linux/Host.h" | |
| #include "lldb/Host/posix/Support.h" | |
| #include "lldb/Utility/DataExtractor.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| namespace { | |
| enum class ProcessState { | |
| Unknown, | |
| Dead, | |
| DiskSleep, | |
| Idle, | |
| Paging, | |
| Parked, | |
| Running, | |
| Sleeping, | |
| TracedOrStopped, | |
| Zombie, | |
| }; | |
| struct StatFields { | |
| ::pid_t pid = LLDB_INVALID_PROCESS_ID; | |
| // comm | |
| char state; | |
| ::pid_t ppid = LLDB_INVALID_PROCESS_ID; | |
| ::pid_t pgrp = LLDB_INVALID_PROCESS_ID; | |
| ::pid_t session = LLDB_INVALID_PROCESS_ID; | |
| int tty_nr; | |
| int tpgid; | |
| unsigned flags; | |
| long unsigned minflt; | |
| long unsigned cminflt; | |
| long unsigned majflt; | |
| long unsigned cmajflt; | |
| long unsigned utime; | |
| long unsigned stime; | |
| long cutime; | |
| long cstime; | |
| // In proc_pid_stat(5) this field is specified as priority | |
| // but documented as realtime priority. To keep with the adopted | |
| // nomenclature in ProcessInstanceInfo, we adopt the documented | |
| // naming here. | |
| long realtime_priority; | |
| long priority; | |
| // .... other things. We don't need them below | |
| }; | |
| } | |
| namespace lldb_private { | |
| class ProcessLaunchInfo; | |
| } | |
| static bool GetStatusInfo(::pid_t Pid, ProcessInstanceInfo &ProcessInfo, | |
| ProcessState &State, ::pid_t &TracerPid, | |
| ::pid_t &Tgid) { | |
| Log *log = GetLog(LLDBLog::Host); | |
| auto BufferOrError = getProcFile(Pid, "stat"); | |
| if (!BufferOrError) | |
| return false; | |
| llvm::StringRef Rest = BufferOrError.get()->getBuffer(); | |
| if (Rest.empty()) | |
| return false; | |
| StatFields stat_fields; | |
| if (sscanf( | |
| Rest.data(), | |
| "%d %*s %c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld", | |
| &stat_fields.pid, /* comm, */ &stat_fields.state, | |
| &stat_fields.ppid, &stat_fields.pgrp, &stat_fields.session, | |
| &stat_fields.tty_nr, &stat_fields.tpgid, &stat_fields.flags, | |
| &stat_fields.minflt, &stat_fields.cminflt, &stat_fields.majflt, | |
| &stat_fields.cmajflt, &stat_fields.utime, &stat_fields.stime, | |
| &stat_fields.cutime, &stat_fields.cstime, | |
| &stat_fields.realtime_priority, &stat_fields.priority) < 0) { | |
| return false; | |
| } | |
| auto convert = [sc_clk_ticks = sysconf(_SC_CLK_TCK)](auto time_in_ticks) { | |
| ProcessInstanceInfo::timespec ts; | |
| if (sc_clk_ticks <= 0) { | |
| return ts; | |
| } | |
| ts.tv_sec = time_in_ticks / sc_clk_ticks; | |
| double remainder = | |
| (static_cast<double>(time_in_ticks) / sc_clk_ticks) - ts.tv_sec; | |
| ts.tv_usec = | |
| std::chrono::microseconds{std::lround(1e+6 * remainder)}.count(); | |
| return ts; | |
| }; | |
| // Priority (nice) values run from 19 to -20 inclusive (in linux). In the | |
| // prpsinfo struct pr_nice is a char. | |
| auto priority_value = static_cast<int8_t>( | |
| (stat_fields.priority < 0 ? 0x80 : 0x00) | (stat_fields.priority & 0x7f)); | |
| ProcessInfo.SetParentProcessID(stat_fields.ppid); | |
| ProcessInfo.SetProcessGroupID(stat_fields.pgrp); | |
| ProcessInfo.SetProcessSessionID(stat_fields.session); | |
| ProcessInfo.SetUserTime(convert(stat_fields.utime)); | |
| ProcessInfo.SetSystemTime(convert(stat_fields.stime)); | |
| ProcessInfo.SetCumulativeUserTime(convert(stat_fields.cutime)); | |
| ProcessInfo.SetCumulativeSystemTime(convert(stat_fields.cstime)); | |
| ProcessInfo.SetPriorityValue(priority_value); | |
| switch (stat_fields.state) { | |
| case 'R': | |
| State = ProcessState::Running; | |
| break; | |
| case 'S': | |
| State = ProcessState::Sleeping; | |
| break; | |
| case 'D': | |
| State = ProcessState::DiskSleep; | |
| break; | |
| case 'Z': | |
| State = ProcessState::Zombie; | |
| break; | |
| case 'X': | |
| State = ProcessState::Dead; | |
| break; | |
| case 'P': | |
| State = ProcessState::Parked; | |
| break; | |
| case 'W': | |
| State = ProcessState::Paging; | |
| break; | |
| case 'I': | |
| State = ProcessState::Idle; | |
| break; | |
| case 'T': // Stopped on a signal or (before Linux 2.6.33) trace stopped | |
| [[fallthrough]]; | |
| case 't': | |
| State = ProcessState::TracedOrStopped; | |
| break; | |
| default: | |
| State = ProcessState::Unknown; | |
| break; | |
| } | |
| ProcessInfo.SetIsZombie(State == ProcessState::Zombie); | |
| if (State == ProcessState::Unknown) { | |
| LLDB_LOG(log, "Unknown process state {0}", stat_fields.state); | |
| } | |
| BufferOrError = getProcFile(Pid, "status"); | |
| if (!BufferOrError) | |
| return false; | |
| Rest = BufferOrError.get()->getBuffer(); | |
| if (Rest.empty()) | |
| return false; | |
| while (!Rest.empty()) { | |
| llvm::StringRef Line; | |
| std::tie(Line, Rest) = Rest.split('\n'); | |
| if (Line.consume_front("Gid:")) { | |
| // Real, effective, saved set, and file system GIDs. Read the first two. | |
| Line = Line.ltrim(); | |
| uint32_t RGid, EGid; | |
| Line.consumeInteger(10, RGid); | |
| Line = Line.ltrim(); | |
| Line.consumeInteger(10, EGid); | |
| ProcessInfo.SetGroupID(RGid); | |
| ProcessInfo.SetEffectiveGroupID(EGid); | |
| } else if (Line.consume_front("Uid:")) { | |
| // Real, effective, saved set, and file system UIDs. Read the first two. | |
| Line = Line.ltrim(); | |
| uint32_t RUid, EUid; | |
| Line.consumeInteger(10, RUid); | |
| Line = Line.ltrim(); | |
| Line.consumeInteger(10, EUid); | |
| ProcessInfo.SetUserID(RUid); | |
| ProcessInfo.SetEffectiveUserID(EUid); | |
| } else if (Line.consume_front("TracerPid:")) { | |
| Line = Line.ltrim(); | |
| Line.consumeInteger(10, TracerPid); | |
| } else if (Line.consume_front("Tgid:")) { | |
| Line = Line.ltrim(); | |
| Line.consumeInteger(10, Tgid); | |
| } else if (Line.consume_front("CoreDumping:")) { | |
| uint32_t coredumping; | |
| Line = Line.ltrim(); | |
| if (!Line.consumeInteger(2, coredumping)) | |
| ProcessInfo.SetIsCoreDumping(coredumping); | |
| } | |
| } | |
| return true; | |
| } | |
| static bool IsDirNumeric(const char *dname) { | |
| for (; *dname; dname++) { | |
| if (!isdigit(*dname)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| static ArchSpec GetELFProcessCPUType(llvm::StringRef exe_path) { | |
| Log *log = GetLog(LLDBLog::Host); | |
| auto buffer_sp = FileSystem::Instance().CreateDataBuffer(exe_path, 0x20, 0); | |
| if (!buffer_sp) | |
| return ArchSpec(); | |
| uint8_t exe_class = | |
| llvm::object::getElfArchType( | |
| {reinterpret_cast<const char *>(buffer_sp->GetBytes()), | |
| size_t(buffer_sp->GetByteSize())}) | |
| .first; | |
| switch (exe_class) { | |
| case llvm::ELF::ELFCLASS32: | |
| return HostInfo::GetArchitecture(HostInfo::eArchKind32); | |
| case llvm::ELF::ELFCLASS64: | |
| return HostInfo::GetArchitecture(HostInfo::eArchKind64); | |
| default: | |
| LLDB_LOG(log, "Unknown elf class ({0}) in file {1}", exe_class, exe_path); | |
| return ArchSpec(); | |
| } | |
| } | |
| static void GetProcessArgs(::pid_t pid, ProcessInstanceInfo &process_info) { | |
| auto BufferOrError = getProcFile(pid, "cmdline"); | |
| if (!BufferOrError) | |
| return; | |
| std::unique_ptr<llvm::MemoryBuffer> Cmdline = std::move(*BufferOrError); | |
| llvm::StringRef Arg0, Rest; | |
| std::tie(Arg0, Rest) = Cmdline->getBuffer().split('\0'); | |
| process_info.SetArg0(Arg0); | |
| while (!Rest.empty()) { | |
| llvm::StringRef Arg; | |
| std::tie(Arg, Rest) = Rest.split('\0'); | |
| process_info.GetArguments().AppendArgument(Arg); | |
| } | |
| } | |
| static void GetExePathAndArch(::pid_t pid, ProcessInstanceInfo &process_info) { | |
| Log *log = GetLog(LLDBLog::Process); | |
| std::string ExePath(PATH_MAX, '\0'); | |
| // We can't use getProcFile here because proc/[pid]/exe is a symbolic link. | |
| llvm::SmallString<64> ProcExe; | |
| (llvm::Twine("/proc/") + llvm::Twine(pid) + "/exe").toVector(ProcExe); | |
| ssize_t len = readlink(ProcExe.c_str(), &ExePath[0], PATH_MAX); | |
| if (len > 0) { | |
| ExePath.resize(len); | |
| } else { | |
| LLDB_LOG(log, "failed to read link exe link for {0}: {1}", pid, | |
| Status(errno, eErrorTypePOSIX)); | |
| ExePath.resize(0); | |
| } | |
| // If the binary has been deleted, the link name has " (deleted)" appended. | |
| // Remove if there. | |
| llvm::StringRef PathRef = ExePath; | |
| PathRef.consume_back(" (deleted)"); | |
| if (!PathRef.empty()) { | |
| process_info.GetExecutableFile().SetFile(PathRef, FileSpec::Style::native); | |
| process_info.SetArchitecture(GetELFProcessCPUType(PathRef)); | |
| } | |
| } | |
| static void GetProcessEnviron(::pid_t pid, ProcessInstanceInfo &process_info) { | |
| // Get the process environment. | |
| auto BufferOrError = getProcFile(pid, "environ"); | |
| if (!BufferOrError) | |
| return; | |
| std::unique_ptr<llvm::MemoryBuffer> Environ = std::move(*BufferOrError); | |
| llvm::StringRef Rest = Environ->getBuffer(); | |
| while (!Rest.empty()) { | |
| llvm::StringRef Var; | |
| std::tie(Var, Rest) = Rest.split('\0'); | |
| process_info.GetEnvironment().insert(Var); | |
| } | |
| } | |
| static bool GetProcessAndStatInfo(::pid_t pid, | |
| ProcessInstanceInfo &process_info, | |
| ProcessState &State, ::pid_t &tracerpid) { | |
| ::pid_t tgid; | |
| tracerpid = 0; | |
| process_info.Clear(); | |
| process_info.SetProcessID(pid); | |
| GetExePathAndArch(pid, process_info); | |
| GetProcessArgs(pid, process_info); | |
| GetProcessEnviron(pid, process_info); | |
| // Get User and Group IDs and get tracer pid. | |
| if (!GetStatusInfo(pid, process_info, State, tracerpid, tgid)) | |
| return false; | |
| return true; | |
| } | |
| uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info, | |
| ProcessInstanceInfoList &process_infos) { | |
| static const char procdir[] = "/proc/"; | |
| DIR *dirproc = opendir(procdir); | |
| if (dirproc) { | |
| struct dirent *direntry = nullptr; | |
| const uid_t our_uid = getuid(); | |
| const lldb::pid_t our_pid = getpid(); | |
| bool all_users = match_info.GetMatchAllUsers(); | |
| while ((direntry = readdir(dirproc)) != nullptr) { | |
| if (direntry->d_type != DT_DIR || !IsDirNumeric(direntry->d_name)) | |
| continue; | |
| lldb::pid_t pid = atoi(direntry->d_name); | |
| // Skip this process. | |
| if (pid == our_pid) | |
| continue; | |
| ::pid_t tracerpid; | |
| ProcessState State; | |
| ProcessInstanceInfo process_info; | |
| if (!GetProcessAndStatInfo(pid, process_info, State, tracerpid)) | |
| continue; | |
| // Skip if process is being debugged. | |
| if (tracerpid != 0) | |
| continue; | |
| if (State == ProcessState::Zombie) | |
| continue; | |
| // Check for user match if we're not matching all users and not running | |
| // as root. | |
| if (!all_users && (our_uid != 0) && (process_info.GetUserID() != our_uid)) | |
| continue; | |
| if (match_info.Matches(process_info)) { | |
| process_infos.push_back(process_info); | |
| } | |
| } | |
| closedir(dirproc); | |
| } | |
| return process_infos.size(); | |
| } | |
| bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) { | |
| bool tids_changed = false; | |
| static const char procdir[] = "/proc/"; | |
| static const char taskdir[] = "/task/"; | |
| std::string process_task_dir = procdir + llvm::to_string(pid) + taskdir; | |
| DIR *dirproc = opendir(process_task_dir.c_str()); | |
| if (dirproc) { | |
| struct dirent *direntry = nullptr; | |
| while ((direntry = readdir(dirproc)) != nullptr) { | |
| if (direntry->d_type != DT_DIR || !IsDirNumeric(direntry->d_name)) | |
| continue; | |
| lldb::tid_t tid = atoi(direntry->d_name); | |
| TidMap::iterator it = tids_to_attach.find(tid); | |
| if (it == tids_to_attach.end()) { | |
| tids_to_attach.insert(TidPair(tid, false)); | |
| tids_changed = true; | |
| } | |
| } | |
| closedir(dirproc); | |
| } | |
| return tids_changed; | |
| } | |
| bool Host::GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &process_info) { | |
| ::pid_t tracerpid; | |
| ProcessState State; | |
| return GetProcessAndStatInfo(pid, process_info, State, tracerpid); | |
| } | |
| Status Host::ShellExpandArguments(ProcessLaunchInfo &launch_info) { | |
| return Status::FromErrorString("unimplemented"); | |
| } | |
| std::optional<lldb::pid_t> lldb_private::getPIDForTID(lldb::pid_t tid) { | |
| ::pid_t tracerpid, tgid = LLDB_INVALID_PROCESS_ID; | |
| ProcessInstanceInfo process_info; | |
| ProcessState state; | |
| if (!GetStatusInfo(tid, process_info, state, tracerpid, tgid) || | |
| tgid == LLDB_INVALID_PROCESS_ID) | |
| return std::nullopt; | |
| return tgid; | |
| } |