1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
|
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-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.
*/
#pragma once
#include "containers/span.h"
#include "core_checks/core_validation.h"
#include "state_tracker/buffer_state.h"
#include "error_message/logging.h"
#include <array>
#include <functional>
#include <sstream>
#include <string>
#include <string_view>
/* This class aims at helping with the validation of a family of VUIDs referring to the same buffer device address.
For example, take those VUIDs for VkDescriptorBufferBindingInfoEXT:
VUID-VkDescriptorBufferBindingInfoEXT-usage-08122
If usage includes VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT, address must be an address within a valid buffer that was
created with VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT
For usage to be valid, since the mentioned address can refer to multiple buffers, one must find a buffer that satisfies *all*
of them. One must *not* consider those VUIDs independantly, each time trying to find a buffer that satisfies the considered VUID
but not necessarily the others in the family.
The VVL heuristic wants that the vast majority of the time, functions calls and structures are valid, thus validation
should be fast and avoid to do things related to error logging unless necessary. To comply with that, buffer address
validation is done in two passes: one to look for a buffer satisfying all VUIDs of the considered family. If none
is found, another pass is done, this time building a per VUID error message (not per buffer) regrouping all buffers
violating it. Outputting for each buffer every VUID it violates would lead to unnecessary log clutter.
This two pass process is tedious to do without an helper class, hence BufferAddressValidation was created.
The idea is to ask for user to provide the only necessary data:
a VUID, how it is validated, what to log when a buffer violates it, and a snippet of text appended to the error message header.
Then, just a call to ValidateDeviceAddress is needed to do validation and, eventually, error logging.
For an example of how to use BufferAddressValidation, see for instance how "VUID-VkDescriptorBufferBindingInfoEXT-usage-08122"
and friends are validated.
More details in https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/7517
*/
// This file usage is very limited, such that we should be able to claim this name in global userspace
using ErrorMsgBuffer = std::function<std::string(const vvl::Buffer&)>;
// These are to help unify the various checks to use the same language
// The only reason to not use these is if you need special extra/modified information printed out
static inline const ErrorMsgBuffer kEmptyErrorMsgBuffer = [](const vvl::Buffer&) { return ""; };
static inline const ErrorMsgBuffer kUsageErrorMsgBuffer = [](const vvl::Buffer& buffer_state) {
return "has usage " + string_VkBufferUsageFlags2(buffer_state.usage);
};
template <size_t ChecksCount = 1>
class BufferAddressValidation {
using IsInvalidFunction = std::function<bool(const vvl::Buffer&)>;
using ErrorMsgHeaderFunction = std::function<std::string()>;
using UpdateCallback = std::function<void(const vvl::Buffer&)>;
public:
struct VuidAndValidation {
std::string_view vuid{};
// Return true if for a given buffer, it is invalid
IsInvalidFunction is_invalid_func;
// Text appended to error message header (for the VU as a whole)
ErrorMsgHeaderFunction error_msg_header_func;
// List dedicated error per buffer
ErrorMsgBuffer error_msg_buffer_func;
};
// +1 for extra check for "valid generic VkDeviceAddress" check
std::array<VuidAndValidation, ChecksCount + 1> vuid_and_validations;
// There are times the caller will want to update state for each buffer object found
UpdateCallback update_callback = [](const vvl::Buffer&) {};
// We use vvl::DeviceProxy instead of CoreChecks here as a current hack to allow GPU-AV to use this. We still need a better
// system to share CoreChecks with GPU-AV
[[nodiscard]] bool ValidateDeviceAddress(const vvl::DeviceProxy& validator, const Location& device_address_loc,
const LogObjectList& objlist, VkDeviceAddress device_address,
VkDeviceSize range_size = 0) noexcept {
bool skip = false;
// There will be an implicit VU like "must be a valid VkDeviceAddress value" and if can't be zero, stateless validation
// should have caught this already
if (device_address == 0) {
return skip;
}
vvl::span<vvl::Buffer* const> buffer_list = validator.GetBuffersByAddress(device_address);
if (buffer_list.empty()) {
NearestBufferResult nearest = validator.GetNearestBuffersByAddress(device_address);
std::ostringstream ss;
ss << "(0x" << std::hex << device_address
<< ") is not a valid buffer address. No call to vkGetBufferDeviceAddress has this buffer in its range.";
if (!nearest.above_buffers.empty()) {
ss << "\nAbove at range " << string_range_hex(nearest.above_range) << " has buffers:";
for (const auto buffer : nearest.above_buffers) {
// use std::to_string() to print as simple way to print size as decimal, not hex, like everything else
ss << "\n " << buffer->Describe(validator);
}
}
if (!nearest.below_buffers.empty()) {
ss << "\nBelow at range " << string_range_hex(nearest.below_range) << " has buffers:";
for (const auto buffer : nearest.below_buffers) {
ss << "\n " << buffer->Describe(validator);
}
}
skip |= validator.LogError("VUID-VkDeviceAddress-size-11364", objlist, device_address_loc, "%s", ss.str().c_str());
}
// Checks if memory is in a completely and contiguously to a single VkDeviceMemory object
// Everyone needs to check for this in order to be a valid VkDeviceAddress
vuid_and_validations[ChecksCount] = {
"VUID-VkDeviceAddress-None-10894",
[](const vvl::Buffer& buffer_state) { return !buffer_state.sparse && !buffer_state.IsMemoryBound(); },
[]() { return "The following buffers are not bound to memory or it has been freed"; },
[&validator](const vvl::Buffer& buffer_state) {
const auto memory_state = buffer_state.MemoryState();
if (memory_state && memory_state->Destroyed()) {
return "buffer is bound to memory (" + validator.FormatHandle(memory_state->Handle()) +
") but it has been freed";
}
return std::string("buffer has not been bound to memory");
}};
if (!HasValidBuffer(buffer_list)) {
skip |= LogInvalidBuffers(validator, buffer_list, device_address_loc, objlist, device_address, range_size);
}
return skip;
}
private:
// Look for a buffer that satisfies all VUIDs
[[nodiscard]] bool HasValidBuffer(vvl::span<vvl::Buffer* const> buffer_list) const noexcept;
// For every vuid, build an error mentioning every buffer from buffer_list that violates it, then log this error
// using details provided by the other parameters.
[[nodiscard]] bool LogInvalidBuffers(const vvl::DeviceProxy& validator, vvl::span<vvl::Buffer* const> buffer_list,
const Location& device_address_loc, const LogObjectList& objlist,
VkDeviceAddress device_address, VkDeviceSize range_size) const noexcept;
struct Error {
LogObjectList objlist;
std::string error_msg;
bool Empty() const { return error_msg.empty(); }
};
};
template <size_t ChecksCount>
bool BufferAddressValidation<ChecksCount>::HasValidBuffer(vvl::span<vvl::Buffer* const> buffer_list) const noexcept {
bool any_buffer_found = false;
for (const auto& buffer : buffer_list) {
ASSERT_AND_CONTINUE(buffer);
// Call here as we will need to update once for each buffer
update_callback(*buffer);
bool is_buffer_valid = true;
// Once we find any buffer is valid, can just skip checking
if (!any_buffer_found) {
for (const auto& vav : vuid_and_validations) {
if (vav.is_invalid_func(*buffer)) {
is_buffer_valid = false;
break;
}
}
}
any_buffer_found |= is_buffer_valid;
}
return any_buffer_found;
}
template <size_t ChecksCount>
bool BufferAddressValidation<ChecksCount>::LogInvalidBuffers(const vvl::DeviceProxy& validator,
vvl::span<vvl::Buffer* const> buffer_list,
const Location& device_address_loc, const LogObjectList& objlist,
VkDeviceAddress device_address,
VkDeviceSize range_size) const noexcept {
std::array<Error, ChecksCount + 1> errors;
// Build error message beginning. Then, only per buffer error needs to be appended.
std::string error_msg_beginning;
// Some checks only care about the address, but for those that have a range, print it here so it is the same across all error
// messages
if (range_size != 0) {
std::ostringstream ss;
ss << "[0x" << std::hex << device_address << ", 0x" << (device_address + range_size) << ") (";
if (range_size == VK_WHOLE_SIZE) {
ss << "VK_WHOLE_SIZE";
} else {
ss << std::dec << range_size << " bytes";
}
ss << ") has no buffer(s) associated that are valid.\n";
// VK_WHOLE_SIZE is "allowed" in VkDescriptorAddressInfoEXT to express a null descriptor
// Everywhere else, it is invalid (https://gitlab.khronos.org/vulkan/vulkan/-/issues/4538)
if (range_size == VK_WHOLE_SIZE) {
ss << "(VK_WHOLE_SIZE can't be used alongside a VkDeviceAdress, there is no guarantee which VkBuffer is used so the "
"user needs to provide a size.)\n";
}
error_msg_beginning = ss.str();
} else {
std::ostringstream ss;
ss << "(0x" << std::hex << device_address << ") has no buffer(s) associated that are valid.\n";
error_msg_beginning = ss.str();
}
// For each buffer, and for each violated VUID, build an error message
for (const auto& buffer : buffer_list) {
ASSERT_AND_CONTINUE(buffer);
for (size_t i = 0; i < (ChecksCount + 1); ++i) {
[[maybe_unused]] const auto& [vuid, is_invalid_func, error_msg_header_func, error_msg_buffer_func] =
vuid_and_validations[i];
if (!is_invalid_func(*buffer)) {
continue;
}
// Add faulty buffer to current vuid LogObjectList
errors[i].objlist.add(buffer->Handle());
auto& error_msg = errors[i].error_msg;
// Append faulty buffer error message
if (error_msg.empty()) {
error_msg += error_msg_beginning;
error_msg += error_msg_header_func();
error_msg += ":\n";
}
// Always print the buffer range/size
error_msg += " "; // small indent help to visualize
error_msg += buffer->Describe(validator);
error_msg += " ";
error_msg += error_msg_buffer_func(*buffer);
error_msg += "\n";
}
}
// Output the error messages
bool skip = false;
for (size_t i = 0; i < (ChecksCount + 1); ++i) {
const auto& vav = vuid_and_validations[i];
auto& error = errors[i];
if (!error.Empty()) {
// Add user provided handles, typically the current command buffer or the device
for (const auto& obj : objlist) {
error.objlist.add(obj);
}
skip |= validator.LogError(vav.vuid.data(), error.objlist, device_address_loc, "%s", error.error_msg.c_str());
}
}
return skip;
}
[[maybe_unused]] static std::string PrintBufferRanges(const CoreChecks& validator, vvl::span<vvl::Buffer* const> buffers) {
std::ostringstream ss;
for (const auto& buffer : buffers) {
ss << " " << buffer->Describe(validator) << '\n';
}
return ss.str();
}
|