mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Implement macOS-specific support for the ProcessMemory plugin and update plugin discovery/build. - Add macOS build/install support: include plugins/ProcessMemory in top-level CMake and copy the built plugin into Reclass.app/Contents/PlugIns on macOS. - Implement Apple-specific ProcessMemoryProvider: task_for_pid usage, mach_vm_read_overwrite/mach_vm_write, proc_pidpath/proc_regionfilename based module caching, region enumeration, symbol formatting, module enumeration, and proper cleanup (mach_port_deallocate). - Extend plugin header to track m_task and adjust readability checks for macOS. - Add macOS handling in getInitialBaseAddress and process enumeration to find base addresses and processes using proc APIs. - Improve PluginManager to probe multiple plugin locations (including Contents/PlugIns inside macOS bundles), aggregate/log candidate counts, and continue scanning multiple dirs. - Add macOS screenshot to README (docs/README_PIC6.png) and reference it in README. These changes enable the ProcessMemory plugin to operate on macOS and make plugin discovery more robust on macOS app bundles.
1176 lines
38 KiB
C++
1176 lines
38 KiB
C++
#include "ProcessMemoryPlugin.h"
|
|
|
|
#include "../../src/processpicker.h"
|
|
|
|
#include <QStyle>
|
|
#include <QApplication>
|
|
#include <QRegularExpression>
|
|
#include <QMessageBox>
|
|
#include <QPixmap>
|
|
#include <QImage>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QMap>
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(_WIN32)
|
|
#include <QtWin>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <tlhelp32.h>
|
|
#include <psapi.h>
|
|
#include <shellapi.h>
|
|
|
|
typedef struct _UNICODE_STRING { USHORT Length, MaximumLength; PWSTR Buffer; } UNICODE_STRING;
|
|
typedef struct _CLIENT_ID { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID;
|
|
typedef struct _SYSTEM_THREAD_INFORMATION {
|
|
LARGE_INTEGER KernelTime, UserTime, CreateTime;
|
|
ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId;
|
|
LONG Priority, BasePriority; ULONG ContextSwitches, ThreadState, WaitReason;
|
|
} SYSTEM_THREAD_INFORMATION;
|
|
typedef struct _SYSTEM_PROCESS_INFORMATION {
|
|
ULONG NextEntryOffset; // 0x000
|
|
ULONG NumberOfThreads; // 0x004
|
|
LARGE_INTEGER WorkingSetPrivateSize; // 0x008
|
|
ULONG HardFaultCount; // 0x010
|
|
ULONG NumberOfThreadsHighWatermark; // 0x014
|
|
ULONGLONG CycleTime; // 0x018
|
|
LARGE_INTEGER CreateTime; // 0x020
|
|
LARGE_INTEGER UserTime; // 0x028
|
|
LARGE_INTEGER KernelTime; // 0x030
|
|
UNICODE_STRING ImageName; // 0x038
|
|
LONG BasePriority; // 0x048
|
|
HANDLE UniqueProcessId; // 0x050
|
|
PVOID InheritedFromUniqueProcessId; // 0x058
|
|
ULONG HandleCount; // 0x060
|
|
ULONG SessionId; // 0x064
|
|
ULONG_PTR UniqueProcessKey; // 0x068
|
|
SIZE_T PeakVirtualSize; // 0x070
|
|
SIZE_T VirtualSize; // 0x078
|
|
ULONG PageFaultCount; // 0x080
|
|
ULONG _pad0; // 0x084
|
|
SIZE_T PeakWorkingSetSize; // 0x088
|
|
SIZE_T WorkingSetSize; // 0x090
|
|
SIZE_T QuotaPeakPagedPoolUsage; // 0x098
|
|
SIZE_T QuotaPagedPoolUsage; // 0x0A0
|
|
SIZE_T QuotaPeakNonPagedPoolUsage; // 0x0A8
|
|
SIZE_T QuotaNonPagedPoolUsage; // 0x0B0
|
|
SIZE_T PagefileUsage; // 0x0B8
|
|
SIZE_T PeakPagefileUsage; // 0x0C0
|
|
SIZE_T PrivatePageCount; // 0x0C8
|
|
LARGE_INTEGER ReadOperationCount; // 0x0D0
|
|
LARGE_INTEGER WriteOperationCount; // 0x0D8
|
|
LARGE_INTEGER OtherOperationCount; // 0x0E0
|
|
LARGE_INTEGER ReadTransferCount; // 0x0E8
|
|
LARGE_INTEGER WriteTransferCount; // 0x0F0
|
|
LARGE_INTEGER OtherTransferCount; // 0x0F8
|
|
} SYSTEM_PROCESS_INFORMATION; // sizeof = 0x100
|
|
typedef struct alignas(8) _THREAD_BASIC_INFORMATION {
|
|
NTSTATUS ExitStatus; // 0x00
|
|
ULONG _pad; // 0x04
|
|
PVOID TebBaseAddress; // 0x08
|
|
CLIENT_ID ClientId; // 0x10
|
|
ULONG_PTR AffinityMask; // 0x20
|
|
LONG Priority; // 0x28
|
|
LONG BasePriority; // 0x2C
|
|
} THREAD_BASIC_INFORMATION;
|
|
#elif defined(__linux__)
|
|
#include <climits>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/uio.h>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
#elif defined(__APPLE__)
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_vm.h>
|
|
#include <libproc.h>
|
|
#include <sys/proc_info.h>
|
|
#include <unistd.h>
|
|
#include <cstring>
|
|
#endif
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
// ProcessMemoryProvider implementation
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
|
|
#ifdef _WIN32
|
|
|
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
|
: m_handle(nullptr)
|
|
, m_pid(pid)
|
|
, m_processName(processName)
|
|
, m_writable(false)
|
|
, m_base(0)
|
|
{
|
|
// Try to open with write access first
|
|
m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
|
|
FALSE, pid);
|
|
if (m_handle)
|
|
m_writable = true;
|
|
else
|
|
{
|
|
// Fall back to read-only
|
|
m_handle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid);
|
|
m_writable = false;
|
|
}
|
|
|
|
if (m_handle) {
|
|
// Detect 32-bit (WoW64) process
|
|
BOOL isWow64 = FALSE;
|
|
if (IsWow64Process(m_handle, &isWow64) && isWow64)
|
|
m_pointerSize = 4;
|
|
// Query PEB address via NtQueryInformationProcess
|
|
{
|
|
typedef NTSTATUS(NTAPI* NtQIP_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
|
static NtQIP_t pNtQIP = (NtQIP_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
|
|
if (pNtQIP) {
|
|
struct { PVOID r1; PVOID PebBaseAddress; PVOID r2[2]; ULONG_PTR pid; PVOID r3; } pbi = {};
|
|
ULONG retLen = 0;
|
|
if (pNtQIP(m_handle, /*ProcessBasicInformation*/0, &pbi, sizeof(pbi), &retLen) >= 0 && pbi.PebBaseAddress)
|
|
m_peb = (uint64_t)(uintptr_t)pbi.PebBaseAddress;
|
|
}
|
|
}
|
|
cacheModules();
|
|
}
|
|
}
|
|
|
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
|
{
|
|
if (!m_handle || len <= 0) return false;
|
|
|
|
SIZE_T bytesRead = 0;
|
|
ReadProcessMemory(m_handle, (LPCVOID)(addr), buf, (SIZE_T)len, &bytesRead);
|
|
if ((int)bytesRead < len)
|
|
memset((char*)buf + bytesRead, 0, len - bytesRead);
|
|
return bytesRead > 0;
|
|
}
|
|
|
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
|
{
|
|
if (!m_handle || !m_writable || len <= 0) return false;
|
|
|
|
SIZE_T bytesWritten = 0;
|
|
if (WriteProcessMemory(m_handle, (LPVOID)(addr), buf, (SIZE_T)len, &bytesWritten))
|
|
return bytesWritten == (SIZE_T)len;
|
|
return false;
|
|
}
|
|
|
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
|
{
|
|
for (const auto& mod : m_modules)
|
|
{
|
|
if (addr >= mod.base && addr < mod.base + mod.size)
|
|
{
|
|
uint64_t offset = addr - mod.base;
|
|
return QStringLiteral("%1+0x%2")
|
|
.arg(mod.name)
|
|
.arg(offset, 0, 16, QChar('0'));
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void ProcessMemoryProvider::cacheModules()
|
|
{
|
|
HMODULE mods[1024];
|
|
DWORD needed = 0;
|
|
if (!EnumProcessModulesEx(m_handle, mods, sizeof(mods),
|
|
&needed, LIST_MODULES_ALL))
|
|
return;
|
|
int count = qMin((int)(needed / sizeof(HMODULE)), 1024);
|
|
m_modules.reserve(count);
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
MODULEINFO mi{};
|
|
WCHAR modName[MAX_PATH];
|
|
if (GetModuleInformation(m_handle, mods[i], &mi, sizeof(mi))
|
|
&& GetModuleBaseNameW(m_handle, mods[i], modName, MAX_PATH))
|
|
{
|
|
if ( i == 0 )
|
|
m_base = (uint64_t)mi.lpBaseOfDll;
|
|
|
|
WCHAR modPath[MAX_PATH];
|
|
QString fullPath;
|
|
if (GetModuleFileNameExW(m_handle, mods[i], modPath, MAX_PATH))
|
|
fullPath = QString::fromWCharArray(modPath);
|
|
|
|
m_modules.push_back(ModuleInfo{
|
|
QString::fromWCharArray(modName),
|
|
fullPath,
|
|
(uint64_t)mi.lpBaseOfDll,
|
|
(uint64_t)mi.SizeOfImage
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
QVector<rcx::Provider::ModuleEntry> ProcessMemoryProvider::enumerateModules() const
|
|
{
|
|
QVector<ModuleEntry> result;
|
|
result.reserve(m_modules.size());
|
|
for (const auto& m : m_modules)
|
|
result.push_back(ModuleEntry{m.name, m.fullPath, m.base, m.size});
|
|
return result;
|
|
}
|
|
|
|
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
|
{
|
|
QVector<rcx::MemoryRegion> regions;
|
|
if (!m_handle) return regions;
|
|
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
uint64_t addr = 0;
|
|
|
|
while (VirtualQueryEx(m_handle, (LPCVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {
|
|
if (mbi.State == MEM_COMMIT &&
|
|
!(mbi.Protect & PAGE_NOACCESS) &&
|
|
!(mbi.Protect & PAGE_GUARD))
|
|
{
|
|
rcx::MemoryRegion region;
|
|
region.base = (uint64_t)mbi.BaseAddress;
|
|
region.size = mbi.RegionSize;
|
|
region.readable = true;
|
|
region.writable = (mbi.Protect & PAGE_READWRITE) ||
|
|
(mbi.Protect & PAGE_WRITECOPY) ||
|
|
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
|
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
|
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
|
|
(mbi.Protect & PAGE_EXECUTE_READ) ||
|
|
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
|
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
|
|
|
// Match module name from cached module list
|
|
for (const auto& mod : m_modules) {
|
|
if (region.base >= mod.base && region.base < mod.base + mod.size) {
|
|
region.moduleName = mod.name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
regions.append(region);
|
|
}
|
|
|
|
uint64_t next = (uint64_t)mbi.BaseAddress + mbi.RegionSize;
|
|
if (next <= addr) break; // overflow protection
|
|
addr = next;
|
|
}
|
|
|
|
return regions;
|
|
}
|
|
|
|
#elif defined(__linux__)
|
|
|
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
|
: m_fd(-1)
|
|
, m_pid(pid)
|
|
, m_processName(processName)
|
|
, m_writable(false)
|
|
, m_base(0)
|
|
{
|
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
|
QByteArray pathUtf8 = memPath.toUtf8();
|
|
|
|
// Try read-write first
|
|
m_fd = ::open(pathUtf8.constData(), O_RDWR);
|
|
if (m_fd >= 0)
|
|
m_writable = true;
|
|
else
|
|
{
|
|
// Fall back to read-only
|
|
m_fd = ::open(pathUtf8.constData(), O_RDONLY);
|
|
m_writable = false;
|
|
}
|
|
|
|
if (m_fd >= 0) {
|
|
// Detect 32-bit ELF process
|
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
|
QByteArray exePathUtf8 = exePath.toUtf8();
|
|
int exeFd = ::open(exePathUtf8.constData(), O_RDONLY);
|
|
if (exeFd >= 0) {
|
|
unsigned char elfClass = 0;
|
|
// ELF e_ident[EI_CLASS] is at offset 4
|
|
if (::pread(exeFd, &elfClass, 1, 4) == 1 && elfClass == 1) // ELFCLASS32
|
|
m_pointerSize = 4;
|
|
::close(exeFd);
|
|
}
|
|
cacheModules();
|
|
}
|
|
}
|
|
|
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
|
{
|
|
if (m_fd < 0 || len <= 0) return false;
|
|
|
|
// Try process_vm_readv first (faster, no fd seek contention)
|
|
struct iovec local;
|
|
local.iov_base = buf;
|
|
local.iov_len = static_cast<size_t>(len);
|
|
|
|
struct iovec remote;
|
|
remote.iov_base = reinterpret_cast<void*>(addr);
|
|
remote.iov_len = static_cast<size_t>(len);
|
|
|
|
ssize_t nread = process_vm_readv(m_pid, &local, 1, &remote, 1, 0);
|
|
if (nread == static_cast<ssize_t>(len))
|
|
return true;
|
|
|
|
// Fallback: pread on /proc/<pid>/mem
|
|
nread = ::pread(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
|
|
return nread == static_cast<ssize_t>(len);
|
|
}
|
|
|
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
|
{
|
|
if (m_fd < 0 || !m_writable || len <= 0) return false;
|
|
|
|
// Try process_vm_writev first
|
|
struct iovec local;
|
|
local.iov_base = const_cast<void*>(buf);
|
|
local.iov_len = static_cast<size_t>(len);
|
|
|
|
struct iovec remote;
|
|
remote.iov_base = reinterpret_cast<void*>(addr);
|
|
remote.iov_len = static_cast<size_t>(len);
|
|
|
|
ssize_t nwritten = process_vm_writev(m_pid, &local, 1, &remote, 1, 0);
|
|
if (nwritten == static_cast<ssize_t>(len))
|
|
return true;
|
|
|
|
// Fallback: pwrite on /proc/<pid>/mem
|
|
nwritten = ::pwrite(m_fd, buf, static_cast<size_t>(len), static_cast<off_t>(addr));
|
|
return nwritten == static_cast<ssize_t>(len);
|
|
}
|
|
|
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
|
{
|
|
for (const auto& mod : m_modules)
|
|
{
|
|
if (addr >= mod.base && addr < mod.base + mod.size)
|
|
{
|
|
uint64_t offset = addr - mod.base;
|
|
return QStringLiteral("%1+0x%2")
|
|
.arg(mod.name)
|
|
.arg(offset, 0, 16, QChar('0'));
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void ProcessMemoryProvider::cacheModules()
|
|
{
|
|
// Parse /proc/<pid>/maps to discover loaded modules
|
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
|
std::ifstream mapsFile(mapsPath.toStdString());
|
|
if (!mapsFile.is_open()) return;
|
|
|
|
// Accumulate base/end per path, then convert to ModuleInfo
|
|
struct Range { uint64_t base; uint64_t end; };
|
|
QMap<QString, Range> moduleRanges;
|
|
|
|
std::string line;
|
|
bool firstExec = true;
|
|
while (std::getline(mapsFile, line))
|
|
{
|
|
// Format: addr_start-addr_end perms offset dev inode pathname
|
|
// Example: 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/foo
|
|
std::istringstream iss(line);
|
|
std::string addrRange, perms, offset, dev, inode, pathname;
|
|
iss >> addrRange >> perms >> offset >> dev >> inode;
|
|
std::getline(iss, pathname);
|
|
|
|
// Trim leading whitespace from pathname
|
|
size_t start = pathname.find_first_not_of(" \t");
|
|
if (start == std::string::npos) continue;
|
|
pathname = pathname.substr(start);
|
|
|
|
// Skip non-file mappings
|
|
if (pathname.empty() || pathname[0] != '/') continue;
|
|
// Skip special mappings
|
|
if (pathname.find("/dev/") == 0 || pathname.find("/memfd:") == 0) continue;
|
|
|
|
// Parse address range
|
|
auto dash = addrRange.find('-');
|
|
if (dash == std::string::npos) continue;
|
|
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
|
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
|
|
|
|
QString qpath = QString::fromStdString(pathname);
|
|
|
|
// Track first executable mapping as the base address
|
|
if (firstExec && perms.size() >= 3 && perms[2] == 'x')
|
|
{
|
|
m_base = addrStart;
|
|
firstExec = false;
|
|
}
|
|
|
|
auto it = moduleRanges.find(qpath);
|
|
if (it != moduleRanges.end())
|
|
{
|
|
if (addrStart < it->base) it->base = addrStart;
|
|
if (addrEnd > it->end) it->end = addrEnd;
|
|
}
|
|
else
|
|
{
|
|
moduleRanges.insert(qpath, {addrStart, addrEnd});
|
|
}
|
|
}
|
|
|
|
m_modules.reserve(moduleRanges.size());
|
|
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
|
{
|
|
QFileInfo fi(it.key());
|
|
m_modules.push_back(ModuleInfo{
|
|
fi.fileName(),
|
|
it.key(),
|
|
it->base,
|
|
it->end - it->base
|
|
});
|
|
}
|
|
}
|
|
|
|
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
|
{
|
|
QVector<rcx::MemoryRegion> regions;
|
|
if (m_fd < 0) return regions;
|
|
|
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
|
std::ifstream mapsFile(mapsPath.toStdString());
|
|
if (!mapsFile.is_open()) return regions;
|
|
|
|
std::string line;
|
|
while (std::getline(mapsFile, line)) {
|
|
std::istringstream iss(line);
|
|
std::string addrRange, perms, offset, dev, inode, pathname;
|
|
iss >> addrRange >> perms >> offset >> dev >> inode;
|
|
std::getline(iss, pathname);
|
|
|
|
auto dash = addrRange.find('-');
|
|
if (dash == std::string::npos) continue;
|
|
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
|
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
|
|
|
|
if (perms.size() < 4) continue;
|
|
bool readable = (perms[0] == 'r');
|
|
bool writable = (perms[1] == 'w');
|
|
bool executable = (perms[2] == 'x');
|
|
|
|
if (!readable) continue;
|
|
|
|
rcx::MemoryRegion region;
|
|
region.base = addrStart;
|
|
region.size = addrEnd - addrStart;
|
|
region.readable = readable;
|
|
region.writable = writable;
|
|
region.executable = executable;
|
|
|
|
// Extract module name from pathname
|
|
size_t start = pathname.find_first_not_of(" \t");
|
|
if (start != std::string::npos) {
|
|
QString qpath = QString::fromStdString(pathname.substr(start));
|
|
if (qpath.startsWith('/') && !qpath.startsWith("/dev/") &&
|
|
!qpath.startsWith("/memfd:")) {
|
|
QFileInfo fi(qpath);
|
|
region.moduleName = fi.fileName();
|
|
}
|
|
}
|
|
|
|
regions.append(region);
|
|
}
|
|
|
|
return regions;
|
|
}
|
|
|
|
#elif defined(__APPLE__)
|
|
|
|
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
|
: m_task(0)
|
|
, m_pid(pid)
|
|
, m_processName(processName)
|
|
, m_writable(false)
|
|
, m_base(0)
|
|
{
|
|
mach_port_t task = MACH_PORT_NULL;
|
|
kern_return_t kr = task_for_pid(mach_task_self(), static_cast<int>(pid), &task);
|
|
if (kr != KERN_SUCCESS || task == MACH_PORT_NULL)
|
|
return;
|
|
|
|
m_task = static_cast<uint32_t>(task);
|
|
m_writable = true;
|
|
|
|
proc_bsdinfo bsdInfo{};
|
|
int infoLen = proc_pidinfo(static_cast<int>(pid), PROC_PIDTBSDINFO, 0, &bsdInfo, sizeof(bsdInfo));
|
|
if (infoLen == (int)sizeof(bsdInfo)) {
|
|
#ifdef PROC_FLAG_LP64
|
|
m_pointerSize = (bsdInfo.pbi_flags & PROC_FLAG_LP64) ? 8 : 4;
|
|
#else
|
|
m_pointerSize = 8;
|
|
#endif
|
|
}
|
|
|
|
cacheModules();
|
|
}
|
|
|
|
bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
|
{
|
|
if (m_task == 0 || len <= 0)
|
|
return false;
|
|
|
|
mach_vm_size_t outSize = 0;
|
|
kern_return_t kr = mach_vm_read_overwrite(
|
|
static_cast<mach_port_name_t>(m_task),
|
|
static_cast<mach_vm_address_t>(addr),
|
|
static_cast<mach_vm_size_t>(len),
|
|
reinterpret_cast<mach_vm_address_t>(buf),
|
|
&outSize);
|
|
|
|
if ((int)outSize < len)
|
|
memset((char*)buf + outSize, 0, len - outSize);
|
|
|
|
return kr == KERN_SUCCESS && outSize > 0;
|
|
}
|
|
|
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
|
{
|
|
if (m_task == 0 || !m_writable || len <= 0)
|
|
return false;
|
|
|
|
kern_return_t kr = mach_vm_write(
|
|
static_cast<mach_port_name_t>(m_task),
|
|
static_cast<mach_vm_address_t>(addr),
|
|
reinterpret_cast<vm_offset_t>(const_cast<void*>(buf)),
|
|
static_cast<mach_msg_type_number_t>(len));
|
|
return kr == KERN_SUCCESS;
|
|
}
|
|
|
|
QString ProcessMemoryProvider::getSymbol(uint64_t addr) const
|
|
{
|
|
for (const auto& mod : m_modules)
|
|
{
|
|
if (addr >= mod.base && addr < mod.base + mod.size)
|
|
{
|
|
uint64_t offset = addr - mod.base;
|
|
return QStringLiteral("%1+0x%2")
|
|
.arg(mod.name)
|
|
.arg(offset, 0, 16, QChar('0'));
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void ProcessMemoryProvider::cacheModules()
|
|
{
|
|
if (m_task == 0)
|
|
return;
|
|
|
|
m_modules.clear();
|
|
|
|
char mainPathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
QString mainPath;
|
|
if (proc_pidpath((int)m_pid, mainPathBuf, sizeof(mainPathBuf)) > 0)
|
|
mainPath = QString::fromUtf8(mainPathBuf);
|
|
|
|
struct Range { uint64_t base; uint64_t end; };
|
|
QMap<QString, Range> moduleRanges;
|
|
|
|
mach_vm_address_t addr = 0;
|
|
uint32_t depth = 0;
|
|
for (;;) {
|
|
mach_vm_size_t size = 0;
|
|
vm_region_submap_info_data_64_t info{};
|
|
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
|
kern_return_t kr = mach_vm_region_recurse(
|
|
static_cast<mach_port_name_t>(m_task),
|
|
&addr,
|
|
&size,
|
|
&depth,
|
|
reinterpret_cast<vm_region_recurse_info_t>(&info),
|
|
&count);
|
|
if (kr != KERN_SUCCESS)
|
|
break;
|
|
|
|
if (info.is_submap) {
|
|
++depth;
|
|
continue;
|
|
}
|
|
|
|
if (size == 0) {
|
|
++addr;
|
|
continue;
|
|
}
|
|
|
|
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
int pathLen = proc_regionfilename((int)m_pid, (uint64_t)addr, pathBuf, sizeof(pathBuf));
|
|
if (pathLen > 0) {
|
|
QString fullPath = QString::fromUtf8(pathBuf);
|
|
|
|
uint64_t regionBase = (uint64_t)addr;
|
|
uint64_t regionEnd = regionBase + (uint64_t)size;
|
|
auto it = moduleRanges.find(fullPath);
|
|
if (it == moduleRanges.end()) {
|
|
moduleRanges.insert(fullPath, {regionBase, regionEnd});
|
|
} else {
|
|
if (regionBase < it->base) it->base = regionBase;
|
|
if (regionEnd > it->end) it->end = regionEnd;
|
|
}
|
|
|
|
if (m_base == 0 && !mainPath.isEmpty() && fullPath == mainPath && (info.protection & VM_PROT_EXECUTE))
|
|
m_base = regionBase;
|
|
}
|
|
|
|
uint64_t next = (uint64_t)addr + (uint64_t)size;
|
|
if (next <= (uint64_t)addr)
|
|
break;
|
|
addr = (mach_vm_address_t)next;
|
|
}
|
|
|
|
m_modules.reserve(moduleRanges.size());
|
|
for (auto it = moduleRanges.begin(); it != moduleRanges.end(); ++it)
|
|
{
|
|
QFileInfo fi(it.key());
|
|
m_modules.push_back(ModuleInfo{
|
|
fi.fileName(),
|
|
it.key(),
|
|
it->base,
|
|
it->end - it->base
|
|
});
|
|
}
|
|
|
|
if (m_base == 0 && !m_modules.isEmpty())
|
|
m_base = m_modules.front().base;
|
|
}
|
|
|
|
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
|
{
|
|
QVector<rcx::MemoryRegion> regions;
|
|
if (m_task == 0)
|
|
return regions;
|
|
|
|
mach_vm_address_t addr = 0;
|
|
uint32_t depth = 0;
|
|
for (;;) {
|
|
mach_vm_size_t size = 0;
|
|
vm_region_submap_info_data_64_t info{};
|
|
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
|
kern_return_t kr = mach_vm_region_recurse(
|
|
static_cast<mach_port_name_t>(m_task),
|
|
&addr,
|
|
&size,
|
|
&depth,
|
|
reinterpret_cast<vm_region_recurse_info_t>(&info),
|
|
&count);
|
|
if (kr != KERN_SUCCESS)
|
|
break;
|
|
|
|
if (info.is_submap) {
|
|
++depth;
|
|
continue;
|
|
}
|
|
|
|
if (size == 0) {
|
|
++addr;
|
|
continue;
|
|
}
|
|
|
|
bool readable = (info.protection & VM_PROT_READ) != 0;
|
|
if (readable)
|
|
{
|
|
rcx::MemoryRegion region;
|
|
region.base = (uint64_t)addr;
|
|
region.size = (uint64_t)size;
|
|
region.readable = readable;
|
|
region.writable = (info.protection & VM_PROT_WRITE) != 0;
|
|
region.executable = (info.protection & VM_PROT_EXECUTE) != 0;
|
|
|
|
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
int pathLen = proc_regionfilename((int)m_pid, region.base, pathBuf, sizeof(pathBuf));
|
|
if (pathLen > 0) {
|
|
QFileInfo fi(QString::fromUtf8(pathBuf));
|
|
region.moduleName = fi.fileName();
|
|
}
|
|
|
|
regions.append(region);
|
|
}
|
|
|
|
uint64_t next = (uint64_t)addr + (uint64_t)size;
|
|
if (next <= (uint64_t)addr)
|
|
break;
|
|
addr = (mach_vm_address_t)next;
|
|
}
|
|
|
|
return regions;
|
|
}
|
|
|
|
#endif // platform
|
|
|
|
#ifndef _WIN32
|
|
QVector<rcx::Provider::ModuleEntry> ProcessMemoryProvider::enumerateModules() const
|
|
{
|
|
QVector<ModuleEntry> result;
|
|
result.reserve(m_modules.size());
|
|
for (const auto& m : m_modules)
|
|
result.push_back(ModuleEntry{m.name, m.fullPath, m.base, m.size});
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
uint64_t ProcessMemoryProvider::symbolToAddress(const QString& name) const
|
|
{
|
|
for (const auto& mod : m_modules) {
|
|
if (mod.name.compare(name, Qt::CaseInsensitive) == 0)
|
|
return mod.base;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ProcessMemoryProvider::~ProcessMemoryProvider()
|
|
{
|
|
#ifdef _WIN32
|
|
if (m_handle)
|
|
CloseHandle(m_handle);
|
|
#elif defined(__linux__)
|
|
if (m_fd >= 0)
|
|
::close(m_fd);
|
|
#elif defined(__APPLE__)
|
|
if (m_task != 0)
|
|
mach_port_deallocate(mach_task_self(), static_cast<mach_port_name_t>(m_task));
|
|
#endif
|
|
}
|
|
|
|
int ProcessMemoryProvider::size() const
|
|
{
|
|
#ifdef _WIN32
|
|
return m_handle ? 0x10000 : 0;
|
|
#elif defined(__linux__)
|
|
return (m_fd >= 0) ? 0x10000 : 0;
|
|
#elif defined(__APPLE__)
|
|
return (m_task != 0) ? 0x10000 : 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
QVector<rcx::Provider::ThreadInfo> ProcessMemoryProvider::tebs() const
|
|
{
|
|
#ifdef _WIN32
|
|
QVector<ThreadInfo> result;
|
|
if (!m_handle || !m_peb) return result;
|
|
|
|
typedef NTSTATUS(NTAPI* NtQSI_t)(ULONG, PVOID, ULONG, PULONG);
|
|
typedef NTSTATUS(NTAPI* NtQIT_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
|
static auto pNtQSI = (NtQSI_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
|
|
static auto pNtQIT = (NtQIT_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread");
|
|
if (!pNtQSI || !pNtQIT) return result;
|
|
|
|
// Enumerate threads via SystemProcessInformation (class 5)
|
|
ULONG retLen = 0;
|
|
ULONG bufSize = 1 << 20;
|
|
QByteArray buf(bufSize, 0);
|
|
NTSTATUS qsiSt;
|
|
for (int attempt = 0; attempt < 8; ++attempt) {
|
|
qsiSt = pNtQSI(5, buf.data(), bufSize, &retLen);
|
|
if ((uint32_t)qsiSt != 0xC0000004u) break;
|
|
bufSize *= 2;
|
|
buf.resize(bufSize);
|
|
}
|
|
if (qsiSt < 0) return result;
|
|
|
|
// Walk process entries to find ours
|
|
auto* proc = (SYSTEM_PROCESS_INFORMATION*)buf.data();
|
|
for (;;) {
|
|
if ((uintptr_t)proc->UniqueProcessId == m_pid) {
|
|
auto* threads = (SYSTEM_THREAD_INFORMATION*)((char*)proc + sizeof(*proc));
|
|
for (ULONG i = 0; i < proc->NumberOfThreads; ++i) {
|
|
DWORD tid = (DWORD)(uintptr_t)threads[i].ClientId.UniqueThread;
|
|
HANDLE hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, tid);
|
|
if (!hThread) continue;
|
|
THREAD_BASIC_INFORMATION tbi = {};
|
|
ULONG tbiLen = 0;
|
|
NTSTATUS qitSt = pNtQIT(hThread, 0, &tbi, sizeof(tbi), &tbiLen);
|
|
if (qitSt >= 0 && tbi.TebBaseAddress)
|
|
result.push_back(ThreadInfo{(uint64_t)(uintptr_t)tbi.TebBaseAddress, tid});
|
|
CloseHandle(hThread);
|
|
}
|
|
break;
|
|
}
|
|
if (!proc->NextEntryOffset) break;
|
|
proc = (SYSTEM_PROCESS_INFORMATION*)((char*)proc + proc->NextEntryOffset);
|
|
}
|
|
return result;
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
// ProcessMemoryPlugin implementation
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
|
|
QIcon ProcessMemoryPlugin::Icon() const
|
|
{
|
|
return qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
|
}
|
|
|
|
bool ProcessMemoryPlugin::canHandle(const QString& target) const
|
|
{
|
|
// Target format: "pid:name" or just "pid"
|
|
QRegularExpression re("^\\d+");
|
|
return re.match(target).hasMatch();
|
|
}
|
|
|
|
std::unique_ptr<rcx::Provider> ProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
|
|
{
|
|
// Parse target: "pid:name" or just "pid"
|
|
QStringList parts = target.split(':');
|
|
bool ok = false;
|
|
uint32_t pid = parts[0].toUInt(&ok);
|
|
|
|
if (!ok || pid == 0)
|
|
{
|
|
if (errorMsg) *errorMsg = "Invalid PID: " + target;
|
|
return nullptr;
|
|
}
|
|
|
|
QString name = parts.size() > 1 ? parts[1] : QString("PID %1").arg(pid);
|
|
|
|
auto provider = std::make_unique<ProcessMemoryProvider>(pid, name);
|
|
if (!provider->isValid())
|
|
{
|
|
if (errorMsg)
|
|
*errorMsg = QString("Failed to open process %1 (PID: %2)\n"
|
|
"Ensure the process is running and you have sufficient permissions.")
|
|
.arg(name).arg(pid);
|
|
return nullptr;
|
|
}
|
|
|
|
return provider;
|
|
}
|
|
|
|
uint64_t ProcessMemoryPlugin::getInitialBaseAddress(const QString& target) const
|
|
{
|
|
#ifdef _WIN32
|
|
// Parse PID from target
|
|
QStringList parts = target.split(':');
|
|
bool ok = false;
|
|
DWORD pid = parts[0].toUInt(&ok);
|
|
if (!ok || pid == 0) return 0;
|
|
|
|
// Open process to get main module base
|
|
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
|
if (!hProc) return 0;
|
|
|
|
uint64_t base = 0;
|
|
HMODULE hMod = nullptr;
|
|
DWORD needed = 0;
|
|
|
|
if (EnumProcessModulesEx(hProc, &hMod, sizeof(hMod), &needed, LIST_MODULES_ALL) && hMod)
|
|
{
|
|
MODULEINFO mi{};
|
|
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi)))
|
|
base = (uint64_t)mi.lpBaseOfDll;
|
|
}
|
|
|
|
CloseHandle(hProc);
|
|
return base;
|
|
#elif defined(__linux__)
|
|
// Parse PID from target
|
|
QStringList parts = target.split(':');
|
|
bool ok = false;
|
|
uint32_t pid = parts[0].toUInt(&ok);
|
|
if (!ok || pid == 0) return 0;
|
|
|
|
// Find first executable mapping from /proc/<pid>/maps
|
|
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(pid);
|
|
std::ifstream mapsFile(mapsPath.toStdString());
|
|
if (!mapsFile.is_open()) return 0;
|
|
|
|
std::string line;
|
|
while (std::getline(mapsFile, line)) {
|
|
std::istringstream iss(line);
|
|
std::string addrRange, perms;
|
|
iss >> addrRange >> perms;
|
|
if (perms.size() >= 3 && perms[2] == 'x') {
|
|
auto dash = addrRange.find('-');
|
|
if (dash != std::string::npos) {
|
|
return std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
#elif defined(__APPLE__)
|
|
QStringList parts = target.split(':');
|
|
bool ok = false;
|
|
uint32_t pid = parts[0].toUInt(&ok);
|
|
if (!ok || pid == 0)
|
|
return 0;
|
|
|
|
mach_port_t task = MACH_PORT_NULL;
|
|
kern_return_t tkr = task_for_pid(mach_task_self(), static_cast<int>(pid), &task);
|
|
if (tkr != KERN_SUCCESS || task == MACH_PORT_NULL)
|
|
return 0;
|
|
|
|
char mainPathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
QString mainPath;
|
|
if (proc_pidpath((int)pid, mainPathBuf, sizeof(mainPathBuf)) > 0)
|
|
mainPath = QString::fromUtf8(mainPathBuf);
|
|
|
|
uint64_t base = 0;
|
|
mach_vm_address_t addr = 0;
|
|
uint32_t depth = 0;
|
|
for (;;) {
|
|
mach_vm_size_t size = 0;
|
|
vm_region_submap_info_data_64_t info{};
|
|
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
|
|
kern_return_t kr = mach_vm_region_recurse(task, &addr, &size, &depth,
|
|
reinterpret_cast<vm_region_recurse_info_t>(&info),
|
|
&count);
|
|
if (kr != KERN_SUCCESS)
|
|
break;
|
|
|
|
if (info.is_submap) {
|
|
++depth;
|
|
continue;
|
|
}
|
|
|
|
if (size == 0) {
|
|
++addr;
|
|
continue;
|
|
}
|
|
|
|
if ((info.protection & VM_PROT_EXECUTE) != 0) {
|
|
if (!mainPath.isEmpty()) {
|
|
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
int pathLen = proc_regionfilename((int)pid, (uint64_t)addr, pathBuf, sizeof(pathBuf));
|
|
if (pathLen > 0 && QString::fromUtf8(pathBuf) == mainPath) {
|
|
base = (uint64_t)addr;
|
|
break;
|
|
}
|
|
} else {
|
|
base = (uint64_t)addr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint64_t next = (uint64_t)addr + (uint64_t)size;
|
|
if (next <= (uint64_t)addr)
|
|
break;
|
|
addr = (mach_vm_address_t)next;
|
|
}
|
|
|
|
mach_port_deallocate(mach_task_self(), task);
|
|
return base;
|
|
#else
|
|
Q_UNUSED(target);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
bool ProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
|
{
|
|
// Use custom process enumeration from plugin
|
|
QVector<PluginProcessInfo> pluginProcesses = enumerateProcesses();
|
|
|
|
// Convert to ProcessInfo for ProcessPicker
|
|
QList<ProcessInfo> processes;
|
|
for (const auto& pinfo : pluginProcesses)
|
|
{
|
|
ProcessInfo info;
|
|
info.pid = pinfo.pid;
|
|
info.name = pinfo.name;
|
|
info.path = pinfo.path;
|
|
info.icon = pinfo.icon;
|
|
info.is32Bit = pinfo.is32Bit;
|
|
processes.append(info);
|
|
}
|
|
|
|
// Show ProcessPicker with custom process list
|
|
ProcessPicker picker(processes, parent);
|
|
if (picker.exec() == QDialog::Accepted) {
|
|
uint32_t pid = picker.selectedProcessId();
|
|
QString name = picker.selectedProcessName();
|
|
|
|
// Format target as "pid:name"
|
|
*target = QString("%1:%2").arg(pid).arg(name);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QVector<PluginProcessInfo> ProcessMemoryPlugin::enumerateProcesses()
|
|
{
|
|
QVector<PluginProcessInfo> processes;
|
|
|
|
#ifdef _WIN32
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (snapshot == INVALID_HANDLE_VALUE) {
|
|
return processes;
|
|
}
|
|
|
|
PROCESSENTRY32W entry;
|
|
entry.dwSize = sizeof(entry);
|
|
|
|
if (Process32FirstW(snapshot, &entry)) {
|
|
do {
|
|
PluginProcessInfo info;
|
|
info.pid = entry.th32ProcessID;
|
|
info.name = QString::fromWCharArray(entry.szExeFile);
|
|
|
|
// Try to get full path and icon
|
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
|
|
if (hProcess) {
|
|
wchar_t path[MAX_PATH * 2];
|
|
DWORD pathLen = sizeof(path) / sizeof(wchar_t);
|
|
|
|
// Try QueryFullProcessImageNameW first
|
|
if (QueryFullProcessImageNameW(hProcess, 0, path, &pathLen)) {
|
|
info.path = QString::fromWCharArray(path);
|
|
|
|
// Extract icon
|
|
SHFILEINFOW sfi = {};
|
|
if (SHGetFileInfoW(path, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_SMALLICON)) {
|
|
if (sfi.hIcon) {
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QPixmap pixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
|
#else
|
|
QPixmap pixmap = QtWin::fromHICON(sfi.hIcon);
|
|
#endif
|
|
info.icon = QIcon(pixmap);
|
|
DestroyIcon(sfi.hIcon);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Detect 32-bit (WoW64) process
|
|
BOOL isWow64 = FALSE;
|
|
if (IsWow64Process(hProcess, &isWow64) && isWow64)
|
|
info.is32Bit = true;
|
|
|
|
CloseHandle(hProcess);
|
|
}
|
|
|
|
processes.append(info);
|
|
|
|
} while (Process32NextW(snapshot, &entry));
|
|
}
|
|
|
|
CloseHandle(snapshot);
|
|
#elif defined(__linux__)
|
|
QDir procDir("/proc");
|
|
QStringList entries = procDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
|
|
|
for (const QString& entry : entries) {
|
|
bool ok = false;
|
|
uint32_t pid = entry.toUInt(&ok);
|
|
if (!ok || pid == 0) continue;
|
|
|
|
// Read process name from /proc/<pid>/comm
|
|
QString commPath = QStringLiteral("/proc/%1/comm").arg(pid);
|
|
QFile commFile(commPath);
|
|
QString procName;
|
|
if (commFile.open(QIODevice::ReadOnly)) {
|
|
procName = QString::fromUtf8(commFile.readAll()).trimmed();
|
|
commFile.close();
|
|
}
|
|
if (procName.isEmpty()) continue; // Skip kernel threads with no name
|
|
|
|
// Read exe path from /proc/<pid>/exe symlink
|
|
QString exePath = QStringLiteral("/proc/%1/exe").arg(pid);
|
|
QFileInfo exeInfo(exePath);
|
|
QString resolvedPath;
|
|
if (exeInfo.exists())
|
|
resolvedPath = exeInfo.symLinkTarget();
|
|
|
|
// Skip if we can't read the process memory (no access)
|
|
QString memPath = QStringLiteral("/proc/%1/mem").arg(pid);
|
|
if (::access(memPath.toUtf8().constData(), R_OK) != 0)
|
|
continue;
|
|
|
|
PluginProcessInfo info;
|
|
info.pid = pid;
|
|
info.name = procName;
|
|
info.path = resolvedPath;
|
|
info.icon = defaultIcon;
|
|
|
|
// Detect 32-bit ELF process
|
|
int exeFd = ::open(exePath.toUtf8().constData(), O_RDONLY);
|
|
if (exeFd >= 0) {
|
|
unsigned char elfClass = 0;
|
|
if (::pread(exeFd, &elfClass, 1, 4) == 1 && elfClass == 1) // ELFCLASS32
|
|
info.is32Bit = true;
|
|
::close(exeFd);
|
|
}
|
|
|
|
processes.append(info);
|
|
}
|
|
#elif defined(__APPLE__)
|
|
QIcon defaultIcon = qApp->style()->standardIcon(QStyle::SP_ComputerIcon);
|
|
|
|
int bytes = proc_listpids(PROC_ALL_PIDS, 0, nullptr, 0);
|
|
if (bytes <= 0)
|
|
return processes;
|
|
|
|
int count = bytes / (int)sizeof(pid_t);
|
|
QVector<pid_t> pids(count);
|
|
bytes = proc_listpids(PROC_ALL_PIDS, 0, pids.data(), count * (int)sizeof(pid_t));
|
|
if (bytes <= 0)
|
|
return processes;
|
|
|
|
count = bytes / (int)sizeof(pid_t);
|
|
for (int i = 0; i < count; ++i) {
|
|
pid_t pid = pids[i];
|
|
if (pid <= 0)
|
|
continue;
|
|
|
|
mach_port_t task = MACH_PORT_NULL;
|
|
if (task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS || task == MACH_PORT_NULL)
|
|
continue;
|
|
mach_port_deallocate(mach_task_self(), task);
|
|
|
|
char nameBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
int nameLen = proc_name(pid, nameBuf, sizeof(nameBuf));
|
|
QString procName;
|
|
if (nameLen > 0)
|
|
procName = QString::fromUtf8(nameBuf);
|
|
if (procName.isEmpty())
|
|
continue;
|
|
|
|
char pathBuf[PROC_PIDPATHINFO_MAXSIZE] = {};
|
|
QString procPath;
|
|
if (proc_pidpath(pid, pathBuf, sizeof(pathBuf)) > 0)
|
|
procPath = QString::fromUtf8(pathBuf);
|
|
|
|
PluginProcessInfo info;
|
|
info.pid = static_cast<uint32_t>(pid);
|
|
info.name = procName;
|
|
info.path = procPath;
|
|
info.icon = defaultIcon;
|
|
|
|
proc_bsdinfo bsdInfo{};
|
|
int infoLen = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdInfo, sizeof(bsdInfo));
|
|
if (infoLen == (int)sizeof(bsdInfo)) {
|
|
#ifdef PROC_FLAG_LP64
|
|
info.is32Bit = (bsdInfo.pbi_flags & PROC_FLAG_LP64) == 0;
|
|
#else
|
|
info.is32Bit = false;
|
|
#endif
|
|
}
|
|
|
|
processes.append(info);
|
|
}
|
|
#endif
|
|
|
|
return processes;
|
|
}
|
|
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
// Plugin factory
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
|
|
extern "C" RCX_PLUGIN_EXPORT IPlugin* CreatePlugin()
|
|
{
|
|
return new ProcessMemoryPlugin();
|
|
}
|