Files
FEX-Emu--FEX/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Semaphore.cpp
T
Lioncache 1f756fb386 General: Replace usages of .at(0) with .data() where applicable
This is generally more straightforward in cases where bounds checking isn't necessary.
2025-09-02 13:03:37 -04:00

462 lines
15 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: LinuxSyscalls|syscalls-x86-32
$end_info$
*/
#include "LinuxSyscalls/Syscalls.h"
#include "LinuxSyscalls/x32/Syscalls.h"
#include "LinuxSyscalls/x32/Types.h"
#include "LinuxSyscalls/x64/Syscalls.h"
#include <FEXCore/Debug/InternalThreadState.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/fextl/vector.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <cstdint>
#include <errno.h>
#include <limits>
#include <string.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <time.h>
#include <type_traits>
namespace FEXCore::Core {
struct CpuStateFrame;
}
namespace FEX::HLE::x32 {
// Define the IPC ops
enum IPCOp {
OP_SEMOP = 1,
OP_SEMGET = 2,
OP_SEMCTL = 3,
OP_SEMTIMEDOP = 4,
OP_MSGSND = 11,
OP_MSGRCV = 12,
OP_MSGGET = 13,
OP_MSGCTL = 14,
OP_SHMAT = 21,
OP_SHMDT = 22,
OP_SHMGET = 23,
OP_SHMCTL = 24,
};
struct msgbuf_32 {
compat_long_t mtype;
char mtext[1];
};
union semun_32 {
int32_t val; // Value for SETVAL
compat_ptr<semid_ds_32> buf32; // struct semid_ds* - Buffer ptr for IPC_STAT, IPC_SET
compat_ptr<semid_ds_64> buf64; // struct semid_ds* - Buffer ptr for IPC_STAT, IPC_SET
uint32_t array; // uint16_t array for GETALL, SETALL
compat_ptr<struct fex_seminfo> __buf; // struct seminfo * - Buffer for IPC_INFO
};
union msgun_32 {
int32_t val; // Value for SETVAL
compat_ptr<msqid_ds_32> buf32; // struct msgid_ds* - Buffer ptr for IPC_STAT, IPC_SET
compat_ptr<msqid_ds_64> buf64; // struct msgid_ds* - Buffer ptr for IPC_STAT, IPC_SET
uint32_t array; // uint16_t array for GETALL, SETALL
compat_ptr<struct msginfo> __buf; // struct msginfo * - Buffer for IPC_INFO
};
union shmun_32 {
int32_t val; // Value for SETVAL
compat_ptr<shmid_ds_32> buf32; // struct shmid_ds* - Buffer ptr for IPC_STAT, IPC_SET
compat_ptr<shmid_ds_64> buf64; // struct shmid_ds* - Buffer ptr for IPC_STAT, IPC_SET
uint32_t array; // uint16_t array for GETALL, SETALL
compat_ptr<struct shminfo_32> __buf32; // struct shminfo * - Buffer for IPC_INFO
compat_ptr<struct shminfo_64> __buf64; // struct shminfo * - Buffer for IPC_INFO
compat_ptr<struct shm_info_32> __buf_info_32; // struct shm_info * - Buffer for SHM_INFO
};
union semun {
int val; /* value for SETVAL */
struct semid_ds_32* buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short* array; /* array for GETALL & SETALL */
struct fex_seminfo* __buf; /* buffer for IPC_INFO */
void* __pad;
};
uint64_t _ipc(FEXCore::Core::CpuStateFrame* Frame, uint32_t call, uint32_t first, uint32_t second, uint32_t third, uint32_t ptr, uint32_t fifth) {
uint64_t Result {};
const int Version = call >> 16;
call &= 0xffff;
switch (static_cast<IPCOp>(call)) {
case OP_SEMOP: {
Result = ::syscall(SYSCALL_DEF(semop), first, reinterpret_cast<struct sembuf*>(ptr), second);
break;
}
case OP_SEMGET: {
Result = ::syscall(SYSCALL_DEF(semget), first, second, third);
break;
}
case OP_SEMCTL: {
uint32_t semid = first;
uint32_t semnum = second;
// Upper 16bits used for a different flag?
int32_t cmd = third & 0xFF;
auto_compat_ptr<semun_32> semun(ptr);
bool IPC64 = third & 0x100;
switch (cmd) {
case IPC_SET: {
struct semid64_ds buf {};
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsReadable(semun->buf64, sizeof(*semun->buf64));
buf = *semun->buf64;
} else {
FaultSafeUserMemAccess::VerifyIsReadable(semun->buf32, sizeof(*semun->buf32));
buf = *semun->buf32;
}
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, &buf);
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf64, sizeof(*semun->buf64));
*semun->buf64 = buf;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf32, sizeof(*semun->buf32));
*semun->buf32 = buf;
}
}
break;
}
case SEM_STAT:
case SEM_STAT_ANY:
case IPC_STAT: {
struct semid64_ds buf {};
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, &buf);
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf64, sizeof(*semun->buf64));
*semun->buf64 = buf;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf32, sizeof(*semun->buf32));
*semun->buf32 = buf;
}
}
break;
}
case SEM_INFO:
case IPC_INFO: {
struct fex_seminfo si {};
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, &si);
if (Result != -1) {
FaultSafeUserMemAccess::VerifyIsWritable(semun->__buf, sizeof(*semun->__buf));
memcpy(semun->__buf, &si, sizeof(si));
}
break;
}
case GETALL:
case SETALL: {
// ptr is just a int32_t* in this case
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, semun->array);
break;
}
case SETVAL: {
// ptr is just a int32_t in this case
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, semun->val);
break;
}
case IPC_RMID:
case GETPID:
case GETNCNT:
case GETZCNT:
case GETVAL: Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd); break;
default: LOGMAN_MSG_A_FMT("Unhandled semctl cmd: {}", cmd); return -EINVAL;
}
break;
}
case OP_SEMTIMEDOP: {
timespec32* timeout = reinterpret_cast<timespec32*>(fifth);
struct timespec tp64 {};
struct timespec* timed_ptr {};
if (timeout) {
FaultSafeUserMemAccess::VerifyIsReadable(timeout, sizeof(*timeout));
tp64 = *timeout;
timed_ptr = &tp64;
}
Result = ::syscall(SYSCALL_DEF(semtimedop), first, reinterpret_cast<struct sembuf*>(ptr), second, timed_ptr);
break;
}
case OP_MSGSND: {
// Requires a temporary buffer
fextl::vector<uint8_t> Tmp(second + sizeof(size_t));
struct msgbuf* TmpMsg = reinterpret_cast<struct msgbuf*>(Tmp.data());
msgbuf_32* src = reinterpret_cast<msgbuf_32*>(ptr);
FaultSafeUserMemAccess::VerifyIsReadable(src, sizeof(*src));
FaultSafeUserMemAccess::VerifyIsReadable(src->mtext, second);
TmpMsg->mtype = src->mtype;
memcpy(TmpMsg->mtext, src->mtext, second);
Result = ::syscall(SYSCALL_DEF(msgsnd), first, TmpMsg, second, third);
break;
}
case OP_MSGRCV: {
fextl::vector<uint8_t> Tmp(second + sizeof(size_t));
struct msgbuf* TmpMsg = reinterpret_cast<struct msgbuf*>(Tmp.data());
if (Version != 0) {
Result = ::syscall(SYSCALL_DEF(msgrcv), first, TmpMsg, second, fifth, third);
if (Result != -1) {
msgbuf_32* src = reinterpret_cast<msgbuf_32*>(ptr);
FaultSafeUserMemAccess::VerifyIsWritable(src, sizeof(*src));
FaultSafeUserMemAccess::VerifyIsWritable(src->mtext, Result);
src->mtype = TmpMsg->mtype;
memcpy(src->mtext, TmpMsg->mtext, Result);
}
} else {
struct compat_ipc_kludge {
compat_uptr_t msgp;
compat_long_t msgtyp;
};
compat_ipc_kludge* ipck = reinterpret_cast<compat_ipc_kludge*>(ptr);
Result = ::syscall(SYSCALL_DEF(msgrcv), first, TmpMsg, second, ipck->msgtyp, third);
if (Result != -1) {
msgbuf_32* src = reinterpret_cast<msgbuf_32*>(ipck->msgp);
FaultSafeUserMemAccess::VerifyIsWritable(src, sizeof(*src));
FaultSafeUserMemAccess::VerifyIsWritable(src->mtext, Result);
ipck->msgtyp = TmpMsg->mtype;
memcpy(src->mtext, TmpMsg->mtext, Result);
}
}
break;
}
case OP_MSGGET: {
Result = ::syscall(SYSCALL_DEF(msgget), first, second);
break;
}
case OP_MSGCTL: {
uint32_t msqid = first;
int32_t cmd = second & 0xFF;
msgun_32 msgun {};
msgun.val = ptr;
bool IPC64 = second & 0x100;
switch (cmd) {
case IPC_SET: {
struct msqid64_ds buf {};
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsReadable(msgun.buf64, sizeof(*msgun.buf64));
buf = *msgun.buf64;
} else {
FaultSafeUserMemAccess::VerifyIsReadable(msgun.buf32, sizeof(*msgun.buf32));
buf = *msgun.buf32;
}
Result = ::syscall(SYSCALL_DEF(msgctl), msqid, cmd, &buf);
break;
}
case MSG_STAT:
case MSG_STAT_ANY:
case IPC_STAT: {
struct msqid64_ds buf {};
Result = ::syscall(SYSCALL_DEF(msgctl), msqid, cmd, &buf);
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(msgun.buf64, sizeof(*msgun.buf64));
*msgun.buf64 = buf;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(msgun.buf32, sizeof(*msgun.buf32));
*msgun.buf32 = buf;
}
}
break;
}
case MSG_INFO:
case IPC_INFO: {
struct msginfo mi {};
Result = ::syscall(SYSCALL_DEF(msgctl), msqid, cmd, reinterpret_cast<struct msqid_ds*>(&mi));
if (Result != -1) {
FaultSafeUserMemAccess::VerifyIsWritable(msgun.__buf, sizeof(mi));
memcpy(msgun.__buf, &mi, sizeof(mi));
}
break;
}
case IPC_RMID: Result = ::syscall(SYSCALL_DEF(msgctl), msqid, cmd, nullptr); break;
default: LOGMAN_MSG_A_FMT("Unhandled msgctl cmd: {}", cmd); return -EINVAL;
}
break;
}
case OP_SHMAT: {
if (Version == 1) {
// shmat explicitly doesn't support version 1.
return -EINVAL;
}
auto Result = FEX::HLE::_SyscallHandler->GuestShmat(false, Frame->Thread, first, reinterpret_cast<const void*>(ptr), second);
if (!FEX::HLE::HasSyscallError(Result)) {
*reinterpret_cast<uint32_t*>(third) = Result;
}
return Result;
}
case OP_SHMDT: {
return FEX::HLE::_SyscallHandler->GuestShmdt(false, Frame->Thread, reinterpret_cast<const void*>(ptr));
}
case OP_SHMGET: {
Result = ::shmget(first, second, third);
break;
}
case OP_SHMCTL: {
int32_t shmid = first;
int32_t shmcmd = second;
int32_t cmd = shmcmd & 0xFF;
bool IPC64 = shmcmd & 0x100;
shmun_32 shmun {};
shmun.val = reinterpret_cast<uint32_t>(ptr);
switch (cmd) {
case IPC_SET: {
struct shmid64_ds buf {};
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsReadable(shmun.buf64, sizeof(*shmun.buf64));
buf = *shmun.buf64;
} else {
FaultSafeUserMemAccess::VerifyIsReadable(shmun.buf32, sizeof(*shmun.buf32));
buf = *shmun.buf32;
}
Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, &buf);
// IPC_SET sets the internal data structure that the kernel uses
// No need to writeback
break;
}
case SHM_STAT:
case SHM_STAT_ANY:
case IPC_STAT: {
struct shmid64_ds buf {};
Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, &buf);
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(shmun.buf64, sizeof(*shmun.buf64));
*shmun.buf64 = buf;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(shmun.buf32, sizeof(*shmun.buf32));
*shmun.buf32 = buf;
}
}
break;
}
case IPC_INFO: {
struct shminfo si {};
Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, reinterpret_cast<struct shmid_ds*>(&si));
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(shmun.__buf64, sizeof(*shmun.__buf64));
*shmun.__buf64 = si;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(shmun.__buf32, sizeof(*shmun.__buf32));
*shmun.__buf32 = si;
}
}
break;
}
case SHM_INFO: {
struct shm_info si {};
Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, reinterpret_cast<struct shmid_ds*>(&si));
if (Result != -1) {
FaultSafeUserMemAccess::VerifyIsWritable(shmun.__buf_info_32, sizeof(*shmun.__buf_info_32));
// SHM_INFO doesn't follow IPC64 behaviour
*shmun.__buf_info_32 = si;
}
break;
}
case SHM_LOCK: Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, nullptr); break;
case SHM_UNLOCK: Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, nullptr); break;
case IPC_RMID: Result = ::syscall(SYSCALL_DEF(shmctl), shmid, cmd, nullptr); break;
default: LOGMAN_MSG_A_FMT("Unhandled shmctl cmd: {}", cmd); return -EINVAL;
}
break;
}
default: return -ENOSYS;
}
SYSCALL_ERRNO();
}
void RegisterSemaphore(FEX::HLE::SyscallHandler* Handler) {
REGISTER_SYSCALL_IMPL_X32(ipc, _ipc);
REGISTER_SYSCALL_IMPL_X32(semctl, [](FEXCore::Core::CpuStateFrame* Frame, int semid, int semnum, int cmd, semun_32* semun) -> uint64_t {
uint64_t Result {};
bool IPC64 = cmd & 0x100;
switch (cmd) {
case IPC_SET: {
struct semid64_ds buf {};
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsReadable(semun->buf64, sizeof(*semun->buf64));
buf = *semun->buf64;
} else {
FaultSafeUserMemAccess::VerifyIsReadable(semun->buf32, sizeof(*semun->buf32));
buf = *semun->buf32;
}
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, &buf);
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf64, sizeof(*semun->buf64));
*semun->buf64 = buf;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf32, sizeof(*semun->buf32));
*semun->buf32 = buf;
}
}
break;
}
case SEM_STAT:
case SEM_STAT_ANY:
case IPC_STAT: {
struct semid64_ds buf {};
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, &buf);
if (Result != -1) {
if (IPC64) {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf64, sizeof(*semun->buf64));
*semun->buf64 = buf;
} else {
FaultSafeUserMemAccess::VerifyIsWritable(semun->buf32, sizeof(*semun->buf32));
*semun->buf32 = buf;
}
}
break;
}
case SEM_INFO:
case IPC_INFO: {
struct fex_seminfo si {};
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, &si);
if (Result != -1) {
FaultSafeUserMemAccess::VerifyIsWritable(semun->__buf, sizeof(*semun->__buf));
memcpy(semun->__buf, &si, sizeof(si));
}
break;
}
case GETALL:
case SETALL: {
// ptr is just a int32_t* in this case
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, semun->array);
break;
}
case SETVAL: {
// ptr is just a int32_t in this case
Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, semun->val);
break;
}
case IPC_RMID:
case GETPID:
case GETNCNT:
case GETZCNT:
case GETVAL: Result = ::syscall(SYSCALL_DEF(semctl), semid, semnum, cmd, semun); break;
default: LOGMAN_MSG_A_FMT("Unhandled semctl cmd: {}", cmd); return -EINVAL;
}
SYSCALL_ERRNO();
});
}
} // namespace FEX::HLE::x32