Merge pull request #8725 from orezraey/fix/libkernel-sync-on-address-measured-ps5

fix(libkernel): follow sceKernelSyncOnAddressWait and Wake as measured on a PS5
This commit is contained in:
Dean authored and GitHub committed 2026-10-27 20:03:20 +13:00
commit 81267b2204
5 files changed
+208 -130

No files matched your search

@@ -131,25 +131,55 @@ bool WaitOnKeyUntil(std::uintptr_t key, TReady ready, std::optional<std::uint64_
}
template <class TValue>
bool WaitOnAddressUntil(TValue* address, TValue expected, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
return WaitOnKeyUntil(reinterpret_cast<std::uintptr_t>(address), [&] { return std::atomic_ref<TValue>(*address).load() != expected; }, deadlineNanos, caller);
TValue LoadAddressValue(TValue* address) {
if (reinterpret_cast<std::uintptr_t>(address) % alignof(TValue) == 0) return std::atomic_ref<TValue>(*address).load();
TValue value;
asm volatile("mov %1, %0" : "=r"(value) : "m"(*reinterpret_cast<const unsigned char (*)[sizeof(TValue)]>(address)));
return value;
}
template <class TValue>
int WaitOnAddress(TValue* address, TValue expected, const KernelUseconds* timeout, const void* caller) {
const auto deadline = timeout == nullptr ? std::nullopt : std::optional<std::uint64_t>(TimedWait::DeadlineNanos(*timeout));
return WaitOnAddressUntil(address, expected, deadline, caller) ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT;
bool WaitOnAddressUntil(TValue* address, TValue expected, std::optional<std::uint64_t> deadlineNanos, const void* caller, bool& differed) {
return WaitOnKeyUntil(reinterpret_cast<std::uintptr_t>(address), [&] { return differed = LoadAddressValue(address) != expected; }, deadlineNanos, caller);
}
void WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
template <class TValue>
bool WaitOnAddressUntil(TValue* address, TValue expected, std::optional<std::uint64_t> deadlineNanos, const void* caller) {
bool differed = false;
return WaitOnAddressUntil(address, expected, deadlineNanos, caller, differed);
}
template <class TValue>
int WaitOnAddress(TValue* address, TValue expected, KernelUseconds* timeout, const void* caller) {
if (timeout != nullptr && *timeout == 0) {
return SCE_KERNEL_ERROR_ETIMEDOUT;
}
if (address == nullptr) {
return SCE_KERNEL_ERROR_EFAULT;
}
const auto deadline = timeout == nullptr ? std::nullopt : std::optional<std::uint64_t>(TimedWait::DeadlineNanos(*timeout));
bool differed = false;
const bool woken = WaitOnAddressUntil(address, expected, deadline, caller, differed);
if (differed) {
return SCE_KERNEL_ERROR_EAGAIN;
}
if (timeout != nullptr) {
const std::uint64_t now = TimedWait::NowNanos();
*timeout = woken && *deadline > now ? static_cast<KernelUseconds>((*deadline - now) / 1000) : 0;
}
return woken ? SYNC_ON_ADDRESS_OK : SCE_KERNEL_ERROR_ETIMEDOUT;
}
std::int32_t WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
auto& bucket = BucketOf(key);
std::lock_guard<std::mutex> lock(bucket.mutex);
const auto entry = bucket.waiters.find(key);
if (entry == bucket.waiters.end()) {
return;
return 0;
}
auto& queue = entry->second;
for (std::int32_t woken = 0; !queue.empty() && (woken < count || (wakesAtLeastOne && woken == 0)); ++woken) {
std::int32_t woken = 0;
for (; !queue.empty() && (woken < count || (wakesAtLeastOne && woken == 0)); ++woken) {
AddressWaiter* waiter = queue.front();
queue.pop_front();
waiter->woken = true;
@@ -158,10 +188,13 @@ void WakeAddressLocked(std::uintptr_t key, std::int32_t count, bool wakesAtLeast
if (queue.empty()) {
bucket.waiters.erase(entry);
}
return woken;
}
void WakeAddress(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
DeferringDelivery([&] { WakeAddressLocked(key, count, wakesAtLeastOne); });
std::int32_t WakeAddress(std::uintptr_t key, std::int32_t count, bool wakesAtLeastOne) {
std::int32_t woken = 0;
DeferringDelivery([&] { woken = WakeAddressLocked(key, count, wakesAtLeastOne); });
return woken;
}
std::uint64_t SaturatingNanos(std::int64_t seconds, std::int64_t nanos) {
@@ -306,54 +339,33 @@ int SemWake(UserSemaphore* semaphore) {
extern "C" {
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) {
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name) {
(void)name;
if (!IsAlignedAddress<std::uint32_t>(reinterpret_cast<std::uintptr_t>(address))) {
APS5_INVALID_ARG_EX;
}
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
}
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, const KernelUseconds* timeout, const char* name) {
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, KernelUseconds* timeout, const char* name) {
(void)name;
if (!IsAlignedAddress<std::uint8_t>(reinterpret_cast<std::uintptr_t>(address))) {
APS5_INVALID_ARG_EX;
}
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
}
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, const KernelUseconds* timeout, const char* name) {
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, KernelUseconds* timeout, const char* name) {
(void)name;
if (!IsAlignedAddress<std::uint16_t>(reinterpret_cast<std::uintptr_t>(address))) {
APS5_INVALID_ARG_EX;
}
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
}
int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name) {
int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name) {
(void)name;
if (!IsAlignedAddress<std::uint32_t>(reinterpret_cast<std::uintptr_t>(address))) {
APS5_INVALID_ARG_EX;
}
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
}
int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name) {
int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, KernelUseconds* timeout, const char* name) {
(void)name;
if (!IsAlignedAddress<std::uint64_t>(reinterpret_cast<std::uintptr_t>(address))) {
APS5_INVALID_ARG_EX;
}
return WaitOnAddress(address, expected, timeout, __builtin_return_address(0));
}
int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count) {
const auto key = reinterpret_cast<std::uintptr_t>(address);
if (!IsAlignedAddress<std::uint8_t>(key) || count < 0) {
APS5_INVALID_ARG_EX;
}
WakeAddress(key, count, false);
return SYNC_ON_ADDRESS_OK;
return WakeAddress(reinterpret_cast<std::uintptr_t>(address), count, true);
}
int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t value, void* sizeArgument, void* timeArgument) {
+5 -5
View File
@@ -626,11 +626,11 @@ std::int64_t APS5_VABI _sendto_nid_postfix(int descriptor, const void* buffer, s
std::int64_t APS5_VABI _recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength);
std::int64_t APS5_VABI _sendmsg_nid_postfix(int descriptor, const GuestMsghdr* message, int flags);
std::int64_t APS5_VABI _recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, int flags);
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, const KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, const KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, const KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, const KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait8(std::uint8_t* address, std::uint8_t expected, KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait16(std::uint16_t* address, std::uint16_t expected, KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait32(std::uint32_t* address, std::uint32_t expected, KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWait64(std::uint64_t* address, std::uint64_t expected, KernelUseconds* timeout, const char* name);
int APS5_VABI sceKernelSyncOnAddressWake(void* address, std::int32_t count);
int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t value, void* sizeArgument, void* timeArgument);
int APS5_VABI sceCoredumpRegisterCoredumpHandler(uint64_t handler, size_t stackSize, uint64_t context);
+1 -1
View File
@@ -402,7 +402,7 @@ static void* APS5_VABI AddressWaking(void* arg) {
auto& worker = *static_cast<Worker*>(arg);
worker.id = std::this_thread::get_id();
worker.started.store(true);
while (!worker.stop.load()) Require(sceKernelSyncOnAddressWake(&addressWord, 1) == 0);
while (!worker.stop.load()) Require(sceKernelSyncOnAddressWake(&addressWord, 1) >= 0);
return nullptr;
}
+152 -85
View File
@@ -6,23 +6,17 @@
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <cstring>
#include <thread>
#include <vector>
static constexpr int SCE_OK = 0;
static constexpr KernelUseconds FAILSAFE_TIMEOUT = 10000000;
static constexpr KernelUseconds SHORT_TIMEOUT = 1000;
static constexpr auto SETTLE = std::chrono::milliseconds(100);
static void Require(bool value) { if (!value) std::abort(); }
template <class TCall>
static bool Rejects(TCall call) {
try { call(); }
catch (const std::invalid_argument&) { return true; }
return false;
}
static void AwaitCount(const std::atomic<int>& counter, int value) {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (counter.load() != value) {
@@ -31,81 +25,93 @@ static void AwaitCount(const std::atomic<int>& counter, int value) {
}
}
static void ReturnsWhenTheValueDiffers() {
static void FailsAgainWhenTheValueDiffers() {
std::uint32_t word = 2;
Require(sceKernelSyncOnAddressWait(&word, 1, nullptr, "differs") == SCE_OK);
const KernelUseconds timeout = 0;
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, nullptr) == SCE_OK);
Require(sceKernelSyncOnAddressWait(&word, 1, nullptr, "differs") == SCE_KERNEL_ERROR_EAGAIN);
KernelUseconds timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, nullptr) == SCE_KERNEL_ERROR_EAGAIN && timeout == SHORT_TIMEOUT);
timeout = 0;
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, nullptr) == SCE_KERNEL_ERROR_ETIMEDOUT);
}
static void TimesOutWhileTheValueMatches() {
std::uint32_t word = 1;
const KernelUseconds none = 0;
Require(sceKernelSyncOnAddressWait(&word, 1, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
KernelUseconds none = 0;
Require(sceKernelSyncOnAddressWait(&word, 1, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT && none == 0);
const KernelUseconds timeout = 20000;
KernelUseconds timeout = 20000;
const auto start = std::chrono::steady_clock::now();
Require(sceKernelSyncOnAddressWait(&word, 1, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == SCE_OK);
Require(timeout == 0);
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == 0);
}
static void WakeReleasesAWaiterWithoutAValueChange() {
static void WakeReleasesAWaiterAndReportsTheTimeLeft() {
std::uint32_t word = 1;
std::atomic<int> started{0};
std::atomic<int> finished{0};
int result = -1;
KernelUseconds timeout = FAILSAFE_TIMEOUT;
std::thread waiter([&] {
result = sceKernelSyncOnAddressWait(&word, 1, &FAILSAFE_TIMEOUT, "wake");
++started;
result = sceKernelSyncOnAddressWait(&word, 1, &timeout, "wake");
++finished;
});
AwaitCount(started, 1);
std::this_thread::sleep_for(SETTLE);
int woken = 0;
while (finished.load() == 0) {
Require(sceKernelSyncOnAddressWake(&word, 1) == SCE_OK);
woken += sceKernelSyncOnAddressWake(&word, 1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
waiter.join();
Require(result == SCE_OK);
Require(result == SCE_OK && woken == 1);
Require(timeout > 0 && timeout < FAILSAFE_TIMEOUT);
}
static void WakeHonoursTheCountAndTheAddress() {
static void WakeCountsOfOneOrLessWakeOneWaiter() {
std::uint32_t word = 1;
std::uint32_t other = 1;
std::atomic<int> started{0};
std::atomic<int> finished{0};
std::atomic<int> otherFinished{0};
std::vector<int> results(3, -1);
std::vector<int> results(5, -1);
int otherResult = -1;
std::vector<std::thread> waiters;
for (int i = 0; i < 3; ++i) {
for (int i = 0; i < 5; ++i) {
waiters.emplace_back([&, i] {
++started;
results[i] = sceKernelSyncOnAddressWait(&word, 1, &FAILSAFE_TIMEOUT, "count");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
results[i] = sceKernelSyncOnAddressWait(&word, 1, &timeout, "count");
++finished;
});
}
std::thread otherWaiter([&] {
++started;
otherResult = sceKernelSyncOnAddressWait(&other, 1, &FAILSAFE_TIMEOUT, "other");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
otherResult = sceKernelSyncOnAddressWait(&other, 1, &timeout, "other");
++otherFinished;
});
AwaitCount(started, 4);
AwaitCount(started, 6);
std::this_thread::sleep_for(SETTLE);
Require(sceKernelSyncOnAddressWake(&word, 0) == SCE_OK);
std::this_thread::sleep_for(SETTLE);
Require(finished.load() == 0);
int expected = 0;
for (const std::int32_t count : {0, -1, 1}) {
Require(sceKernelSyncOnAddressWake(&word, count) == 1);
AwaitCount(finished, ++expected);
std::this_thread::sleep_for(SETTLE);
Require(finished.load() == expected);
}
Require(sceKernelSyncOnAddressWake(&word, 1) == SCE_OK);
AwaitCount(finished, 1);
std::this_thread::sleep_for(SETTLE);
Require(finished.load() == 1);
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == SCE_OK);
AwaitCount(finished, 3);
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == 2);
AwaitCount(finished, 5);
std::this_thread::sleep_for(SETTLE);
Require(otherFinished.load() == 0);
Require(sceKernelSyncOnAddressWake(&word, INT_MAX) == 0);
Require(sceKernelSyncOnAddressWake(&other, 1) == SCE_OK);
Require(sceKernelSyncOnAddressWake(&other, 1) == 1);
for (auto& waiter : waiters) waiter.join();
otherWaiter.join();
for (const int result : results) Require(result == SCE_OK);
@@ -117,38 +123,42 @@ static void WaitsOnTheLowHalfOfAnOnceState() {
auto* word = reinterpret_cast<std::uint32_t*>(&state);
std::thread waiter([&] {
while (state.load() == 1) {
Require(sceKernelSyncOnAddressWait(word, 1, nullptr, "once") == SCE_OK);
const int result = sceKernelSyncOnAddressWait(word, 1, nullptr, "once");
Require(result == SCE_OK || result == SCE_KERNEL_ERROR_EAGAIN);
}
});
std::this_thread::sleep_for(std::chrono::milliseconds(5));
state.store(2);
Require(sceKernelSyncOnAddressWake(word, INT_MAX) == SCE_OK);
Require(sceKernelSyncOnAddressWake(word, INT_MAX) >= 0);
waiter.join();
}
static void SizedWaitsTimeOutWhileTheValueMatches() {
std::uint32_t word = 5;
std::uint64_t value = 0x500000005ULL;
const KernelUseconds none = 0;
Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_OK);
KernelUseconds timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait32(&word, 4, &timeout, "differs") == SCE_KERNEL_ERROR_EAGAIN);
KernelUseconds none = 0;
Require(sceKernelSyncOnAddressWait32(&word, 4, &none, "differs") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWait32(&word, 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
const KernelUseconds timeout = 20000;
timeout = 20000;
auto start = std::chrono::steady_clock::now();
Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWait32(&word, 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT && timeout == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
timeout = 20000;
start = std::chrono::steady_clock::now();
Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWait64(&value, 0x500000005ULL, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT && timeout == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
}
static void Wait64ComparesTheHighHalf() {
std::uint64_t value = 0x100000001ULL;
const KernelUseconds none = 0;
Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &none, "high") == SCE_OK);
Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &none, "high") == SCE_OK);
Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &none, "high") == SCE_KERNEL_ERROR_ETIMEDOUT);
KernelUseconds timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait64(&value, 0x1ULL, &timeout, "high") == SCE_KERNEL_ERROR_EAGAIN);
Require(sceKernelSyncOnAddressWait64(&value, 0x200000001ULL, &timeout, "high") == SCE_KERNEL_ERROR_EAGAIN);
Require(sceKernelSyncOnAddressWait64(&value, 0x100000001ULL, &timeout, "high") == SCE_KERNEL_ERROR_ETIMEDOUT);
}
static void WakeReleasesWaitersOfEverySize() {
@@ -161,23 +171,26 @@ static void WakeReleasesWaitersOfEverySize() {
std::vector<std::thread> waiters;
waiters.emplace_back([&] {
++started;
results[0] = sceKernelSyncOnAddressWait(word, 1, &FAILSAFE_TIMEOUT, "sized");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
results[0] = sceKernelSyncOnAddressWait(word, 1, &timeout, "sized");
++finished;
});
waiters.emplace_back([&] {
++started;
results[1] = sceKernelSyncOnAddressWait32(word, 1, &FAILSAFE_TIMEOUT, "sized");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
results[1] = sceKernelSyncOnAddressWait32(word, 1, &timeout, "sized");
++finished;
});
waiters.emplace_back([&] {
++started;
results[2] = sceKernelSyncOnAddressWait64(&value, 1, &FAILSAFE_TIMEOUT, "sized");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
results[2] = sceKernelSyncOnAddressWait64(&value, 1, &timeout, "sized");
++finished;
});
AwaitCount(started, 3);
std::this_thread::sleep_for(SETTLE);
Require(sceKernelSyncOnAddressWake(&value, 1) == SCE_OK);
Require(sceKernelSyncOnAddressWake(&value, 1) == 1);
AwaitCount(finished, 1);
std::this_thread::sleep_for(SETTLE);
Require(finished.load() == 1);
@@ -185,7 +198,7 @@ static void WakeReleasesWaitersOfEverySize() {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (finished.load() != 3) {
Require(std::chrono::steady_clock::now() < deadline);
Require(sceKernelSyncOnAddressWake(&value, INT_MAX) == SCE_OK);
Require(sceKernelSyncOnAddressWake(&value, INT_MAX) >= 0);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
for (auto& waiter : waiters) waiter.join();
@@ -193,14 +206,15 @@ static void WakeReleasesWaitersOfEverySize() {
}
static void NarrowWaitsCompareOnlyTheirWidth() {
const KernelUseconds none = 0;
KernelUseconds timeout = SHORT_TIMEOUT;
alignas(4) std::uint8_t bytes[4] = {1, 5, 7, 7};
Require(sceKernelSyncOnAddressWait8(&bytes[1], 4, &none, "differs") == SCE_OK);
Require(sceKernelSyncOnAddressWait8(&bytes[1], 5, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWait8(&bytes[1], 4, &timeout, "differs") == SCE_KERNEL_ERROR_EAGAIN);
Require(sceKernelSyncOnAddressWait8(&bytes[1], 5, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
timeout = SHORT_TIMEOUT;
alignas(4) std::uint16_t halves[2] = {1, 0x0705};
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0005, &none, "high") == SCE_OK);
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0705, &none, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0005, &timeout, "high") == SCE_KERNEL_ERROR_EAGAIN);
Require(sceKernelSyncOnAddressWait16(&halves[1], 0x0705, &timeout, "timeout") == SCE_KERNEL_ERROR_ETIMEDOUT);
}
static void WakeReleasesNarrowWaitersAtTheirOwnAddress() {
@@ -216,26 +230,28 @@ static void WakeReleasesNarrowWaitersAtTheirOwnAddress() {
std::thread byteWaiter([&] {
++started;
byteResult = sceKernelSyncOnAddressWait8(&bytes[1], 3, &FAILSAFE_TIMEOUT, "byte");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
byteResult = sceKernelSyncOnAddressWait8(&bytes[1], 3, &timeout, "byte");
++byteFinished;
});
std::thread halfWaiter([&] {
++started;
halfResult = sceKernelSyncOnAddressWait16(half, 9, &FAILSAFE_TIMEOUT, "half");
KernelUseconds timeout = FAILSAFE_TIMEOUT;
halfResult = sceKernelSyncOnAddressWait16(half, 9, &timeout, "half");
++halfFinished;
});
AwaitCount(started, 2);
std::this_thread::sleep_for(SETTLE);
Require(sceKernelSyncOnAddressWake(&bytes[0], INT_MAX) == SCE_OK);
Require(sceKernelSyncOnAddressWake(&bytes[3], INT_MAX) == SCE_OK);
Require(sceKernelSyncOnAddressWake(&bytes[0], INT_MAX) == 0);
Require(sceKernelSyncOnAddressWake(&bytes[3], INT_MAX) == 0);
std::this_thread::sleep_for(SETTLE);
Require(byteFinished.load() == 0 && halfFinished.load() == 0);
Require(sceKernelSyncOnAddressWake(&bytes[1], 1) == SCE_OK);
Require(sceKernelSyncOnAddressWake(&bytes[1], 1) == 1);
AwaitCount(byteFinished, 1);
Require(halfFinished.load() == 0);
Require(sceKernelSyncOnAddressWake(half, 1) == SCE_OK);
Require(sceKernelSyncOnAddressWake(half, 1) == 1);
AwaitCount(halfFinished, 1);
byteWaiter.join();
@@ -243,36 +259,87 @@ static void WakeReleasesNarrowWaitersAtTheirOwnAddress() {
Require(byteResult == SCE_OK && halfResult == SCE_OK);
}
static void RejectsInvalidArguments() {
std::uint32_t words[2] = {1, 1};
auto* misaligned = reinterpret_cast<std::uint32_t*>(reinterpret_cast<unsigned char*>(words) + 1);
Require(Rejects([&] { sceKernelSyncOnAddressWait(nullptr, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWait(misaligned, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWait32(misaligned, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWake(nullptr, 1); }));
Require(sceKernelSyncOnAddressWake(misaligned, 1) == SCE_OK);
Require(Rejects([&] { sceKernelSyncOnAddressWait8(nullptr, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWait16(nullptr, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWait16(reinterpret_cast<std::uint16_t*>(reinterpret_cast<unsigned char*>(words) + 1), 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWake(words, -1); }));
static void MisalignedAddressesWaitAndWake() {
alignas(16) unsigned char storage[16] = {};
auto* word = reinterpret_cast<std::uint32_t*>(storage + 1);
auto* quad = reinterpret_cast<std::uint64_t*>(storage + 4);
const std::uint32_t wordValue = 0x11223344;
std::memcpy(word, &wordValue, sizeof(wordValue));
KernelUseconds timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait32(word, 1, &timeout, "misaligned") == SCE_KERNEL_ERROR_EAGAIN);
Require(sceKernelSyncOnAddressWait32(word, wordValue, &timeout, "misaligned") == SCE_KERNEL_ERROR_ETIMEDOUT && timeout == 0);
timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait64(quad, 1, &timeout, "misaligned") == SCE_KERNEL_ERROR_EAGAIN);
timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait16(reinterpret_cast<std::uint16_t*>(storage + 9), 1, &timeout, "misaligned") == SCE_KERNEL_ERROR_EAGAIN);
std::uint64_t values[2] = {1, 1};
auto* wordAligned = reinterpret_cast<std::uint64_t*>(reinterpret_cast<unsigned char*>(values) + sizeof(std::uint32_t));
Require(Rejects([&] { sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr); }));
Require(Rejects([&] { sceKernelSyncOnAddressWait64(wordAligned, 1, nullptr, nullptr); }));
std::atomic<int> started{0};
int result = -1;
std::thread waiter([&] {
++started;
KernelUseconds waitTimeout = FAILSAFE_TIMEOUT;
result = sceKernelSyncOnAddressWait32(word, wordValue, &waitTimeout, "misaligned");
});
AwaitCount(started, 1);
std::this_thread::sleep_for(SETTLE);
Require(sceKernelSyncOnAddressWake(storage, INT_MAX) == 0);
Require(sceKernelSyncOnAddressWake(word, 1) == 1);
waiter.join();
Require(result == SCE_OK);
auto* shared = reinterpret_cast<std::uint64_t*>(storage + 3);
std::atomic<bool> stop{false};
std::atomic<int> rounds{0};
std::thread changer([&] {
std::uint64_t next = 1;
while (!stop.load()) {
__atomic_store_n(reinterpret_cast<unsigned char*>(shared), static_cast<unsigned char>(next), __ATOMIC_RELAXED);
++next;
sceKernelSyncOnAddressWake(shared, INT_MAX);
}
});
for (int round = 0; round < 2000; ++round) {
unsigned char bytes[sizeof(std::uint64_t)];
bytes[0] = __atomic_load_n(reinterpret_cast<unsigned char*>(shared), __ATOMIC_RELAXED);
std::memcpy(bytes + 1, reinterpret_cast<unsigned char*>(shared) + 1, sizeof(bytes) - 1);
std::uint64_t seen = 0;
std::memcpy(&seen, bytes, sizeof(seen));
KernelUseconds waitTimeout = SHORT_TIMEOUT;
const int waited = sceKernelSyncOnAddressWait64(shared, seen, &waitTimeout, "changing");
Require(waited == SCE_OK || waited == SCE_KERNEL_ERROR_EAGAIN || waited == SCE_KERNEL_ERROR_ETIMEDOUT);
++rounds;
}
stop.store(true);
changer.join();
Require(rounds.load() == 2000);
}
static void NullAddresses() {
KernelUseconds timeout = SHORT_TIMEOUT;
Require(sceKernelSyncOnAddressWait(nullptr, 1, &timeout, nullptr) == SCE_KERNEL_ERROR_EFAULT && timeout == SHORT_TIMEOUT);
Require(sceKernelSyncOnAddressWait8(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
Require(sceKernelSyncOnAddressWait16(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
Require(sceKernelSyncOnAddressWait32(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
Require(sceKernelSyncOnAddressWait64(nullptr, 1, nullptr, nullptr) == SCE_KERNEL_ERROR_EFAULT);
KernelUseconds none = 0;
Require(sceKernelSyncOnAddressWait32(nullptr, 1, &none, nullptr) == SCE_KERNEL_ERROR_ETIMEDOUT);
Require(sceKernelSyncOnAddressWake(nullptr, 1) == 0);
Require(sceKernelSyncOnAddressWake(nullptr, 0) == 0);
std::uint32_t word = 0;
Require(sceKernelSyncOnAddressWake(&word, -1) == 0);
}
int main() {
ReturnsWhenTheValueDiffers();
FailsAgainWhenTheValueDiffers();
TimesOutWhileTheValueMatches();
WakeReleasesAWaiterWithoutAValueChange();
WakeHonoursTheCountAndTheAddress();
WakeReleasesAWaiterAndReportsTheTimeLeft();
WakeCountsOfOneOrLessWakeOneWaiter();
WaitsOnTheLowHalfOfAnOnceState();
SizedWaitsTimeOutWhileTheValueMatches();
Wait64ComparesTheHighHalf();
WakeReleasesWaitersOfEverySize();
NarrowWaitsCompareOnlyTheirWidth();
WakeReleasesNarrowWaitersAtTheirOwnAddress();
RejectsInvalidArguments();
MisalignedAddressesWaitAndWake();
NullAddresses();
}
+1 -2
View File
@@ -70,8 +70,7 @@
- [Foreign guest module image protection on Windows](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMprotect` on Windows allows protection mutations on mapped PE images containing a `.elf0` section (as relinked guest modules produce, such as NVIDIA Real-time Denoisers `libNRD.prx` bundled in Dying Light 2: Stay Human PPSA02262), registering their sections into `GuestAllocations`. Host libraries and system DLLs (such as `libkernel.prx` and `kernel32.dll`) without a `.elf0` section continue to be rejected
- [_umtx_op](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the mutex, condition variable, read-write lock, legacy lock and `SEM2` operations throw, and so does a misaligned object. Implemented as FreeBSD 11's [kern_umtx.c](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_umtx.c): `UMTX_OP_WAIT`, `UMTX_OP_WAKE`, `UMTX_OP_WAIT_UINT`, `UMTX_OP_WAIT_UINT_PRIVATE`, `UMTX_OP_WAKE_PRIVATE`, `UMTX_OP_NWAKE_PRIVATE`, `UMTX_OP_SEM_WAIT` and `UMTX_OP_SEM_WAKE` (the last two built under `COMPAT_FREEBSD9`/`10`). Operations 0 and 1 fail with `EOPNOTSUPP`, as measured on a PS5 (firmware 12.70). The `SEM2` operations, 23 and 24, were measured on the console and returned `0` for a count of 0 where FreeBSD would time out, so they stay unimplemented.
- [sceKernelCreateSema, sceKernelPollSema, sceKernelSignalSema, sceKernelDeleteSema](../../../core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp) (libkernel) - a poll, a signal or a second delete of a deleted semaphore reads freed memory here, where the console returns `0x80020003` (ESRCH) ([measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernel.golden.tsv#L17-L19)). A null output pointer to `sceKernelCreateSema` (a fault on PS5 firmware 13.60) and attributes 0x100 to 0x102 (accepted there, effect unknown) throw. Other attributes above 2 and negative maximum and signal counts return `EINVAL` here, as there. `EINVAL` for an initial count above the maximum or negative, a maximum of 0, a null name, a poll of 0 or above the maximum and a signal of 0 is [measured](https://github.com/mattias800/prosper/blob/3ea5cb9a846b6dcd00f5073360462bbda4f72288/prosper/tests/data/console_oracle/kernel.golden.tsv#L10-L23).
- [sceKernelSyncOnAddressWait](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - the only known caller passes a null timeout and a name string as the fourth argument; the timeout is assumed to point to microseconds like the other kernel waits, and the name is ignored
- [sceKernelSyncOnAddressWait8 / sceKernelSyncOnAddressWait16 / sceKernelSyncOnAddressWait32 / sceKernelSyncOnAddressWait64](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - libc's `__std_atomic_wait_direct_8/16/32/64` pass a microsecond timeout or null and ignore the result; the by-value comparand, return values and the 2- and 8-byte alignment of Wait16 and Wait64 are assumed
- [sceKernelSyncOnAddressWait, sceKernelSyncOnAddressWait8/16/32/64 and sceKernelSyncOnAddressWake](../../../core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp) (libkernel) - follow a payload process on a PS5 (firmware 13.60), not a title. Only a null address is checked for `EFAULT`: any other unreadable address is read and faults the host, where the console returned `EFAULT` for address 0x10. The time left that a woken wait writes back is taken from the host clock, so it can differ from the console's by the cost of the call
- [scePthreadRwlockDestroy, pthread_rwlock_destroy](../../../core/libs/prx/libkernel/Pthread/src/Rwlock.cpp) (libkernel) - measured on a PS5 (firmware 13.60): destroying a rwlock returns 0 whether it is free or held for reading or writing, and leaves 1 in the handle; every call on that handle then returns `EINVAL` until it is initialized again, and a zero handle is initialized on first use by the `scePthreadRwlock` calls as by the POSIX ones. A rwlock destroyed while held is not freed, so its memory leaks. Destroying a rwlock while another thread is acquiring it is not guarded
- [scePthreadCondWait / scePthreadCondTimedwait](../../../core/libs/prx/libkernel/Pthread/src/Cond.cpp) (libkernel) - the mutex's owner is checked before waiting, as in FreeBSD libthr's [`cond_wait_common`](https://github.com/freebsd/freebsd-src/blob/main/lib/libthr/thread/thr_cond.c#L1992-L2029). Measured in a payload process on a PS5 (firmware 13.60): `EPERM` (`0x80020001`) for an unowned mutex of every type, the adaptive initializer (slot 1), an uninitialized slot and a mutex owned by another thread, `EINVAL` for the destroyed mutex (slot 2), and a recursive mutex is released completely and relocked to its depth. `scePthreadCondDestroy` stores 1, which every condition call refuses with `EINVAL`, and leaves an uninitialized slot alone. Cancellation restores the mutex and depth before cleanup and a canceled waiter forwards a wake, per POSIX; its ordering is not measured
- [sceKernelOpenEventFlag](../../../core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp) (libkernel) - opens only flags this process created with attribute 0x100. On the console, other processes' 0x100 flags, such as the shell's `SceShellCoreUtilAppFocus`, can be opened too. They do not exist here, so opening them returns `SCE_KERNEL_ERROR_ESRCH`. A null output with the name of an openable flag returns `SCE_KERNEL_ERROR_EFAULT`, the host's answer to the console's fault there, not a measured code. With a plain flag's name it returns `SCE_KERNEL_ERROR_ESRCH`, as measured; with a missing name, or a null output and a missing name, it returns `SCE_KERNEL_ERROR_ESRCH`, as a payload process on PS5 firmware 13.60 got.