Merge pull request #4809 from MarcZX100/codex/unaligned-shader-abi-4782

fix(agc): accept unaligned shader headers in ABI resolution
This commit is contained in:
Dean 2026-10-13 15:36:40 +13:00 committed by GitHub
commit 255a91abd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 24 additions and 17 deletions

View File

@ -299,6 +299,13 @@ bool FusedHullHeader(const ShaderSnapshot& snapshot, Shader used, const Shader&
return true;
}
static Shader GuestShaderFields(const Shader* shader) {
GuestMemory::CheckRange(shader, sizeof(Shader), 1);
Shader fields;
std::memcpy(&fields, static_cast<const void*>(shader), sizeof(Shader));
return fields;
}
bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) {
if (snapshot.headerAddress == reinterpret_cast<std::uintptr_t>(shader)) return true;
if (snapshot.header.size() < sizeof(Shader)) return false;
@ -312,7 +319,7 @@ bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) {
}
std::shared_ptr<const ShaderSnapshot> RegisteredHeader(const ShaderRegistry& registry, const Shader* shader) {
const auto found = registry.find(reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code)));
const auto found = registry.find(reinterpret_cast<std::uintptr_t>(const_cast<const void*>(GuestShaderFields(shader).code)));
if (found == registry.end()) return {};
for (const auto& snapshot : found->second) {
if (snapshot->headerAddress == reinterpret_cast<std::uintptr_t>(shader)) return snapshot;
@ -950,8 +957,8 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
std::lock_guard lock(mutex);
registry = shaders;
for (const auto* shader : stages) {
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
const auto fields = GuestShaderFields(shader);
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(fields.code));
require(registry != nullptr && registry->contains(address), "graphics ABI refers to an unregistered shader");
const auto selected = RegisteredHeader(*registry, shader);
require(selected != nullptr, "graphics ABI refers to a replaced shader header");
@ -962,10 +969,10 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
registered.shader.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.shader.insert_or_assign(offset, value); });
registered.context.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.context.insert_or_assign(offset, value); });
registered.userConfig.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.userConfig.insert_or_assign(offset, value); });
if (shader->type != snapshot.type) {
const std::size_t count = shader->num_sh_registers;
GuestMemory::CheckRange(shader->sh_registers, count * sizeof(ShaderRegister), alignof(ShaderRegister));
for (std::size_t index = 0; index < count; ++index) state.shader.insert_or_assign(shader->sh_registers[index].offset, shader->sh_registers[index].value);
if (fields.type != snapshot.type) {
const std::size_t count = fields.num_sh_registers;
GuestMemory::CheckRange(fields.sh_registers, count * sizeof(ShaderRegister), alignof(ShaderRegister));
for (std::size_t index = 0; index < count; ++index) state.shader.insert_or_assign(fields.sh_registers[index].offset, fields.sh_registers[index].value);
}
}
}
@ -1018,8 +1025,8 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
PerformanceTimer timing("Shader.ResolveAbi");
ShaderPreparationTransaction transaction;
CheckFailure();
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
if (shader->type != 0 && shader->type != 1) {
const auto fields = GuestShaderFields(shader);
if (fields.type != 0 && fields.type != 1) {
const std::array<const Shader*, 1> stages{shader};
ResolveGraphicsStagesAbi(stages, context, primitive);
transaction.Commit();
@ -1028,7 +1035,7 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
std::shared_ptr<const ShaderSnapshot> snapshot;
{
std::lock_guard lock(mutex);
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(fields.code));
require(shaders != nullptr && shaders->contains(address), "static ABI refers to an unregistered shader");
snapshot = RegisteredHeader(*shaders, shader);
require(snapshot != nullptr, "static ABI refers to a replaced shader header");
@ -1095,10 +1102,8 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::
std::shared_ptr<const ShaderSnapshot> fragment;
{
std::lock_guard lock(mutex);
GuestMemory::CheckRange(vertex, sizeof(Shader), alignof(Shader));
GuestMemory::CheckRange(pixel, sizeof(Shader), alignof(Shader));
const auto lookup = [&](const Shader* shader) {
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(GuestShaderFields(shader).code));
require(shaders != nullptr && shaders->contains(address), "rectangle ABI refers to an unregistered shader");
const auto snapshot = RegisteredHeader(*shaders, shader);
require(snapshot != nullptr, "rectangle ABI refers to a replaced shader header");

View File

@ -477,10 +477,12 @@ void Registration(bool indirect) {
Shader copy = header.shader;
copy.user_data = reinterpret_cast<ShaderUserData*>(&copy);
AgcDriverResolveShaderAbi_nid_postfix(&copy, {}, {});
alignas(Shader) std::array<std::byte, sizeof(Shader)> padded{};
std::memcpy(padded.data(), &copy, sizeof(Shader));
alignas(Shader) std::array<std::byte, sizeof(Shader) + 4> padded{};
auto* unaligned = padded.data() + 4;
Require(reinterpret_cast<std::uintptr_t>(unaligned) % alignof(Shader) != 0, "shader ABI test header is unexpectedly aligned");
std::memcpy(unaligned, &copy, sizeof(Shader));
padded.back() = std::byte{0x7d};
AgcDriverResolveShaderAbi_nid_postfix(reinterpret_cast<Shader*>(padded.data()), {}, {});
AgcDriverResolveShaderAbi_nid_postfix(reinterpret_cast<Shader*>(unaligned), {}, {});
copy.target ^= 1u;
ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(&copy, {}, {}); }, "replaced shader header");
header.registers[1].value |= 0x100u;

View File

@ -15,7 +15,7 @@
- [Zero-stride vertex fetch range](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - for a stride-0 V# with OOB_SELECT 2 the whole fetch reads memory unless NUM_RECORDS is 0; with OOB_SELECT 3 it reads zero when NUM_RECORDS is 0 and throws when the fetch only partly fits. Rules taken from the GFX10 OOB_SELECT table in Mesa [ac_descriptors.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/ac_descriptors.c), not measured on hardware. Whether OOB_SELECT 3 checks each dword of a multi-dword fetch separately is unknown. OOB_SELECT 0 and 1 keep the structured record checks.
- [Resource registration](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - registration always fails with `RESOURCE_REGISTRATION_UNAVAILABLE`, so the owner and resource queries, `sceAgcDriverSetResourceUserData`, `sceAgcDriverFindResourcesPublic` and `sceAgcDriverUnregisterAllResourcesForOwner` return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. `sceAgcDriverGetResourceRegistrationMaxNameLength` writes 0xfc and returns 0, as [prosper](https://github.com/mattias800/prosper) does from a title's registration configuration; the `uint32_t*` parameter matches the SDK wrapper `sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*)`. sharpemu writes 256 and KytyPS5 returns `RESOURCE_REGISTRATION_UNAVAILABLE`, so the value is unverified; a null output throws
- [sceAgcDriverRegisterMultipleResources](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified.
- [Shader registration](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a shader header is accepted at any address: PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary; whether the console refuses an alignment is unknown
- [Shader headers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - registration and ABI resolution read fixed header fields through aligned copies, so the header pointer itself may have any alignment. PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary, and PPSA01317 passes 4-byte-aligned headers to ABI resolution; console alignment requirements are unknown
- [NGG vertex workgroups](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - type-2 PRIMGEN programs using DS instructions or `s_barrier` are prepared with the draw's routing and primitive registers on the existing mesh path. The workgroup requirement follows [Mesa's NGG lowering](https://github.com/mihawk-99/PS5_Mesa/blob/b3588f78dd7fd46fbadc7ecd487e5f14d4213e4b/src/amd/common/nir/ac_nir_lower_ngg.c#L1135); the mesh path supplies LDS and two guest lanes per host invocation for wave64 on a 32-lane host subgroup. Console routing has not been measured. Passthrough and tessellation keep their existing paths; unsupported mesh assembly and subgroup budgets throw.
- [Shader header copies](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a header bound for a draw or dispatch at another address than the registered one is accepted when its fixed fields match the registered header except `user_data`; the registered header's metadata is used. PPSA21567 binds such a copy of a vertex shader header with `user_data` set to null. Whether the console reads the copy's `user_data` is unknown
- [HtileFillCovers](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a fill is taken to cover a depth surface's whole HTILE when it spans 32 KiB per 1024x512 pixel block from DB_HTILE_DATA_BASE, the pipe-aligned SW_64KB_Z_X metadata extent AMD PAL's gfx10 addrlib gives for GB_ADDR_CONFIG 0x00100044 (1-8 samples, D16/D32); other metadata layouts are not decoded. A partial fill is ignored (the surface keeps its contents); not measured on the console.