feat: remove nonce/bootstrap from remote process IPC, use PID-only naming

Shared memory names simplified to Local\RCX_SHM_<pid>, no bootstrap
handshake needed. Payload uses CreateTimerQueueTimer (10ms poll) instead
of a dedicated server thread.
This commit is contained in:
IChooseYou
2026-02-22 11:36:24 -07:00
parent 25aaace382
commit f651edd740
6 changed files with 124 additions and 292 deletions

View File

@@ -5,9 +5,7 @@
#include <QStyle>
#include <QApplication>
#include <QMessageBox>
#include <QInputDialog>
#include <QPushButton>
#include <QUuid>
#include <QDir>
#include <QFileInfo>
#include <QPixmap>
@@ -65,12 +63,12 @@ struct IpcClient {
/* ── connect / disconnect ──────────────────────────────────────── */
bool connect(uint32_t pid, const QByteArray& nonce, int timeoutMs = 5000)
bool connect(uint32_t pid, int timeoutMs = 5000)
{
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce.constData());
rcx_rpc_req_name(reqName, sizeof(reqName), pid, nonce.constData());
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid, nonce.constData());
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32
/* poll for shared memory to appear (payload creating it) */
@@ -373,51 +371,6 @@ static QString payloadPath()
#endif
}
/* Create bootstrap shared memory with the nonce */
static bool createBootstrapShm(uint32_t pid, const QByteArray& nonce)
{
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
#ifdef _WIN32
HANDLE hBoot = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_BOOT_SIZE,
bootName);
if (!hBoot) return false;
auto* view = static_cast<RcxRpcBootHeader*>(
MapViewOfFile(hBoot, FILE_MAP_WRITE, 0, 0, RCX_RPC_BOOT_SIZE));
if (!view) { CloseHandle(hBoot); return false; }
memset(view, 0, RCX_RPC_BOOT_SIZE);
view->nonceLength = (uint32_t)nonce.size();
memcpy(view->nonce, nonce.constData(), qMin(nonce.size(), 59));
UnmapViewOfFile(view);
/* keep hBoot open until payload reads it (payload unlinks after reading) */
/* leak intentional: closed when process exits or payload consumes it */
return true;
#else
int fd = shm_open(bootName, O_CREAT | O_RDWR, 0600);
if (fd < 0) return false;
if (ftruncate(fd, RCX_RPC_BOOT_SIZE) != 0) { close(fd); return false; }
void* view = mmap(nullptr, RCX_RPC_BOOT_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (view == MAP_FAILED) return false;
auto* boot = static_cast<RcxRpcBootHeader*>(view);
memset(boot, 0, RCX_RPC_BOOT_SIZE);
boot->nonceLength = (uint32_t)nonce.size();
memcpy(boot->nonce, nonce.constData(), qMin(nonce.size(), 59));
munmap(view, RCX_RPC_BOOT_SIZE);
/* payload unlinks after consuming */
return true;
#endif
}
#ifdef _WIN32
/* ── Windows injection: CreateRemoteThread + LoadLibraryA ─────────── */
@@ -717,24 +670,23 @@ bool RemoteProcessMemoryPlugin::canHandle(const QString& target) const
std::unique_ptr<rcx::Provider>
RemoteProcessMemoryPlugin::createProvider(const QString& target, QString* errorMsg)
{
/* target = "rpm:{pid}:{nonce}:{name}" */
/* target = "rpm:{pid}:{name}" */
QStringList parts = target.split(':');
if (parts.size() < 4 || parts[0] != QStringLiteral("rpm")) {
if (parts.size() < 3 || parts[0] != QStringLiteral("rpm")) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid target: ") + target;
return nullptr;
}
bool ok;
uint32_t pid = parts[1].toUInt(&ok);
QString nonce = parts[2];
QString name = parts.mid(3).join(':'); /* name may contain colons */
uint32_t pid = parts[1].toUInt(&ok);
QString name = parts.mid(2).join(':'); /* name may contain colons */
if (!ok || pid == 0) {
if (errorMsg) *errorMsg = QStringLiteral("Invalid PID in target.");
return nullptr;
}
auto ipc = getOrCreateConnection(pid, nonce, errorMsg);
auto ipc = getOrCreateConnection(pid, errorMsg);
if (!ipc) return nullptr;
return std::make_unique<RemoteProcessProvider>(pid, name, ipc);
@@ -745,7 +697,7 @@ uint64_t RemoteProcessMemoryPlugin::getInitialBaseAddress(const QString& target)
/* Read imageBase directly from the shared-memory header -- zero IPC cost.
The payload filled it at init from PEB->Ldr (Win) / /proc/self/maps (Linux). */
QStringList parts = target.split(':');
if (parts.size() < 3 || parts[0] != QStringLiteral("rpm"))
if (parts.size() < 2 || parts[0] != QStringLiteral("rpm"))
return 0;
bool ok;
@@ -793,35 +745,17 @@ bool RemoteProcessMemoryPlugin::selectTarget(QWidget* parent, QString* target)
QAbstractButton* clicked = box.clickedButton();
if (clicked == injectBtn) {
/* generate nonce */
QString nonce = QUuid::createUuid().toString(QUuid::Id128).left(16);
QByteArray nonceUtf8 = nonce.toUtf8();
/* create bootstrap, inject */
if (!createBootstrapShm(pid, nonceUtf8)) {
QMessageBox::critical(parent, QStringLiteral("Error"),
QStringLiteral("Failed to create bootstrap shared memory."));
return false;
}
QString injectErr;
if (!injectPayload(pid, &injectErr)) {
QMessageBox::critical(parent, QStringLiteral("Injection Failed"), injectErr);
return false;
}
*target = QStringLiteral("rpm:%1:%2:%3").arg(pid).arg(nonce, name);
*target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true;
}
else if (clicked == connectBtn) {
bool ok;
QString nonce = QInputDialog::getText(parent,
QStringLiteral("Connect to Payload"),
QStringLiteral("Enter the payload nonce:"),
QLineEdit::Normal, QString(), &ok);
if (!ok || nonce.isEmpty()) return false;
*target = QStringLiteral("rpm:%1:%2:%3").arg(pid).arg(nonce, name);
*target = QStringLiteral("rpm:%1:%2").arg(pid).arg(name);
return true;
}
@@ -903,7 +837,7 @@ QVector<PluginProcessInfo> RemoteProcessMemoryPlugin::enumerateProcesses()
std::shared_ptr<IpcClient>
RemoteProcessMemoryPlugin::getOrCreateConnection(
uint32_t pid, const QString& nonce, QString* errorMsg)
uint32_t pid, QString* errorMsg)
{
QMutexLocker lock(&m_connectionsMutex);
@@ -912,7 +846,7 @@ RemoteProcessMemoryPlugin::getOrCreateConnection(
return *it;
auto ipc = std::make_shared<IpcClient>();
if (!ipc->connect(pid, nonce.toUtf8())) {
if (!ipc->connect(pid)) {
if (errorMsg)
*errorMsg = QStringLiteral("Failed to connect IPC to PID %1.\n"
"Is the payload running?").arg(pid);

View File

@@ -77,7 +77,7 @@ public:
private:
std::shared_ptr<IpcClient> getOrCreateConnection(
uint32_t pid, const QString& nonce, QString* errorMsg);
uint32_t pid, QString* errorMsg);
mutable QMutex m_connectionsMutex;
QHash<uint32_t, std::shared_ptr<IpcClient>> m_connections;

View File

@@ -2,9 +2,8 @@
* rcx_payload -- injected into target process.
*
* Pure Win32 / POSIX, NO Qt, minimal footprint.
* Reads a nonce from bootstrap shared memory, creates the main IPC
* channel (shared memory + events/semaphores), and runs a server
* thread that handles RPC commands from the editor plugin.
* Creates the main IPC channel (shared memory + events/semaphores)
* using PID-only naming and uses a timer queue for polling.
*/
#include "../rcx_rpc_protocol.h"
@@ -18,11 +17,12 @@
#include <psapi.h>
/* ── globals ──────────────────────────────────────────────────────── */
static HANDLE g_hShm = nullptr;
static void* g_mappedView = nullptr;
static HANDLE g_hReqEvent = nullptr;
static HANDLE g_hRspEvent = nullptr;
static HANDLE g_hThread = nullptr;
static HANDLE g_hShm = nullptr;
static void* g_mappedView = nullptr;
static HANDLE g_hReqEvent = nullptr;
static HANDLE g_hRspEvent = nullptr;
static HANDLE g_hTimerQueue = nullptr;
static HANDLE g_hTimer = nullptr;
static volatile LONG g_shutdown = 0;
/* ── memory safety via VirtualQuery ────────────────────────────────── */
@@ -167,63 +167,59 @@ static void handle_enum_modules(RcxRpcHeader* hdr, uint8_t* data)
hdr->status = RCX_RPC_STATUS_OK;
}
/* ── server thread ────────────────────────────────────────────────── */
/* ── timer callback (replaces server thread) ─────────────────────── */
static DWORD WINAPI ServerThread(LPVOID)
static VOID CALLBACK PollCallback(PVOID, BOOLEAN)
{
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
if (InterlockedCompareExchange(&g_shutdown, 0, 0))
return;
DWORD rc = WaitForSingleObject(g_hReqEvent, 0);
if (rc != WAIT_OBJECT_0)
return;
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
auto* data = reinterpret_cast<uint8_t*>(g_mappedView) + RCX_RPC_DATA_OFFSET;
/* signal readiness */
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 1);
hdr->status = RCX_RPC_STATUS_OK;
while (!InterlockedCompareExchange(&g_shutdown, 0, 0)) {
DWORD rc = WaitForSingleObject(g_hReqEvent, 250);
if (rc == WAIT_TIMEOUT)
continue;
if (rc != WAIT_OBJECT_0)
break;
hdr->status = RCX_RPC_STATUS_OK;
switch (static_cast<RcxRpcCommand>(hdr->command)) {
case RPC_CMD_READ_BATCH: handle_read_batch(hdr, data); break;
case RPC_CMD_WRITE: handle_write(hdr, data); break;
case RPC_CMD_ENUM_MODULES: handle_enum_modules(hdr, data); break;
case RPC_CMD_PING: break;
case RPC_CMD_SHUTDOWN:
InterlockedExchange(&g_shutdown, 1);
break;
default:
hdr->status = RCX_RPC_STATUS_ERROR;
break;
}
SetEvent(g_hRspEvent);
if (static_cast<RcxRpcCommand>(hdr->command) == RPC_CMD_SHUTDOWN)
break;
switch (static_cast<RcxRpcCommand>(hdr->command)) {
case RPC_CMD_READ_BATCH: handle_read_batch(hdr, data); break;
case RPC_CMD_WRITE: handle_write(hdr, data); break;
case RPC_CMD_ENUM_MODULES: handle_enum_modules(hdr, data); break;
case RPC_CMD_PING: break;
case RPC_CMD_SHUTDOWN:
InterlockedExchange(&g_shutdown, 1);
break;
default:
hdr->status = RCX_RPC_STATUS_ERROR;
break;
}
/* mark not-ready so the host process can detect shutdown */
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 0);
return 0;
SetEvent(g_hRspEvent);
}
/* ── cleanup ──────────────────────────────────────────────────────── */
static void Cleanup(bool waitThread)
static void Cleanup()
{
InterlockedExchange(&g_shutdown, 1);
/* wake the thread if it's blocked on REQ */
if (g_hReqEvent) SetEvent(g_hReqEvent);
if (waitThread && g_hThread) {
WaitForSingleObject(g_hThread, 2000);
if (g_hTimer) {
DeleteTimerQueueTimer(g_hTimerQueue, g_hTimer, INVALID_HANDLE_VALUE);
g_hTimer = nullptr;
}
if (g_hTimerQueue) {
DeleteTimerQueueEx(g_hTimerQueue, INVALID_HANDLE_VALUE);
g_hTimerQueue = nullptr;
}
if (g_mappedView) {
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 0);
UnmapViewOfFile(g_mappedView);
g_mappedView = nullptr;
}
if (g_hThread) { CloseHandle(g_hThread); g_hThread = nullptr; }
if (g_mappedView){ UnmapViewOfFile(g_mappedView); g_mappedView = nullptr; }
if (g_hShm) { CloseHandle(g_hShm); g_hShm = nullptr; }
if (g_hReqEvent) { CloseHandle(g_hReqEvent); g_hReqEvent = nullptr; }
if (g_hRspEvent) { CloseHandle(g_hRspEvent); g_hRspEvent = nullptr; }
@@ -231,36 +227,16 @@ static void Cleanup(bool waitThread)
/* ── DllMain ──────────────────────────────────────────────────────── */
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID reserved)
BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH) {
uint32_t pid = GetCurrentProcessId();
/* ── read nonce from bootstrap shm ── */
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
HANDLE hBoot = OpenFileMappingA(FILE_MAP_READ, FALSE, bootName);
if (!hBoot) return TRUE; /* no bootstrap = nothing to do */
auto* bootView = static_cast<const RcxRpcBootHeader*>(
MapViewOfFile(hBoot, FILE_MAP_READ, 0, 0, RCX_RPC_BOOT_SIZE));
if (!bootView) { CloseHandle(hBoot); return TRUE; }
char nonce[64] = {};
uint32_t nLen = bootView->nonceLength;
if (nLen > 59) nLen = 59;
memcpy(nonce, bootView->nonce, nLen);
nonce[nLen] = '\0';
UnmapViewOfFile(bootView);
CloseHandle(hBoot);
/* ── create main shared memory ── */
/* ── create main shared memory (PID-only naming) ── */
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce);
rcx_rpc_req_name(reqName, sizeof(reqName), pid, nonce);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid, nonce);
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
g_hShm = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_SHM_SIZE, shmName);
@@ -273,27 +249,36 @@ BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID reserved)
auto* hdr = static_cast<RcxRpcHeader*>(g_mappedView);
hdr->version = RCX_RPC_VERSION;
/* image base from PEB: gs:[0x60] PEB, +0x18 Ldr, Flink first entry, +0x30 DllBase */
/* image base from PEB: gs:[0x60] -> PEB, +0x18 -> Ldr, Flink -> first entry, +0x30 -> DllBase */
{
uint64_t peb;
asm volatile("mov %%gs:0x60, %0" : "=r"(peb));
uint64_t ldr = *reinterpret_cast<uint64_t*>(peb + 0x18);
uint64_t firstLink = *reinterpret_cast<uint64_t*>(ldr + 0x10); /* InLoadOrderModuleList.Flink */
hdr->imageBase = *reinterpret_cast<uint64_t*>(firstLink + 0x30); /* DllBase */
uint64_t firstLink = *reinterpret_cast<uint64_t*>(ldr + 0x10);
hdr->imageBase = *reinterpret_cast<uint64_t*>(firstLink + 0x30);
}
/* ── create events ── */
g_hReqEvent = CreateEventA(nullptr, FALSE, FALSE, reqName);
g_hRspEvent = CreateEventA(nullptr, FALSE, FALSE, rspName);
if (!g_hReqEvent || !g_hRspEvent) { Cleanup(false); return TRUE; }
if (!g_hReqEvent || !g_hRspEvent) { Cleanup(); return TRUE; }
/* ── start server thread (payloadReady set by the thread) ── */
g_hThread = CreateThread(nullptr, 0, ServerThread, nullptr, 0, nullptr);
if (!g_hThread) { Cleanup(false); return TRUE; }
/* ── start timer queue (10ms poll interval) ── */
g_hTimerQueue = CreateTimerQueue();
if (!g_hTimerQueue) { Cleanup(); return TRUE; }
if (!CreateTimerQueueTimer(&g_hTimer, g_hTimerQueue,
PollCallback, nullptr, 0, 10,
WT_EXECUTEDEFAULT)) {
Cleanup();
return TRUE;
}
/* signal readiness */
InterlockedExchange(reinterpret_cast<volatile LONG*>(&hdr->payloadReady), 1);
}
else if (reason == DLL_PROCESS_DETACH) {
/* reserved != NULL → process is terminating (threads already dead) */
Cleanup(reserved == nullptr);
Cleanup();
}
return TRUE;
@@ -529,37 +514,14 @@ static void payload_init()
{
uint32_t pid = (uint32_t)getpid();
/* ── read nonce from bootstrap shm ── */
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
int bootFd = shm_open(bootName, O_RDONLY, 0);
if (bootFd < 0) return;
void* bootView = mmap(nullptr, RCX_RPC_BOOT_SIZE, PROT_READ,
MAP_SHARED, bootFd, 0);
close(bootFd);
if (bootView == MAP_FAILED) return;
auto* boot = static_cast<const RcxRpcBootHeader*>(bootView);
char nonce[64] = {};
uint32_t nLen = boot->nonceLength;
if (nLen > 59) nLen = 59;
memcpy(nonce, boot->nonce, nLen);
nonce[nLen] = '\0';
munmap(bootView, RCX_RPC_BOOT_SIZE);
/* one-shot, unlink bootstrap */
shm_unlink(bootName);
/* ── open /proc/self/mem for safe access ── */
g_memFd = open("/proc/self/mem", O_RDWR);
if (g_memFd < 0) return;
/* ── create main shared memory ── */
rcx_rpc_shm_name(g_shmName, sizeof(g_shmName), pid, nonce);
rcx_rpc_req_name(g_reqName, sizeof(g_reqName), pid, nonce);
rcx_rpc_rsp_name(g_rspName, sizeof(g_rspName), pid, nonce);
/* ── create main shared memory (PID-only naming) ── */
rcx_rpc_shm_name(g_shmName, sizeof(g_shmName), pid);
rcx_rpc_req_name(g_reqName, sizeof(g_reqName), pid);
rcx_rpc_rsp_name(g_rspName, sizeof(g_rspName), pid);
g_shmFd = shm_open(g_shmName, O_CREAT | O_RDWR, 0600);
if (g_shmFd < 0) return;

View File

@@ -15,7 +15,6 @@
#define RCX_RPC_HEADER_SIZE 4096
#define RCX_RPC_DATA_OFFSET RCX_RPC_HEADER_SIZE
#define RCX_RPC_DATA_SIZE (RCX_RPC_SHM_SIZE - RCX_RPC_DATA_OFFSET)
#define RCX_RPC_BOOT_SIZE 64
/* status codes */
#define RCX_RPC_STATUS_OK 0
@@ -83,47 +82,32 @@ struct RcxRpcHeader {
uint8_t _pad[RCX_RPC_HEADER_SIZE - 48];
};
/* Bootstrap shm -- 64 bytes, carries the nonce from plugin to payload */
struct RcxRpcBootHeader {
uint32_t nonceLength;
char nonce[60];
};
/* ── name formatting helpers (PID-only, no nonce) ─────────────────── */
/* ── name formatting helpers ───────────────────────────────────────── */
static inline void rcx_rpc_boot_name(char* buf, int n, uint32_t pid) {
static inline void rcx_rpc_shm_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_BOOT_%u", pid);
snprintf(buf, n, "Local\\RCX_SHM_%u", pid);
#else
snprintf(buf, n, "/rcx_boot_%u", pid);
snprintf(buf, n, "/rcx_shm_%u", pid);
#endif
}
static inline void rcx_rpc_shm_name(char* buf, int n, uint32_t pid, const char* nonce) {
static inline void rcx_rpc_req_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_SHM_%u_%s", pid, nonce);
snprintf(buf, n, "Local\\RCX_REQ_%u", pid);
#else
snprintf(buf, n, "/rcx_shm_%u_%s", pid, nonce);
snprintf(buf, n, "/rcx_req_%u", pid);
#endif
}
static inline void rcx_rpc_req_name(char* buf, int n, uint32_t pid, const char* nonce) {
static inline void rcx_rpc_rsp_name(char* buf, int n, uint32_t pid) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_REQ_%u_%s", pid, nonce);
snprintf(buf, n, "Local\\RCX_RSP_%u", pid);
#else
snprintf(buf, n, "/rcx_req_%u_%s", pid, nonce);
#endif
}
static inline void rcx_rpc_rsp_name(char* buf, int n, uint32_t pid, const char* nonce) {
#ifdef _WIN32
snprintf(buf, n, "Local\\RCX_RSP_%u_%s", pid, nonce);
#else
snprintf(buf, n, "/rcx_rsp_%u_%s", pid, nonce);
snprintf(buf, n, "/rcx_rsp_%u", pid);
#endif
}
#ifdef __cplusplus
static_assert(sizeof(RcxRpcHeader) == RCX_RPC_HEADER_SIZE, "Header must be 4096 bytes");
static_assert(sizeof(RcxRpcBootHeader) <= RCX_RPC_BOOT_SIZE, "Boot header must fit 64 bytes");
static_assert(sizeof(RcxRpcHeader) == RCX_RPC_HEADER_SIZE, "Header must be 4096 bytes");
#endif

View File

@@ -4,7 +4,7 @@
*
* Usage:
* test_rpc_client (auto-spawn host)
* test_rpc_client <pid> <nonce> [testbuf_hex testlen]
* test_rpc_client <pid> [testbuf_hex testlen]
*/
#include "../rcx_rpc_protocol.h"
@@ -45,12 +45,12 @@ struct TestIpcClient {
void* view = nullptr;
bool ok = false;
bool connect(uint32_t pid, const char* nonce, int timeoutMs = 5000)
bool connect(uint32_t pid, int timeoutMs = 5000)
{
char shmName[128], reqName[128], rspName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce);
rcx_rpc_req_name(reqName, sizeof(reqName), pid, nonce);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid, nonce);
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
rcx_rpc_req_name(reqName, sizeof(reqName), pid);
rcx_rpc_rsp_name(rspName, sizeof(rspName), pid);
#ifdef _WIN32
ULONGLONG deadline = GetTickCount64() + (ULONGLONG)timeoutMs;
@@ -268,7 +268,7 @@ static pid_t g_hostPid = 0;
#endif
static FILE* g_hostPipe = nullptr;
static bool spawn_host(uint32_t* outPid, char* outNonce,
static bool spawn_host(uint32_t* outPid,
uint64_t* outTestBuf, uint32_t* outTestLen)
{
/* resolve path to test_rpc_host next to ourselves */
@@ -302,11 +302,11 @@ static bool spawn_host(uint32_t* outPid, char* outNonce,
return false;
}
/* parse: READY pid=X nonce=Y testbuf=0xZ testlen=N */
/* parse: READY pid=X testbuf=0xZ testlen=N */
unsigned long long tbuf = 0;
unsigned tlen = 0;
if (sscanf(line, "READY pid=%u nonce=%63s testbuf=0x%llx testlen=%u",
outPid, outNonce, &tbuf, &tlen) < 2) {
if (sscanf(line, "READY pid=%u testbuf=0x%llx testlen=%u",
outPid, &tbuf, &tlen) < 1) {
fprintf(stderr, "ERROR: cannot parse host output: %s\n", line);
return false;
}
@@ -341,30 +341,28 @@ static void print_fail(const char* name) { printf(" [FAIL] %s\n", name); exit(1
int main(int argc, char** argv)
{
uint32_t pid = 0;
char nonce[64] = {};
uint64_t testBuf = 0;
uint32_t testLen = 0;
bool autoMode = false;
if (argc >= 3) {
if (argc >= 2) {
pid = (uint32_t)atoi(argv[1]);
strncpy(nonce, argv[2], 63);
if (argc >= 5) {
testBuf = (uint64_t)strtoull(argv[3], nullptr, 0);
testLen = (uint32_t)atoi(argv[4]);
if (argc >= 4) {
testBuf = (uint64_t)strtoull(argv[2], nullptr, 0);
testLen = (uint32_t)atoi(argv[3]);
}
} else {
autoMode = true;
printf("Auto-spawning test_rpc_host...\n");
if (!spawn_host(&pid, nonce, &testBuf, &testLen)) return 1;
if (!spawn_host(&pid, &testBuf, &testLen)) return 1;
}
printf("Connecting to PID=%u nonce=%s testbuf=0x%llx testlen=%u\n\n",
pid, nonce, (unsigned long long)testBuf, testLen);
printf("Connecting to PID=%u testbuf=0x%llx testlen=%u\n\n",
pid, (unsigned long long)testBuf, testLen);
/* ── connect ── */
TestIpcClient ipc;
if (!ipc.connect(pid, nonce)) {
if (!ipc.connect(pid)) {
fprintf(stderr, "ERROR: IPC connect failed\n");
if (autoMode) cleanup_host();
return 1;

View File

@@ -1,7 +1,7 @@
/*
* test_rpc_host -- loads rcx_payload in-process, acts as the "target".
*
* Usage: test_rpc_host [nonce]
* Usage: test_rpc_host
*
* Prints a READY line (machine-parseable), then waits for the payload
* to shut down (RPC_CMD_SHUTDOWN from the client).
@@ -68,50 +68,11 @@ static int payload_path(char* out, int outLen)
return 0;
}
/* Create bootstrap shared memory with the nonce */
static int create_bootstrap(uint32_t pid, const char* nonce)
{
char bootName[128];
rcx_rpc_boot_name(bootName, sizeof(bootName), pid);
#ifdef _WIN32
HANDLE h = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, RCX_RPC_BOOT_SIZE, bootName);
if (!h) return -1;
void* v = MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, RCX_RPC_BOOT_SIZE);
if (!v) { CloseHandle(h); return -1; }
RcxRpcBootHeader* boot = (RcxRpcBootHeader*)v;
memset(boot, 0, RCX_RPC_BOOT_SIZE);
boot->nonceLength = (uint32_t)strlen(nonce);
strncpy(boot->nonce, nonce, 59);
UnmapViewOfFile(v);
/* keep h open for payload to read */
return 0;
#else
int fd = shm_open(bootName, O_CREAT | O_RDWR, 0600);
if (fd < 0) return -1;
if (ftruncate(fd, RCX_RPC_BOOT_SIZE) != 0) { close(fd); return -1; }
void* v = mmap(nullptr, RCX_RPC_BOOT_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (v == MAP_FAILED) return -1;
RcxRpcBootHeader* boot = (RcxRpcBootHeader*)v;
memset(boot, 0, RCX_RPC_BOOT_SIZE);
boot->nonceLength = (uint32_t)strlen(nonce);
strncpy(boot->nonce, nonce, 59);
munmap(v, RCX_RPC_BOOT_SIZE);
return 0;
#endif
}
/* Open the main shared memory (read-only, just to monitor payloadReady) */
static void* open_main_shm(uint32_t pid, const char* nonce)
static void* open_main_shm(uint32_t pid)
{
char shmName[128];
rcx_rpc_shm_name(shmName, sizeof(shmName), pid, nonce);
rcx_rpc_shm_name(shmName, sizeof(shmName), pid);
#ifdef _WIN32
HANDLE h = nullptr;
@@ -142,21 +103,14 @@ static uint8_t g_testBuf[65536];
/* ── main ─────────────────────────────────────────────────────────── */
int main(int argc, char** argv)
int main(int, char**)
{
const char* nonce = (argc > 1) ? argv[1] : "test0001";
uint32_t pid = current_pid();
/* fill test buffer with known pattern */
for (int i = 0; i < (int)sizeof(g_testBuf); ++i)
g_testBuf[i] = (uint8_t)(i & 0xFF);
/* create bootstrap shm */
if (create_bootstrap(pid, nonce) != 0) {
fprintf(stderr, "ERROR: failed to create bootstrap shm\n");
return 1;
}
/* load payload */
char plPath[1024];
if (payload_path(plPath, sizeof(plPath)) != 0) {
@@ -180,7 +134,7 @@ int main(int argc, char** argv)
#endif
/* open main shm and wait for payloadReady */
void* shmView = open_main_shm(pid, nonce);
void* shmView = open_main_shm(pid);
if (!shmView) {
fprintf(stderr, "ERROR: failed to open main shared memory\n");
return 1;
@@ -197,8 +151,8 @@ int main(int argc, char** argv)
}
/* print READY line for the client to parse */
printf("READY pid=%u nonce=%s testbuf=0x%llx testlen=%u\n",
pid, nonce,
printf("READY pid=%u testbuf=0x%llx testlen=%u\n",
pid,
(unsigned long long)(uintptr_t)g_testBuf,
(unsigned)sizeof(g_testBuf));
fflush(stdout);
@@ -210,7 +164,7 @@ int main(int argc, char** argv)
printf("Payload shut down, exiting.\n");
#ifdef _WIN32
/* give the server thread a moment to exit */
/* give the timer queue a moment to drain */
Sleep(200);
FreeLibrary(hPayload);
if (shmView) UnmapViewOfFile(shmView);