Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ vvl_sources = [
"layers/gpuav/instrumentation/register_validation.h",
"layers/gpuav/instrumentation/sanitizer.cpp",
"layers/gpuav/instrumentation/shared_memory_data_race.cpp",
"layers/gpuav/instrumentation/array_oob.cpp",
"layers/gpuav/instrumentation/trace_ray.cpp",
"layers/gpuav/instrumentation/vertex_attribute_fetch_oob.cpp",
"layers/gpuav/resources/gpuav_vulkan_objects.cpp",
Expand Down Expand Up @@ -264,6 +265,8 @@ vvl_sources = [
"layers/gpuav/spirv/sanitizer_pass.h",
"layers/gpuav/spirv/shared_memory_data_race_pass.h",
"layers/gpuav/spirv/shared_memory_data_race_pass.cpp",
"layers/gpuav/spirv/array_oob_pass.cpp",
"layers/gpuav/spirv/array_oob_pass.h",
"layers/gpuav/spirv/debug_descriptor_pass.cpp",
"layers/gpuav/spirv/debug_descriptor_pass.h",
"layers/gpuav/spirv/debug_printf_pass.cpp",
Expand Down
1 change: 1 addition & 0 deletions layers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ target_sources(vvl PRIVATE
gpuav/instrumentation/gpuav_shader_instrumentor.h
gpuav/instrumentation/gpuav_instrumentation.h
gpuav/instrumentation/gpuav_instrumentation.cpp
gpuav/instrumentation/array_oob.cpp
gpuav/instrumentation/buffer_device_address.cpp
gpuav/instrumentation/descriptor_checks.h
gpuav/instrumentation/descriptor_checks.cpp
Expand Down
15 changes: 15 additions & 0 deletions layers/VkLayer_khronos_validation.json.in
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,21 @@
]
}
},
{
"key": "gpuav_array_oob",
"label": "Array Out of Bounds Detection",
"description": "Detects out-of-bounds accesses on arrays and vectors in Workgroup, Private, and Function storage during GPU-AV shader instrumentation.",
"type": "BOOL",
"view": "DEBUG",
"default": true,
"dependence": {
"mode": "ALL",
"settings": [
{ "key": "gpuav_enable", "value": true },
{ "key": "gpuav_shader_instrumentation", "value": true }
]
}
},
{
"key": "gpuav_max_indices_count",
"label": "Maximum Indices",
Expand Down
14 changes: 14 additions & 0 deletions layers/error_message/spirv_logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,20 @@ void FindShaderSource(std::ostringstream& ss, const std::vector<uint32_t>& instr
}
}

uint32_t GetOpcodeAtOffset(const std::vector<uint32_t>& instructions, uint32_t instruction_position_offset) {
uint32_t offset = kModuleStartingOffset;
while (offset < instructions.size()) {
const uint32_t instruction = instructions[offset];
if (offset >= instruction_position_offset) {
const uint32_t opcode = Opcode(instruction);
return opcode;
}
const uint32_t length = Length(instruction);
offset += length;
}
return (uint32_t)spv::OpMax;
}

void FindGlobalName(std::ostringstream& ss, const std::vector<uint32_t>& instructions, uint32_t find_opcode, uint32_t find_id) {
uint32_t shader_debug_info_set_id = 0;
uint32_t offset = kModuleStartingOffset;
Expand Down
3 changes: 3 additions & 0 deletions layers/error_message/spirv_logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ void FindGlobalName(std::ostringstream& ss, const std::vector<uint32_t>& instruc
// Will try to get the OpStruct from a BDA access
void FindOpStructFromBDA(std::ostringstream& ss, const std::vector<uint32_t>& instructions, uint32_t instruction_position_offset);

// Returns the SPIR-V opcode at the given word offset in the instruction stream
uint32_t GetOpcodeAtOffset(const std::vector<uint32_t>& instructions, uint32_t instruction_position_offset);

// These are used where we can't use normal spirv::Instructions.
// The main spot is post-processisng error message in GPU-AV, the time it takes to interchange back from a vector<uint32_t> to a
// vector<Instructions> is too high to do mid-frame. Most things just need these simple helpers
Expand Down
1 change: 1 addition & 0 deletions layers/gpuav/core/gpuav_record.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ void Validator::PreCallRecordBeginCommandBuffer(VkCommandBuffer commandBuffer, c
RegisterMeshShadingValidation(*this, gpuav_cb_state);
RegisterRayQueryValidation(*this, gpuav_cb_state);
RegisterSharedMemoryDataRaceValidation(*this, gpuav_cb_state);
RegisterArrayOobValidation(*this, gpuav_cb_state);
RegisterSanitizer(*this, gpuav_cb_state);
RegisterTraceRayValidation(*this, gpuav_cb_state);
debug_printf::RegisterDebugPrintf(*this, gpuav_cb_state);
Expand Down
5 changes: 4 additions & 1 deletion layers/gpuav/core/gpuav_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ bool GpuAVSettings::IsShaderInstrumentationEnabled() const {
return shader_instrumentation.descriptor_checks || shader_instrumentation.buffer_device_address ||
shader_instrumentation.ray_query || shader_instrumentation.trace_ray || shader_instrumentation.mesh_shading ||
shader_instrumentation.post_process_descriptor_indexing || shader_instrumentation.vertex_attribute_fetch_oob ||
shader_instrumentation.sanitizer || shader_instrumentation.shared_memory_data_race;
shader_instrumentation.sanitizer || shader_instrumentation.shared_memory_data_race ||
shader_instrumentation.array_oob;
}
bool GpuAVSettings::IsSpirvModified() const {
return IsShaderInstrumentationEnabled() || debug_printf_enabled || debug_descriptor_enabled;
Expand All @@ -50,6 +51,7 @@ void GpuAVSettings::DisableShaderInstrumentationAndOptions() {
shader_instrumentation.vertex_attribute_fetch_oob = false;
shader_instrumentation.sanitizer = false;
shader_instrumentation.shared_memory_data_race = false;
shader_instrumentation.array_oob = false;
// Because of this setting, cannot really have an "enabled" parameter to pass to this method
select_instrumented_shaders = false;
}
Expand Down Expand Up @@ -134,6 +136,7 @@ void GpuAVSettings::TracyLogSettings() const {
VVL_TRACY_PRINT_INSTRUMENTATION_SETTING(vertex_attribute_fetch_oob);
VVL_TRACY_PRINT_INSTRUMENTATION_SETTING(sanitizer);
VVL_TRACY_PRINT_INSTRUMENTATION_SETTING(shared_memory_data_race);
VVL_TRACY_PRINT_INSTRUMENTATION_SETTING(array_oob);
VVL_TRACY_PRINT_GPUAV_SETTING(debug_printf_only);
VVL_TRACY_PRINT_GPUAV_SETTING(debug_printf_enabled);
VVL_TRACY_PRINT_GPUAV_SETTING(debug_printf_to_stdout);
Expand Down
1 change: 1 addition & 0 deletions layers/gpuav/core/gpuav_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ struct GpuAVSettings {
bool vertex_attribute_fetch_oob = true;
bool sanitizer = true;
bool shared_memory_data_race = true;
bool array_oob = true;
} shader_instrumentation;

bool IsShaderInstrumentationEnabled() const;
Expand Down
86 changes: 86 additions & 0 deletions layers/gpuav/instrumentation/array_oob.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* Copyright (c) 2026 LunarG, Inc.
Comment thread
spencer-lunarg marked this conversation as resolved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "generated/spirv_grammar_helper.h"
#include "gpuav/core/gpuav.h"
#include "gpuav/resources/gpuav_state_trackers.h"
#include "gpuav/shaders/gpuav_error_codes.h"
#include "gpuav/shaders/gpuav_error_header.h"
#include "error_message/spirv_logging.h"

namespace gpuav {

void RegisterArrayOobValidation(Validator& gpuav, CommandBufferSubState& cb) {
if (!gpuav.gpuav_settings.shader_instrumentation.array_oob) {
return;
}

cb.on_instrumentation_error_logger_register_functions.emplace_back([](Validator& gpuav, CommandBufferSubState& cb,
const LastBound& last_bound) {
CommandBufferSubState::InstrumentationErrorLogger inst_error_logger =
[](Validator& gpuav, const Location& loc, const uint32_t* error_record, const InstrumentedShader* instrumented_shader,
std::string& out_error_msg, std::string& out_vuid_msg) {
using namespace glsl;
bool error_found = false;
if (GetErrorGroup(error_record) != kErrorGroup_ArrayOob) {
return error_found;
}
error_found = true;

const uint32_t index = error_record[kInst_LogError_ParameterOffset_0];
const uint32_t encoded_bound = error_record[kInst_LogError_ParameterOffset_1];
const uint32_t variable_id = error_record[kInst_LogError_ParameterOffset_2];

const uint32_t bound = encoded_bound & 0x00FFFFFFu;
const uint32_t access_type = (encoded_bound >> 24) & 0x3u;
const uint32_t dim_index = (encoded_bound >> 26) & 0x1Fu;

const uint32_t instruction_position_offset = error_record[kHeader_StageInstructionIdOffset] & kInstructionId_Mask;

std::ostringstream strm;
strm << "Variable \"";
if (instrumented_shader) {
::spirv::FindGlobalName(strm, instrumented_shader->original_spirv, (uint32_t)spv::OpVariable, variable_id);
} else {
strm << "[error, original SPIR-V not found]";
}
strm << "\" ";
if (access_type == 1) {
strm << "vector component";
} else {
strm << "array index";
if (dim_index > 0) {
strm << " (dimension " << dim_index << ")";
}
}
strm << " " << index << " is >= " << ((access_type == 1) ? "vector size " : "array size ") << bound << ".";

uint32_t opcode = (uint32_t)spv::OpMax;
if (instrumented_shader) {
opcode = ::spirv::GetOpcodeAtOffset(instrumented_shader->original_spirv, instruction_position_offset);
}
strm << GetSpirvSpecLink(opcode);

out_vuid_msg = std::string("SPIRV-ArrayOob-") + string_SpvOpcode(opcode);

out_error_msg += strm.str();
return error_found;
};

return inst_error_logger;
});
}

} // namespace gpuav
15 changes: 15 additions & 0 deletions layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "gpuav/spirv/ray_query_pass.h"
#include "gpuav/spirv/trace_ray_pass.h"
#include "gpuav/spirv/shared_memory_data_race_pass.h"
#include "gpuav/spirv/array_oob_pass.h"
#include "gpuav/spirv/mesh_shading_pass.h"
#include "gpuav/spirv/debug_printf_pass.h"
#include "gpuav/spirv/debug_descriptor_pass.h"
Expand Down Expand Up @@ -1677,6 +1678,13 @@ bool GpuShaderInstrumentor::InstrumentShader(const vvl::span<const uint32_t>& in
modified |= pass.Run();
}

// Array OOB must run before shared-memory race instrumentation so guarded stores/loads are not
// treated as races on OOB paths, and so cooperative-matrix + OOB cases get consistent errors.
if (gpuav_settings.shader_instrumentation.array_oob) {
spirv::ArrayOobPass pass(module);
modified |= pass.Run();
}

if (gpuav_settings.shader_instrumentation.shared_memory_data_race) {
spirv::SharedMemoryDataRacePass pass(module);
modified |= pass.Run();
Expand Down Expand Up @@ -1981,4 +1989,11 @@ std::string GpuShaderInstrumentor::GenerateDebugInfoMessage(VkCommandBuffer comm
return ss.str();
}

std::string GetSpirvSpecLink(const uint32_t opcode) {
// Currently the Working Group decided to not provide "real" VUIDs as it would become duplicating the SPIR-V spec
// So these are not "UNASSIGNED", but instead are "SPIRV" VUs because we can point to the instruction in the SPIR-V spec
// (https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/7853)
return "\nSee more at https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#" + std::string(string_SpvOpcode(opcode));
}

} // namespace gpuav
2 changes: 2 additions & 0 deletions layers/gpuav/instrumentation/gpuav_shader_instrumentor.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,6 @@ class GpuShaderInstrumentor : public vvl::DeviceProxy {
vvl::unordered_set<VkShaderModule> selected_instrumented_shaders;
};

std::string GetSpirvSpecLink(const uint32_t opcode);

} // namespace gpuav
1 change: 1 addition & 0 deletions layers/gpuav/instrumentation/register_validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ void RegisterMeshShadingValidation(Validator& gpuav, CommandBufferSubState& cb);
void RegisterSanitizer(Validator& gpuav, CommandBufferSubState& cb);
void RegisterVertexAttributeFetchOobValidation(Validator& gpuav, CommandBufferSubState& cb);
void RegisterSharedMemoryDataRaceValidation(Validator& gpuav, CommandBufferSubState& cb);
void RegisterArrayOobValidation(Validator& gpuav, CommandBufferSubState& cb);
void RegisterTraceRayValidation(Validator& gpuav, CommandBufferSubState& cb);

} // namespace gpuav
7 changes: 0 additions & 7 deletions layers/gpuav/instrumentation/sanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@

namespace gpuav {

static std::string GetSpirvSpecLink(const uint32_t opcode) {
// Currently the Working Group decided to not provide "real" VUIDs as it would become duplicating the SPIR-V spec
// So these are not "UNASSIGNED", but instead are "SPIRV" VUs because we can point to the instruction in the SPIR-V spec
// (https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/7853)
return "\nSee more at https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#" + std::string(string_SpvOpcode(opcode));
}

void RegisterSanitizer(Validator& gpuav, CommandBufferSubState& cb) {
if (!gpuav.gpuav_settings.shader_instrumentation.sanitizer) {
return;
Expand Down
1 change: 1 addition & 0 deletions layers/gpuav/shaders/gpuav_error_codes.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const int kErrorGroup_GpuPreBuildAccelerationStructures = 12;
const int kErrorGroup_InstMeshShading = 13;
const int kErrorGroup_SharedMemoryDataRace = 15;
const int kErrorGroup_TraceRay = 16;
const int kErrorGroup_ArrayOob = 17;

// We just take ExecutionModel and normalize it so we only use 5 bits to store it
const int kExecutionModel_Vertex = 0;
Expand Down
35 changes: 35 additions & 0 deletions layers/gpuav/shaders/instrumentation/array_oob.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2026 The Khronos Group Inc.
// Copyright (c) 2026 LunarG, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// NOTE: This file doesn't contain any entrypoints and should be compiled with the --no-link option for glslang

#version 450
#extension GL_GOOGLE_include_directive : enable
#include "common_descriptor_sets.h"
#include "error_payload.h"

bool inst_array_oob(const uint index, const uint encoded_bound, const uint inst_offset, const uint variable_id) {
const uint bound = encoded_bound & 0x00FFFFFFu;
if (index >= bound) {
error_payload = ErrorPayload(
inst_offset,
SpecConstantLinkShaderId | (kErrorGroup_ArrayOob << kErrorGroup_Shift),
index,
encoded_bound,
variable_id);
return false;
}
return true;
}
2 changes: 2 additions & 0 deletions layers/gpuav/spirv/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
add_library(gpu_av_spirv STATIC)
target_sources(gpu_av_spirv PRIVATE
# Passes
array_oob_pass.h
array_oob_pass.cpp
descriptor_indexing_oob_pass.h
descriptor_indexing_oob_pass.cpp
descriptor_class_general_buffer_pass.h
Expand Down
Loading
Loading