blob: b5f050426d88b9c46ae7c65272c7ce146e772433 [file] [log] [blame]
//===-- 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;
}