Files
AnyPS5/core/libs/tests/GuestUnderscoreEntries.cpp
T
NikoandClaude Opus 5.5 0ac2b0bc6f feat(libkernel): export the underscore entry points of the socket, directory and signal calls
The PS5 libkernel exports _accept, _bind, _connect, _getdirentries,
_getpeername, _getsockname, _getsockopt, _listen, _recvfrom, _recvmsg,
_sendmsg, _sendto, _setsockopt, _sigaction and _sigsuspend (PS5 3.20
export list, DNNDHH/PS5-3.20_Libs b2a77c0 libkernel.c), and main
exports none of them, so a title importing one fails at startup. Each
now forwards to the export without the underscore, as _close, _ioctl
and _nanosleep already do.

In FreeBSD 11.4 (8c639945a4) these are libc's plain system call
entries: lib/libc/amd64/SYS.h makes bind, getdirentries, getpeername,
getsockname, getsockopt, listen and setsockopt one stub under both
names, and lib/libc/sys/Makefile.inc gives accept, connect, recvfrom,
recvmsg, sendmsg, sendto, sigaction and sigsuspend a separate public
name that goes through libthr's interposing table, which adds the
cancellation points and the sigaction wrapper. The exports without the
underscore have no libthr layer here, so forwarding gives the system
call. docs/dev/debt/libkernel.md records what stays unverified: the
console is not measured, _sigaction shares the handler table of
sigaction, and a null set throws in _sigsuspend as in sigsuspend,
where FreeBSD's system call returns EFAULT.

guest_underscore_entries calls every new export: a loopback TCP
connection, UDP datagrams with scatter, truncation and sender
addresses, a directory listing compared with getdirentries, a SIGTERM
handler set by _sigaction, and a pending SIGUSR1 that _sigsuspend
delivers before returning EINTR with the mask restored. It checks the
errors they pass on (EBADF, EINVAL, EFAULT, ENOPROTOOPT, EOPNOTSUPP)
and that outputs stay untouched on failure.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 17:02:54 +02:00

331 lines
16 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <set>
#include <string>
struct GuestSignalSet {
std::uint32_t bits[4];
};
struct GuestSigaction {
std::uintptr_t handler;
int flags;
GuestSignalSet mask;
};
static_assert(sizeof(GuestSigaction) == 32);
struct Iovec {
void* base;
std::uint64_t length;
};
struct Msghdr {
void* name;
std::uint32_t nameLength;
Iovec* iov;
int iovLength;
void* control;
std::uint32_t controlLength;
int flags;
};
static_assert(sizeof(Msghdr) == 48);
extern "C" {
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI close_nid_postfix(int);
int* APS5_VABI __error_nid_postfix();
int APS5_VABI _bind_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI _listen_nid_postfix(int, int);
int APS5_VABI _accept_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI _connect_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI _getsockname_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI _getpeername_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI _setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t);
int APS5_VABI _getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*);
std::int64_t APS5_VABI _sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t);
std::int64_t APS5_VABI _recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*);
std::int64_t APS5_VABI _sendmsg_nid_postfix(int, const Msghdr*, int);
std::int64_t APS5_VABI _recvmsg_nid_postfix(int, Msghdr*, int);
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
int APS5_VABI sceKernelClose(int);
std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int);
int APS5_VABI getdirentries_nid_postfix(int, char*, int, std::int64_t*);
int APS5_VABI _getdirentries_nid_postfix(int, char*, int, std::int64_t*);
int APS5_VABI raise_nid_postfix(int);
int APS5_VABI sigprocmask_nid_postfix(int, const void*, void*);
int APS5_VABI _sigaction_nid_postfix(int, const GuestSigaction*, GuestSigaction*);
int APS5_VABI _sigsuspend_nid_postfix(const GuestSignalSet*);
}
static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "underscore entry check failed at line %d, errno %d\n", line, *__error_nid_postfix());
std::abort();
}
}
#define Require(value) Check((value), __LINE__)
constexpr int Inet = 2;
constexpr int Stream = 1;
constexpr int Datagram = 2;
constexpr int TcpLevel = 6;
constexpr int TcpNoDelay = 1;
constexpr int TcpMaximumSegment = 2;
constexpr int OutOfBand = 0x1;
constexpr int Truncated = 0x10;
constexpr int Interrupted = 4;
constexpr int BadDescriptor = 9;
constexpr int BadAddress = 14;
constexpr int Invalid = 22;
constexpr int NoProtocolOption = 42;
constexpr int NotSupported = 45;
using Address = std::array<std::uint8_t, 16>;
static bool Fails(std::int64_t result, int error) {
return result == -1 && *__error_nid_postfix() == error;
}
static bool Filled(const Address& address, std::uint8_t value) {
return std::all_of(address.begin(), address.end(), [value](std::uint8_t byte) { return byte == value; });
}
static void StreamSockets() {
const int listener = socket_nid_postfix(Inet, Stream, 0);
Require(listener >= 0);
Address address{16, Inet, 0, 0, 127, 0, 0, 1};
Require(_bind_nid_postfix(listener, address.data(), address.size()) == 0);
Require(Fails(_bind_nid_postfix(listener, address.data(), address.size()), Invalid));
Require(_listen_nid_postfix(listener, 4) == 0);
std::uint32_t size = address.size();
Require(_getsockname_nid_postfix(listener, address.data(), &size) == 0 && size == 16);
Require(address[0] == 16 && address[1] == Inet && (address[2] != 0 || address[3] != 0) && address[4] == 127 && address[7] == 1);
const int client = socket_nid_postfix(Inet, Stream, 0);
Require(client >= 0);
Require(Fails(_accept_nid_postfix(client, nullptr, nullptr), Invalid));
Require(_connect_nid_postfix(client, address.data(), address.size()) == 0);
Address peer;
peer.fill(0xa5);
size = peer.size();
const int accepted = _accept_nid_postfix(listener, peer.data(), &size);
Require(accepted >= 0 && size == 16);
Address local{};
size = local.size();
Require(_getsockname_nid_postfix(client, local.data(), &size) == 0 && size == 16 && local == peer);
Address remote{};
size = remote.size();
Require(_getpeername_nid_postfix(client, remote.data(), &size) == 0 && size == 16 && remote == address);
remote.fill(0);
size = remote.size();
Require(_getpeername_nid_postfix(accepted, remote.data(), &size) == 0 && size == 16 && remote == local);
int value = -1;
std::uint32_t valueSize = sizeof(value);
Require(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &valueSize) == 0 && value == 0 && valueSize == sizeof(value));
const int enabled = 1;
Require(_setsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &enabled, sizeof(enabled)) == 0);
valueSize = sizeof(value);
Require(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &valueSize) == 0 && value == 4 && valueSize == sizeof(value));
Require(Fails(_setsockopt_nid_postfix(client, TcpLevel, TcpMaximumSegment, &enabled, sizeof(enabled)), NoProtocolOption));
const char text[] = "underscore stream";
Require(_sendto_nid_postfix(client, text, sizeof(text), 0, nullptr, 0) == sizeof(text));
char received[sizeof(text)]{};
Address sender;
sender.fill(0xa5);
std::uint32_t senderSize = sender.size();
Require(_recvfrom_nid_postfix(accepted, received, sizeof(received), 0, sender.data(), &senderSize) == sizeof(text));
Require(std::memcmp(received, text, sizeof(text)) == 0 && senderSize == 0 && Filled(sender, 0xa5));
for (const int descriptor : {accepted, client, listener}) Require(close_nid_postfix(descriptor) == 0);
Address untouched;
untouched.fill(0xa5);
std::uint32_t untouchedSize = 7;
value = -1;
Require(Fails(_getsockname_nid_postfix(listener, untouched.data(), &untouchedSize), BadDescriptor));
Require(Fails(_getpeername_nid_postfix(client, untouched.data(), &untouchedSize), BadDescriptor));
Require(Fails(_accept_nid_postfix(listener, untouched.data(), &untouchedSize), BadDescriptor));
Require(Fails(_getsockopt_nid_postfix(client, TcpLevel, TcpNoDelay, &value, &untouchedSize), BadDescriptor));
Require(Fails(_listen_nid_postfix(listener, 4), BadDescriptor));
Require(Filled(untouched, 0xa5) && untouchedSize == 7 && value == -1);
}
static void DatagramSockets() {
const int receiver = socket_nid_postfix(Inet, Datagram, 0);
const int sender = socket_nid_postfix(Inet, Datagram, 0);
Require(receiver >= 0 && sender >= 0);
Address destination{16, Inet, 0, 0, 127, 0, 0, 1};
Require(_bind_nid_postfix(receiver, destination.data(), destination.size()) == 0);
std::uint32_t size = destination.size();
Require(_getsockname_nid_postfix(receiver, destination.data(), &size) == 0 && size == 16 && (destination[2] != 0 || destination[3] != 0));
Require(Fails(_listen_nid_postfix(receiver, 1), NotSupported));
const char datagram[] = "underscore datagram";
Require(Fails(_sendto_nid_postfix(sender, datagram, sizeof(datagram), OutOfBand, destination.data(), destination.size()), NotSupported));
Require(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, destination.data(), destination.size()) == sizeof(datagram));
Address senderName{};
size = senderName.size();
Require(_getsockname_nid_postfix(sender, senderName.data(), &size) == 0 && size == 16);
char buffer[32]{};
Address source{};
size = source.size();
Require(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size) == sizeof(datagram));
Require(std::strcmp(buffer, datagram) == 0 && size == 16 && source[0] == 16 && source[1] == Inet);
Require(source[2] == senderName[2] && source[3] == senderName[3] && source[4] == 127 && source[7] == 1);
char head[] = "scatter ";
char tail[] = "gather";
Iovec parts[2]{{head, 8}, {tail, 6}};
Msghdr outgoing{destination.data(), static_cast<std::uint32_t>(destination.size()), parts, 2, nullptr, 0, 0};
Require(_sendmsg_nid_postfix(sender, &outgoing, 0) == 14);
char first[4]{};
char second[16]{};
Iovec pieces[2]{{first, sizeof(first)}, {second, sizeof(second)}};
source.fill(0);
Msghdr incoming{source.data(), static_cast<std::uint32_t>(source.size()), pieces, 2, nullptr, 0, -1};
Require(_recvmsg_nid_postfix(receiver, &incoming, 0) == 14);
Require(std::memcmp(first, "scat", 4) == 0 && std::memcmp(second, "ter gather", 10) == 0);
Require(incoming.flags == 0 && incoming.nameLength == 16 && source[2] == senderName[2] && source[3] == senderName[3] && source[4] == 127);
Require(_sendmsg_nid_postfix(sender, &outgoing, 0) == 14);
char shortBuffer[5]{};
Iovec single{shortBuffer, sizeof(shortBuffer)};
Msghdr truncated{nullptr, 0, &single, 1, nullptr, 0, 0};
Require(_recvmsg_nid_postfix(receiver, &truncated, 0) == 5 && std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == Truncated);
Require(Fails(_sendmsg_nid_postfix(sender, nullptr, 0), BadAddress));
Require(Fails(_recvmsg_nid_postfix(receiver, nullptr, 0), BadAddress));
Require(Fails(_connect_nid_postfix(sender, destination.data(), 15), Invalid));
Require(_connect_nid_postfix(sender, destination.data(), destination.size()) == 0);
Address peer{};
size = peer.size();
Require(_getpeername_nid_postfix(sender, peer.data(), &size) == 0 && size == 16 && peer == destination);
Require(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, nullptr, 0) == sizeof(datagram));
std::memset(buffer, 0, sizeof(buffer));
Require(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, nullptr, nullptr) == sizeof(datagram) && std::strcmp(buffer, datagram) == 0);
Require(close_nid_postfix(receiver) == 0 && close_nid_postfix(sender) == 0);
std::memset(buffer, 0x5a, sizeof(buffer));
source.fill(0xa5);
size = 9;
const int enabled = 1;
Require(Fails(_recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size), BadDescriptor));
Require(Fails(_recvmsg_nid_postfix(receiver, &incoming, 0), BadDescriptor));
Require(Fails(_sendto_nid_postfix(sender, datagram, sizeof(datagram), 0, nullptr, 0), BadDescriptor));
Require(Fails(_sendmsg_nid_postfix(sender, &outgoing, 0), BadDescriptor));
Require(Fails(_bind_nid_postfix(receiver, destination.data(), destination.size()), BadDescriptor));
Require(Fails(_connect_nid_postfix(sender, destination.data(), destination.size()), BadDescriptor));
Require(Fails(_setsockopt_nid_postfix(sender, TcpLevel, TcpNoDelay, &enabled, sizeof(enabled)), BadDescriptor));
Require(std::all_of(std::begin(buffer), std::end(buffer), [](char byte) { return byte == 0x5a; }));
Require(Filled(source, 0xa5) && size == 9);
}
static std::set<std::string> Names(const char* records, int bytes) {
std::set<std::string> names;
for (int offset = 0; offset < bytes;) {
std::uint16_t recordLength = 0;
std::memcpy(&recordLength, records + offset + 4, sizeof(recordLength));
Require(recordLength >= 12 && offset + recordLength <= bytes);
names.emplace(records + offset + 8, static_cast<std::uint8_t>(records[offset + 7]));
offset += recordLength;
}
return names;
}
static void DirectoryEntries() {
const auto root = std::filesystem::path("anyps5-underscore-entries-test-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
Require(std::filesystem::create_directory(root));
{ std::ofstream stream(root / "entry"); }
const int directory = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
Require(directory >= 0);
std::array<char, 4096> records{};
std::int64_t base = -1;
const int read = _getdirentries_nid_postfix(directory, records.data(), static_cast<int>(records.size()), &base);
Require(read > 0 && base == 0);
const std::set<std::string> listed{".", "..", "entry"};
Require(Names(records.data(), read) == listed);
Require(sceKernelLseek(directory, 0, 0) == 0);
std::array<char, 4096> expected{};
std::int64_t expectedBase = -1;
Require(getdirentries_nid_postfix(directory, expected.data(), static_cast<int>(expected.size()), &expectedBase) == read);
Require(expectedBase == base && std::memcmp(expected.data(), records.data(), static_cast<std::size_t>(read)) == 0);
std::int64_t end = -1;
Require(_getdirentries_nid_postfix(directory, records.data(), static_cast<int>(records.size()), &end) == 0 && end > 0);
records.fill(0x5a);
std::int64_t untouched = 1234;
Require(Fails(_getdirentries_nid_postfix(directory, records.data(), 0, &untouched), Invalid));
Require(Fails(_getdirentries_nid_postfix(directory, nullptr, 64, &untouched), BadAddress));
Require(sceKernelClose(directory) == 0);
Require(Fails(_getdirentries_nid_postfix(directory, records.data(), static_cast<int>(records.size()), &untouched), BadDescriptor));
Require(untouched == 1234 && std::all_of(records.begin(), records.end(), [](char byte) { return byte == 0x5a; }));
std::filesystem::remove_all(root);
}
volatile std::sig_atomic_t received = 0;
void APS5_VABI Handler(int value) {
received = value;
}
static bool Same(const GuestSigaction& left, const GuestSigaction& right) {
return left.handler == right.handler && left.flags == right.flags && std::memcmp(&left.mask, &right.mask, sizeof(left.mask)) == 0;
}
static void Signals() {
const auto handler = reinterpret_cast<std::uintptr_t>(Handler);
const GuestSigaction term{handler, 0, {{0x4000, 0, 0, 0x80000000u}}};
GuestSigaction initial{};
Require(_sigaction_nid_postfix(15, nullptr, &initial) == 0);
GuestSigaction previous;
std::memset(&previous, 0xa5, sizeof(previous));
Require(_sigaction_nid_postfix(15, &term, &previous) == 0 && Same(previous, initial));
received = 0;
Require(raise_nid_postfix(15) == 0 && received == 15);
GuestSigaction current;
std::memset(&current, 0xa5, sizeof(current));
Require(_sigaction_nid_postfix(15, nullptr, &current) == 0 && Same(current, term));
GuestSigaction untouched;
std::memset(&untouched, 0xa5, sizeof(untouched));
for (const int invalid : {0, 129}) {
std::memset(&current, 0xa5, sizeof(current));
Require(Fails(_sigaction_nid_postfix(invalid, &term, &current), Invalid) && std::memcmp(&current, &untouched, sizeof(current)) == 0);
}
for (const int fixed : {9, 17}) {
std::memset(&current, 0xa5, sizeof(current));
Require(Fails(_sigaction_nid_postfix(fixed, &term, &current), Invalid) && std::memcmp(&current, &untouched, sizeof(current)) == 0);
}
Require(_sigaction_nid_postfix(15, &initial, &current) == 0 && Same(current, term));
const GuestSigaction user{handler, 0, {}};
GuestSigaction initialUser{};
Require(_sigaction_nid_postfix(30, &user, &initialUser) == 0);
const GuestSignalSet blocked{{1u << 29, 0, 0, 0}};
const GuestSignalSet empty{{0, 0, 0, 0}};
GuestSignalSet saved{};
Require(sigprocmask_nid_postfix(3, &blocked, &saved) == 0);
received = 0;
Require(raise_nid_postfix(30) == 0 && received == 0);
Require(Fails(_sigsuspend_nid_postfix(&empty), Interrupted) && received == 30);
GuestSignalSet restored{};
Require(sigprocmask_nid_postfix(1, nullptr, &restored) == 0 && std::memcmp(&restored, &blocked, sizeof(restored)) == 0);
Require(sigprocmask_nid_postfix(3, &saved, nullptr) == 0);
Require(_sigaction_nid_postfix(30, &initialUser, nullptr) == 0);
}
int main() {
StreamSockets();
DatagramSockets();
DirectoryEntries();
Signals();
}