Files
AnyPS5/core/libs/prx/libScePosixForWebKit/Resolver.cpp
T
b42ef24e07 fix(libs): keep thread_local objects with destructors out of module teardown on Windows (#4029)
* fix(agc): keep the driver's per-thread scratch out of thread_local teardown

With MinGW's emulated TLS, a thread_local with a destructor in a DLL is
destroyed after its storage is gone: when a std::thread ends, winpthread
runs libgcc's emutls key destructor, which frees the thread's thread_local
storage, and only then does the loader's DLL_THREAD_DETACH run the DLL's
tls_atexit list, so the destructors run on freed memory. The queue worker
touches four of them in libSceAgcDriver (the dispatch-ahead map, the
pending waits, the draw-snapshot compare vectors and the image decode
memo), and Driver::stop's join of the worker then corrupts the heap.
Under cdb's checking heap agc_driver_draw_cache_reuse stopped at the
worker's exit with heap corruption in all 127 runs, with graphics
pipeline libraries, with APS5_NO_PIPELINE_LTO=1 and with APS5_NO_GPL=1.
Without it the damage shows intermittently, as exit 0xc0000374 (2 of 143
runs with a cold NVIDIA shader cache) or as a fault in whatever allocates
next, such as NVIDIA's compiler on the optimized-link thread (#2980).

These objects now live in HostThreadLocal, fiber-local storage that owns
them, as the recorder's buffers do since e424b6b7b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(libs): keep thread_local objects with destructors out of module teardown

The teardown order behind the driver's heap corruption holds in every
module: with MinGW's emulated TLS a thread's thread_local storage is freed
before the module's DLL_THREAD_DETACH runs its thread_local destructors,
so they run on freed objects. PreciseSleepUs's timer (used, among others,
by the video-out vblank thread) then closes a stale handle; libkernel's
directory path, socket waker and UUID random device, libSceRandom's random
device and libScePosixForWebKit's random device and host lookup free or
close stale objects. They move to HostThreadLocal. The other thread_locals
in the modules are trivially destructible or only built for Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(libs): fail when a module registers a thread_local destructor

prx_thread_local_destructors lists the symbols of every built module and,
where MinGW's thread_local destructor support is linked in, disassembles
the module for calls to __cxa_thread_atexit outside libc's registration of
guest destructors. A module that keeps a thread_local with a destructor
fails it with the function that registers one: with main's libSceRandom
it reports sceRandomGetRandomNumber, and with this branch all 122 modules
pass, libc being the only one that links the support.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(agc): keep the image memo behind its pointer

The image decode memo went into HostThreadLocal as the whole array. On
Linux, where HostThreadLocal is a plain thread_local, that put 1024 memo
entries into every thread's TLS block instead of one pointer. It keeps
its unique_ptr as before, now owned by HostThreadLocal, so Linux builds
the same thread_local as main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dean Galvin <dean.galvin@live.com>
2026-10-12 18:25:29 +13:00

299 lines
13 KiB
C++

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <cstdint>
#include <array>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <new>
#include <string>
#include <vector>
#include "GuestResolver.hpp"
extern "C" int* APS5_VABI sceNetErrnoLoc();
namespace {
struct HostLookup {
GuestResolver::HostEntry entry{};
std::string name;
std::vector<std::string> aliasNames;
std::vector<char*> aliases;
std::vector<std::string> addressBytes;
std::vector<char*> addresses;
};
bool NumericAddress(const char* name, std::array<unsigned char, 4>& address) {
std::array<unsigned char, 3> parts{};
std::size_t count = 0;
std::uint64_t value;
bool digit;
for (;;) {
if (*name < '0' || *name > '9') return false;
value = 0;
int base = 10;
digit = false;
if (*name == '0') {
++name;
if (*name == 'x' || *name == 'X') { base = 16; ++name; }
else { base = 8; digit = true; }
}
for (;;) {
const int next = *name >= '0' && *name <= '9' ? *name - '0' :
*name >= 'a' && *name <= 'f' ? *name - 'a' + 10 :
*name >= 'A' && *name <= 'F' ? *name - 'A' + 10 : -1;
if (next < 0 || next >= base) break;
value = value * base + next;
digit = true;
++name;
}
if (*name != '.') break;
if (count == parts.size() || value > 255) return false;
parts[count++] = static_cast<unsigned char>(value);
++name;
}
if (!digit) return false;
if (*name != '\0' && *name != ' ' && *name != '\t' && *name != '\n' &&
*name != '\r' && *name != '\f' && *name != '\v') return false;
if (count && value >= (std::uint64_t{1} << (8 * (4 - count)))) return false;
auto result = static_cast<std::uint32_t>(value);
for (std::size_t i = 0; i < count; ++i)
result |= static_cast<std::uint32_t>(parts[i]) << (24 - 8 * i);
for (std::size_t i = 0; i < address.size(); ++i)
address[i] = static_cast<unsigned char>(result >> (24 - 8 * i));
return true;
}
bool Ready() {
#ifdef _WIN32
static const int status = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }();
return status == 0;
#else
return true;
#endif
}
int GuestError(int error) {
if (error == 0) return 0;
if (error == EAI_AGAIN) return 2;
if (error == EAI_BADFLAGS) return 3;
if (error == EAI_FAMILY) return 5;
if (error == EAI_MEMORY) return 6;
if (error == EAI_NONAME) return 8;
if (error == EAI_SERVICE) return 9;
if (error == EAI_SOCKTYPE) return 10;
#ifdef _WIN32
if (error == WSAEFAULT) return 14;
#else
if (error == EAI_OVERFLOW) return 14;
#endif
return 4;
}
}
extern "C" {
GuestResolver::HostEntry* APS5_VABI gethostbyname_nid_postfix(const char* name) {
if (!name || !Ready()) { *sceNetErrnoLoc() = 3; return nullptr; }
try {
std::array<unsigned char, 4> numericAddress{};
char* numericAliases[]{nullptr};
char* numericAddresses[]{reinterpret_cast<char*>(numericAddress.data()), nullptr};
hostent nativeEntry{const_cast<char*>(name), numericAliases, AF_INET, 4, numericAddresses};
hostent* native = &nativeEntry;
const bool numeric = NumericAddress(name, numericAddress);
#ifdef _WIN32
if (!numeric) native = ::gethostbyname(name);
if (!native) {
const auto error = WSAGetLastError();
*sceNetErrnoLoc() = error == WSAHOST_NOT_FOUND ? 1 : error == WSATRY_AGAIN ? 2 : error == WSANO_DATA ? 4 : 3;
return nullptr;
}
#else
std::vector<char> buffer;
int hostError = 0;
int error = 0;
if (!numeric) {
buffer.resize(1024);
while ((error = ::gethostbyname_r(name, &nativeEntry, buffer.data(), buffer.size(), &native, &hostError)) == ERANGE)
buffer.resize(buffer.size() * 2);
}
if (error != 0 || !native) {
*sceNetErrnoLoc() = hostError != 0 ? hostError : 3;
if (hostError == -1) errno = error == EAGAIN ? 35 : error;
return nullptr;
}
#endif
struct LookupTag {};
auto& lookup = HostThreadLocal<HostLookup, LookupTag>();
lookup.name = native->h_name;
lookup.aliasNames.clear();
lookup.aliases.clear();
lookup.addressBytes.clear();
lookup.addresses.clear();
for (auto** alias = native->h_aliases; *alias; ++alias) lookup.aliasNames.emplace_back(*alias);
for (auto& alias : lookup.aliasNames) lookup.aliases.push_back(alias.data());
lookup.aliases.push_back(nullptr);
for (auto** address = native->h_addr_list; *address; ++address)
lookup.addressBytes.emplace_back(*address, native->h_length);
for (auto& address : lookup.addressBytes) lookup.addresses.push_back(address.data());
lookup.addresses.push_back(nullptr);
lookup.entry = {lookup.name.data(), lookup.aliases.data(), native->h_addrtype == AF_INET6 ? 28 : 2,
native->h_length, lookup.addresses.data()};
if (numeric) *sceNetErrnoLoc() = 0;
return &lookup.entry;
} catch (const std::bad_alloc&) {
*sceNetErrnoLoc() = -1;
errno = ENOMEM;
return nullptr;
}
}
void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo* first) {
while (first) {
auto* next = first->next;
std::free(first->canonicalName);
std::free(first->address);
std::free(first);
first = next;
}
}
int APS5_VABI getaddrinfo_nid_postfix(const char* node, const char* service,
const GuestResolver::AddressInfo* hints, GuestResolver::AddressInfo** output) {
if (!output) return 4;
*output = nullptr;
addrinfo nativeHints{};
if (hints) {
if (hints->addressLength || hints->address || hints->canonicalName || hints->next) return 12;
if ((hints->flags & ~0xd0f) != 0) return 3;
if (hints->family != 0 && hints->family != 2 && hints->family != 28) return 5;
if (hints->socketType != 0 && hints->socketType != 1 && hints->socketType != 2) return 10;
nativeHints.ai_family = hints->family == 28 ? AF_INET6 : hints->family == 2 ? AF_INET : AF_UNSPEC;
nativeHints.ai_socktype = hints->socketType == 1 ? SOCK_STREAM : hints->socketType == 2 ? SOCK_DGRAM : 0;
nativeHints.ai_protocol = hints->protocol;
if (hints->flags & 1) nativeHints.ai_flags |= AI_PASSIVE;
if (hints->flags & 2) nativeHints.ai_flags |= AI_CANONNAME;
if (hints->flags & 4) nativeHints.ai_flags |= AI_NUMERICHOST;
if (hints->flags & 8) nativeHints.ai_flags |= AI_NUMERICSERV;
if (hints->flags & 0x100) nativeHints.ai_flags |= AI_ALL;
if (hints->flags & 0x400) nativeHints.ai_flags |= AI_ADDRCONFIG;
if (hints->flags & 0x800) nativeHints.ai_flags |= AI_V4MAPPED;
}
if (!Ready()) return 4;
addrinfo* native = nullptr;
const auto error = ::getaddrinfo(node, service, &nativeHints, &native);
if (error) return GuestError(error);
GuestResolver::AddressInfo* first = nullptr;
auto** tail = &first;
int failure = 0;
for (auto* item = native; item; item = item->ai_next) {
if (item->ai_family != AF_INET && item->ai_family != AF_INET6) { failure = 5; break; }
auto* converted = static_cast<GuestResolver::AddressInfo*>(std::calloc(1, sizeof(GuestResolver::AddressInfo)));
if (!converted) { failure = 6; break; }
*tail = converted;
tail = &converted->next;
converted->flags = hints ? hints->flags : 0;
converted->family = item->ai_family == AF_INET ? 2 : 28;
converted->socketType = item->ai_socktype == SOCK_STREAM ? 1 : item->ai_socktype == SOCK_DGRAM ? 2 : 0;
converted->protocol = item->ai_protocol;
converted->addressLength = item->ai_family == AF_INET ? 16 : 28;
auto* bytes = static_cast<unsigned char*>(std::calloc(1, converted->addressLength));
converted->address = bytes;
if (!bytes) { failure = 6; break; }
bytes[0] = static_cast<unsigned char>(converted->addressLength);
bytes[1] = static_cast<unsigned char>(converted->family);
if (item->ai_family == AF_INET) {
const auto* v4 = reinterpret_cast<const sockaddr_in*>(item->ai_addr);
std::memcpy(bytes + 2, &v4->sin_port, 2);
std::memcpy(bytes + 4, &v4->sin_addr, 4);
} else {
const auto* v6 = reinterpret_cast<const sockaddr_in6*>(item->ai_addr);
std::memcpy(bytes + 2, &v6->sin6_port, 2);
std::memcpy(bytes + 4, &v6->sin6_flowinfo, 4);
std::memcpy(bytes + 8, &v6->sin6_addr, 16);
std::memcpy(bytes + 24, &v6->sin6_scope_id, 4);
}
if (item->ai_canonname) {
const auto size = std::strlen(item->ai_canonname) + 1;
converted->canonicalName = static_cast<char*>(std::malloc(size));
if (!converted->canonicalName) { failure = 6; break; }
std::memcpy(converted->canonicalName, item->ai_canonname, size);
}
}
::freeaddrinfo(native);
if (failure) { freeaddrinfo_nid_postfix(first); return failure; }
*output = first;
return 0;
}
int APS5_VABI getnameinfo_nid_postfix(const void* address, std::uint32_t length,
char* host, std::uint32_t hostLength, char* service, std::uint32_t serviceLength, int flags) {
// PS5 sockaddr begins with byte-sized length and family. The host's does not.
if (!address || length < 2) return 5;
if ((!host && hostLength) || (!service && serviceLength)) return 4;
if ((flags & ~0x1f) != 0) return 3;
const auto* bytes = static_cast<const unsigned char*>(address);
sockaddr_storage native{};
socklen_t nativeLength;
if (bytes[1] == 2) {
if (length < 16 || bytes[0] != 16) return 5;
auto& v4 = reinterpret_cast<sockaddr_in&>(native);
v4.sin_family = AF_INET;
std::memcpy(&v4.sin_port, bytes + 2, 2);
std::memcpy(&v4.sin_addr, bytes + 4, 4);
nativeLength = sizeof(v4);
} else if (bytes[1] == 28) {
if (length < 28 || bytes[0] != 28) return 5;
auto& v6 = reinterpret_cast<sockaddr_in6&>(native);
v6.sin6_family = AF_INET6;
std::memcpy(&v6.sin6_port, bytes + 2, 2);
std::memcpy(&v6.sin6_flowinfo, bytes + 4, 4);
std::memcpy(&v6.sin6_addr, bytes + 8, 16);
std::memcpy(&v6.sin6_scope_id, bytes + 24, 4);
nativeLength = sizeof(v6);
} else return 5;
int nativeFlags = 0;
if (flags & 1) nativeFlags |= NI_NOFQDN;
if (flags & 2) nativeFlags |= NI_NUMERICHOST;
if (flags & 4) nativeFlags |= NI_NAMEREQD;
if (flags & 8) nativeFlags |= NI_NUMERICSERV;
if (flags & 16) nativeFlags |= NI_DGRAM;
if (!Ready()) return 4;
// Stage results so host-specific short-buffer errors do not leak into the
// guest ABI, and neither guest output is partially modified on overflow.
char resolvedHost[NI_MAXHOST]{};
char resolvedService[NI_MAXSERV]{};
const bool wantHost = host && hostLength;
const bool wantService = service && serviceLength;
const int error = ::getnameinfo(reinterpret_cast<const sockaddr*>(&native), nativeLength,
wantHost ? resolvedHost : nullptr, wantHost ? sizeof(resolvedHost) : 0,
wantService ? resolvedService : nullptr, wantService ? sizeof(resolvedService) : 0, nativeFlags);
if (error) return GuestError(error);
if ((wantHost && std::strlen(resolvedHost) >= hostLength) ||
(wantService && std::strlen(resolvedService) >= serviceLength)) return 14;
if (wantHost) std::memcpy(host, resolvedHost, std::strlen(resolvedHost) + 1);
if (wantService) std::memcpy(service, resolvedService, std::strlen(resolvedService) + 1);
return 0;
}
const char* APS5_VABI gai_strerror_nid_postfix(int error) {
static const char* messages[] = {
"Success", "Address family for hostname not supported", "Temporary failure in name resolution",
"Invalid flags", "Name resolution failed", "Address family not supported",
"Memory allocation failed", "No address for hostname", "Name or service not known",
"Service not supported for socket type", "Socket type not supported", "System error",
"Invalid hints", "Unknown protocol", "Buffer too small"
};
if (error < 0 || error >= static_cast<int>(sizeof(messages) / sizeof(messages[0])))
return "Unknown address resolution error";
return messages[error];
}
}