mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
fix: keep hint-less guest mappings at the canonical PS5 base (fixes the #135 macOS regression) (#138)
* fix: keep hint-less guest mappings at the canonical PS5 base FindGuestFreeRange searched the low system-managed range first for mappings with no address hint, so the first hint-less direct-memory map could land as low as 0x200000. The PS5 kernel never places hint-less user mappings below 0x200000000 and guest code relies on that: Sony's libc maps 4 MiB of direct memory for its internal heap, fails its mspace setup when the returned address is that low, and the first malloc then dereferences a null mspace (a read at 0x38, the mspace magic check). On macOS this made Raiden III crash on the main guest thread a couple of seconds after boot, 100 percent reproducible with --printf-direction Silent. Search from the canonical base first, fall back to the user range, and keep the low system-managed range only as a last resort. The mmap path already anchored hint-less searches at 0x200000000; this aligns the shared search helper with it. Adds two regression tests: the libc-shaped allocation must come back at or above the canonical base and hold writes, and direct-memory content must survive an unmap and remap of the same physical range. * macos: make the fatal-report memory dumps fault-safe IsReadableRange returned true for any nonzero address on macOS, so the fatal report's guest memory dumps dereferenced whatever the crashed thread had in its registers. A fault inside the reporter re-enters the signal handler and wedges the reporting thread, which hid real guest crashes whenever logging was enabled: the game kept running with a dead thread and the report was never completed. Walk the Mach regions covering the range and require read permission before dumping, the same contract the Linux implementation provides. * do not fallthrough HOST_SYSTEM_MANAGED_MIN --------- Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
1 parent
c1a5927036
commit
0f550d1fd0
3 files changed
+166
-5
No files matched your search
+10
-2
@@ -818,6 +818,11 @@ static void MemoryPoolSubtractCommitted(uint64_t l
|
|||||||
// Keep host mappings, physical blocks, placeholders, and virtual ranges in step.
|
// Keep host mappings, physical blocks, placeholders, and virtual ranges in step.
|
||||||
static std::recursive_mutex g_memory_operation_mutex;
|
static std::recursive_mutex g_memory_operation_mutex;
|
||||||
|
|
||||||
|
// The base address the PS5 kernel hands out for hint-less user mappings. Guest code can
|
||||||
|
// assume mappings it did not place explicitly are at or above this (Sony's libc rejects a
|
||||||
|
// heap below it), so hint-less searches must not fall back to the low system-managed range.
|
||||||
|
static constexpr uint64_t GUEST_DEFAULT_MAP_BASE = 0x200000000ull;
|
||||||
|
|
||||||
static uint64_t FindGuestFreeRange(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
static uint64_t FindGuestFreeRange(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
||||||
EXIT_IF(g_guest_address_space == nullptr || g_virtual_ranges == nullptr);
|
EXIT_IF(g_guest_address_space == nullptr || g_virtual_ranges == nullptr);
|
||||||
|
|
||||||
@@ -845,8 +850,11 @@ static uint64_t FindGuestFreeRange(uint64_t search_addr, uint64_t size, uint64_t
|
|||||||
if (search_addr != 0) {
|
if (search_addr != 0) {
|
||||||
return find_in(search_addr, HOST_USER_MAX + 1u);
|
return find_in(search_addr, HOST_USER_MAX + 1u);
|
||||||
}
|
}
|
||||||
auto addr = find_in(HOST_SYSTEM_MANAGED_MIN, HOST_SYSTEM_MANAGED_MAX + 1u);
|
auto addr = find_in(GUEST_DEFAULT_MAP_BASE, HOST_SYSTEM_MANAGED_MAX + 1u);
|
||||||
return addr != 0 ? addr : find_in(HOST_USER_MIN, HOST_USER_MAX + 1u);
|
if (addr == 0) {
|
||||||
|
addr = find_in(HOST_USER_MIN, HOST_USER_MAX + 1u);
|
||||||
|
}
|
||||||
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
|
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
|
||||||
|
|||||||
@@ -38,7 +38,10 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#else
|
#else
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <mach/mach_vm.h>
|
||||||
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -722,7 +725,26 @@ static bool IsReadableRange(uint64_t addr, uint64_t size) {
|
|||||||
}
|
}
|
||||||
current = std::min(region_end, end);
|
current = std::min(region_end, end);
|
||||||
}
|
}
|
||||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
|
// Walk the Mach regions covering the range and require read permission. The fatal
|
||||||
|
// report dumps memory behind raw register values, and a fault inside the reporter
|
||||||
|
// re-enters the signal handler and wedges the reporting thread.
|
||||||
|
uint64_t current = addr;
|
||||||
|
while (current < end) {
|
||||||
|
mach_vm_address_t region_addr = current;
|
||||||
|
mach_vm_size_t region_size = 0;
|
||||||
|
vm_region_basic_info_data_64_t info {};
|
||||||
|
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||||
|
mach_port_t object_name = MACH_PORT_NULL;
|
||||||
|
if (mach_vm_region(mach_task_self(), ®ion_addr, ®ion_size, VM_REGION_BASIC_INFO_64,
|
||||||
|
reinterpret_cast<vm_region_info_t>(&info), &count,
|
||||||
|
&object_name) != KERN_SUCCESS ||
|
||||||
|
region_addr > current || (info.protection & VM_PROT_READ) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
current = region_addr + region_size;
|
||||||
|
}
|
||||||
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
const auto page_size = static_cast<uint64_t>(sysconf(_SC_PAGESIZE));
|
const auto page_size = static_cast<uint64_t>(sysconf(_SC_PAGESIZE));
|
||||||
if (page_size == 0) {
|
if (page_size == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -752,7 +774,7 @@ static bool IsReadableRange(uint64_t addr, uint64_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool IsDumpableRange(uint64_t addr, uint64_t size) {
|
static bool IsDumpableRange(uint64_t addr, uint64_t size) {
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
return IsReadableRange(addr, size);
|
return IsReadableRange(addr, size);
|
||||||
#else
|
#else
|
||||||
(void)size;
|
(void)size;
|
||||||
|
|||||||
@@ -1356,6 +1356,135 @@ void TestLargeDirectMapAliasesAcrossChunks() {
|
|||||||
std::printf("[host] %-48s ok\n", test);
|
std::printf("[host] %-48s ok\n", test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestHintlessDirectMapUsesCanonicalGuestBase() {
|
||||||
|
// Mirrors the allocation Sony's libc.prx makes for its internal heap: 4 MiB of
|
||||||
|
// direct memory, 2 MiB aligned, mapped with no address hint. The PS5 kernel never
|
||||||
|
// places hint-less user mappings below 0x200000000 and guest code relies on that
|
||||||
|
// (libc fails its mspace setup for a lower heap address, and the first malloc then
|
||||||
|
// dereferences a null mspace). Writes through the mapping must also stick.
|
||||||
|
const char* test = "HintlessDirectMapUsesCanonicalGuestBase";
|
||||||
|
|
||||||
|
constexpr uint64_t Len = 0x400000;
|
||||||
|
constexpr uint64_t Align = 0x200000;
|
||||||
|
|
||||||
|
int64_t phys_addr = 0;
|
||||||
|
CheckOk(test,
|
||||||
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(0, 0x260000000ull, Len, Align, 12,
|
||||||
|
&phys_addr),
|
||||||
|
"KernelAllocateDirectMemory");
|
||||||
|
|
||||||
|
void* address = nullptr;
|
||||||
|
CheckOk(test,
|
||||||
|
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&address, Len, SceKernelProtCpuRw,
|
||||||
|
0, phys_addr, Align, "libc_heap"),
|
||||||
|
"KernelMapNamedDirectMemory");
|
||||||
|
const auto base = reinterpret_cast<uint64_t>(address);
|
||||||
|
{
|
||||||
|
char message[128] = {};
|
||||||
|
std::snprintf(message, sizeof(message),
|
||||||
|
"hint-less direct map landed below the PS5 base: 0x%016" PRIx64, base);
|
||||||
|
Check(test, base >= 0x200000000ull, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* header = reinterpret_cast<uint64_t*>(base);
|
||||||
|
header[0] = 0x4d53504143453030ull; // "MSPACE00"
|
||||||
|
header[7] = 0x58585858ull; // magic at +0x38, like the libc mspace
|
||||||
|
*reinterpret_cast<uint64_t*>(base + Len - 8) = 0x454e444d41524bull;
|
||||||
|
|
||||||
|
Check(test, header[0] == 0x4d53504143453030ull, "immediate readback of header[0] failed");
|
||||||
|
Check(test, header[7] == 0x58585858ull, "immediate readback of header[7] failed");
|
||||||
|
Check(test, *reinterpret_cast<const uint64_t*>(base + Len - 8) == 0x454e444d41524bull,
|
||||||
|
"immediate readback of tail failed");
|
||||||
|
|
||||||
|
uint64_t backing = 0;
|
||||||
|
Check(test, Libs::LibKernel::Memory::TryReadBacking(base + 0x38, &backing, sizeof(backing)),
|
||||||
|
"TryReadBacking(header+0x38)");
|
||||||
|
Check(test, backing == 0x58585858ull, "backing store does not see the guest write at +0x38");
|
||||||
|
|
||||||
|
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, Len), "KernelMunmap");
|
||||||
|
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, Len),
|
||||||
|
"KernelReleaseDirectMemory");
|
||||||
|
|
||||||
|
std::printf("[host] %-48s ok\n", test);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestDirectMemoryContentPersistsAcrossRemap() {
|
||||||
|
const char* test = "DirectMemoryContentPersistsAcrossRemap";
|
||||||
|
|
||||||
|
constexpr uint64_t MapSize = SceKernelPageSize * 4;
|
||||||
|
|
||||||
|
int64_t phys_addr = 0;
|
||||||
|
CheckOk(test,
|
||||||
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||||
|
SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
||||||
|
MapSize, SceKernelPageSize, SceKernelMtypeC, &phys_addr),
|
||||||
|
"KernelAllocateDirectMemory");
|
||||||
|
|
||||||
|
// Direct memory is physical: contents must survive unmapping and remapping, including
|
||||||
|
// a remap of a sub-range at a nonzero physical offset.
|
||||||
|
void* address = nullptr;
|
||||||
|
CheckOk(test,
|
||||||
|
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&address, MapSize,
|
||||||
|
SceKernelProtCpuRw, 0, phys_addr,
|
||||||
|
SceKernelPageSize, "persist_a"),
|
||||||
|
"KernelMapNamedDirectMemory(first)");
|
||||||
|
const auto base = reinterpret_cast<uint64_t>(address);
|
||||||
|
for (uint64_t offset = 0; offset < MapSize; offset += sizeof(uint64_t)) {
|
||||||
|
*reinterpret_cast<uint64_t*>(base + offset) = offset ^ 0x4b5954595045525aull; // "KYTYPERZ"
|
||||||
|
}
|
||||||
|
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, MapSize), "KernelMunmap(first)");
|
||||||
|
|
||||||
|
void* remap = nullptr;
|
||||||
|
CheckOk(test,
|
||||||
|
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&remap, MapSize,
|
||||||
|
SceKernelProtCpuRw, 0, phys_addr,
|
||||||
|
SceKernelPageSize, "persist_b"),
|
||||||
|
"KernelMapNamedDirectMemory(remap)");
|
||||||
|
const auto remap_base = reinterpret_cast<uint64_t>(remap);
|
||||||
|
for (uint64_t offset = 0; offset < MapSize; offset += sizeof(uint64_t)) {
|
||||||
|
const auto expected = offset ^ 0x4b5954595045525aull;
|
||||||
|
const auto actual = *reinterpret_cast<const uint64_t*>(remap_base + offset);
|
||||||
|
if (actual != expected) {
|
||||||
|
char message[160] = {};
|
||||||
|
std::snprintf(message, sizeof(message),
|
||||||
|
"content lost across remap at offset 0x%" PRIx64 ": expected 0x%016" PRIx64
|
||||||
|
", read 0x%016" PRIx64,
|
||||||
|
offset, expected, actual);
|
||||||
|
Fail(test, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(remap_base, MapSize), "KernelMunmap(remap)");
|
||||||
|
|
||||||
|
// Sub-range remap at a nonzero physical offset: page 2 of the original allocation.
|
||||||
|
void* partial = nullptr;
|
||||||
|
CheckOk(test,
|
||||||
|
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
|
||||||
|
&partial, SceKernelPageSize, SceKernelProtCpuRw, 0,
|
||||||
|
phys_addr + static_cast<int64_t>(SceKernelPageSize * 2), SceKernelPageSize,
|
||||||
|
"persist_c"),
|
||||||
|
"KernelMapNamedDirectMemory(partial)");
|
||||||
|
const auto partial_base = reinterpret_cast<uint64_t>(partial);
|
||||||
|
for (uint64_t offset = 0; offset < SceKernelPageSize; offset += sizeof(uint64_t)) {
|
||||||
|
const auto expected = (SceKernelPageSize * 2 + offset) ^ 0x4b5954595045525aull;
|
||||||
|
const auto actual = *reinterpret_cast<const uint64_t*>(partial_base + offset);
|
||||||
|
if (actual != expected) {
|
||||||
|
char message[160] = {};
|
||||||
|
std::snprintf(message, sizeof(message),
|
||||||
|
"content lost in partial remap at offset 0x%" PRIx64
|
||||||
|
": expected 0x%016" PRIx64 ", read 0x%016" PRIx64,
|
||||||
|
offset, expected, actual);
|
||||||
|
Fail(test, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(partial_base, SceKernelPageSize),
|
||||||
|
"KernelMunmap(partial)");
|
||||||
|
|
||||||
|
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, MapSize),
|
||||||
|
"KernelReleaseDirectMemory");
|
||||||
|
|
||||||
|
std::printf("[host] %-48s ok\n", test);
|
||||||
|
}
|
||||||
|
|
||||||
void TestDirectMapUnmapReusesHostAddress() {
|
void TestDirectMapUnmapReusesHostAddress() {
|
||||||
const char* test = "DirectMapUnmapReusesHostAddress";
|
const char* test = "DirectMapUnmapReusesHostAddress";
|
||||||
|
|
||||||
@@ -2150,6 +2279,8 @@ int main() {
|
|||||||
RunTest(TestDirectAlignmentStaysWithinSearchRange);
|
RunTest(TestDirectAlignmentStaysWithinSearchRange);
|
||||||
RunTest(TestDefaultDirectMapUsesSystemAddressRange);
|
RunTest(TestDefaultDirectMapUsesSystemAddressRange);
|
||||||
RunTest(TestLargeDirectMapAliasesAcrossChunks);
|
RunTest(TestLargeDirectMapAliasesAcrossChunks);
|
||||||
|
RunTest(TestHintlessDirectMapUsesCanonicalGuestBase);
|
||||||
|
RunTest(TestDirectMemoryContentPersistsAcrossRemap);
|
||||||
RunTest(TestDirectMapUnmapReusesHostAddress);
|
RunTest(TestDirectMapUnmapReusesHostAddress);
|
||||||
RunTest(TestFixedReserveReplacesPartialDirectMapping);
|
RunTest(TestFixedReserveReplacesPartialDirectMapping);
|
||||||
RunTest(TestFixedReserveRollbackConsumesRestoredPlaceholder);
|
RunTest(TestFixedReserveRollbackConsumesRestoredPlaceholder);
|
||||||
|
|||||||
Reference in new issue
Block a user