Merge pull request #4283 from MarcZX100/codex/carry-guest-thread-stacks-3283
fix(libkernel): place Linux guest thread stacks inside application area
This commit is contained in:
commit
b0d41be1ff
|
|
@ -984,6 +984,8 @@ int DoMprotect(const void* addr, size_t len, int prot) {
|
|||
const auto bytes = static_cast<std::size_t>(end - first);
|
||||
const auto* pointer = reinterpret_cast<const void*>(first);
|
||||
const auto nativeProtection = LinuxProtFromSce(prot);
|
||||
if (GuestThreadStackHostRangeOverlaps(address, address + len))
|
||||
throw std::system_error(EACCES, std::generic_category(), "mprotect overlaps host-only guest thread stack pages");
|
||||
std::uintptr_t stackStart = 0;
|
||||
std::uintptr_t stackEnd = 0;
|
||||
if ((prot & GuestProtGpuReadWrite) == 0 && GuestThreadStack(address, &stackStart, &stackEnd) && address >= stackStart && address + len <= stackEnd) {
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@ struct PthreadPrivate {
|
|||
#ifndef _WIN32
|
||||
clockid_t hostCpuClock{};
|
||||
bool hostCpuClockBound = false;
|
||||
void* guestStack = nullptr;
|
||||
std::size_t guestStackBytes = 0;
|
||||
int hostTid = 0;
|
||||
#endif
|
||||
std::mutex nameLock;
|
||||
std::string name;
|
||||
|
|
@ -149,6 +152,7 @@ struct PthreadPrivate {
|
|||
};
|
||||
|
||||
bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end);
|
||||
bool GuestThreadStackHostRangeOverlaps(std::uintptr_t start, std::uintptr_t end);
|
||||
void NoteGuestStackProtection(std::uintptr_t start, std::size_t bytes, bool readWrite);
|
||||
int GuestThreadCpuClockId(const PthreadPrivate* thread);
|
||||
bool GuestThreadCpuNanos(int cpuClockThread, std::uint64_t* nanos);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/KernelErrors.hpp"
|
||||
#include "prx/libc/include/CpuTopology.hpp"
|
||||
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
#include <limits>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
|
@ -73,6 +75,101 @@ static GuestThreadState& CurrentGuestState() {
|
|||
static std::mutex stackLock;
|
||||
static std::map<PthreadPrivate*, std::pair<std::uintptr_t, std::uintptr_t>> liveStacks;
|
||||
static std::map<std::uintptr_t, std::size_t> protectedStackPages;
|
||||
#if defined(__linux__)
|
||||
struct GuestStackMapping {
|
||||
std::uintptr_t mappingEnd;
|
||||
std::uintptr_t stackStart;
|
||||
std::uintptr_t stackEnd;
|
||||
};
|
||||
static std::mutex guestStackMappingsLock;
|
||||
static std::map<std::uintptr_t, GuestStackMapping> guestStackMappings;
|
||||
|
||||
static void RegisterGuestStackMapping(PthreadPrivate* thread) {
|
||||
const auto mappingStart = reinterpret_cast<std::uintptr_t>(thread->guestStack);
|
||||
const auto stackStart = reinterpret_cast<std::uintptr_t>(thread->stackAddress);
|
||||
std::lock_guard lock(guestStackMappingsLock);
|
||||
guestStackMappings[mappingStart] = {mappingStart + thread->guestStackBytes, stackStart, stackStart + thread->stackSize};
|
||||
}
|
||||
|
||||
static void UnregisterGuestStackMapping(void* mapping) {
|
||||
std::lock_guard lock(guestStackMappingsLock);
|
||||
guestStackMappings.erase(reinterpret_cast<std::uintptr_t>(mapping));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
static constexpr std::size_t GUEST_STACK_GUARD = PS5_PAGE_SIZE;
|
||||
|
||||
struct RetiredStack {
|
||||
void* address;
|
||||
std::size_t bytes;
|
||||
int hostTid;
|
||||
};
|
||||
|
||||
static std::mutex retiredStacksLock;
|
||||
static std::vector<RetiredStack> retiredStacks;
|
||||
|
||||
static void AllocateGuestStack(PthreadPrivate* thread, std::size_t usableBytes) {
|
||||
const std::size_t bytes = (usableBytes + PS5_PAGE_SIZE - 1) / PS5_PAGE_SIZE * PS5_PAGE_SIZE + GUEST_STACK_GUARD;
|
||||
void* stack = nullptr;
|
||||
if (DoMapAnon(&stack, bytes, 3, 0) != 0)
|
||||
throw std::runtime_error("scePthreadCreate: cannot map the guest thread stack");
|
||||
if (DoMprotect(stack, GUEST_STACK_GUARD, 0) != 0) {
|
||||
DoMunmap(stack, bytes);
|
||||
throw std::runtime_error("scePthreadCreate: cannot protect the guest thread stack guard");
|
||||
}
|
||||
thread->guestStack = stack;
|
||||
thread->guestStackBytes = bytes;
|
||||
}
|
||||
|
||||
static void FreeGuestStack(PthreadPrivate* thread) {
|
||||
if (thread->guestStack == nullptr) return;
|
||||
void* const mapping = thread->guestStack;
|
||||
DoMunmap(thread->guestStack, thread->guestStackBytes);
|
||||
UnregisterGuestStackMapping(mapping);
|
||||
thread->guestStack = nullptr;
|
||||
thread->guestStackBytes = 0;
|
||||
}
|
||||
|
||||
static bool HostThreadGone(int hostTid) {
|
||||
if (hostTid == 0) return false;
|
||||
char path[48];
|
||||
std::snprintf(path, sizeof(path), "/proc/self/task/%d", hostTid);
|
||||
return access(path, F_OK) != 0;
|
||||
}
|
||||
|
||||
static void ReapRetiredStacks() {
|
||||
std::vector<RetiredStack> ready;
|
||||
{
|
||||
std::lock_guard lock(retiredStacksLock);
|
||||
for (auto it = retiredStacks.begin(); it != retiredStacks.end();) {
|
||||
if (HostThreadGone(it->hostTid)) {
|
||||
ready.push_back(*it);
|
||||
it = retiredStacks.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& stack : ready) {
|
||||
DoMunmap(stack.address, stack.bytes);
|
||||
UnregisterGuestStackMapping(stack.address);
|
||||
}
|
||||
}
|
||||
|
||||
static void RetireGuestStack(PthreadPrivate* thread) {
|
||||
if (thread->guestStack == nullptr) return;
|
||||
{
|
||||
std::lock_guard lock(retiredStacksLock);
|
||||
retiredStacks.push_back({thread->guestStack, thread->guestStackBytes, thread->hostTid});
|
||||
}
|
||||
thread->guestStack = nullptr;
|
||||
thread->guestStackBytes = 0;
|
||||
}
|
||||
#else
|
||||
static void FreeGuestStack(PthreadPrivate*) {}
|
||||
static void RetireGuestStack(PthreadPrivate*) {}
|
||||
#endif
|
||||
|
||||
static bool HostStackLimits(std::uintptr_t* low, std::uintptr_t* high) {
|
||||
#ifdef _WIN32
|
||||
|
|
@ -176,6 +273,24 @@ bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintpt
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GuestThreadStackHostRangeOverlaps(std::uintptr_t start, std::uintptr_t end) {
|
||||
#if defined(__linux__)
|
||||
std::lock_guard lock(guestStackMappingsLock);
|
||||
auto it = guestStackMappings.upper_bound(start);
|
||||
if (it != guestStackMappings.begin()) --it;
|
||||
for (; it != guestStackMappings.end() && it->first < end; ++it) {
|
||||
const auto mappingStart = it->first;
|
||||
const auto& stack = it->second;
|
||||
if (start < stack.stackStart && end > mappingStart) return true;
|
||||
if (start < stack.mappingEnd && end > stack.stackEnd) return true;
|
||||
}
|
||||
#else
|
||||
(void)start;
|
||||
(void)end;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
struct CpuClockRegistry {
|
||||
std::mutex lock;
|
||||
std::map<int, PthreadPrivate*> threads;
|
||||
|
|
@ -518,6 +633,10 @@ static void* StartNativeThread(void* opaque) {
|
|||
throw std::runtime_error("Cannot query guest thread stack");
|
||||
self->stackAddress = reinterpret_cast<void*>(high - reserve - self->stackSize);
|
||||
self->threadId = std::this_thread::get_id();
|
||||
#if defined(__linux__)
|
||||
self->hostTid = static_cast<int>(gettid());
|
||||
RegisterGuestStackMapping(self);
|
||||
#endif
|
||||
args->initialized.set_value();
|
||||
} catch (...) {
|
||||
args->initialized.set_exception(std::current_exception());
|
||||
|
|
@ -601,12 +720,22 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread
|
|||
sigset_t creatorMask;
|
||||
pthread_sigmask(SIG_SETMASK, nullptr, &creatorMask);
|
||||
args->signalMask = creatorMask;
|
||||
#if defined(__linux__)
|
||||
ReapRetiredStacks();
|
||||
AllocateGuestStack(p.get(), nativeStack);
|
||||
#endif
|
||||
auto native = std::make_unique<NativeThreadArgs>(NativeThreadArgs{std::move(args), start.get_future(), {}});
|
||||
auto initialized = native->initialized.get_future();
|
||||
pthread_attr_t nativeAttr;
|
||||
if (const int result = pthread_attr_init(&nativeAttr); result != 0)
|
||||
if (const int result = pthread_attr_init(&nativeAttr); result != 0) {
|
||||
FreeGuestStack(p.get());
|
||||
throw std::system_error(result, std::generic_category(), "Creating guest thread");
|
||||
}
|
||||
#if defined(__linux__)
|
||||
int created = pthread_attr_setstack(&nativeAttr, static_cast<char*>(p->guestStack) + GUEST_STACK_GUARD, p->guestStackBytes - GUEST_STACK_GUARD);
|
||||
#else
|
||||
int created = pthread_attr_setstacksize(&nativeAttr, nativeStack);
|
||||
#endif
|
||||
if (created == 0) {
|
||||
sigset_t allSignals;
|
||||
sigfillset(&allSignals);
|
||||
|
|
@ -615,18 +744,22 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread
|
|||
pthread_sigmask(SIG_SETMASK, &creatorMask, nullptr);
|
||||
}
|
||||
pthread_attr_destroy(&nativeAttr);
|
||||
if (created != 0)
|
||||
if (created != 0) {
|
||||
FreeGuestStack(p.get());
|
||||
throw std::system_error(created, std::generic_category(), "Creating guest thread");
|
||||
}
|
||||
native.release();
|
||||
try {
|
||||
initialized.get();
|
||||
if (detached) {
|
||||
if (const int result = pthread_detach(p->hostThread); result != 0)
|
||||
throw std::system_error(result, std::generic_category(), "Detaching guest thread");
|
||||
RetireGuestStack(p.get());
|
||||
}
|
||||
} catch (...) {
|
||||
start.set_value(false);
|
||||
pthread_join(p->hostThread, nullptr);
|
||||
FreeGuestStack(p.get());
|
||||
throw;
|
||||
}
|
||||
auto* published = p.release();
|
||||
|
|
@ -655,6 +788,7 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval) {
|
|||
#else
|
||||
if (const int result = pthread_join(thread->hostThread, nullptr); result != 0)
|
||||
throw std::system_error(result, std::generic_category(), "Joining guest thread");
|
||||
FreeGuestStack(thread);
|
||||
if (retval) *retval = thread->_retval;
|
||||
ReleaseThread(thread);
|
||||
#endif
|
||||
|
|
@ -670,6 +804,7 @@ int APS5_VABI scePthreadDetach(Pthread thread) {
|
|||
#else
|
||||
if (const int result = pthread_detach(thread->hostThread); result != 0)
|
||||
throw std::system_error(result, std::generic_category(), "Detaching guest thread");
|
||||
RetireGuestStack(thread);
|
||||
ReleaseThread(thread);
|
||||
#endif
|
||||
return SCE_OK;
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
|
|||
int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end);
|
||||
int APS5_VABI sceKernelMapFlexibleMemory(void** addr, std::size_t len, int prot, int flags);
|
||||
int APS5_VABI sceKernelMunmap(std::uint64_t addr, std::size_t len);
|
||||
int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, std::uint64_t infoSize);
|
||||
}
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000000FC00000000ull;
|
||||
static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast<int>(0x8002000d);
|
||||
static int global = 0;
|
||||
|
||||
|
|
@ -24,6 +26,8 @@ struct WorkerContext {
|
|||
void* start = nullptr;
|
||||
void* end = nullptr;
|
||||
int result = -1;
|
||||
VirtualQueryInfo region{};
|
||||
int regionResult = -1;
|
||||
};
|
||||
|
||||
static void* APS5_VABI Worker(void* arg) {
|
||||
|
|
@ -31,6 +35,7 @@ static void* APS5_VABI Worker(void* arg) {
|
|||
int local = 0;
|
||||
context.local = &local;
|
||||
context.result = sceKernelIsStack(&local, &context.start, &context.end);
|
||||
context.regionResult = sceKernelVirtualQuery(&local, 0, &context.region, sizeof(context.region));
|
||||
context.queried.store(true);
|
||||
while (!context.release.load()) {}
|
||||
return nullptr;
|
||||
|
|
@ -55,6 +60,11 @@ int main() {
|
|||
while (!context.queried.load()) {}
|
||||
Require(context.result == SCE_OK);
|
||||
Require(context.start <= context.local && context.local < context.end);
|
||||
Require(context.regionResult == SCE_OK);
|
||||
Require(context.region.start <= reinterpret_cast<std::uintptr_t>(context.local) && reinterpret_cast<std::uintptr_t>(context.local) < context.region.end);
|
||||
#if defined(__linux__)
|
||||
Require(reinterpret_cast<std::uintptr_t>(context.local) < APPLICATION_AREA_END);
|
||||
#endif
|
||||
Require(sceKernelIsStack(&local, &start, &end) == SCE_OK);
|
||||
Require(context.end <= start || context.start >= end);
|
||||
void* workerStart = nullptr;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ static constexpr std::uintptr_t Page = 0x4000;
|
|||
static constexpr std::size_t GuardBytes = 0x10000;
|
||||
static constexpr std::size_t HostBytes = 0x40000;
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
#define Require(value) do { if (!(value)) { std::fprintf(stderr, "GuestKernelStackProtect failed at line %d\n", __LINE__); std::abort(); } } while (false)
|
||||
|
||||
struct PageState {
|
||||
bool mapped;
|
||||
|
|
@ -81,6 +81,18 @@ static void* APS5_VABI Worker(void* arg) {
|
|||
Require(sceKernelIsStack(&local, &start, &end) == SCE_OK);
|
||||
const auto low = (reinterpret_cast<std::uintptr_t>(start) + Page - 1) & ~(Page - 1);
|
||||
Require(low + GuardBytes + Page < reinterpret_cast<std::uintptr_t>(&local));
|
||||
#ifdef __linux__
|
||||
std::uintptr_t hostGuard = 0;
|
||||
for (auto cursor = low; cursor >= Page && low - cursor <= 4 * 1024 * 1024; cursor -= Page) {
|
||||
const auto page = QueryPage(cursor);
|
||||
if (!page.mapped) break;
|
||||
if (!page.writable) { hostGuard = cursor; break; }
|
||||
}
|
||||
Require(hostGuard != 0);
|
||||
const auto hostGuardBefore = QueryPage(hostGuard).raw;
|
||||
Require(mprotect_nid_postfix(reinterpret_cast<void*>(hostGuard), Page, 3) == -1);
|
||||
Require(QueryPage(hostGuard).raw == hostGuardBefore);
|
||||
#endif
|
||||
auto* guard = reinterpret_cast<volatile unsigned char*>(low);
|
||||
Require(mprotect_nid_postfix(const_cast<unsigned char*>(guard), GuardBytes, 0) == 0);
|
||||
#ifdef _WIN32
|
||||
|
|
@ -152,7 +164,11 @@ int main() {
|
|||
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
||||
for (std::uintptr_t offset = 0; offset < GuardBytes; offset += Page) {
|
||||
const auto left = QueryPage(leftGuard.load() + offset);
|
||||
#ifdef __linux__
|
||||
Require(!left.mapped || left.writable || left.raw == 1);
|
||||
#else
|
||||
Require(!left.mapped || left.writable);
|
||||
#endif
|
||||
}
|
||||
Require(scePthreadCreate(&thread, nullptr, WriteLeftGuard, nullptr, nullptr) == SCE_OK);
|
||||
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
||||
|
|
|
|||
Loading…
Reference in New Issue