VK_EXT_device_fault

本文档概述了允许应用程序在设备丢失后查询额外诊断信息的功能。

1. 问题陈述

设备丢失错误可能难以诊断。它们可能由多种问题触发,包括无效的应用程序行为、驱动程序错误以及硬件的物理故障或移除。虽然建议将 Vulkan 验证层作为诊断大多数 API 使用问题的首选步骤,但它们无法解决设备丢失的所有可能原因。

本提案旨在为应用程序开发人员提供可能有助于诊断此类错误的额外信息。

2. 解决方案空间

已考虑了以下几种选择

  • 提供基础扩展以支持崩溃后分析工具的开发

  • 开发旨在将故障归因于各个 Vulkan 对象的扩展或工具

  • 依赖于各个供应商的工具和扩展

本提案侧重于第一种选择。它代表了一个部分解决方案,需要进一步扩展才能完全启用崩溃后分析工具。

3. 提案

3.1. API 特性

VK_EXT_device_fault 扩展公开了以下特性

typedef struct VkPhysicalDeviceFaultFeaturesEXT {
    VkStructureType    sType;
    void*              pNext;
    VkBool32           deviceFault;
    VkBool32           deviceFaultVendorBinary;
} VkPhysicalDeviceFaultFeaturesEXT;

deviceFault 是启用此扩展功能的主要特性,如果支持此扩展,则必须支持此特性。

deviceFaultVendorBinary 是一个可选特性,它支持供应商特定的二进制崩溃转储,这些崩溃转储可以通过外部供应商工具进行解释。

3.2. 查询故障信息

设备丢失后,应用程序可以通过调用 vkGetDeviceFaultInfoEXT 查询额外的诊断信息。

typedef struct VkDeviceFaultCountsEXT {
    VkStructureType    sType;
    void*              pNext;
    uint32_t           addressInfoCount;
    uint32_t           vendorInfoCount;
    VkDeviceSize       vendorBinarySize;
} VkDeviceFaultCountsEXT;

typedef struct VkDeviceFaultInfoEXT {
    VkStructureType                 sType;
    void*                           pNext;
    char                            description[VK_MAX_DESCRIPTION_SIZE];
    VkDeviceFaultAddressInfoEXT*    pAddressInfos;
    VkDeviceFaultVendorInfoEXT*     pVendorInfos;
    void*                           pVendorBinaryData;
} VkDeviceFaultInfoEXT;

VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceFaultInfoEXT(
    VkDevice                                    device,
    VkDeviceFaultCountsEXT*                     pFaultCounts,
    VkDeviceFaultInfoEXT*                       pFaultInfo);

vkGetDeviceFaultInfoEXT 的签名旨在镜像现有查询函数的设计,其中第二个参数 (pFaultCounts) 表示输出数组的大小或写入的结果数。但是,设备故障信息需要多个输出数组。因此,使用 VkDeviceFaultCountsEXT 结构来一次指定多个数组的大小。

// Query number of available results
VkDeviceFaultCountsEXT faultCounts{};
faultCounts.sType = VK_STRUCTURE_TYPE_DEVICE_FAULT_COUNTS_EXT;

vkGetDeviceFaultInfoEXT(device, &faultCounts, NULL);

// Allocate output arrays and query fault data
VkDeviceFaultInfoEXT faultInfo{}
info.sType             = VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_EXT;
info.pAddressInfos = (VkDeviceFaultAddressInfoEXT*) malloc(sizeof(VkDeviceFaultAddressInfoEXT) *
                                                           faultCounts.addressInfoCount);
info.pVendorInfos  = (VkDeviceFaultVendorInfoEXT*)  malloc(sizeof(VkDeviceFaultVendorInfoEXT)  *
                                                           faultCounts.vendorInfoCount);
info.pVendorBinaryData = malloc(faultCounts.vendorBinarySize);

vkGetDeviceFaultInfoEXT(device, &faultCounts, &faultInfo);

3.3. 解释 GPU 虚拟地址

实现可能会返回有关由无效内存访问生成的页面错误以及指示故障发生时正在执行的指令的指令指针的信息。

typedef enum VkDeviceFaultAddressTypeEXT {
    VK_DEVICE_FAULT_ADDRESS_TYPE_NONE_EXT = 0,
    VK_DEVICE_FAULT_ADDRESS_TYPE_READ_INVALID_EXT = 1,
    VK_DEVICE_FAULT_ADDRESS_TYPE_WRITE_INVALID_EXT = 2,
    VK_DEVICE_FAULT_ADDRESS_TYPE_EXECUTE_INVALID_EXT = 3,
    VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_UNKNOWN_EXT = 4,
    VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_INVALID_EXT = 5,
    VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_FAULT_EXT = 6,
    VK_DEVICE_FAULT_ADDRESS_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF
} VkDeviceFaultAddressTypeEXT;

typedef struct VkDeviceFaultAddressInfoEXT {
    VkDeviceFaultAddressTypeEXT    addressType;
    VkDeviceAddress                reportedAddress;
    VkDeviceSize                   addressPrecision;
} VkDeviceFaultAddressInfoEXT;

页面地址和指令指针以 GPU 虚拟地址的形式报告,可能需要额外的扩展或供应商工具才能将这些扩展与各个 Vulkan 对象相关联。

实现可能只能以有限的精度报告这些地址。 reportedAddressaddressPrecision 的组合允许计算地址的可能范围,例如

lower_address = (pInfo->reportedAddress & ~(pInfo->addressPrecision-1))
upper_address = (pInfo->reportedAddress |  (pInfo->addressPrecision-1))

reportedAddress 可以包含比 addressPrecision 指示的更精确的地址,这是有效的。在这种情况下,reportedAddress 的值应被视为关于触发页面错误的地址值或指令指针值的额外提示。

3.4. 厂商二进制崩溃转储

可选地,实现还可以支持生成包含额外诊断信息的厂商特定二进制 blob。所有厂商特定的二进制文件都将以一个公共头部开始。二进制 blob 的其余部分内容是厂商特定的,需要厂商特定的文档或工具来解释。

typedef struct VkDeviceFaultVendorBinaryHeaderVersionOneEXT {
    uint32_t                                     headerSize;
    VkDeviceFaultVendorBinaryHeaderVersionEXT    headerVersion;
    uint32_t                                     vendorID;
    uint32_t                                     deviceID;
    uint32_t                                     driverVersion;
    uint8_t                                      pipelineCacheUUID[VK_UUID_SIZE];
    uint32_t                                     applicationNameOffset;
    uint32_t                                     applicationVersion;
    uint32_t                                     engineNameOffset;
} VkDeviceFaultVendorBinaryHeaderVersionOneEXT;

4. 问题

1) vkGetDeviceFaultInfoEXT 是否应该返回多个错误?

已解决:否。此扩展仅旨在识别单个错误作为设备丢失的可能原因,而不是维护多个错误的日志。我们预计,在 GPU 确实遇到多个错误的情况下,这些错误很可能是重复的,例如由同一缺陷代码的并行执行引起的错误。

2) 是否可以在设备丢失之前调用 vkGetDeviceFaultInfoEXT

已解决:否。VulkanSC 中的 VK_KHR_fault_handling 支持与此等效的功能,但 VK_KHR_fault_handling 旨在解决不同的用例,即在设备丢失之前轮询错误日志,以便采取补救措施。

3) 页面错误是否需要报告实际访问的地址,还是我们应该允许报告页面地址?

已解决:一些 IHV 硬件报告页面错误时,使用页面对齐,或者使用某些其他硬件单元相关的粒度,而不是触发错误的精确地址。所有地址都以硬件单元相关的粒度报告,并附带相关的精度指示符。此信息可用于计算包含触发错误的原始地址的地址范围。

4) 我们应该如何报告多个管道可能导致错误的情况?

已解决:在无法将错误归因于单个唯一管道的情况下,报告可能的候选管道集是可取的。

5) 页面错误和指令地址信息结构具有相似的结构。它们应该合并吗?

已解决:是的。这些已合并为 VkDeviceFaultAddressInfoEXT,以减少 API 表面积。

6) 对于厂商特定的错误,实现者应该如何处理可扩展性?他们应该依赖 pNext 链,还是扩展应该引入一个通用结构来在基本结构中返回厂商错误代码和人类可读的描述?

已解决:在适用的情况下,实现者应使用通用的 VkDeviceFaultVendorInfoEXT 结构,并在其不足的情况下回退到扩展 pNext 链。在需要 pNext 链的情况下,厂商应定制其人类可读的错误描述,以建议开发人员可能有其他信息可用。