blob: 90182b90ad56c8effcaa96839cdd474d5e216e45 [file] [log] [blame]
/*------------------------------------------------------------------------
* Vulkan Conformance Tests
* ------------------------
*
* Copyright (c) 2020 The Khronos Group 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.
*
*//*!
* \file
* \brief Experimental crash postmortem use after free tests
*//*--------------------------------------------------------------------*/
#include "vktTestCase.hpp"
#include "vktTestCaseUtil.hpp"
#include "vktTestGroupUtil.hpp"
#include "vktCustomInstancesDevices.hpp"
#include "vktPostmortemTests.hpp"
#include "vktPostmortemUseAfterFreeTests.hpp"
#include "vkDefs.hpp"
#include "vkRef.hpp"
#include "vkRefUtil.hpp"
#include "vkPlatform.hpp"
#include "vkPrograms.hpp"
#include "vkRefUtil.hpp"
#include "vkMemUtil.hpp"
#include "vkBarrierUtil.hpp"
#include "vkQueryUtil.hpp"
#include "vkBuilderUtil.hpp"
#include "vkTypeUtil.hpp"
#include "vkDeviceUtil.hpp"
#include "vkCmdUtil.hpp"
#include "vkObjUtil.hpp"
#include "vkBufferWithMemory.hpp"
#include "tcuCommandLine.hpp"
#include "tcuTestLog.hpp"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deRandom.hpp"
#include "vktPostmortemUtil.hpp"
#include <vector>
#include <memory>
using namespace vk;
namespace vkt
{
namespace postmortem
{
namespace
{
enum BufferType
{
BUFFER_TYPE_UNIFORM = 0,
BUFFER_TYPE_SSBO,
};
class Buffer
{
public:
Buffer (const vk::DeviceInterface& vk,
const vk::VkDevice device,
vk::Allocator& allocator,
const vk::VkBufferCreateInfo& bufferCreateInfo,
const vk::MemoryRequirement memoryRequirement);
const vk::VkBuffer& get (void) const { return *m_buffer; }
const vk::VkBuffer& operator* (void) const { return get(); }
vk::Allocation& getAllocation (void) const { return *m_allocation; }
void freeAllocation (void) { delete m_allocation.release(); }
private:
de::MovePtr<vk::Allocation> m_allocation;
vk::Move<vk::VkBuffer> m_buffer;
Buffer(const Buffer&); // "deleted"
Buffer& operator= (const Buffer&);
};
Buffer::Buffer(const DeviceInterface& vk,
const VkDevice device,
Allocator& allocator,
const VkBufferCreateInfo& bufferCreateInfo,
const MemoryRequirement memoryRequirement)
{
m_buffer = createBuffer(vk, device, &bufferCreateInfo);
m_allocation = allocator.allocate(getBufferMemoryRequirements(vk, device, *m_buffer), memoryRequirement);
VK_CHECK(vk.bindBufferMemory(device, *m_buffer, m_allocation->getMemory(), m_allocation->getOffset()));
}
class UseAfterFreeTestCase : public vkt::TestCase
{
public:
void initPrograms (vk::SourceCollections& sourceCollections) const;
TestInstance* createInstance (Context& context) const;
static UseAfterFreeTestCase* UBOToSSBOInvertCase (tcu::TestContext& testCtx,
const std::string& name,
const std::string& description,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize);
static UseAfterFreeTestCase* CopyInvertSSBOCase (tcu::TestContext& testCtx,
const std::string& name,
const std::string& description,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize);
private:
UseAfterFreeTestCase (tcu::TestContext& testCtx,
const std::string& name,
const std::string& description,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize,
const BufferType bufferType);
const BufferType m_bufferType;
const deUint32 m_numValues;
const tcu::IVec3 m_localSize;
const tcu::IVec3 m_workSize;
};
class UseAfterFreeTestInstance : public PostmortemTestInstance
{
public:
UseAfterFreeTestInstance(Context& context,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize,
const BufferType bufferType);
tcu::TestStatus iterate (void);
private:
const BufferType m_bufferType;
const deUint32 m_numValues;
const tcu::IVec3 m_localSize;
const tcu::IVec3 m_workSize;
};
template<typename T, int size>
T multiplyComponents(const tcu::Vector<T, size>& v)
{
T accum = 1;
for (int i = 0; i < size; ++i)
accum *= v[i];
return accum;
}
UseAfterFreeTestCase::UseAfterFreeTestCase (tcu::TestContext& testCtx,
const std::string& name,
const std::string& description,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize,
const BufferType bufferType)
: TestCase (testCtx, name, description)
, m_bufferType (bufferType)
, m_numValues (numValues)
, m_localSize (localSize)
, m_workSize (workSize)
{
DE_ASSERT(m_numValues % (multiplyComponents(m_workSize) * multiplyComponents(m_localSize)) == 0);
DE_ASSERT(m_bufferType == BUFFER_TYPE_UNIFORM || m_bufferType == BUFFER_TYPE_SSBO);
}
UseAfterFreeTestCase* UseAfterFreeTestCase::UBOToSSBOInvertCase (tcu::TestContext& testCtx,
const std::string& name,
const std::string& description,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize)
{
return new UseAfterFreeTestCase(testCtx, name, description, numValues, localSize, workSize, BUFFER_TYPE_UNIFORM);
}
UseAfterFreeTestCase* UseAfterFreeTestCase::CopyInvertSSBOCase (tcu::TestContext& testCtx,
const std::string& name,
const std::string& description,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize)
{
return new UseAfterFreeTestCase(testCtx, name, description, numValues, localSize, workSize, BUFFER_TYPE_SSBO);
}
void UseAfterFreeTestCase::initPrograms (SourceCollections& sourceCollections) const
{
std::ostringstream src;
if (m_bufferType == BUFFER_TYPE_UNIFORM)
{
src << "#version 310 es\n"
<< "layout (local_size_x = " << m_localSize.x() << ", local_size_y = " << m_localSize.y() << ", local_size_z = " << m_localSize.z() << ") in;\n"
<< "layout(binding = 0) readonly uniform Input {\n"
<< " uint values[" << m_numValues << "];\n"
<< "} ub_in;\n"
<< "layout(binding = 1, std140) writeonly buffer Output {\n"
<< " uint values[" << m_numValues << "];\n"
<< "} sb_out;\n"
<< "void main (void) {\n"
<< " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
<< " uint numValuesPerInv = uint(ub_in.values.length()) / (size.x*size.y*size.z);\n"
<< " uint groupNdx = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
<< " uint offset = numValuesPerInv*groupNdx;\n"
<< "\n"
<< " for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
<< " sb_out.values[offset + ndx] = ~ub_in.values[offset + ndx];\n"
<< "}\n";
}
else if (m_bufferType == BUFFER_TYPE_SSBO)
{
src << "#version 310 es\n"
<< "layout (local_size_x = " << m_localSize.x() << ", local_size_y = " << m_localSize.y() << ", local_size_z = " << m_localSize.z() << ") in;\n"
<< "layout(binding = 0, std140) readonly buffer Input {\n"
<< " uint values[" << m_numValues << "];\n"
<< "} sb_in;\n"
<< "layout (binding = 1, std140) writeonly buffer Output {\n"
<< " uint values[" << m_numValues << "];\n"
<< "} sb_out;\n"
<< "void main (void) {\n"
<< " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
<< " uint numValuesPerInv = uint(sb_in.values.length()) / (size.x*size.y*size.z);\n"
<< " uint groupNdx = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
<< " uint offset = numValuesPerInv*groupNdx;\n"
<< "\n"
<< " for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
<< " sb_out.values[offset + ndx] = ~sb_in.values[offset + ndx];\n"
<< "}\n";
}
sourceCollections.glslSources.add("comp") << glu::ComputeSource(src.str());
}
Move<VkPipeline> makeComputePipeline (const DeviceInterface& vk,
const VkDevice device,
const VkPipelineLayout pipelineLayout,
const VkPipelineCreateFlags pipelineFlags,
const VkShaderModule shaderModule,
const VkPipelineShaderStageCreateFlags shaderFlags)
{
const VkPipelineShaderStageCreateInfo pipelineShaderStageParams =
{
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // VkStructureType sType;
DE_NULL, // const void* pNext;
shaderFlags, // VkPipelineShaderStageCreateFlags flags;
VK_SHADER_STAGE_COMPUTE_BIT, // VkShaderStageFlagBits stage;
shaderModule, // VkShaderModule module;
"main", // const char* pName;
DE_NULL, // const VkSpecializationInfo* pSpecializationInfo;
};
const VkComputePipelineCreateInfo pipelineCreateInfo =
{
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, // VkStructureType sType;
DE_NULL, // const void* pNext;
pipelineFlags, // VkPipelineCreateFlags flags;
pipelineShaderStageParams, // VkPipelineShaderStageCreateInfo stage;
pipelineLayout, // VkPipelineLayout layout;
DE_NULL, // VkPipeline basePipelineHandle;
0, // deInt32 basePipelineIndex;
};
return createComputePipeline(vk, device, DE_NULL, &pipelineCreateInfo);
}
Move<VkPipeline> makeComputePipeline(const DeviceInterface& vk,
const VkDevice device,
const VkPipelineLayout pipelineLayout,
const VkShaderModule shaderModule)
{
return makeComputePipeline(vk, device, pipelineLayout, static_cast<VkPipelineCreateFlags>(0u), shaderModule, static_cast<VkPipelineShaderStageCreateFlags>(0u));
}
TestInstance* UseAfterFreeTestCase::createInstance(Context& context) const
{
return new UseAfterFreeTestInstance(context, m_numValues, m_localSize, m_workSize, m_bufferType);
}
UseAfterFreeTestInstance::UseAfterFreeTestInstance (Context& context,
const deUint32 numValues,
const tcu::IVec3& localSize,
const tcu::IVec3& workSize,
const BufferType bufferType)
: PostmortemTestInstance(context)
, m_bufferType(bufferType)
, m_numValues(numValues)
, m_localSize(localSize)
, m_workSize(workSize)
{
}
tcu::TestStatus UseAfterFreeTestInstance::iterate(void)
{
const VkDevice device = *m_logicalDevice;
const DeviceInterface& vk = m_deviceDriver;
const VkQueue queue = m_queue;
const deUint32 queueFamilyIndex = m_queueFamilyIndex;
Allocator& allocator = m_allocator;
// Customize the test based on buffer type
const VkBufferUsageFlags inputBufferUsageFlags = (m_bufferType == BUFFER_TYPE_UNIFORM ? VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT : VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
const VkDescriptorType inputBufferDescriptorType = (m_bufferType == BUFFER_TYPE_UNIFORM ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
const deUint32 randomSeed = (m_bufferType == BUFFER_TYPE_UNIFORM ? 0x111223f : 0x124fef);
// Create an input buffer
const VkDeviceSize bufferSizeBytes = sizeof(tcu::UVec4) * m_numValues;
Buffer inputBuffer(vk, device, allocator, makeBufferCreateInfo(bufferSizeBytes, inputBufferUsageFlags), MemoryRequirement::HostVisible);
// Fill the input buffer with data
{
de::Random rnd(randomSeed);
const Allocation& inputBufferAllocation = inputBuffer.getAllocation();
tcu::UVec4* bufferPtr = static_cast<tcu::UVec4*>(inputBufferAllocation.getHostPtr());
for (deUint32 i = 0; i < m_numValues; ++i)
bufferPtr[i].x() = rnd.getUint32();
flushAlloc(vk, device, inputBufferAllocation);
}
// Create an output buffer
Buffer outputBuffer(vk, device, allocator, makeBufferCreateInfo(bufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), MemoryRequirement::HostVisible);
// Create descriptor set
const Unique<VkDescriptorSetLayout> descriptorSetLayout(
DescriptorSetLayoutBuilder()
.addSingleBinding(inputBufferDescriptorType, VK_SHADER_STAGE_COMPUTE_BIT)
.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT)
.build(vk, device));
const Unique<VkDescriptorPool> descriptorPool(
DescriptorPoolBuilder()
.addType(inputBufferDescriptorType)
.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
.build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
const VkDescriptorBufferInfo inputBufferDescriptorInfo = makeDescriptorBufferInfo(*inputBuffer, 0ull, bufferSizeBytes);
const VkDescriptorBufferInfo outputBufferDescriptorInfo = makeDescriptorBufferInfo(*outputBuffer, 0ull, bufferSizeBytes);
DescriptorSetUpdateBuilder()
.writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), inputBufferDescriptorType, &inputBufferDescriptorInfo)
.writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(1u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &outputBufferDescriptorInfo)
.update(vk, device);
// Perform the computation
const Unique<VkShaderModule> shaderModule(createShaderModule(vk, device, m_context.getBinaryCollection().get("comp"), 0u));
const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout));
const Unique<VkPipeline> pipeline(makeComputePipeline(vk, device, *pipelineLayout, *shaderModule));
const VkBufferMemoryBarrier hostWriteBarrier = makeBufferMemoryBarrier(VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, *inputBuffer, 0ull, bufferSizeBytes);
const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *outputBuffer, 0ull, bufferSizeBytes);
const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex));
const Unique<VkCommandBuffer> cmdBuffer(allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
// Start recording commands
beginCommandBuffer(vk, *cmdBuffer);
vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, *pipelineLayout, 0u, 1u, &descriptorSet.get(), 0u, DE_NULL);
vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, (VkDependencyFlags)0, 0, (const VkMemoryBarrier*)DE_NULL, 1, &hostWriteBarrier, 0, (const VkImageMemoryBarrier*)DE_NULL);
vk.cmdDispatch(*cmdBuffer, m_workSize.x(), m_workSize.y(), m_workSize.z());
vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, (VkDependencyFlags)0, 0, (const VkMemoryBarrier*)DE_NULL, 1, &shaderWriteBarrier, 0, (const VkImageMemoryBarrier*)DE_NULL);
endCommandBuffer(vk, *cmdBuffer);
// Free the memory backing the buffer
inputBuffer.freeAllocation();
outputBuffer.freeAllocation();
// Wait for completion
submitCommandsAndWait(vk, device, queue, *cmdBuffer);
// Pointers are invalid, so nothing to verify
return tcu::TestStatus::pass("Test succeeded without device loss");
}
}
tcu::TestCaseGroup* createUseAfterFreeTests(tcu::TestContext& testCtx)
{
de::MovePtr<tcu::TestCaseGroup> useAfterFreeGroup(new tcu::TestCaseGroup(testCtx, "use_after_free", "Use buffer after free."));
useAfterFreeGroup->addChild(UseAfterFreeTestCase::UBOToSSBOInvertCase(testCtx, "ubo_to_ssbo_single_invocation", "Copy from UBO to SSBO, inverting bits", 256, tcu::IVec3(1, 1, 1), tcu::IVec3(1, 1, 1)));
useAfterFreeGroup->addChild(UseAfterFreeTestCase::CopyInvertSSBOCase (testCtx, "ssbo_to_ssbo_single_invocation", "Copy from SSBO to SSBO, inverting bits", 256, tcu::IVec3(1, 1, 1), tcu::IVec3(1, 1, 1)));
return useAfterFreeGroup.release();
}
} // postmortem
} // vkt