gc: Start libproc-based Darwin roots

This commit is contained in:
Artemis Tosini 2024-03-29 23:35:07 -04:00
parent d8d1dd1b83
commit 80361ab769

View file

@ -1,6 +1,7 @@
#include "derivations.hh"
#include "globals.hh"
#include "local-store.hh"
#include "logging.hh"
#include "signals.hh"
#include "finally.hh"
@ -21,6 +22,11 @@
#include <sys/un.h>
#include <unistd.h>
#ifdef __APPLE__
#include <sys/proc_info.h>
#include <libproc.h>
#endif
namespace nix {
@ -357,12 +363,9 @@ static void readFileRoots(const char * path, UncheckedRoots & roots)
throw;
}
}
#endif
void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
static void readProcfsRoots(const Path & storeDir, UncheckedRoots & unchecked)
{
UncheckedRoots unchecked;
auto procDir = AutoCloseDir{opendir("/proc")};
if (procDir) {
struct dirent * ent;
@ -418,8 +421,99 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
if (errno)
throw SysError("iterating /proc");
}
}
#endif
#if !defined(__linux__)
#ifdef __APPLE__
static void readLibprocRoots(const Path & storeDir, UncheckedRoots & unchecked)
{
int pidBufSize = proc_listpids(PROC_ALL_PIDS, 0, nullptr, 0);
if (pidBufSize <= 0 || pidBufSize % sizeof(int) != 0)
throw SysError("Listing PIDs");
std::vector<int> pids(pidBufSize / sizeof(int));
pidBufSize = proc_listpids(PROC_ALL_PIDS, 0, pids.data(), pidBufSize);
if (pidBufSize <= 0 || pidBufSize % sizeof(int) != 0)
throw SysError("Listing PIDs");
pids.resize(pidBufSize / sizeof(int));
for (auto pid: pids) {
// It doesn't make sense to ask about the kernel
if (pid == 0)
continue;
// Process cwd/root directory
struct proc_vnodepathinfo vnodeInfo;
if (proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vnodeInfo, sizeof(vnodeInfo)) <= 0) {
if (errno == ESRCH) continue;
throw SysError("Getting PID info");
};
unchecked[std::string(vnodeInfo.pvi_cdir.vip_path)].emplace("{libproc/cwd}");
unchecked[std::string(vnodeInfo.pvi_rdir.vip_path)].emplace("{libproc/rootdir}");
// File descriptors
int fdBufSize = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, nullptr, 0);
if (fdBufSize <= 0 || fdBufSize % sizeof(struct proc_fdinfo) != 0)
throw SysError("Listing PID's file descriptors");
std::vector<struct proc_fdinfo> fds(fdBufSize / sizeof(struct proc_fdinfo));
fdBufSize = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds.data(), fdBufSize);
if (fdBufSize <= 0)
throw SysError("Listing PID's file descriptors");
fds.resize(fdBufSize / sizeof(struct proc_fdinfo));
for (auto fd: fds) {
// While there are names for other FD types, only vnodes are in the filesystem
if (fd.proc_fdtype != PROX_FDTYPE_VNODE)
continue;
struct vnode_fdinfowithpath fdInfo;
if (proc_pidfdinfo(pid, fd.proc_fd, PROC_PIDFDVNODEPATHINFO, &fdInfo, sizeof(fdInfo)) <= 0)
throw SysError("Getting file descriptor path");
unchecked[std::string(fdInfo.pvip.vip_path)].emplace("{libproc/fd}");
}
// Regions (e.g. mmapped files, executables, shared libraries)
struct proc_regionwithpathinfo regionInfo;
uint64_t nextAddr = 0;
while (true) {
// Seriously, what are you doing XNU?
// There's 3 flavors of PROC_PIDREGIONPATHINFO:
// * PROC_PIDREGIONPATHINFO includes all regions
// * PROC_PIDREGIONPATHINFO2 includes regions backed by a vnode
// * PROC_PIDREGIONPATHINFO3 includes regions backed by a vnode on a specified filesystem
// Only PROC_PIDREGIONPATHINFO is documented.
// Unfortunately, using it would make finding gcroots take about 50x as long
// and tests would fail from timeout.
// According to the Frida source code, PROC_PIDREGIONPATHINFO2 has been available
// since XNU 2782.1.97 in OS X 10.10
//
// This is PROC_PIDREGIONPATHINFO2
if (proc_pidinfo(pid, 22, nextAddr, &regionInfo, sizeof(regionInfo)) <= 0) {
if ((errno == ESRCH) || (errno == EINVAL))
break;
throw SysError("Getting region path");
}
unchecked[std::string(regionInfo.prp_vip.vip_path)].emplace("{libproc/region}");
nextAddr = regionInfo.prp_prinfo.pri_address + regionInfo.prp_prinfo.pri_size;
}
// TODO: per-thread cwds (apparently exist but I don't see them much)
}
}
#endif
void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
{
UncheckedRoots unchecked;
#ifdef __APPLE__
readLibprocRoots(storeDir, unchecked);
#elif defined(__linux__)
readProcfsRoots(storeDir, unchecked);
#else
try {
std::regex lsofRegex(R"(^n(/.*)$)");
auto lsofLines =