layers: Remove object handle race from FreeMemory
Fixes a race between a freeing and allocating threads s.t.
core_validation internal object data was being corrupted.
Change-Id: I8e6767fff6c665a229ababf3c72f9e2a5e1e502b
diff --git a/layers/core_validation.cpp b/layers/core_validation.cpp
index 562318c..2e1598b 100644
--- a/layers/core_validation.cpp
+++ b/layers/core_validation.cpp
@@ -389,6 +389,17 @@
return mem_it->second.get();
}
+// Extracts (moves) the mem info structure from the object map, passing ownership to the caller
+std::unique_ptr<DEVICE_MEM_INFO> ExtractMemObjInfo(layer_data *dev_data, const VkDeviceMemory mem) {
+ std::unique_ptr<DEVICE_MEM_INFO> extracted;
+ auto mem_it = dev_data->memObjMap.find(mem);
+ if (mem_it != dev_data->memObjMap.end()) {
+ extracted = std::move(mem_it->second);
+ dev_data->memObjMap.erase(mem_it);
+ }
+ return extracted;
+}
+
static void add_mem_obj_info(layer_data *dev_data, void *object, const VkDeviceMemory mem,
const VkMemoryAllocateInfo *pAllocateInfo) {
assert(object != NULL);
@@ -3022,7 +3033,7 @@
}
// For given obj node, if it is use, flag a validation error and return callback result, else return false
-bool ValidateObjectNotInUse(const layer_data *dev_data, BASE_NODE *obj_node, VK_OBJECT obj_struct, const char *caller_name,
+bool ValidateObjectNotInUse(const layer_data *dev_data, const BASE_NODE *obj_node, VK_OBJECT obj_struct, const char *caller_name,
UNIQUE_VALIDATION_ERROR_CODE error_code) {
if (dev_data->instance_data->disabled.object_in_use) return false;
bool skip = false;
@@ -3035,19 +3046,23 @@
return skip;
}
-static bool PreCallValidateFreeMemory(layer_data *dev_data, VkDeviceMemory mem, DEVICE_MEM_INFO **mem_info, VK_OBJECT *obj_struct) {
+// Remove the mem_info from the map to avoid race with Free/Alloc memory when unique_objects is disabled
+static void PreCallRecordFreeMemory(layer_data *dev_data, VkDeviceMemory mem, std::unique_ptr<DEVICE_MEM_INFO> *mem_info,
+ VK_OBJECT *obj_struct) {
*obj_struct = {HandleToUint64(mem), kVulkanObjectTypeDeviceMemory};
if (mem == VK_NULL_HANDLE) {
*mem_info = nullptr;
- return false;
} else {
- *mem_info = GetMemObjInfo(dev_data, mem);
+ *mem_info = ExtractMemObjInfo(dev_data, mem);
}
+}
+static bool PreCallValidateFreeMemory(layer_data *dev_data, VkDeviceMemory mem, const DEVICE_MEM_INFO *mem_info,
+ const VK_OBJECT &obj_struct) {
if (dev_data->instance_data->disabled.free_memory) return false;
bool skip = false;
- if (*mem_info) {
- skip |= ValidateObjectNotInUse(dev_data, *mem_info, *obj_struct, "vkFreeMemory", VALIDATION_ERROR_2880054a);
+ if (mem_info) {
+ skip |= ValidateObjectNotInUse(dev_data, mem_info, obj_struct, "vkFreeMemory", VALIDATION_ERROR_2880054a);
}
return skip;
}
@@ -3058,20 +3073,6 @@
// If mem_info is null (and mem isn't), this error will be reported by object_tracker, so silently be defensive here.
if (!mem_info) return;
- // device_data has been unlocked between Pre and Post. Looking to see if a racing double free occurred.
- DEVICE_MEM_INFO *mem_info_check = GetMemObjInfo(dev_data, mem);
- if (mem_info_check != mem_info) { // Device data memory object information has changed since the Pre call
- log_msg(dev_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT,
- HandleToUint64(mem), VALIDATION_ERROR_UNDEFINED,
- "vkFreeMemory(): Device memory 0x%" PRIx64 " on device 0x%" PRIx64
- ". INTERNAL VALIDATION STATE CHANGED (core validation layer) during free. Potentially a "
- "'Host Synchronization' error, combined with a double free operation",
- HandleToUint64(mem), HandleToUint64(dev_data->device));
- // Assume the old state is stale, which means the new state is a later allocation, so any cleanup we do will corrupt its
- // state, and thus do nothing
- return;
- }
-
for (auto obj : mem_info->obj_bindings) {
log_msg(dev_data->report_data, VK_DEBUG_REPORT_INFORMATION_BIT_EXT, get_debug_report_enum[obj.type], obj.handle,
MEMTRACK_FREED_MEM_REF, "VK Object 0x%" PRIx64 " still has a reference to mem obj 0x%" PRIx64,
@@ -3095,22 +3096,26 @@
}
// Any bound cmd buffers are now invalid
invalidateCommandBuffers(dev_data, mem_info->cb_bindings, obj_struct);
- dev_data->memObjMap.erase(mem);
}
VKAPI_ATTR void VKAPI_CALL FreeMemory(VkDevice device, VkDeviceMemory mem, const VkAllocationCallbacks *pAllocator) {
layer_data *dev_data = GetLayerDataPtr(get_dispatch_key(device), layer_data_map);
- DEVICE_MEM_INFO *mem_info = nullptr;
+ std::unique_ptr<DEVICE_MEM_INFO> mem_info;
VK_OBJECT obj_struct;
unique_lock_t lock(global_lock);
- bool skip = PreCallValidateFreeMemory(dev_data, mem, &mem_info, &obj_struct);
+ PreCallRecordFreeMemory(dev_data, mem, &mem_info, &obj_struct);
+ bool skip = PreCallValidateFreeMemory(dev_data, mem, mem_info.get(), obj_struct);
if (!skip) {
lock.unlock();
dev_data->dispatch_table.FreeMemory(device, mem, pAllocator);
lock.lock();
if (mem != VK_NULL_HANDLE) {
- PostCallRecordFreeMemory(dev_data, mem, mem_info, obj_struct);
+ PostCallRecordFreeMemory(dev_data, mem, mem_info.get(), obj_struct);
+ // Note that the mem_info will be freed when it goes out of scope
}
+ } else {
+ // Reinsert the extract mem_info into the object map as we didn't free anything
+ dev_data->memObjMap[mem] = std::move(mem_info);
}
}
diff --git a/layers/core_validation_types.h b/layers/core_validation_types.h
index 8cf2d0f..1ad3d78 100644
--- a/layers/core_validation_types.h
+++ b/layers/core_validation_types.h
@@ -902,7 +902,7 @@
void AddCommandBufferBindingImageView(const layer_data *, GLOBAL_CB_NODE *, IMAGE_VIEW_STATE *);
void AddCommandBufferBindingBuffer(const layer_data *, GLOBAL_CB_NODE *, BUFFER_STATE *);
void AddCommandBufferBindingBufferView(const layer_data *, GLOBAL_CB_NODE *, BUFFER_VIEW_STATE *);
-bool ValidateObjectNotInUse(const layer_data *dev_data, BASE_NODE *obj_node, VK_OBJECT obj_struct, const char *caller_name,
+bool ValidateObjectNotInUse(const layer_data *dev_data, const BASE_NODE *obj_node, VK_OBJECT obj_struct, const char *caller_name,
UNIQUE_VALIDATION_ERROR_CODE error_code);
void invalidateCommandBuffers(const layer_data *dev_data, std::unordered_set<GLOBAL_CB_NODE *> const &cb_nodes, VK_OBJECT obj);
void RemoveImageMemoryRange(uint64_t handle, DEVICE_MEM_INFO *mem_info);