Files
AnyPS5/core/libs/tests/GuestKernelIsStack.cpp
T
milescovielloandClaude Opus 5.5 1c9d9d6bc5 fix(build): run the CPU-side tests in an ANYPS5_ENABLE_ASAN build
Under -DANYPS5_ENABLE_ASAN=ON with ASAN_OPTIONS=detect_leaks=0, 14 of
the 849 tests outside the AGC driver, shader and VideoOut suites failed
on main for reasons that come from the ASan build itself, not from the
code they test:

- libc copies the main executable's file-backed writable pages before
  remapping them anonymously. In an instrumented test executable those
  pages hold ASan's redzones around globals, so the memcpy reported a
  global-buffer-overflow at load (patched_libraries,
  guest_memory_lifetime). The ASan build copies the pages word by word
  without instrumentation.
- Five tests check that a local variable is on the thread's stack.
  With detect_stack_use_after_return, which is on by default on Linux,
  instrumented locals live on ASan's fake stack. These tests now run
  with it off. guest_kernel_is_stack also bounded the worker stack by
  the application area without the ASan build's arena offset.
- The relinker module tests and loadexec_arguments load instrumented
  libraries into a Python process or a relinked guest, so the ASan
  runtime is not first in the initial library list. They now run with
  verify_asan_link_order=0. Preloading libasan instead put its
  __cxa_throw interceptor ahead of libc.prx's and broke the unwind of
  an exception that LoadExec throws and catches.
- guest_allocator_failures limits RLIMIT_DATA to 128 MiB and then
  commits 300 GiB. ASan maps 37.5 GiB of shadow for that commit, which
  the limit refuses, and aborts. The ASan build skips that section.
- fiber checks the address of a local against the fiber's context too,
  so it runs without fake stacks as well. Its CheckActiveContext then
  reads the fiber stack word by word above that local, through the
  redzones of the frames on that stack, and is no longer instrumented.
- agc_multisample_guest_access passed aligned_alloc a size that is not
  a multiple of the alignment, which C11 does not allow and ASan
  rejects. It now rounds the size up.

add_asan_test_options appends to ASAN_OPTIONS for one test in an ASan
build and does nothing otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:55:21 +00:00

89 lines
3.5 KiB
C++

#include "SceTypes.hpp"
#include <atomic>
#include <cstdint>
#include <cstdlib>
extern "C" {
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
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;
#ifdef ANYPS5_ENABLE_ASAN
static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000010FC00000000ull;
#else
static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000000FC00000000ull;
#endif
static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast<int>(0x8002000d);
static int global = 0;
static void Require(bool value) { if (!value) std::abort(); }
struct WorkerContext {
std::atomic<bool> queried{false};
std::atomic<bool> release{false};
void* local = nullptr;
void* start = nullptr;
void* end = nullptr;
int result = -1;
VirtualQueryInfo region{};
int regionResult = -1;
};
static void* APS5_VABI Worker(void* arg) {
auto& context = *static_cast<WorkerContext*>(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;
}
int main() {
int local = 0;
void* start = nullptr;
void* end = nullptr;
Require(sceKernelIsStack(&local, &start, &end) == SCE_OK);
Require(start <= static_cast<void*>(&local) && static_cast<void*>(&local) < end);
Require(sceKernelIsStack(&local, nullptr, nullptr) == SCE_OK);
start = &local;
end = &local;
Require(sceKernelIsStack(&global, &start, &end) == SCE_OK);
Require(start == nullptr && end == nullptr);
WorkerContext context;
Pthread thread = nullptr;
Require(scePthreadCreate(&thread, nullptr, Worker, &context, nullptr) == SCE_OK);
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;
void* workerEnd = nullptr;
Require(sceKernelIsStack(context.local, &workerStart, &workerEnd) == SCE_OK);
Require(workerStart == context.start && workerEnd == context.end);
context.release.store(true);
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
constexpr std::size_t page = 0x4000;
void* mapped = nullptr;
Require(sceKernelMapFlexibleMemory(&mapped, page, 3, 0) == SCE_OK);
Require(sceKernelIsStack(mapped, &start, &end) == SCE_OK);
Require(start == nullptr && end == nullptr);
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapped), page) == SCE_OK);
Require(sceKernelIsStack(mapped, &start, &end) == SCE_KERNEL_ERROR_EACCES);
}