Files
AnyPS5/core/libs/tests/GuestNanosleep.cpp
T

190 lines
7.3 KiB
C++

#include "prx/libkernel/KernelErrors.hpp"
#include "prx/libkernel/include/Api.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libc/include/Api.hpp"
#include "SceTypes.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
using Nanosleep = int (APS5_VABI *)(const KernelTimespec*, KernelTimespec*);
static constexpr int SCE_OK = 0;
static constexpr int GUEST_EACCES = 13;
static constexpr int GUEST_EFAULT = 14;
static constexpr int GUEST_EINVAL = 22;
static constexpr int GUEST_CLOCK_MONOTONIC = 4;
static constexpr std::int64_t NANOS_PER_SECOND = 1000000000LL;
static constexpr std::int64_t NANOS_PER_MILLISECOND = 1000000LL;
static constexpr std::int64_t SLEEP_NANOS = 50 * NANOS_PER_MILLISECOND;
static constexpr std::int64_t EARLY_WAKE_MARGIN_NANOS = 5 * NANOS_PER_MILLISECOND;
static constexpr std::int64_t NO_SLEEP_LIMIT_NANOS = 2 * NANOS_PER_SECOND;
static void Require(bool value) { if (!value) std::abort(); }
static std::int64_t MonotonicNanos() {
KernelTimespec time{-1, -1};
Require(clock_gettime_nid_postfix(GUEST_CLOCK_MONOTONIC, &time) == SCE_OK);
return time.tv_sec * NANOS_PER_SECOND + time.tv_nsec;
}
static void PosixRejects(Nanosleep sleep, const KernelTimespec* request, int error) {
*__error_nid_postfix() = GUEST_EACCES;
Require(sleep(request, nullptr) == -1);
Require(*__error_nid_postfix() == error);
}
static void PosixRejectsInvalidRequests(Nanosleep sleep) {
const KernelTimespec negativeNanos{0, -1};
const KernelTimespec wholeSecondOfNanos{0, NANOS_PER_SECOND};
const KernelTimespec negativeBoth{-1, -1};
PosixRejects(sleep, &negativeNanos, GUEST_EINVAL);
PosixRejects(sleep, &wholeSecondOfNanos, GUEST_EINVAL);
PosixRejects(sleep, &negativeBoth, GUEST_EINVAL);
PosixRejects(sleep, nullptr, GUEST_EFAULT);
}
static void SceRejectsInvalidRequests() {
const KernelTimespec negativeNanos{0, -1};
const KernelTimespec wholeSecondOfNanos{0, NANOS_PER_SECOND};
const KernelTimespec negativeBoth{-1, -1};
Require(sceKernelNanosleep(&negativeNanos, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(sceKernelNanosleep(&wholeSecondOfNanos, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(sceKernelNanosleep(&negativeBoth, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(sceKernelNanosleep(nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
}
static void ReturnsAtOnce(Nanosleep sleep, KernelTimespec request) {
const std::int64_t start = MonotonicNanos();
*__error_nid_postfix() = GUEST_EACCES;
Require(sleep(&request, nullptr) == SCE_OK);
Require(*__error_nid_postfix() == GUEST_EACCES);
Require(MonotonicNanos() - start < NO_SLEEP_LIMIT_NANOS);
}
static void NegativeSecondsDoNotSleep(Nanosleep sleep) {
ReturnsAtOnce(sleep, {-1, 0});
ReturnsAtOnce(sleep, {-1, NANOS_PER_SECOND - 1});
ReturnsAtOnce(sleep, {INT64_MIN, 0});
ReturnsAtOnce(sleep, {0, 0});
}
static void SleepsForTheRequest(Nanosleep sleep) {
const KernelTimespec request{0, SLEEP_NANOS};
const std::int64_t start = MonotonicNanos();
*__error_nid_postfix() = GUEST_EACCES;
Require(sleep(&request, nullptr) == SCE_OK);
Require(*__error_nid_postfix() == GUEST_EACCES);
Require(MonotonicNanos() - start >= SLEEP_NANOS - EARLY_WAKE_MARGIN_NANOS);
}
static void CompletedSleepLeavesTheRemainingTimeAlone(Nanosleep sleep) {
const KernelTimespec request{0, NANOS_PER_MILLISECOND};
KernelTimespec remaining{7, 9};
Require(sleep(&request, &remaining) == SCE_OK);
Require(remaining.tv_sec == 7 && remaining.tv_nsec == 9);
KernelTimespec shared = request;
for (int i = 0; i < 3; ++i) {
Require(sleep(&shared, &shared) == SCE_OK);
Require(shared.tv_sec == request.tv_sec && shared.tv_nsec == request.tv_nsec);
}
const KernelTimespec invalid{0, NANOS_PER_SECOND};
remaining = {7, 9};
Require(sleep(&invalid, &remaining) != SCE_OK);
Require(remaining.tv_sec == 7 && remaining.tv_nsec == 9);
}
class GuardedRequest {
public:
GuardedRequest() {
#ifdef _WIN32
SYSTEM_INFO info{};
GetSystemInfo(&info);
pageSize = info.dwPageSize;
memory = static_cast<unsigned char*>(VirtualAlloc(nullptr, 2 * pageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
Require(memory != nullptr);
#else
const long size = sysconf(_SC_PAGESIZE);
Require(size > 0);
pageSize = static_cast<std::size_t>(size);
void* mapping = mmap(nullptr, 2 * pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(mapping != MAP_FAILED);
memory = static_cast<unsigned char*>(mapping);
#endif
}
~GuardedRequest() {
#ifdef _WIN32
Require(VirtualFree(memory, 0, MEM_RELEASE) != 0);
#else
Require(munmap(memory, 2 * pageSize) == 0);
#endif
}
KernelTimespec* AtBoundary(std::size_t readableBytes) {
return reinterpret_cast<KernelTimespec*>(memory + pageSize - readableBytes);
}
void Protect(bool readable) {
#ifdef _WIN32
DWORD oldProtect{};
Require(VirtualProtect(memory + pageSize, pageSize, readable ? PAGE_READWRITE : PAGE_NOACCESS, &oldProtect) != 0);
#else
Require(mprotect(memory + pageSize, pageSize, readable ? PROT_READ | PROT_WRITE : PROT_NONE) == 0);
#endif
}
private:
unsigned char* memory{};
std::size_t pageSize{};
};
static void RejectsUnreadableRequests(Nanosleep sleep, bool sce) {
GuardedRequest mapping;
for (const std::size_t readableBytes : {std::size_t{0}, sizeof(std::int64_t)}) {
auto* request = mapping.AtBoundary(readableBytes);
const KernelTimespec zero{};
std::memcpy(request, &zero, sizeof(zero));
Require(sleep(request, nullptr) == SCE_OK);
mapping.Protect(false);
KernelTimespec remaining{7, 9};
*__error_nid_postfix() = GUEST_EACCES;
const int result = sleep(request, &remaining);
Require(result == (sce ? SCE_KERNEL_ERROR_EFAULT : -1));
Require(*__error_nid_postfix() == (sce ? GUEST_EACCES : GUEST_EFAULT));
Require(remaining.tv_sec == 7 && remaining.tv_nsec == 9);
auto* inside = mapping.AtBoundary(sizeof(KernelTimespec));
std::memcpy(inside, &zero, sizeof(zero));
Require(sleep(inside, nullptr) == SCE_OK);
mapping.Protect(true);
std::memcpy(request, &zero, sizeof(zero));
Require(sleep(request, nullptr) == SCE_OK);
}
}
int main() {
RejectsUnreadableRequests(nanosleep_nid_postfix, false);
RejectsUnreadableRequests(_nanosleep_nid_postfix, false);
RejectsUnreadableRequests(sceKernelNanosleep, true);
PosixRejectsInvalidRequests(nanosleep_nid_postfix);
PosixRejectsInvalidRequests(_nanosleep_nid_postfix);
SceRejectsInvalidRequests();
NegativeSecondsDoNotSleep(nanosleep_nid_postfix);
NegativeSecondsDoNotSleep(_nanosleep_nid_postfix);
NegativeSecondsDoNotSleep(sceKernelNanosleep);
SleepsForTheRequest(nanosleep_nid_postfix);
SleepsForTheRequest(_nanosleep_nid_postfix);
SleepsForTheRequest(sceKernelNanosleep);
CompletedSleepLeavesTheRemainingTimeAlone(nanosleep_nid_postfix);
CompletedSleepLeavesTheRemainingTimeAlone(_nanosleep_nid_postfix);
CompletedSleepLeavesTheRemainingTimeAlone(sceKernelNanosleep);
}