diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cb4fb85c..a48a0b337 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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=$) @@ -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() diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index fea67b8cc..356f465c9 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -32,10 +32,15 @@ std::atomic commitGeneration{1}; std::atomic 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(alignment) - 1); diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index bfab22098..5bccb6d1e 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -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(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(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(std::numeric_limits::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() { diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 50e7023fe..15cfe9803 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -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. |