feat(build): support Linux AddressSanitizer builds

This commit is contained in:
MarcZX100 2026-10-11 15:40:36 +00:00
parent a11c8f7f70
commit e56a727375
4 changed files with 41 additions and 12 deletions

View File

@ -24,6 +24,7 @@ if(BUILD_TESTING)
endif()
option(ANYPS5_ENABLE_SPIRV_TOOLS "Enable SPIRV-Tools validation and optimization" OFF)
option(ANYPS5_ENABLE_ASAN "Build Linux targets with AddressSanitizer and use an ASan-compatible guest arena range" OFF)
option(APS5_ENABLE_TIMING_LOG "Write frame timings asynchronously to frame-timing.log" OFF)
option(ANYPS5_USE_SYSTEM_DEPS "Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the bundled submodules" OFF)
add_compile_definitions(APS5_ENABLE_TIMING_LOG=$<BOOL:${APS5_ENABLE_TIMING_LOG}>)
@ -32,7 +33,17 @@ include(cmake/SystemDeps.cmake)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT APPLE)
if(ANYPS5_ENABLE_ASAN)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "ANYPS5_ENABLE_ASAN is supported only on Linux")
endif()
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
message(FATAL_ERROR "ANYPS5_ENABLE_ASAN requires GCC or Clang")
endif()
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
add_compile_definitions(ANYPS5_ENABLE_ASAN=1)
elseif(NOT APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
endif()

View File

@ -32,10 +32,15 @@ std::atomic<std::uint64_t> commitGeneration{1};
std::atomic<void (*)(std::uintptr_t, std::size_t, std::uint64_t)> privateMappingObserver{nullptr};
#endif
constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull;
constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull;
constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull;
constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull;
#ifdef ANYPS5_ENABLE_ASAN
constexpr std::uintptr_t GuestArenaOffset = 0x0000100000000000ull;
#else
constexpr std::uintptr_t GuestArenaOffset = 0;
#endif
constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull + GuestArenaOffset;
constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull + GuestArenaOffset;
constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull + GuestArenaOffset;
constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull + GuestArenaOffset;
std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) {
return (value + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1);

View File

@ -813,8 +813,10 @@ static void CheckGuestModuleImageProtection() {
static void CheckUnhintedKernelMappingsLandInTheWindow() {
constexpr std::size_t page = 0x4000;
constexpr std::uintptr_t windowStart = 0x200000000ull;
constexpr std::uintptr_t windowEnd = 0xFC00000000ull;
std::uintptr_t windowStart = 0;
std::size_t windowSize = 0;
GuestArena::GuestArenaRange_nid_postfix(&windowStart, &windowSize);
const auto windowEnd = windowStart + windowSize;
std::int64_t phys = 0;
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0);
void* direct = nullptr;
@ -889,11 +891,17 @@ static void CheckArenaHonoursHintsWithoutGoingBelowThem() {
static void CheckFixedMappingsReachTheApplicationAreaEnd() {
constexpr std::size_t page = 0x4000;
constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull;
std::uintptr_t base = 0;
std::size_t size = 0;
GuestArena::GuestArenaRange_nid_postfix(&base, &size);
Require(base == 0x200000000ull && base + size == applicationAreaEnd);
constexpr std::uintptr_t expectedOffset =
#ifdef ANYPS5_ENABLE_ASAN
0x0000100000000000ull;
#else
0;
#endif
constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull + expectedOffset;
Require(base == 0x200000000ull + expectedOffset && base + size == applicationAreaEnd);
std::int64_t phys = 0;
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
void* const requested = reinterpret_cast<void*>(applicationAreaEnd - page * 2);
@ -919,7 +927,7 @@ static void CheckFixedMappingsReachTheApplicationAreaEnd() {
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
}
#if defined(__linux__)
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
static std::size_t LockedKilobytes() {
std::ifstream status("/proc/self/status");
std::string line;
@ -943,7 +951,7 @@ static void CheckMlock() {
Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0);
auto* bytes = static_cast<unsigned char*>(mapped);
Require(sceKernelMlock_nid_postfix(mapped, 0) == 0);
#if defined(__linux__)
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
const auto lockedBefore = LockedKilobytes();
#endif
Require(sceKernelMlock_nid_postfix(bytes + 1, length - page) == 0);
@ -954,7 +962,7 @@ static void CheckMlock() {
Require(GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &limits) && minimum >= length && maximum > minimum);
Require(VirtualUnlock(mapped, length));
Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED);
#elif defined(__linux__)
#elif defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
Require(LockedKilobytes() - lockedBefore == length / 1024);
#endif
Require(sceKernelMlock_nid_postfix(mapped, length) == 0);
@ -963,10 +971,14 @@ static void CheckMlock() {
Require(sceKernelMlock_nid_postfix(reinterpret_cast<void*>(std::numeric_limits<std::uintptr_t>::max() - page + 1), page * 2) == invalid);
void* reserved = nullptr;
Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0);
#ifndef ANYPS5_ENABLE_ASAN
Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory);
#endif
Require(sceKernelMunmap(reserved, page) == 0);
Require(sceKernelMunmap(mapped, length) == 0);
#ifndef ANYPS5_ENABLE_ASAN
Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory);
#endif
}
static void CheckSharedDirectMemoryLifecycle() {

View File

@ -57,6 +57,7 @@ Project switches accept `ON` or `OFF`:
| `-DBUILD_TESTING=ON` | `OFF` | Build and register tests. |
| `-DANYPS5_RELINKER_ONLY=ON` | `OFF` | Build only the relinker and its tests, without third-party dependencies. |
| `-DANYPS5_ENABLE_SPIRV_TOOLS=ON` | `OFF` | Enable SPIR-V validation and optimization. |
| `-DANYPS5_ENABLE_ASAN=ON` | `OFF` | Build with AddressSanitizer on Linux and move the guest arena out of ASan's shadow gap. |
| `-DANYPS5_USE_SYSTEM_DEPS=ON` | `OFF` | Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the submodules. |
| `-DAPS5_ENABLE_TIMING_LOG=ON` | `OFF` | Compile frame timing logging. |
| `-DAPS5_AGC_CREATE_LOG=OFF` | `ON` | Disable successful `sceAgcCreateShader` logging. |