mirror of https://github.com/PCSX2/pcsx2.git
IPC: implement batch command processing
This commit is contained in:
parent
86757fd36f
commit
89ce774d7e
201
pcsx2/IPC.cpp
201
pcsx2/IPC.cpp
|
@ -105,13 +105,11 @@ SocketIPC::SocketIPC(SysCoreThread* vm)
|
||||||
void SocketIPC::ExecuteTaskInThread()
|
void SocketIPC::ExecuteTaskInThread()
|
||||||
{
|
{
|
||||||
int msgsock = 0;
|
int msgsock = 0;
|
||||||
// for the sake of speed we malloc once a return buffer and reuse it by just
|
|
||||||
// cropping its size when needed, it is 450k long which is the size of 50k
|
// we allocate once buffers to not have to do mallocs for each IPC
|
||||||
// MsgWrite64 replies, should be good enough even if we implement batch IPC
|
// request, as malloc is expansive when we optimize for µs.
|
||||||
// processing. Coincidentally 650k is the size of 50k MsgWrite64 REQUESTS so
|
m_ret_buffer = new char[MAX_IPC_RETURN_SIZE];
|
||||||
// we just allocate a 1mb buffer in the end, lul
|
m_ipc_buffer = new char[MAX_IPC_SIZE];
|
||||||
ret_buffer = (char*)malloc(450000 * sizeof(char));
|
|
||||||
ipc_buffer = (char*)malloc(650000 * sizeof(char));
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
msgsock = accept(m_sock, 0, 0);
|
msgsock = accept(m_sock, 0, 0);
|
||||||
|
@ -121,14 +119,14 @@ void SocketIPC::ExecuteTaskInThread()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (read_portable(msgsock, ipc_buffer, 650000) < 0)
|
if (read_portable(msgsock, m_ipc_buffer, 650000) < 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto res = ParseCommand(ipc_buffer, ret_buffer);
|
auto res = ParseCommand(m_ipc_buffer, m_ret_buffer);
|
||||||
if (write_portable(msgsock, res.second, res.first) < 0)
|
if (write_portable(msgsock, res.buffer, res.size) < 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -146,8 +144,8 @@ SocketIPC::~SocketIPC()
|
||||||
close(m_sock);
|
close(m_sock);
|
||||||
unlink(SOCKET_NAME);
|
unlink(SOCKET_NAME);
|
||||||
#endif
|
#endif
|
||||||
free(ret_buffer);
|
delete[] m_ret_buffer;
|
||||||
free(ipc_buffer);
|
delete[] m_ipc_buffer;
|
||||||
// destroy the thread
|
// destroy the thread
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -158,99 +156,126 @@ SocketIPC::~SocketIPC()
|
||||||
|
|
||||||
char* SocketIPC::MakeOkIPC(char* ret_buffer)
|
char* SocketIPC::MakeOkIPC(char* ret_buffer)
|
||||||
{
|
{
|
||||||
ret_buffer[0] = (unsigned char)IPC_OK;
|
ret_buffer[0] = IPC_OK;
|
||||||
return ret_buffer;
|
return ret_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* SocketIPC::MakeFailIPC(char* ret_buffer)
|
char* SocketIPC::MakeFailIPC(char* ret_buffer)
|
||||||
{
|
{
|
||||||
ret_buffer[0] = (unsigned char)IPC_FAIL;
|
ret_buffer[0] = IPC_FAIL;
|
||||||
return ret_buffer;
|
return ret_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<int, char*> SocketIPC::ParseCommand(char* buf, char* ret_buffer)
|
SocketIPC::IPCBuffer SocketIPC::ParseCommand(char* buf, char* ret_buffer)
|
||||||
{
|
{
|
||||||
// currently all our instructions require a running VM so we check once
|
// currently all our instructions require a running VM so we check once
|
||||||
// here, will help perf when/if we implement multi-ipc processing in one
|
// here, will help perf when/if we implement multi-ipc processing in one
|
||||||
// socket roundtrip.
|
// socket roundtrip.
|
||||||
if (!m_vm->HasActiveMachine())
|
if (!m_vm->HasActiveMachine())
|
||||||
return std::make_pair(1, MakeFailIPC(ret_buffer));
|
return IPCBuffer{1, MakeFailIPC(ret_buffer)};
|
||||||
|
|
||||||
// IPC Message event (1 byte)
|
|
||||||
// | Memory address (4 byte)
|
|
||||||
// | | argument (VLE)
|
|
||||||
// | | |
|
|
||||||
// format: XX YY YY YY YY ZZ ZZ ZZ ZZ
|
|
||||||
// reply code: 00 = OK, FF = NOT OK
|
|
||||||
// | return value (VLE)
|
|
||||||
// | |
|
|
||||||
// reply: XX ZZ ZZ ZZ ZZ
|
|
||||||
IPCCommand opcode = (IPCCommand)buf[0];
|
|
||||||
|
|
||||||
// return value
|
u16 batch = 1;
|
||||||
std::pair<int, char*> rval;
|
u32 ret_cnt = 1;
|
||||||
|
if ((IPCCommand)buf[0] == MsgMultiCommand)
|
||||||
// YY YY YY YY from schema above
|
|
||||||
u32 a = FromArray<u32>(buf, 1);
|
|
||||||
switch (opcode)
|
|
||||||
{
|
{
|
||||||
case MsgRead8:
|
batch = FromArray<u16>(buf, 1);
|
||||||
|
buf += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u16 i = 0; i < batch; i++)
|
||||||
|
{
|
||||||
|
// YY YY YY YY from schema below
|
||||||
|
u32 a = FromArray<u32>(buf, 1);
|
||||||
|
|
||||||
|
// IPC Message event (1 byte)
|
||||||
|
// | Memory address (4 byte)
|
||||||
|
// | | argument (VLE)
|
||||||
|
// | | |
|
||||||
|
// format: XX YY YY YY YY ZZ ZZ ZZ ZZ
|
||||||
|
// reply code: 00 = OK, FF = NOT OK
|
||||||
|
// | return value (VLE)
|
||||||
|
// | |
|
||||||
|
// reply: XX ZZ ZZ ZZ ZZ
|
||||||
|
//
|
||||||
|
// NB: memory safety checking would be very expansive in our case,
|
||||||
|
// implemented per command and simply a mess. As our threat model is
|
||||||
|
// nonexistant, knowing we can disable the IPC at any time and having
|
||||||
|
// checks client-side it simply makes more sense to not do check
|
||||||
|
// server-side, as bad as this sounds.
|
||||||
|
// Re security threat model: we control the entire emulated memory of
|
||||||
|
// the emulated game, we can DoS our emulator easily by abusing the IPC
|
||||||
|
// features already, and regardless of where you read this there's
|
||||||
|
// probably no sandbox implemented, so simply being able to write to the
|
||||||
|
// game memory code region would make us control the JIT and have probably
|
||||||
|
// full access over the host machine.
|
||||||
|
switch ((IPCCommand)buf[0])
|
||||||
{
|
{
|
||||||
u8 res;
|
case MsgRead8:
|
||||||
res = memRead8(a);
|
{
|
||||||
rval = std::make_pair(2, ToArray(MakeOkIPC(ret_buffer), res, 1));
|
u8 res;
|
||||||
break;
|
res = memRead8(a);
|
||||||
}
|
ToArray(ret_buffer, res, ret_cnt);
|
||||||
case MsgRead16:
|
ret_cnt += 1;
|
||||||
{
|
buf += 5;
|
||||||
u16 res;
|
break;
|
||||||
res = memRead16(a);
|
}
|
||||||
rval = std::make_pair(3, ToArray(MakeOkIPC(ret_buffer), res, 1));
|
case MsgRead16:
|
||||||
break;
|
{
|
||||||
}
|
u16 res;
|
||||||
case MsgRead32:
|
res = memRead16(a);
|
||||||
{
|
ToArray(ret_buffer, res, ret_cnt);
|
||||||
u32 res;
|
ret_cnt += 2;
|
||||||
res = memRead32(a);
|
buf += 5;
|
||||||
rval = std::make_pair(5, ToArray(MakeOkIPC(ret_buffer), res, 1));
|
break;
|
||||||
break;
|
}
|
||||||
}
|
case MsgRead32:
|
||||||
case MsgRead64:
|
{
|
||||||
{
|
u32 res;
|
||||||
u64 res;
|
res = memRead32(a);
|
||||||
memRead64(a, &res);
|
ToArray(ret_buffer, res, ret_cnt);
|
||||||
rval = std::make_pair(9, ToArray(MakeOkIPC(ret_buffer), res, 1));
|
ret_cnt += 4;
|
||||||
break;
|
buf += 5;
|
||||||
}
|
break;
|
||||||
case MsgWrite8:
|
}
|
||||||
{
|
case MsgRead64:
|
||||||
memWrite8(a, FromArray<u8>(buf, 5));
|
{
|
||||||
rval = std::make_pair(1, MakeOkIPC(ret_buffer));
|
u64 res;
|
||||||
break;
|
memRead64(a, &res);
|
||||||
}
|
ToArray(ret_buffer, res, ret_cnt);
|
||||||
case MsgWrite16:
|
ret_cnt += 8;
|
||||||
{
|
buf += 5;
|
||||||
memWrite16(a, FromArray<u16>(buf, 5));
|
break;
|
||||||
rval = std::make_pair(1, MakeOkIPC(ret_buffer));
|
}
|
||||||
break;
|
case MsgWrite8:
|
||||||
}
|
{
|
||||||
case MsgWrite32:
|
memWrite8(a, FromArray<u8>(buf, 5));
|
||||||
{
|
buf += 6;
|
||||||
memWrite32(a, FromArray<u32>(buf, 5));
|
break;
|
||||||
rval = std::make_pair(1, MakeOkIPC(ret_buffer));
|
}
|
||||||
break;
|
case MsgWrite16:
|
||||||
}
|
{
|
||||||
case MsgWrite64:
|
memWrite16(a, FromArray<u16>(buf, 5));
|
||||||
{
|
buf += 7;
|
||||||
memWrite64(a, FromArray<u64>(buf, 5));
|
break;
|
||||||
rval = std::make_pair(1, MakeOkIPC(ret_buffer));
|
}
|
||||||
break;
|
case MsgWrite32:
|
||||||
}
|
{
|
||||||
default:
|
memWrite32(a, FromArray<u32>(buf, 5));
|
||||||
{
|
buf += 9;
|
||||||
rval = std::make_pair(1, MakeFailIPC(ret_buffer));
|
break;
|
||||||
break;
|
}
|
||||||
|
case MsgWrite64:
|
||||||
|
{
|
||||||
|
memWrite64(a, FromArray<u64>(buf, 5));
|
||||||
|
buf += 13;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return IPCBuffer{1, MakeFailIPC(ret_buffer)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rval;
|
return IPCBuffer{(int)ret_cnt, MakeOkIPC(ret_buffer)};
|
||||||
}
|
}
|
||||||
|
|
90
pcsx2/IPC.h
90
pcsx2/IPC.h
|
@ -26,6 +26,7 @@ using namespace Threading;
|
||||||
class SocketIPC : public pxThread
|
class SocketIPC : public pxThread
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// parent thread
|
||||||
typedef pxThread _parent;
|
typedef pxThread _parent;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -33,41 +34,80 @@ protected:
|
||||||
// windows claim to have support for AF_UNIX sockets but that is a blatant lie,
|
// windows claim to have support for AF_UNIX sockets but that is a blatant lie,
|
||||||
// their SDK won't even run their own examples, so we go on TCP sockets.
|
// their SDK won't even run their own examples, so we go on TCP sockets.
|
||||||
#define PORT 28011
|
#define PORT 28011
|
||||||
|
SOCKET m_sock = INVALID_SOCKET;
|
||||||
#else
|
#else
|
||||||
// absolute path of the socket. Stored in the temporary directory in linux since
|
// absolute path of the socket. Stored in the temporary directory in linux since
|
||||||
// /run requires superuser permission
|
// /run requires superuser permission
|
||||||
const char* SOCKET_NAME = "/tmp/pcsx2.sock";
|
const char* SOCKET_NAME = "/tmp/pcsx2.sock";
|
||||||
#endif
|
|
||||||
|
|
||||||
// socket handlers
|
|
||||||
#ifdef _WIN32
|
|
||||||
SOCKET m_sock = INVALID_SOCKET;
|
|
||||||
#else
|
|
||||||
int m_sock = 0;
|
int m_sock = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// buffers that store the ipc request and reply messages.
|
|
||||||
char* ret_buffer;
|
|
||||||
char* ipc_buffer;
|
|
||||||
|
|
||||||
// possible command messages
|
/**
|
||||||
enum IPCCommand
|
* Maximum memory used by an IPC message request.
|
||||||
|
* Equivalent to 50,000 Write64 requests.
|
||||||
|
*/
|
||||||
|
const unsigned int MAX_IPC_SIZE = 650000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum memory used by an IPC message reply.
|
||||||
|
* Equivalent to 50,000 Read64 replies.
|
||||||
|
*/
|
||||||
|
const unsigned int MAX_IPC_RETURN_SIZE = 450000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC return buffer.
|
||||||
|
* A preallocated buffer used to store all IPC replies.
|
||||||
|
* to the size of 50.000 MsgWrite64 IPC calls.
|
||||||
|
*/
|
||||||
|
char* m_ret_buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC messages buffer.
|
||||||
|
* A preallocated buffer used to store all IPC messages.
|
||||||
|
*/
|
||||||
|
char* m_ipc_buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC Command messages opcodes.
|
||||||
|
* A list of possible operations possible by the IPC.
|
||||||
|
* Each one of them is what we call an "opcode" and is the first
|
||||||
|
* byte sent by the IPC to differentiate between commands.
|
||||||
|
*/
|
||||||
|
enum IPCCommand : unsigned char
|
||||||
{
|
{
|
||||||
MsgRead8 = 0,
|
MsgRead8 = 0, /**< Read 8 bit value to memory. */
|
||||||
MsgRead16 = 1,
|
MsgRead16 = 1, /**< Read 16 bit value to memory. */
|
||||||
MsgRead32 = 2,
|
MsgRead32 = 2, /**< Read 32 bit value to memory. */
|
||||||
MsgRead64 = 3,
|
MsgRead64 = 3, /**< Read 64 bit value to memory. */
|
||||||
MsgWrite8 = 4,
|
MsgWrite8 = 4, /**< Write 8 bit value to memory. */
|
||||||
MsgWrite16 = 5,
|
MsgWrite16 = 5, /**< Write 16 bit value to memory. */
|
||||||
MsgWrite32 = 6,
|
MsgWrite32 = 6, /**< Write 32 bit value to memory. */
|
||||||
MsgWrite64 = 7
|
MsgWrite64 = 7, /**< Write 64 bit value to memory. */
|
||||||
|
MsgMultiCommand = 0xFF /**< Treats multiple IPC commands in batch. */
|
||||||
};
|
};
|
||||||
|
|
||||||
// possible result codes
|
|
||||||
enum IPCResult
|
/**
|
||||||
|
* IPC message buffer.
|
||||||
|
* A list of all needed fields to store an IPC message.
|
||||||
|
*/
|
||||||
|
struct IPCBuffer
|
||||||
{
|
{
|
||||||
IPC_OK = 0,
|
int size; /**< Size of the buffer. */
|
||||||
IPC_FAIL = 0xFF
|
char* buffer; /**< Buffer. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC result codes.
|
||||||
|
* A list of possible result codes the IPC can send back.
|
||||||
|
* Each one of them is what we call an "opcode" or "tag" and is the
|
||||||
|
* first byte sent by the IPC to differentiate between results.
|
||||||
|
*/
|
||||||
|
enum IPCResult : unsigned char
|
||||||
|
{
|
||||||
|
IPC_OK = 0, /**< IPC command successfully completed. */
|
||||||
|
IPC_FAIL = 0xFF /**< IPC command failed to complete. */
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle to the main vm thread
|
// handle to the main vm thread
|
||||||
|
@ -79,9 +119,9 @@ protected:
|
||||||
/* Internal function, Parses an IPC command.
|
/* Internal function, Parses an IPC command.
|
||||||
* buf: buffer containing the IPC command.
|
* buf: buffer containing the IPC command.
|
||||||
* ret_buffer: buffer that will be used to send the reply.
|
* ret_buffer: buffer that will be used to send the reply.
|
||||||
* return value: pair containing a buffer with the result
|
* return value: IPCBuffer containing a buffer with the result
|
||||||
* of the command and its size. */
|
* of the command and its size. */
|
||||||
std::pair<int, char*> ParseCommand(char* buf, char* ret_buffer);
|
IPCBuffer ParseCommand(char* buf, char* ret_buffer);
|
||||||
|
|
||||||
/* Formats an IPC buffer
|
/* Formats an IPC buffer
|
||||||
* ret_buffer: return buffer to use.
|
* ret_buffer: return buffer to use.
|
||||||
|
|
Loading…
Reference in New Issue