fix(recompiler): read SRT words through a null user-data pointer as zero
This commit is contained in:
parent
9206c3e621
commit
27e1ddca99
|
|
@ -2529,7 +2529,12 @@ void verifyGuardedNullPointers() {
|
|||
}
|
||||
NestedRequest rootless(branchCode, nullptr);
|
||||
AgcDriver::ShaderMemory rootlessMemory({});
|
||||
expectFailure([&] { static_cast<void>(rootlessMemory.Capture(rootless.request)); }, "null or misaligned address", "guarded pointer: a null user-data pointer was accepted");
|
||||
const auto rootlessCapture = rootlessMemory.Capture(rootless.request);
|
||||
require(rootlessCapture->snapshot.nullRootReads != 0u && GuardRecords(*rootlessCapture), "guarded pointer: reads through a null user-data pointer were not counted");
|
||||
require(std::all_of(rootlessCapture->snapshot.buffers.begin(), rootlessCapture->snapshot.buffers.end(), [](const DescriptorValue& value) { return std::all_of(value.dwords.begin(), value.dwords.end(), [](std::uint32_t word) { return word == 0u; }); }), "guarded pointer: a V# read through a null user-data pointer is not zero");
|
||||
rootless.request.context.memory = rootlessMemory.Regions();
|
||||
const auto rootlessResult = Recompile(rootless.request, *rootlessCapture);
|
||||
require(rootlessResult->poisonedSrtReads >= rootlessCapture->snapshot.nullRootReads, "guarded pointer: a capture through a null user-data pointer is cacheable");
|
||||
point(0u);
|
||||
#ifdef _WIN32
|
||||
VirtualFree(reserved, 0, MEM_RELEASE);
|
||||
|
|
@ -2788,8 +2793,11 @@ int main(int argc, char** argv) {
|
|||
request.context.memory = {};
|
||||
const std::array<std::uint32_t, 2> nullUserData{};
|
||||
request.context.userData = nullUserData;
|
||||
AgcDriver::ShaderMemory invalid({});
|
||||
expectFailure([&] { invalid.Capture(request); }, "null or misaligned address", "null user-data pointer was accepted");
|
||||
AgcDriver::ShaderMemory nullRoot({});
|
||||
const auto nullRootCapture = nullRoot.Capture(request);
|
||||
require(nullRootCapture->snapshot.nullRootReads != 0u, "reads through a null user-data pointer were not counted");
|
||||
request.context.memory = nullRoot.Regions();
|
||||
require(Recompile(request, *nullRootCapture)->poisonedSrtReads >= nullRootCapture->snapshot.nullRootReads, "a capture through a null user-data pointer is cacheable");
|
||||
std::cout << "Shader memory capture, strict validation and deterministic replay passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ struct ResourceSnapshot {
|
|||
std::vector<std::uint32_t> userData;
|
||||
UniformFill uniformFill;
|
||||
std::vector<SrtReadPoison> srtPoison;
|
||||
std::uint32_t nullRootReads = 0;
|
||||
};
|
||||
|
||||
struct UniformFillPlan {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public:
|
|||
void EvaluateUniformValues(const IrResourcePlan& program, std::span<IrValue* const> values, const SrtRuntime& runtime, std::span<std::uint32_t> results) const;
|
||||
void EvaluateDescriptorSource(const IrResourcePlan& program, std::uint32_t source, const SrtRuntime& runtime, DescriptorValue& result) const;
|
||||
void EvaluateDescriptorSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results) const;
|
||||
void EvaluateRuntimeSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison = nullptr) const;
|
||||
void EvaluateRuntimeSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison = nullptr, std::uint32_t* nullRootReads = nullptr) const;
|
||||
void Walk(const IrResourcePlan& program, const SrtRuntime& runtime, std::vector<std::uint32_t>& flat) const;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace ShaderRecompiler::Detail {
|
|||
// Why the last EvaluateRuntimeSourcesImpl call on this thread returned false.
|
||||
const std::string& RuntimeSourceFailureReason();
|
||||
|
||||
bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, bool evaluateFlat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison = nullptr);
|
||||
bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, bool evaluateFlat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison = nullptr, std::uint32_t* nullRootReads = nullptr);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ public:
|
|||
bool Evaluate(IrValue* value, std::uint32_t& result);
|
||||
bool EvaluateWide(IrValue* raw, std::uint64_t& result);
|
||||
void ReportInaccessibleReads(InaccessibleRead* sink) { _inaccessible = sink; }
|
||||
void ReportNullRootReads(std::uint32_t* sink) { _nullRoots = sink; }
|
||||
|
||||
private:
|
||||
static float Float32(std::uint64_t bits);
|
||||
|
|
@ -95,6 +96,7 @@ private:
|
|||
Evaluator* _cleanEvaluator = nullptr;
|
||||
IrValue* _activeMask = nullptr;
|
||||
InaccessibleRead* _inaccessible = nullptr;
|
||||
std::uint32_t* _nullRoots = nullptr;
|
||||
EvaluatedValues _cache;
|
||||
std::vector<IrValue*> _visiting;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ void materializeSnapshot(const IrResourcePlan& plan, const SrtRuntime& runtime,
|
|||
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<std::uint8_t> activeSources;
|
||||
walker.EvaluateRuntimeSources(plan, plan.materializationSources, runtime, values, snapshot.flattenedSrt, plan.cleanFlatSlots, activeSources, &snapshot.srtPoison);
|
||||
walker.EvaluateRuntimeSources(plan, plan.materializationSources, runtime, values, snapshot.flattenedSrt, plan.cleanFlatSlots, activeSources, &snapshot.srtPoison, &snapshot.nullRootReads);
|
||||
|
||||
std::size_t cursor = 0;
|
||||
if (values.size() < plan.info.buffers.size()) {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const DescriptorSource* Source(const IrResourcePlan& program, std::uint32_t sour
|
|||
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, bool evaluateFlat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison) {
|
||||
bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, bool evaluateFlat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison, std::uint32_t* nullRootReads) {
|
||||
failureReason().clear();
|
||||
static const bool debug = std::getenv("APS5_SRT_DEBUG") != nullptr;
|
||||
if (debug) {
|
||||
|
|
@ -94,9 +94,14 @@ bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const s
|
|||
}
|
||||
}
|
||||
InaccessibleRead inaccessible;
|
||||
std::uint32_t nullRoots = 0;
|
||||
if (evaluateFlat && poison != nullptr) {
|
||||
evaluator.ReportInaccessibleReads(&inaccessible);
|
||||
cleanEvaluator.ReportInaccessibleReads(&inaccessible);
|
||||
if (nullRootReads != nullptr) {
|
||||
evaluator.ReportNullRootReads(&nullRoots);
|
||||
cleanEvaluator.ReportNullRootReads(&nullRoots);
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<std::uint32_t, InaccessibleRead>> zeroedSources;
|
||||
std::vector<DescriptorValue> evaluated;
|
||||
|
|
@ -176,6 +181,10 @@ bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span<const s
|
|||
if (poison != nullptr) {
|
||||
*poison = std::move(poisoned);
|
||||
}
|
||||
if (nullRootReads != nullptr) {
|
||||
if (debug && nullRoots != 0u) std::fprintf(stderr, "[srt] %u reads through a null user-data pointer: zero words\n", nullRoots);
|
||||
*nullRootReads = nullRoots;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,11 @@ bool Evaluator::EvaluateRawRead(IrValue& inst, std::uint64_t& result) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (_nullRoots != nullptr && base == 0 && !LoadedFromMemory(*handle)) {
|
||||
++*_nullRoots;
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
if (_inaccessible != nullptr && _runtime.accessible != nullptr && !_runtime.accessible(_runtime.userContext, address, sizeof(std::uint32_t)) && LoadedFromMemory(*handle)) {
|
||||
*_inaccessible = {&inst, address};
|
||||
return false;
|
||||
|
|
@ -192,6 +197,7 @@ bool Evaluator::EvaluateInst(IrValue& inst, std::uint64_t& result) {
|
|||
case IrOpcode::ReadFirstLane: {
|
||||
Evaluator active(_program, _runtime, _cleanFlatSlots, _cleanEvaluator, inst.Argument(1));
|
||||
active.ReportInaccessibleReads(_inaccessible);
|
||||
active.ReportNullRootReads(_nullRoots);
|
||||
return active.EvaluateWide(inst.Argument(0), result);
|
||||
}
|
||||
case IrOpcode::BitCastU32F32:
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ void SrtWalker::EvaluateDescriptorSources(const IrResourcePlan& program, std::sp
|
|||
}
|
||||
}
|
||||
|
||||
void SrtWalker::EvaluateRuntimeSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison) const {
|
||||
if (!Detail::EvaluateRuntimeSourcesImpl(program, sources, runtime, results, flat, true, cleanFlatSlots, activeSources, poison)) {
|
||||
void SrtWalker::EvaluateRuntimeSources(const IrResourcePlan& program, std::span<const std::uint32_t> sources, const SrtRuntime& runtime, std::vector<DescriptorValue>& results, std::vector<std::uint32_t>& flat, std::span<const std::uint8_t> cleanFlatSlots, std::vector<std::uint8_t>& activeSources, std::vector<SrtReadPoison>* poison, std::uint32_t* nullRootReads) const {
|
||||
if (!Detail::EvaluateRuntimeSourcesImpl(program, sources, runtime, results, flat, true, cleanFlatSlots, activeSources, poison, nullRootReads)) {
|
||||
throw std::runtime_error("SrtWalker::EvaluateRuntimeSources failed to evaluate runtime sources: " + Detail::RuntimeSourceFailureReason());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -769,7 +769,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
|
|||
result.workgroupMemoryDwords = WorkgroupMemoryStrideDwords(variant.info.info);
|
||||
result.bindings = std::move(bindings.bindings);
|
||||
result.pushConstants = std::move(bindings.pushConstants);
|
||||
result.poisonedSrtReads = static_cast<std::uint32_t>(snapshot.srtPoison.size());
|
||||
result.poisonedSrtReads = static_cast<std::uint32_t>(snapshot.srtPoison.size()) + snapshot.nullRootReads;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -356,7 +356,8 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Shader MODE register access](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) reads the initial rounding fields from `GuestContext::floatMode`; missing metadata retains the legacy round-to-nearest-even assumption. Immediate rounding writes are accepted only when they preserve that state. State-changing or SGPR-sourced MODE writes throw until runtime mode tracking and affected arithmetic are implemented. Non-rounding register reads still throw; `s_setreg_b32` writes to other hardware registers are still ignored. Tests follow the public RDNA2/LLVM contract, not console measurements.
|
||||
- A [flat SRT read](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) through a null or unmapped pointer loaded from memory raises a [BDA fault](../../core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp) only if the program executes it, then stops or reads zero like a BDA read.
|
||||
- Repeated `s_load`s of one [flat SRT](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) dword through a null or unmapped pointer report one of their pcs, and an `s_load` whose inaccessible dwords are all unused, or only address repeated loads, reports no fault of its own.
|
||||
- A value read through a null or unmapped pointer still throws at record time if a [branch condition, uniform fill or table base](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) needs it, or if the pointer comes from user data, even if the console skips the read.
|
||||
- A value read through a null or unmapped pointer still throws at record time if a [branch condition, uniform fill or table base](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) needs it, or if the pointer comes from user data and is not null, even if the console skips the read.
|
||||
- A [V# or flat SRT word](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp) read through a null pointer taken from user data is zero and counts as a [poisoned SRT read](../../core/shader/recompiler/Recompiler.cpp), so a dispatch that reads it is not cached. The console faults if the program executes the read; PPSA28416 branches around it with `s_cbranch_execz`. The fault is not modeled.
|
||||
- A [V#, T# or S#](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) loaded through a null or unmapped pointer read from memory is zero when the work is recorded, and the capture throws if no flat SRT slot records that read.
|
||||
- [2D accesses through a 1D T#](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read the texel the 1D addressing selects and ignore the second coordinate, as simonbbby measured on RDNA2 (gfx1036) in #2237 for `image_sample_lz` and mip-less `image_load` with point filtering and wrap, clamp and border addressing; the PS5's gfx1013 is not measured. Other 2D accesses through a 1D T# (storage, depth, packed, BY loads, mips, derivatives, comparison) still throw.
|
||||
- On a host subgroup narrower than 32 lanes, [`threadBit`](../../core/shader/recompiler/Translation/src/TranslationContext/ExecMask.cpp) reads an EXEC written as a value (`s_mov_b32`/`s_mov_b64`) at bit (subgroup invocation id) of EXEC_LO, so lanes at or above the subgroup size read a lower lane's bit.
|
||||
|
|
|
|||
Loading…
Reference in New Issue