gpuav: Select shader to instrument post creation
diff --git a/layers/chassis/chassis_modification_state.h b/layers/chassis/chassis_modification_state.h
index 2a62d5f..a1fc0ed 100644
--- a/layers/chassis/chassis_modification_state.h
+++ b/layers/chassis/chassis_modification_state.h
@@ -68,6 +68,7 @@
 
     // When using GPU-AV the pCreateInfo is modified on the user
     bool is_modified = false;
+    bool force_shader_instrumentation = false;
     std::vector<vku::safe_VkShaderCreateInfoEXT> modified_create_infos;
     const VkShaderCreateInfoEXT* pCreateInfos = nullptr;
 
diff --git a/layers/chassis/dispatch_object.h b/layers/chassis/dispatch_object.h
index 95ec081..27688cb 100644
--- a/layers/chassis/dispatch_object.h
+++ b/layers/chassis/dispatch_object.h
@@ -254,6 +254,7 @@
     void UnwrapPnextChainHandles(const void* pNext);
     void UnwrapComputePipelineCreateInfoHandles(vku::safe_VkComputePipelineCreateInfo& safe_ci);
     void UnwrapGraphicsPipelineCreateInfoHandles(vku::safe_VkGraphicsPipelineCreateInfo& safe_ci);
+    void UnwrapShaderObjectCreateInfoHandles(vku::safe_VkShaderCreateInfoEXT& safe_ci);
 
     static std::atomic<uint64_t> global_unique_id;
     static vvl::concurrent_unordered_map<uint64_t, uint64_t, 4, HashedUint64> unique_id_mapping;
diff --git a/layers/chassis/dispatch_object_manual.cpp b/layers/chassis/dispatch_object_manual.cpp
index 7c6a408..441a561 100644
--- a/layers/chassis/dispatch_object_manual.cpp
+++ b/layers/chassis/dispatch_object_manual.cpp
@@ -2803,9 +2803,20 @@
     return result;
 }
 
+void HandleWrapper::UnwrapShaderObjectCreateInfoHandles(vku::safe_VkShaderCreateInfoEXT& safe_ci) {
+    UnwrapMappingInfo(this, safe_ci.pNext);
+
+    if (safe_ci.pSetLayouts) {
+        for (uint32_t index1 = 0; index1 < safe_ci.setLayoutCount; ++index1) {
+            safe_ci.pSetLayouts[index1] = Unwrap(safe_ci.pSetLayouts[index1]);
+        }
+    }
+}
+
 VkResult DispatchDevice::CreateShadersEXT(VkDevice device, uint32_t createInfoCount, const VkShaderCreateInfoEXT* pCreateInfos,
                                   const VkAllocationCallbacks* pAllocator, VkShaderEXT* pShaders) {
     if (!wrap_handles) return device_dispatch_table.CreateShadersEXT(device, createInfoCount, pCreateInfos, pAllocator, pShaders);
+
     small_vector<vku::safe_VkShaderCreateInfoEXT, DISPATCH_MAX_STACK_ALLOCATIONS> var_local_pCreateInfos;
     vku::safe_VkShaderCreateInfoEXT* local_pCreateInfos = nullptr;
     if (pCreateInfos) {
@@ -2814,14 +2825,7 @@
 
         for (uint32_t index0 = 0; index0 < createInfoCount; ++index0) {
             local_pCreateInfos[index0].initialize(&pCreateInfos[index0]);
-
-            UnwrapMappingInfo(this, local_pCreateInfos[index0].pNext);
-
-            if (local_pCreateInfos[index0].pSetLayouts) {
-                for (uint32_t index1 = 0; index1 < local_pCreateInfos[index0].setLayoutCount; ++index1) {
-                    local_pCreateInfos[index0].pSetLayouts[index1] = Unwrap(local_pCreateInfos[index0].pSetLayouts[index1]);
-                }
-            }
+            UnwrapShaderObjectCreateInfoHandles(local_pCreateInfos[index0]);
         }
     }
 
diff --git a/layers/gpuav/core/gpuav_setup.cpp b/layers/gpuav/core/gpuav_setup.cpp
index 1474042..b3741f7 100644
--- a/layers/gpuav/core/gpuav_setup.cpp
+++ b/layers/gpuav/core/gpuav_setup.cpp
@@ -82,7 +82,9 @@
     DescriptorIdPool& desc_id_pool = shared_resources_cache.Get<DescriptorIdPool>();
     obj.SetSubState(container_type, std::make_unique<TensorViewSubState>(obj, desc_id_pool));
 }
-void Validator::Created(vvl::ShaderObject& obj) { obj.SetSubState(container_type, std::make_unique<ShaderObjectSubState>(obj)); }
+void Validator::Created(vvl::ShaderObject& obj) {
+    obj.SetSubState(container_type, std::make_unique<ShaderObjectSubState>(*this, obj));
+}
 
 void Validator::Created(vvl::Pipeline& obj) { obj.SetSubState(container_type, std::make_unique<PipelineSubState>(*this, obj)); }
 
diff --git a/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp b/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp
index b67d0e7..a9d2f1e 100644
--- a/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp
+++ b/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp
@@ -414,7 +414,7 @@
     if (!gpuav_settings.IsSpirvModified()) {
         return;
     }
-    if (pNameInfo->objectType != VK_OBJECT_TYPE_PIPELINE || !pNameInfo->pObjectName) {
+    if (!pNameInfo->pObjectName) {
         return;
     }
 
@@ -428,43 +428,87 @@
         return;
     }
 
-    VkPipeline wrapped_pipeline = CastFromUint64<VkPipeline>(pNameInfo->objectHandle);
-    auto pipeline_state = Get<vvl::Pipeline>(wrapped_pipeline);
-    ASSERT_AND_RETURN(pipeline_state);
-    PipelineSubState& pipeline_sub_state = SubState(*pipeline_state);
+    if (pNameInfo->objectType == VK_OBJECT_TYPE_PIPELINE) {
+        VkPipeline wrapped_pipeline = CastFromUint64<VkPipeline>(pNameInfo->objectHandle);
+        auto pipeline_state = Get<vvl::Pipeline>(wrapped_pipeline);
+        ASSERT_AND_RETURN(pipeline_state);
+        PipelineSubState& pipeline_sub_state = SubState(*pipeline_state);
 
-    if (pipeline_sub_state.status.host.is_instrumented) {
-        return;
-    }
-
-    if (!NeedPipelineCreationShaderInstrumentation(*pipeline_state, record_obj.location)) {
-        return;
-    }
-
-    auto layer_data = vvl::GetDispatchDevice(device);
-    ASSERT_AND_RETURN(layer_data);
-
-    // The pipeline was selected by name, not by individual shader name, so force all its shaders to be instrumented
-    for (const auto& stage_state : pipeline_state->stage_states) {
-        if (stage_state.module_state && stage_state.module_state->VkHandle() != VK_NULL_HANDLE) {
-            selected_instrumented_shaders.insert(stage_state.module_state->VkHandle());
+        if (pipeline_sub_state.status.host.is_instrumented) {
+            return;
         }
-    }
 
-    VkPipeline instrumented_pipeline = VK_NULL_HANDLE;
-    // Can't instrument ray tracing pipeline post creation,
-    // As corresponding shader binding tables may have already been created.
-    if (pipeline_state->linking_shaders == 0 &&
-        IsValueIn(pipeline_state->pipeline_type, {VK_PIPELINE_BIND_POINT_GRAPHICS, VK_PIPELINE_BIND_POINT_COMPUTE})) {
-        std::vector<chassis::ShaderInstrumentationMetadata> shader_instrumentation_metadata;
-        if (pipeline_state->pipeline_type == VK_PIPELINE_BIND_POINT_GRAPHICS) {
+        if (!NeedPipelineCreationShaderInstrumentation(*pipeline_state, record_obj.location)) {
+            return;
+        }
+
+        auto layer_data = vvl::GetDispatchDevice(device);
+        ASSERT_AND_RETURN(layer_data);
+
+        // The pipeline was selected by name, not by individual shader name, so force all its shaders to be instrumented
+        for (const auto& stage_state : pipeline_state->stage_states) {
+            if (stage_state.module_state && stage_state.module_state->VkHandle() != VK_NULL_HANDLE) {
+                selected_instrumented_shaders.insert(stage_state.module_state->VkHandle());
+            }
+        }
+
+        VkPipeline instrumented_pipeline = VK_NULL_HANDLE;
+        // Can't instrument ray tracing pipeline post creation,
+        // As corresponding shader binding tables may have already been created.
+        if (pipeline_state->linking_shaders == 0 &&
+            IsValueIn(pipeline_state->pipeline_type, {VK_PIPELINE_BIND_POINT_GRAPHICS, VK_PIPELINE_BIND_POINT_COMPUTE})) {
+            std::vector<chassis::ShaderInstrumentationMetadata> shader_instrumentation_metadata;
+            if (pipeline_state->pipeline_type == VK_PIPELINE_BIND_POINT_GRAPHICS) {
+                vku::safe_VkGraphicsPipelineCreateInfo new_pipeline_ci(pipeline_state->GraphicsCreateInfo());
+                new_pipeline_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT;
+                const bool success = PreCallRecordPipelineCreationShaderInstrumentation(
+                    nullptr, *pipeline_state, new_pipeline_ci, uint32_t(pipeline_state->stage_states.size()), record_obj.location,
+                    shader_instrumentation_metadata);
+                if (!success) {
+                    InternalError(device, record_obj.location,
+                                  "Failed to instrument graphics pipeline in SetDebugUtilsObjectNameEXT.");
+                    return;
+                }
+
+                layer_data->UnwrapGraphicsPipelineCreateInfoHandles(new_pipeline_ci);
+                const VkResult result = layer_data->device_dispatch_table.CreateGraphicsPipelines(
+                    device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline);
+                if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) {
+                    InternalError(device, record_obj.location,
+                                  "Failed to create instrumented graphics pipeline in SetDebugUtilsObjectNameEXT.");
+                    return;
+                }
+            } else if (pipeline_state->pipeline_type == VK_PIPELINE_BIND_POINT_COMPUTE) {
+                vku::safe_VkComputePipelineCreateInfo new_pipeline_ci(pipeline_state->ComputeCreateInfo());
+                new_pipeline_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT;
+                const bool success = PreCallRecordPipelineCreationShaderInstrumentation(
+                    nullptr, *pipeline_state, new_pipeline_ci, uint32_t(pipeline_state->stage_states.size()), record_obj.location,
+                    shader_instrumentation_metadata);
+                if (!success) {
+                    InternalError(device, record_obj.location,
+                                  "Failed to instrument compute pipeline in SetDebugUtilsObjectNameEXT.");
+                    return;
+                }
+
+                layer_data->UnwrapComputePipelineCreateInfoHandles(new_pipeline_ci);
+                const VkResult result = layer_data->device_dispatch_table.CreateComputePipelines(
+                    device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline);
+                if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) {
+                    InternalError(device, record_obj.location,
+                                  "Failed to create instrumented compute pipeline in SetDebugUtilsObjectNameEXT.");
+                    return;
+                }
+            }
+
+            PostCallRecordPipelineCreationShaderInstrumentation(*pipeline_state, uint32_t(pipeline_state->stage_states.size()),
+                                                                shader_instrumentation_metadata);
+        } else {
             vku::safe_VkGraphicsPipelineCreateInfo new_pipeline_ci(pipeline_state->GraphicsCreateInfo());
-            new_pipeline_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT;
-            const bool success = PreCallRecordPipelineCreationShaderInstrumentation(
-                nullptr, *pipeline_state, new_pipeline_ci, uint32_t(pipeline_state->stage_states.size()), record_obj.location,
-                shader_instrumentation_metadata);
+            const bool success = PreCallRecordPipelineCreationShaderInstrumentationGPL(nullptr, *pipeline_state, new_pipeline_ci,
+                                                                                       record_obj.location);
             if (!success) {
-                InternalError(device, record_obj.location, "Failed to instrument graphics pipeline in SetDebugUtilsObjectNameEXT.");
+                InternalError(device, record_obj.location,
+                              "Failed to instrument graphics pipeline library in SetDebugUtilsObjectNameEXT.");
                 return;
             }
 
@@ -476,51 +520,52 @@
                               "Failed to create instrumented graphics pipeline in SetDebugUtilsObjectNameEXT.");
                 return;
             }
-        } else if (pipeline_state->pipeline_type == VK_PIPELINE_BIND_POINT_COMPUTE) {
-            vku::safe_VkComputePipelineCreateInfo new_pipeline_ci(pipeline_state->ComputeCreateInfo());
-            new_pipeline_ci.flags &= ~VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT;
-            const bool success = PreCallRecordPipelineCreationShaderInstrumentation(
-                nullptr, *pipeline_state, new_pipeline_ci, uint32_t(pipeline_state->stage_states.size()), record_obj.location,
-                shader_instrumentation_metadata);
-            if (!success) {
-                InternalError(device, record_obj.location, "Failed to instrument compute pipeline in SetDebugUtilsObjectNameEXT.");
-                return;
-            }
-
-            layer_data->UnwrapComputePipelineCreateInfoHandles(new_pipeline_ci);
-            const VkResult result = layer_data->device_dispatch_table.CreateComputePipelines(
-                device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline);
-            if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) {
-                InternalError(device, record_obj.location,
-                              "Failed to create instrumented compute pipeline in SetDebugUtilsObjectNameEXT.");
-                return;
-            }
         }
 
-        PostCallRecordPipelineCreationShaderInstrumentation(*pipeline_state, uint32_t(pipeline_state->stage_states.size()),
-                                                            shader_instrumentation_metadata);
-    } else {
-        vku::safe_VkGraphicsPipelineCreateInfo new_pipeline_ci(pipeline_state->GraphicsCreateInfo());
-        const bool success =
-            PreCallRecordPipelineCreationShaderInstrumentationGPL(nullptr, *pipeline_state, new_pipeline_ci, record_obj.location);
-        if (!success) {
-            InternalError(device, record_obj.location,
-                          "Failed to instrument graphics pipeline library in SetDebugUtilsObjectNameEXT.");
-            return;
-        }
-
-        layer_data->UnwrapGraphicsPipelineCreateInfoHandles(new_pipeline_ci);
-        const VkResult result = layer_data->device_dispatch_table.CreateGraphicsPipelines(
-            device, VK_NULL_HANDLE, 1, new_pipeline_ci.ptr(), nullptr, &instrumented_pipeline);
-        if (result != VK_SUCCESS || instrumented_pipeline == VK_NULL_HANDLE) {
-            InternalError(device, record_obj.location,
-                          "Failed to create instrumented graphics pipeline in SetDebugUtilsObjectNameEXT.");
-            return;
-        }
+        const VkPipeline old_pipeline = layer_data->Replace(pipeline_state->VkHandle(), instrumented_pipeline);
+        pipeline_sub_state.AddHandleToDestroy(old_pipeline);
     }
 
-    const VkPipeline old_pipeline = layer_data->Replace(pipeline_state->VkHandle(), instrumented_pipeline);
-    pipeline_sub_state.AddHandleToDestroy(old_pipeline);
+    if (pNameInfo->objectType == VK_OBJECT_TYPE_SHADER_EXT) {
+        VkShaderEXT wrapped_shader = CastFromUint64<VkShaderEXT>(pNameInfo->objectHandle);
+        auto shader_state = Get<vvl::ShaderObject>(wrapped_shader);
+        ASSERT_AND_RETURN(shader_state);
+        ShaderObjectSubState& shader_sub_state = SubState(*shader_state);
+        if (shader_sub_state.instrumented_status.host.is_instrumented) {
+            return;
+        }
+
+        if (!shader_sub_state.original_module) {
+            return;
+        }
+
+        vku::safe_VkShaderCreateInfoEXT new_shader_ci(shader_sub_state.original_create_info);
+        new_shader_ci.codeSize = shader_sub_state.original_module->words_.size() * sizeof(uint32_t);
+        new_shader_ci.pCode = shader_sub_state.original_module->words_.data();
+        chassis::ShaderObject chassis_state(1, new_shader_ci.ptr());
+
+        VkShaderEXT instrumented_shader = VK_NULL_HANDLE;
+        chassis_state.force_shader_instrumentation = true;
+        PreCallRecordCreateShadersEXT(device, 1, new_shader_ci.ptr(), nullptr, &wrapped_shader, record_obj, chassis_state);
+
+        new_shader_ci = chassis_state.modified_create_infos[0];
+
+        auto layer_data = vvl::GetDispatchDevice(device);
+        ASSERT_AND_RETURN(layer_data);
+
+        layer_data->UnwrapShaderObjectCreateInfoHandles(new_shader_ci);
+        const VkResult result =
+            layer_data->device_dispatch_table.CreateShadersEXT(device, 1, new_shader_ci.ptr(), nullptr, &instrumented_shader);
+        if (result != VK_SUCCESS || instrumented_shader == VK_NULL_HANDLE) {
+            InternalError(device, record_obj.location, "Failed to create instrumented VkShaderEXT in SetDebugUtilsObjectNameEXT.");
+            return;
+        }
+
+        const VkShaderEXT old_shader = layer_data->Replace(wrapped_shader, instrumented_shader);
+        shader_sub_state.AddHandleToDestroy(old_shader);
+
+        PostCallRecordCreateShadersEXT(device, 1, new_shader_ci.ptr(), nullptr, &wrapped_shader, record_obj, chassis_state);
+    }
 }
 
 void GpuShaderInstrumentor::PostCallRecordCreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo,
@@ -640,7 +685,7 @@
                     "solution is use the SPIR-V if you want to use debugging tools.");
             }
             continue;
-        } else if (!chassis_state.module_states[i]) {
+        } else if (!chassis_state.force_shader_instrumentation && !chassis_state.module_states[i]) {
             continue;
         }
 
@@ -663,7 +708,12 @@
                     "for VkShaderEXT created with it, therefore no validation error will be repored for them by GPU-AV at "
                     "runtime.";
             InternalWarning(device, record_obj.location, strm.str().c_str());
-        } else if (gpuav_settings.select_instrumented_shaders && !IsSelectiveInstrumentationEnabled(new_create_info.pNext)) {
+        }
+        // Because shader objects can be instrumented post creation at object labeling time,
+        // a mechanism is needed to bypass selective shader instrumentation at this level.
+        // In this case, the decision to instrument or not is done at RecordSetDebugUtilsObjectNameEXT time.
+        else if (!chassis_state.force_shader_instrumentation && gpuav_settings.select_instrumented_shaders &&
+                 !IsSelectiveInstrumentationEnabled(new_create_info.pNext)) {
             continue;
         } else {
             // Modify the pipeline layout by:
@@ -715,10 +765,12 @@
     if (!gpuav_settings.IsSpirvModified()) {
         return;
     }
-    // This can occur if the driver failed to compile the instrumented shader or if a PreCall step failed
-    if (!chassis_state.is_modified) {
+
+    if (!gpuav_settings.select_instrumented_shaders && !chassis_state.is_modified) {
+        // This can occur if the driver failed to compile the instrumented shader or if a PreCall step failed
         return;
     }
+
     for (uint32_t i = 0; i < createInfoCount; ++i) {
         // If there are multiple shaders being created, and one is bad, will return a non VK_SUCCESS but we need to check if the
         // VkShaderEXT was null or not to actually know if it was created
@@ -726,22 +778,35 @@
         if (shader_handle == VK_NULL_HANDLE) {
             continue;
         }
+        const auto& shader_object_state = Get<vvl::ShaderObject>(shader_handle);
+        ASSERT_AND_CONTINUE(shader_object_state);
+        auto& sub_state = SubState(*shader_object_state);
 
         auto& instrumentation_data = chassis_state.instrumentations_data[i];
 
+        // If select_instrumented_shaders is enabled, GPU-AV might need
+        // SPIR-V code at object labeling time to instrument.
+        // Save state needed to do that.
+        if (gpuav_settings.select_instrumented_shaders && !instrumentation_data.status.host.is_instrumented &&
+            pCreateInfos[i].codeType == VK_SHADER_CODE_TYPE_SPIRV_EXT) {
+            sub_state.original_create_info.initialize(&pCreateInfos[i]);
+            sub_state.original_module = chassis_state.module_states[i];
+        }
+
+        if (!chassis_state.is_modified) {
+            continue;
+        }
+
         // if the shader for some reason was not instrumented, there is nothing to save
         // (like not using VK_SHADER_CODE_TYPE_SPIRV_EXT)
         if (!instrumentation_data.status.host.is_instrumented) {
             continue;
         }
-        const auto& shader_object_state = Get<vvl::ShaderObject>(shader_handle);
-        ASSERT_AND_CONTINUE(shader_object_state);
-        auto& sub_state = SubState(*shader_object_state);
 
+        sub_state.original_create_info.initialize(&pCreateInfos[i]);
         sub_state.instrumented_status.Append(instrumentation_data.status);
         sub_state.unique_shader_id = instrumentation_data.unique_shader_id;
-        // Note - this doesn't make a deep copy of the pCode, but does of the DescriptorSetLayout which we
-        sub_state.original_create_info.initialize(&pCreateInfos[i]);
+        // No pCode deep copy
 
         // We currently need to store a copy of the original, non-instrumented shader so if there is debug information.
         std::vector<uint32_t> code;
diff --git a/layers/gpuav/resources/gpuav_state_trackers.cpp b/layers/gpuav/resources/gpuav_state_trackers.cpp
index 7edccde..a9a707e 100644
--- a/layers/gpuav/resources/gpuav_state_trackers.cpp
+++ b/layers/gpuav/resources/gpuav_state_trackers.cpp
@@ -690,7 +690,8 @@
 
 void TensorViewSubState::NotifyInvalidate(const vvl::StateObject::NodeList& invalid_nodes, bool unlink) { id_tracker.reset(); }
 
-ShaderObjectSubState::ShaderObjectSubState(vvl::ShaderObject& obj) : vvl::ShaderObjectSubState(obj) {}
+ShaderObjectSubState::ShaderObjectSubState(Validator& gpuav, vvl::ShaderObject& obj)
+    : vvl::ShaderObjectSubState(obj), gpuav_(gpuav) {}
 
 PipelineSubState::PipelineSubState(Validator& gpuav, vvl::Pipeline& pipeline) : vvl::PipelineSubState(pipeline), gpuav_(gpuav) {}
 
@@ -793,4 +794,19 @@
     uninstrumented_pipeline = pipeline;
 }
 
+void ShaderObjectSubState::Destroy() {
+    if (stale_handle != VK_NULL_HANDLE) {
+        // vkDestroyShaderEXT expects an unwrapped handle,
+        // so cannot use DispatchDestroyPipeline as it will try to unwrap supplied pipeline handle
+        auto layer_data = vvl::GetDispatchDevice(gpuav_.device);
+        layer_data->device_dispatch_table.DestroyShaderEXT(gpuav_.device, stale_handle, nullptr);
+    }
+}
+
+void ShaderObjectSubState::AddHandleToDestroy(VkShaderEXT shader) {
+    std::unique_lock<std::mutex> lock(mutex_);
+    assert(stale_handle == VK_NULL_HANDLE);
+    stale_handle = shader;
+}
+
 }  // namespace gpuav
diff --git a/layers/gpuav/resources/gpuav_state_trackers.h b/layers/gpuav/resources/gpuav_state_trackers.h
index 9829d1e..918fcb9 100644
--- a/layers/gpuav/resources/gpuav_state_trackers.h
+++ b/layers/gpuav/resources/gpuav_state_trackers.h
@@ -44,6 +44,10 @@
 
 struct LastBound;
 
+namespace spirv {
+struct Module;
+}
+
 namespace gpuav {
 
 class Validator;
@@ -426,7 +430,13 @@
 
 class ShaderObjectSubState : public vvl::ShaderObjectSubState {
   public:
-    explicit ShaderObjectSubState(vvl::ShaderObject &obj);
+    explicit ShaderObjectSubState(Validator& gpuav, vvl::ShaderObject& obj);
+    void Destroy() override;
+
+    // Specifically for shaders instrumented post original creation:
+    // The old shader could be in use at time of instrumentation,
+    // so defer old pipeline destroy to Destroy() call.
+    void AddHandleToDestroy(VkShaderEXT shader);
 
     spirv::InstrumentationStatus instrumented_status;
     uint32_t unique_shader_id = 0;
@@ -434,6 +444,15 @@
     // We need to keep incase the user calls vkGetShaderBinaryDataEXT
     vku::safe_VkShaderCreateInfoEXT original_create_info;
     VkShaderEXT original_handle = VK_NULL_HANDLE;
+
+    VkShaderEXT stale_handle = VK_NULL_HANDLE;
+    std::shared_ptr<::spirv::Module> original_module;
+
+  private:
+    Validator& gpuav_;
+    // Multiple threads can record multiple commands using the same shader,
+    // so shader destruction has to be thread safe
+    mutable std::mutex mutex_{};
 };
 
 static inline ShaderObjectSubState &SubState(vvl::ShaderObject &obj) {
diff --git a/layers/state_tracker/shader_object_state.cpp b/layers/state_tracker/shader_object_state.cpp
index 4d91cee..94ee50d 100644
--- a/layers/state_tracker/shader_object_state.cpp
+++ b/layers/state_tracker/shader_object_state.cpp
@@ -50,4 +50,12 @@
     }
 }
 
+void ShaderObject::Destroy() {
+    for (auto& item : sub_states_) {
+        item.second->Destroy();
+    }
+    sub_states_.clear();
+    StateObject::Destroy();
+}
+
 }  // namespace vvl
diff --git a/layers/state_tracker/shader_object_state.h b/layers/state_tracker/shader_object_state.h
index 4452d90..e4d9590 100644
--- a/layers/state_tracker/shader_object_state.h
+++ b/layers/state_tracker/shader_object_state.h
@@ -42,6 +42,7 @@
 struct ShaderObject : public StateObject, public SubStateManager<ShaderObjectSubState> {
     ShaderObject(DeviceState &dev_data, const VkShaderCreateInfoEXT &create_info_i, VkShaderEXT shader_object,
                  std::shared_ptr<spirv::Module> &spirv_module);
+    void Destroy() override;
 
     const vku::safe_VkShaderCreateInfoEXT safe_create_info_ext;
     const VkShaderCreateInfoEXT &create_info_ext;
diff --git a/tests/unit/gpu_av_scoped.cpp b/tests/unit/gpu_av_scoped.cpp
index a9599a6..b860e57 100644
--- a/tests/unit/gpu_av_scoped.cpp
+++ b/tests/unit/gpu_av_scoped.cpp
@@ -350,6 +350,58 @@
     m_errorMonitor->VerifyFound();
 }
 
+TEST_F(NegativeGpuAVScoped, SelectInstrumentedComputeShaderObjectRegex) {
+    TEST_DESCRIPTION("Selectively instrument a compute shader object for validation, using regexes");
+    SetTargetApiVersion(VK_API_VERSION_1_2);
+    AddRequiredExtensions(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+    AddRequiredExtensions(VK_EXT_SHADER_OBJECT_EXTENSION_NAME);
+    AddRequiredFeature(vkt::Feature::shaderObject);
+    std::vector<VkLayerSettingEXT> layer_settings(2);
+    layer_settings[0] = {OBJECT_LAYER_NAME, "gpuav_select_instrumented_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &kVkTrue};
+    std::array<const char*, 1> shader_regexes = {{"shader_object_foo"}};
+    layer_settings[1] = {OBJECT_LAYER_NAME, "gpuav_shaders_to_instrument", VK_LAYER_SETTING_TYPE_STRING_EXT, size32(shader_regexes),
+                         shader_regexes.data()};
+
+    RETURN_IF_SKIP(InitGpuAvFramework(layer_settings));
+    RETURN_IF_SKIP(InitState());
+
+    vkt::Buffer write_buffer(*m_device, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, kHostVisibleMemProps);
+    OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}});
+
+    const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
+    descriptor_set.WriteDescriptorBufferInfo(0, write_buffer, 0, 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
+    descriptor_set.UpdateDescriptorSets();
+
+    const char cs_source[] = R"glsl(
+        #version 450
+        layout(set = 0, binding = 0) buffer StorageBuffer { uint data[]; } Data;
+        void main() {
+                Data.data[4] = 0xdeadca71;
+        }
+    )glsl";
+
+    const vkt::ShaderEXT cs(*m_device, VK_SHADER_STAGE_COMPUTE_BIT, GLSLToSPV(VK_SHADER_STAGE_COMPUTE_BIT, cs_source),
+                            &descriptor_set.layout_.handle());
+
+    VkDebugUtilsObjectNameInfoEXT name_info = vku::InitStructHelper();
+    name_info.objectType = VK_OBJECT_TYPE_SHADER_EXT;
+    name_info.pObjectName = "shader_object_foo";
+    name_info.objectHandle = uint64_t(cs.handle());
+    vk::SetDebugUtilsObjectNameEXT(device(), &name_info);
+
+    m_command_buffer.Begin();
+    vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0,
+                              nullptr);
+    VkShaderStageFlagBits shader_stages[] = {VK_SHADER_STAGE_COMPUTE_BIT};
+    vk::CmdBindShadersEXT(m_command_buffer, 1, shader_stages, &cs.handle());
+    vk::CmdDispatch(m_command_buffer, 1, 1, 1);
+    m_command_buffer.End();
+
+    m_errorMonitor->SetDesiredErrorRegex("VUID-vkCmdDispatch-None-08613", "shader_object_foo");
+    m_default_queue->SubmitAndWait(m_command_buffer);
+    m_errorMonitor->VerifyFound();
+}
+
 TEST_F(NegativeGpuAVScoped, SelectInstrumentedComputePipelineCDLDump) {
     TEST_DESCRIPTION("Selectively instrument a compute pipeline for validation, using a CDL dump file as input");
     SetTargetApiVersion(VK_API_VERSION_1_2);
diff --git a/tests/unit/gpu_av_scoped_positive.cpp b/tests/unit/gpu_av_scoped_positive.cpp
index 1bd5ae5..b71f91f 100644
--- a/tests/unit/gpu_av_scoped_positive.cpp
+++ b/tests/unit/gpu_av_scoped_positive.cpp
@@ -19,6 +19,7 @@
 #include "pipeline_helper.h"
 #include "descriptor_helper.h"
 #include "shader_object_helper.h"
+#include "utils/math_utils.h"
 
 class PositiveGpuAVScoped : public GpuAVTest {};
 
@@ -133,4 +134,64 @@
     vk::CmdDispatchIndirect(m_command_buffer, indirect_dispatch_parameters_buffer, 0u);
 
     m_command_buffer.End();
-}
\ No newline at end of file
+}
+
+TEST_F(PositiveGpuAVScoped, SelectInstrumentedShaderObjectBinary) {
+    SetTargetApiVersion(VK_API_VERSION_1_2);
+    AddRequiredExtensions(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+    AddRequiredExtensions(VK_EXT_SHADER_OBJECT_EXTENSION_NAME);
+    AddRequiredFeature(vkt::Feature::shaderObject);
+
+    std::vector<VkLayerSettingEXT> layer_settings(2);
+    layer_settings[0] = {OBJECT_LAYER_NAME, "gpuav_select_instrumented_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &kVkTrue};
+    std::array<const char*, 1> shader_regexes = {{"shader_object_foo"}};
+    layer_settings[1] = {OBJECT_LAYER_NAME, "gpuav_shaders_to_instrument", VK_LAYER_SETTING_TYPE_STRING_EXT, size32(shader_regexes),
+                         shader_regexes.data()};
+    RETURN_IF_SKIP(InitGpuAvFramework(layer_settings));
+    RETURN_IF_SKIP(InitState());
+
+    OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}});
+
+    const char cs_source[] = R"glsl(
+        #version 450
+        layout(set = 0, binding = 0) buffer StorageBuffer { uint data[]; } Data;
+        void main() {
+            Data.data[0] = 0u;
+        }
+    )glsl";
+
+    const vkt::ShaderEXT spirv_cs(*m_device, VK_SHADER_STAGE_COMPUTE_BIT, GLSLToSPV(VK_SHADER_STAGE_COMPUTE_BIT, cs_source),
+                                  &descriptor_set.layout_.handle());
+
+    size_t data_size = 0;
+    vk::GetShaderBinaryDataEXT(*m_device, spirv_cs, &data_size, nullptr);
+    if (data_size == 0) {
+        GTEST_SKIP() << "Driver returned no shader binary data";
+    }
+    std::vector<uint8_t> binary(data_size + 15);  // need 16-byte aligned pointers
+    void* binary_ptr = reinterpret_cast<void*>(Align(reinterpret_cast<uintptr_t>(binary.data()), (uintptr_t)16));
+    vk::GetShaderBinaryDataEXT(*m_device, spirv_cs, &data_size, binary_ptr);
+
+    VkShaderCreateInfoEXT create_info = vku::InitStructHelper();
+    create_info.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+    create_info.codeType = VK_SHADER_CODE_TYPE_BINARY_EXT;
+    create_info.codeSize = data_size;
+    create_info.pCode = binary_ptr;
+    create_info.pName = "main";
+    create_info.setLayoutCount = 1u;
+    create_info.pSetLayouts = &descriptor_set.layout_.handle();
+
+    VkShaderEXT binary_cs = VK_NULL_HANDLE;
+    vk::CreateShadersEXT(*m_device, 1u, &create_info, nullptr, &binary_cs);
+    if (binary_cs == VK_NULL_HANDLE) {
+        GTEST_SKIP() << "Driver bug, not reparsing";
+    }
+
+    VkDebugUtilsObjectNameInfoEXT name_info = vku::InitStructHelper();
+    name_info.objectType = VK_OBJECT_TYPE_SHADER_EXT;
+    name_info.pObjectName = "shader_object_foo";
+    name_info.objectHandle = uint64_t(binary_cs);
+    vk::SetDebugUtilsObjectNameEXT(device(), &name_info);
+
+    vk::DestroyShaderEXT(*m_device, binary_cs, nullptr);
+}