fix(ampr): reserve targets before releasing mapped pool pages

(cherry picked from commit 3e9a37eaa34c4d543f775bece42de3eb7a4647ea)
This commit is contained in:
Blazing Cook 2026-10-11 01:36:02 -05:00 committed by MarcZX100
parent ce98f83033
commit a88a4c8e15
3 changed files with 30 additions and 50 deletions

View File

@ -492,12 +492,14 @@ void _ammMap(const Apr::AmmMapCommand& command, bool direct) {
auto& state = _amm();
const std::lock_guard lock(state.lock);
_ammCheckRange(state, command.address, command.size);
_forgetPages(state, command.address, command.size);
_reserve(command.address, command.size);
_forgetPages(state, command.address, command.size, true);
_clearPrt(state, command.address, command.size);
const auto physical = direct ? command.directOffset : _takePoolPages(state, command.size);
try {
_mapRun(command.address, physical, command.size, command.protection);
} catch (...) {
_reserve(command.address, command.size);
if (!direct) _returnPoolPages(state, physical, command.size);
throw;
}

View File

@ -1013,7 +1013,7 @@ void SubmitAmm(Recorder& recorder) {
Require(sceAmprAmmWaitCommandBufferCompletion(id) == 0);
}
void TestAmmRemapKeepsPoolReferences() {
void TestAmmMapFailureKeepsPoolOwnership() {
std::uint64_t start = 0;
std::uint64_t end = 0;
std::uint64_t multimapStart = 0;
@ -1021,55 +1021,33 @@ void TestAmmRemapKeepsPoolReferences() {
Require(sceAmprAmmGetVirtualAddressRanges(&start, &end, &multimapStart, &multimapEnd) == 0);
std::int64_t pool = -1;
Require(sceAmprAmmGiveDirectMemory(0, static_cast<std::int64_t>(sceKernelGetDirectMemorySize()), page, page, 1, &pool) == 0);
const auto original = start + 512 * page;
const auto fresh = start + 516 * page;
const auto source = start + 256 * page;
const auto alias = multimapStart + 256 * page;
const auto fresh = start + 257 * page;
const auto lastAlias = multimapStart + 257 * page;
const auto overlappingAlias = multimapStart + 258 * page;
Recorder maps;
Require(sceAmprAmmCommandBufferMap(&maps.buffer, source, page, 0, cpuReadWrite) == 0);
Require(sceAmprAmmCommandBufferMultiMap(&maps.buffer, alias, source, page, cpuReadWrite) == 0);
SubmitAmm(maps);
At(source) = 0xA1;
Require(At(alias) == 0xA1);
Recorder mapping;
Require(sceAmprAmmCommandBufferMap(&mapping.buffer, original, page, 0, cpuReadWrite) == 0);
SubmitAmm(mapping);
At(original) = 0xA1;
SubmitAmm(mapping);
Require(At(original) == 0xA1 && Protection(original) == cpuReadWrite);
Recorder move;
Require(sceAmprAmmCommandBufferRemap(&move.buffer, alias, source, page, cpuReadWrite) == 0);
SubmitAmm(move);
Require(Protection(source) == 0 && At(alias) == 0xA1);
Recorder oversized;
Require(sceAmprAmmCommandBufferMap(&oversized.buffer, original, 2 * page, 0, cpuReadWrite) == 0);
bool rejected = false;
try {
SubmitAmm(oversized);
} catch (const std::runtime_error& error) {
rejected = std::string(error.what()).starts_with("AMM: no free run of ");
}
Require(rejected);
Require(Protection(original) == 0 && Protection(original + page) == 0);
Recorder exhausted;
Require(sceAmprAmmCommandBufferMap(&exhausted.buffer, fresh, page, 0, cpuReadWrite) == 0);
const auto requireExhausted = [&] {
bool rejected = false;
try {
SubmitAmm(exhausted);
} catch (const std::runtime_error& error) {
rejected = std::string(error.what()).starts_with("AMM: no free run of ");
}
Require(rejected);
};
requireExhausted();
At(alias) = 0xB1;
Require(At(alias) == 0xB1);
Recorder retain;
Require(sceAmprAmmCommandBufferRemap(&retain.buffer, alias, alias, page, cpuReadWrite) == 0);
Require(sceAmprAmmCommandBufferMultiMap(&retain.buffer, lastAlias, alias, page, cpuReadWrite) == 0);
Require(sceAmprAmmCommandBufferRemap(&retain.buffer, lastAlias, alias, 2 * page, cpuReadWrite) == 0);
Require(sceAmprAmmCommandBufferUnmap(&retain.buffer, lastAlias, page) == 0);
SubmitAmm(retain);
Require(At(overlappingAlias) == 0xB1);
requireExhausted();
Recorder release;
Require(sceAmprAmmCommandBufferUnmap(&release.buffer, overlappingAlias, page) == 0);
Require(sceAmprAmmCommandBufferMap(&release.buffer, fresh, page, 0, cpuReadWrite) == 0);
SubmitAmm(release);
Require(At(fresh) == 0xB1);
At(fresh) = 0xC1;
Require(At(fresh) == 0xC1);
Recorder reuse;
Require(sceAmprAmmCommandBufferMap(&reuse.buffer, fresh, page, 0, cpuReadWrite) == 0);
SubmitAmm(reuse);
Require(At(fresh) == 0xA1);
At(fresh) = 0xB1;
Require(At(fresh) == 0xB1 && Protection(original) == 0);
}
void TestAmmRemapAndProtect() {
@ -1372,7 +1350,7 @@ int main() {
TestGatherScatter();
TestAmmRangesAboveHintedMaps();
TestAmmUsageStats();
TestAmmRemapKeepsPoolReferences();
TestAmmMapFailureKeepsPoolOwnership();
TestAmm();
TestAmmRemapAndProtect();
TestAmmPrt();

View File

@ -23,7 +23,7 @@
- [APR resolve ForEach result](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - `sceKernelAprResolveFilepathsToIdsAndFileSizesForEach` returns the number of paths that resolved, with the per-path status in `results` and the failed ids/sizes set to the invalid id and 0. The [ampr_emu](https://github.com/drakmor/ampr_emu) bridge passes the count of its resolver through and does not show what is counted; counting successes is not confirmed on a title. The guest errno of the failing non-`ForEach` resolves is set through `__error_nid_postfix`
- [AMPR counter access](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and checks of `WaitOnCounter_04_00`, `WriteCounter_04_00`, `ConstructNop` and `ConstructMarker` taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands counter commands to the console and does not show their effect. The counters are taken as 32 bits each: the 8-byte access of counter n spans n (low) and n + 1 (high), the 2- and 1-byte accesses select a half or a byte of the counter by its offset, waits compare at the access width (signed and wrapped compares included), the AND mask applies to both sides and the atomic writes wrap within the access. The `ConstructNop` type is not recorded
- [APR gather and scatter reads](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures, argument checks and the read cursor taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, not confirmed on a title: each read leaves the file, the next file offset and the next destination; a gather reads that file at a new offset into the next destination, a scatter continues in the file into a new destination, a gather-scatter takes both. Recording one needs a read file since the last `ResetGatherScatterState`; like ampr_emu, `sceAmprCommandBufferReset` keeps that state, and a gather or scatter with no read before it in the submitted buffer throws instead of returning `SCE_AMPR_ERROR_APR_INVALIDGATHERSCATTERSTATE`
- [AMM map, map direct and unmap](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection mask (CPU, GPU, AMPR and ACP read and write) taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands the remaining checks and the mapping to the console kernel; the mapper model taken from [PS5PCEM](https://github.com/iStark/PS5PCEM): `sceAmprAmmGiveDirectMemory` usage 1 gives the allocated direct memory to the pool plain maps take pages from, usage 0 only allocates it. Not confirmed on a title. The AMM virtual address ranges are a 32 GiB range and a 32 GiB multimap range reserved on first use at the top of the guest arena, above the addresses that maps without `MAP_FIXED` are given lowest first and that titles derive their next hints from; the console's addresses are not known (ampr_emu takes them from `sceKernelMapperGetParam`), and a reservation that cannot be made there throws. Maps outside them throw. Addresses, sizes and direct offsets must be 16 KiB aligned. The memory type and the GPU mask are ignored, AMPR and ACP access maps as CPU access, unmapped pages go back to the pool and stay reserved. Submission runs the buffer before returning, the submit result is always 0 and a failing command throws; the AMM measures return errors sign-extended, as ampr_emu does. `sceAmprAmmGetVirtualAddressRanges` throws for a null output
- [AMM map, map direct and unmap](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection mask (CPU, GPU, AMPR and ACP read and write) taken from the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation, which hands the remaining checks and the mapping to the console kernel; the mapper model taken from [PS5PCEM](https://github.com/iStark/PS5PCEM): `sceAmprAmmGiveDirectMemory` usage 1 gives the allocated direct memory to the pool plain maps take pages from, usage 0 only allocates it. Not confirmed on a title. The AMM virtual address ranges are a 32 GiB range and a 32 GiB multimap range reserved on first use at the top of the guest arena, above the addresses that maps without `MAP_FIXED` are given lowest first and that titles derive their next hints from; the console's addresses are not known (ampr_emu takes them from `sceKernelMapperGetParam`), and a reservation that cannot be made there throws. Maps outside them throw. Addresses, sizes and direct offsets must be 16 KiB aligned. The memory type and the GPU mask are ignored, AMPR and ACP access maps as CPU access, unmapped pages go back to the pool and stay reserved. Submission runs the buffer before returning, the submit result is always 0 and a failing command throws; the AMM measures return errors sign-extended, as ampr_emu does. A map returns its target to the reservation before releasing the old pages, so pool exhaustion leaves that target reserved; mapping failure also restores the reservation before returning newly taken pages to the pool. The console's map failure and rollback behavior is unverified. `sceAmprAmmGetVirtualAddressRanges` throws for a null output
- [AMM remap, multimap and protection changes](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures and the protection and mask checks taken from ampr_emu; the effects are inferred from the names, not confirmed on a title. A remap moves every page of a fully mapped range to the new address with the given protection and leaves the old range reserved; a multimap maps the same pages at a second address, anywhere in the AMM ranges, and a pool page goes back to the pool once its last mapping is unmapped; a multimap onto its own source range throws. A protection change sets the masked bits on every page of a fully mapped range; the memory type is recorded and ignored. A command on a range that is not fully mapped throws
- [AMM PRT ranges](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - signatures, checks and their order taken from ampr_emu, where `MapAsPrt` is a map with the PRT flag and protection 0 and `AllocatePaForPrt` a memory type and protection change with mask 1019, both checking the buffer memory after its size; the effects are inferred from the names, not confirmed on a title. Unbacked PRT pages are zero-filled pages with GPU read access, so CPU reads see zeros instead of faulting and GPU writes fault instead of being dropped. `AllocatePaForPrt` backs each unbacked page with a pool page and sets the type and protection of backed ones; `RemapIntoPrt` moves a fully mapped range into a PRT range, its opcode argument (1011 when 0, as in ampr_emu) is recorded and ignored; `UnmapToPrt` returns the pages to the pool and zero-fills them again. A plain map, unmap or remap over a PRT range ends it; `AllocatePaForPrt`, `RemapIntoPrt` or `UnmapToPrt` outside a PRT range throws
- [AMM kernel write commands](../../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libkernel) - signatures of `sceKernelWriteModifyMtypeProtectCommand` and `sceKernelWriteRemapIntoPrtCommand` taken from [ampr_emu's libkernel hook targets](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/include/ampr_libkernel_hook_targets.h), which forward to the real kernel; the measure mode (`dst` null reports the size, `outSize` null skips the report) follows ampr_emu's measure-then-record calls, not confirmed on a title. The range, protection and mask checks mirror the `libSceAmpr` siblings, and the recorded bytes match them exactly so the submitted buffer executes on the existing paths