From bdf48dcf3b5f345814afc113abd8c4fc1d42fa6a Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 24 Oct 2026 01:39:42 +0200 Subject: [PATCH] shader: complete faulting SRT walker loads as zero reads --- .../shader/recompiler/ir/passes/SrtWalker.cpp | 35 +++++++++++++++---- .../shader/recompiler/ir/passes/SrtWalker.h | 3 ++ src/loader/runtimeLinker.cpp | 6 ++++ src/loader/x64InstructionEmulator.cpp | 6 ++++ src/loader/x64InstructionEmulator.h | 4 +++ tests/ResourceMaterializationTests.cpp | 24 +++++++++++++ 6 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp index 30070e561..babb5959d 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.cpp @@ -176,6 +176,14 @@ bool IsRuntimeUniformOp(ValueOpcode op) { } } +thread_local const Xbyak::CodeGenerator* t_running_walker = nullptr; + +// Every guest load is mov eax, [r10] (41 8b 02), so a fault completes it as a zero read. +void LoadGuest(Xbyak::CodeGenerator& code, const Xbyak::RegExp& address) { + code.lea(code.r10, code.ptr[address]); + code.mov(code.eax, code.dword[code.r10]); +} + class RuntimeValidator { public: explicit RuntimeValidator(const ResourcePlan& program, RuntimeValueType type, @@ -449,8 +457,13 @@ void CompileSrtWalker(ResourcePlan& program) { // descend through its child pointers. Seed the descriptor evaluator's memo. const auto visit = [&](auto&& self, int32_t pointer_offset, const Reads& reads) -> void { code.push(code.rdi); - code.mov(code.rdi, code.qword[code.rdi + pointer_offset]); - code.and_(code.rdi, code.r9); + LoadGuest(code, code.rdi + pointer_offset + 4); + code.shl(code.rax, 32); + code.mov(code.r11, code.rax); + LoadGuest(code, code.rdi + pointer_offset); + code.or_(code.rax, code.r11); + code.and_(code.rax, code.r9); + code.mov(code.rdi, code.rax); const auto first = reads.begin()->first; const auto last = reads.rbegin()->first; if (first < 0) { @@ -467,9 +480,9 @@ void CompileSrtWalker(ResourcePlan& program) { const auto index = read->value.ResolveInstruction()->EvaluationIndex(program.evaluation_value_count); using Entry = ResourcePlan::EvaluationContext::Entry; const auto slot = code.rdx + index * sizeof(Entry); - code.mov(code.r10d, code.dword[code.rdi + offset]); - code.mov(code.dword[code.rsi + read->flat_offset * sizeof(uint32_t)], code.r10d); - code.mov(code.qword[slot + offsetof(Entry, value)], code.r10); + LoadGuest(code, code.rdi + offset); + code.mov(code.dword[code.rsi + read->flat_offset * sizeof(uint32_t)], code.eax); + code.mov(code.qword[slot + offsetof(Entry, value)], code.rax); code.mov(code.qword[slot + offsetof(Entry, generation)], code.rcx); } for (const auto& [offset, read]: reads) { @@ -1146,8 +1159,11 @@ bool SrtWalker::RefreshFlatBuffer(std::vector& flat) { m_context.values.resize(m_program.evaluation_value_count); using Walker = bool KYTY_SYSV_ABI (*)(const uint32_t*, uint32_t*, ResourcePlan::EvaluationContext::Entry*, uint64_t, size_t); - return m_program.srt_code->getCode()(m_runtime.user_data.data(), flat.data(), + t_running_walker = m_program.srt_code.get(); + const bool walked = m_program.srt_code->getCode()(m_runtime.user_data.data(), flat.data(), m_context.values.data(), m_context.generation, m_runtime.user_data.size()); + t_running_walker = nullptr; + return walked; } const auto refresh = [&](uint32_t slot) { if (slot >= m_program.srt_reads.size()) return false; @@ -1228,5 +1244,12 @@ bool ValidateWaveUniformValue(const ResourcePlan& program, Value value) { return RuntimeValidator(program, RuntimeValueType::Any, true).Run(value); } +uint32_t SrtWalkerLoadLength(uint64_t rip) { + if (t_running_walker == nullptr || + rip - reinterpret_cast(t_running_walker->getCode()) >= t_running_walker->getSize()) + return 0; + const auto* code = reinterpret_cast(rip); + return code[0] == 0x41u && code[1] == 0x8bu && code[2] == 0x02u ? 3 : 0; +} } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h index f8b99c092..daa21d480 100644 --- a/src/graphics/shader/recompiler/ir/passes/SrtWalker.h +++ b/src/graphics/shader/recompiler/ir/passes/SrtWalker.h @@ -79,6 +79,9 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value, // This does not imply that the host can evaluate the value. bool ValidateWaveUniformValue(const ResourcePlan& program, Value value); void CompileSrtWalker(ResourcePlan& program); +// Length of the guest load at rip if it belongs to the walker running on this thread, else 0. +// The exception handler completes such a load as a zero read. +uint32_t SrtWalkerLoadLength(uint64_t rip); // Uses the strict reader for values that affect shader specialization. SrtRuntime CleanRuntime(SrtRuntime runtime); diff --git a/src/loader/runtimeLinker.cpp b/src/loader/runtimeLinker.cpp index 10557aec4..8c57790b0 100644 --- a/src/loader/runtimeLinker.cpp +++ b/src/loader/runtimeLinker.cpp @@ -14,6 +14,7 @@ #include "common/threads.h" #include "common/virtualMemory.h" #include "graphics/host_gpu/pageManager.h" +#include "graphics/shader/recompiler/ir/passes/SrtWalker.h" #include "kernel/fileSystem.h" #include "kernel/memory.h" #include "kernel/pthread.h" @@ -672,6 +673,11 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc Common::DebugCounters::Add(Common::DebugCounters::Counter::GpuFaults); return true; } + if (const auto length = + Libs::Graphics::ShaderRecompiler::IR::SrtWalkerLoadLength(info->exception_address)) { + Loader::X64InstructionEmulator::SkipLoad(info->native_context, length); + return true; + } } // Report whatever guest context can be read safely before terminating: which guest thread // faulted, the register file, the faulting code bytes and the top of its stack. diff --git a/src/loader/x64InstructionEmulator.cpp b/src/loader/x64InstructionEmulator.cpp index 7500c3645..6c094fecd 100644 --- a/src/loader/x64InstructionEmulator.cpp +++ b/src/loader/x64InstructionEmulator.cpp @@ -822,4 +822,10 @@ bool TryEmulate(void* native_context) { TryEmulateCpuExtensions(context); } +void SkipLoad(void* native_context, size_t length) { + Context context {static_cast(native_context)}; + context.Gpr(0) = 0; + context.Advance(length); +} + } // namespace Loader::X64InstructionEmulator diff --git a/src/loader/x64InstructionEmulator.h b/src/loader/x64InstructionEmulator.h index 7fddd779a..06b1449a0 100644 --- a/src/loader/x64InstructionEmulator.h +++ b/src/loader/x64InstructionEmulator.h @@ -1,9 +1,13 @@ #ifndef KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_ #define KYTY_LOADER_X64_INSTRUCTION_EMULATOR_H_ +#include + namespace Loader::X64InstructionEmulator { [[nodiscard]] bool TryEmulate(void* native_context); +// Completes a faulting load into EAX as a zero read and resumes after it. +void SkipLoad(void* native_context, size_t length); } // namespace Loader::X64InstructionEmulator diff --git a/tests/ResourceMaterializationTests.cpp b/tests/ResourceMaterializationTests.cpp index b2ee7bd47..672c7896e 100644 --- a/tests/ResourceMaterializationTests.cpp +++ b/tests/ResourceMaterializationTests.cpp @@ -7,6 +7,12 @@ #include #include +#if defined(__linux__) +#include +#include +#include +#endif + namespace { void Check(bool value, const char *text) { @@ -227,6 +233,24 @@ void TestNativeNestedSrtRefresh() { std::vector{(*data)[1], table[0], table[1], (*data)[2]}, "native SRT refresh lost pointer masking, alignment, or current values"); } +#if defined(__linux__) + struct sigaction action {}, previous {}; + action.sa_sigaction = +[](int, siginfo_t *, void *context) { + auto ®isters = static_cast(context)->uc_mcontext.gregs; + const auto length = SrtWalkerLoadLength(static_cast(registers[REG_RIP])); + if (length == 0) std::abort(); + registers[REG_RAX] = 0; + registers[REG_RIP] += length; + }; + action.sa_flags = SA_SIGINFO; + auto *page = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Check(page != MAP_FAILED && sigaction(SIGSEGV, &action, &previous) == 0 && + MaterializeResources(plan, {.user_data = pointer_words(page)}, snapshot, specialization) && + snapshot.flattened_srt == std::vector(4, 0u), + "faulting walker loads of a table and a null pointer did not read zero"); + sigaction(SIGSEGV, &previous, nullptr); + munmap(page, 4096); +#endif Check(!MaterializeResources(plan, {.user_data = std::span(user_data).first(1)}, snapshot, specialization), "native SRT walk accepted truncated user data");