2. 简介

本文档描述了CUDA库中可供任何调试器使用的集合例程和数据结构的API。

Starting with 3.0, the CUDA debugger API includes several major changes, of which only few are directly visible to end-users:
  • 性能得到显著提升,包括与调试器的交互以及被调试应用程序的运行性能。

  • cubin的格式已更改为ELF,因此大多数对调试编译的限制已被取消。关于新对象格式的更多信息如下所示。

The debugger API has significantly changed, reflected in the CUDA-GDB sources.

2.1. Debugger API

CUDA调试器API的开发遵循以下原则:

  • 策略无关

  • 显式

  • 公理化的

  • 可扩展性

  • 面向机器

Being explicit is another way of saying that we minimize the assumptions we make. As much as possible the API reflects machine state, not internal state.

设备主要有两种"模式":停止或运行。我们通过suspendDevice和resumeDevice在这些模式间显式切换,不过机器也可能自行暂停,例如当遇到断点时。

只有在停止状态下,我们才能查询机器的状态。Warp状态包括当前正在运行的函数、所在的块、哪些通道有效等信息。

从CUDA 6.0开始,如果在未首先调用suspendDevice入口点以确保设备停止的情况下调用调试API中的状态收集函数,将返回CUDBG_ERROR_RUNNING_DEVICE错误。

调试API的客户端应在处理CUDBGEvent前暂停所有设备。只有在执行了通过CUDBGAPI_st::setNotifyNewEventCallback()设置的通知回调后,才能确保返回有效的CUDBGEvent。如果在通过CUDBGAPI_st::setNotifyNewEventCallback()设置的通知回调内部调用任何调试API入口点,都将返回CUDBG_ERROR_RECURSIVE_API_CALL错误。

2.2. ELF与DWARF格式

CUDA应用程序以ELF二进制格式编译。

从CUDA 6.0开始,DWARF设备信息通过调用CUDBGAPI_st::getElfImageByHandle API获取,该调用使用从CUDBGEvent类型为CUDBG_EVENT_ELF_IMAGE_LOADED的事件中暴露的句柄。这意味着在CUDA驱动程序加载完成之前,运行时无法获取该信息。DWARF设备信息的有效期持续到其被卸载为止,此时会触发类型为CUDBG_EVENT_ELF_IMAGE_UNLOADED的CUDBGEvent事件。

在CUDA 5.5及更早版本中,DWARF设备信息作为CUDBGEvent类型CUDBG_EVENT_ELF_IMAGE_LOADED的一部分返回。CUDBGEvent55中提供的指针是只读指针,指向由调试API管理的内存。所指向的内存在加载CUDA上下文的生命周期内隐式限定范围。在上下文被销毁后访问返回的指针会导致未定义行为。

DWARF设备信息包含除代码内存外所有设备内存区域的物理地址。地址类别字段(DW_AT_address_class)为所有设备变量设置,用于指示内存段类型(ptxStorageKind)。必须使用几个特定于段的API调用来访问这些物理地址。

For memory reads, see: For memory writes, see: Access to code memory requires a virtual address. This virtual address is embedded for all device code sections in the device ELF image. See the API call: Here is a typical DWARF entry for a device variable located in memory:
<2><321>: Abbrev Number: 18 (DW_TAG_formal_parameter)
     DW_AT_decl_file   : 27
     DW_AT_decl_line   : 5
     DW_AT_name        : res
     DW_AT_type        : <2c6>
     DW_AT_location    : 9 byte block: 3 18 0 0 0 0 0 0 0       (DW_OP_addr: 18)
     DW_AT_address_class: 7

以上显示变量'res'的地址类别为7(ptxParamStorage)。其位置信息表明它位于参数内存段的地址18处。

默认情况下,局部变量不再溢出到本地内存。DWARF现在包含所有变量的寄存器映射和存活期信息。某些情况下变量仍可能溢出到本地内存,这些信息全部以ULEB128编码格式存储在DWARF中(作为DW_AT_location属性中的DW_OP_regx栈操作)。

以下是一个位于本地寄存器中的变量的典型DWARF条目:

<3><359>: Abbrev Number: 20 (DW_TAG_variable)
     DW_AT_decl_file   : 27
     DW_AT_decl_line   : 7
     DW_AT_name        : c
     DW_AT_type        : <1aa>
     DW_AT_location    : 7 byte block: 90 b9 e2 90 b3 d6 4      (DW_OP_regx: 160631632185)
     DW_AT_address_class: 2

这表明变量'c'具有地址类别2(ptxRegStorage),其位置可通过解码ULEB128值DW_OP_regx:160631632185来定位。有关如何解码该值以及如何获取在特定设备PC范围内保存此变量的物理寄存器的信息,请参阅cuda-gdb源代码包中的cuda-tdep.c文件。

Access to physical registers liveness information requires a 0-based physical PC. See the API call:

2.3. ABI兼容性支持

ABI support is handled through the following thread API calls: The return address is not accessible on the local stack and the API call must be used to access its value.

如需了解更多信息,请参阅标题为《Fermi ABI:应用二进制接口》的ABI文档。

2.4. 异常报告

Some kernel exceptions are reported as device events and accessible via the API call: The reported exceptions are listed in the CUDBGException_t enum type. Each prefix, (Device, Warp, Lane), refers to the precision of the exception. That is, the lowest known execution unit that is responsible for the origin of the exception. All lane errors are precise; the exact instruction and lane that caused the error are known. Warp errors are typically within a few instructions of where the actual error occurred, but the exact lane within the warp is not known. On device errors, we may know the kernel that caused it. Explanations about each exception type can be found in the documentation of the struct.

异常报告仅在Fermi架构(sm_20或更高版本)上受支持。

2.5. 附加与分离

调试客户端必须按照以下步骤连接到正在运行的CUDA应用程序:

  1. 附加到与CUDA应用程序对应的CPU进程。此时应用程序的CPU部分将被冻结。

  2. 检查CUDBG_IPC_FLAG_NAME变量是否可从应用程序的内存空间访问。如果不可访问,则意味着应用程序尚未加载CUDA驱动程序,此时对应用程序的附加操作已完成。

  3. 动态(非优先)调用函数cudbgApiInit(),传入参数"2",即"cudbgApiInit(2)",例如在Linux系统上使用ptrace(2)。这将从应用程序中分叉出一个辅助进程,帮助附加到CUDA进程。

  4. 确保CUDA调试API的初始化已完成,或等待API初始化成功(即调用"initialize()" API方法直至成功)。

  5. 调用"initializeAttachStub()" API来初始化之前从应用程序分叉出来的辅助进程。

  6. 从应用程序的内存空间中读取CUDBG_RESUME_FOR_ATTACH_DETACH变量的值:

    • 如果该值为非零,则恢复CUDA应用程序以便收集更多关于该应用程序的数据并发送至调试器。当应用程序恢复时,调试客户端可以预期会从CUDA应用程序接收各种CUDA事件。一旦所有状态收集完毕,调试客户端将收到CUDBG_EVENT_ATTACH_COMPLETE事件。

    • 如果值为零,则表示没有更多附加数据需要收集。在应用程序的进程空间中设置CUDBG_IPC_FLAG_NAME变量为1,这将启用来自CUDA应用程序的更多事件。

  7. 此时,连接到CUDA应用程序的过程已完成,属于该CUDA应用程序的所有GPU将被暂停。

调试客户端必须执行以下步骤以从正在运行的CUDA应用程序中分离:

  1. 检查CUDBG_IPC_FLAG_NAME变量是否可从应用程序的内存空间访问,并确认CUDA调试API已初始化。如果任一条件不满足,则将应用程序视为仅CPU运行并与之分离。

  2. 接下来,调用"clearAttachState" API接口,为CUDA调试API的分离操作做好准备。

  3. 在应用程序的内存空间中动态(非直接)调用函数cudbgApiDetach(),例如在Linux上使用ptrace(2)。这将使CUDA驱动设置分离状态。

  4. 从应用程序的内存空间中读取CUDBG_RESUME_FOR_ATTACH_DETACH变量的值。如果该值非零,则调用"requestCleanupOnDetach" API。

  5. 在应用程序的内存空间中,将CUDBG_DEBUGGER_INITIALIZED变量设置为0。这样可以确保如果调试客户端将来重新附加到应用程序时,调试器会从头开始重新初始化。

  6. 如果在步骤4中发现CUDBG_RESUME_FOR_ATTACH_DETACH变量的值非零,则删除所有断点并恢复CUDA应用程序运行。这允许CUDA驱动程序在调试客户端与其分离前执行清理操作。清理完成后,调试客户端将收到CUDBG_EVENT_DETACH_COMPLETE事件。

  7. 将应用程序内存空间中的CUDBG_IPC_FLAG_NAME变量设置为零。这将阻止CUDA应用程序向调试器发送更多回调。

  8. 客户端随后必须完成CUDA调试API的初始化。

  9. 最后,从CUDA应用的CPU部分分离。此时,属于该CUDA应用的所有GPU都将恢复运行。