2. 简介
本文档描述了 CUDA 库中可用于任何调试器的例程和数据结构的 API。
-
性能得到了极大的提升,无论是在与调试器交互方面,还是在被调试应用程序的性能方面。
-
cubin 的格式已更改为 ELF,因此,大多数对调试编译的限制已被解除。下面包含了关于新对象格式的更多信息。
2.1. Debugger API
CUDA Debugger API 的开发目标是遵循以下原则
-
策略自由
-
显式
-
公理化
-
可扩展
-
面向机器
设备有两种主要的“模式”:停止或运行。我们使用 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 调用获得,该调用使用从 CUDBG_EVENT_ELF_IMAGE_LOADED 类型的 CUDBGEvent 公开的句柄。这意味着信息在运行时才可用,在 CUDA 驱动程序加载之后。DWARF 设备信息的生命周期在卸载之前都是有效的,这会呈现 CUDBG_EVENT_ELF_IMAGE_UNLOADED 类型的 CUDBGEvent。
在 CUDA 5.5 及更早版本中,DWARF 设备信息作为 CUDBG_EVENT_ELF_IMAGE_LOADED 类型的 CUDBGEvent 的一部分返回。CUDBGEvent55 中提供的指针是指向由调试 API 管理的内存的只读指针。指向的内存隐式地限定于加载 CUDA 上下文的生命周期。在上下文被销毁后访问返回的指针会导致未定义的行为。
DWARF 设备信息包含除代码内存之外的所有设备内存区域的物理地址。地址类别字段 (DW_AT_address_class) 为所有设备变量设置,并用于指示内存段类型 (ptxStorageKind)。物理地址必须使用几个特定于段的 API 调用来访问。
<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 现在包含所有变量的变量到寄存器映射和活跃度信息。变量可能会溢出到本地内存,而这一切都包含在 DWARF 信息中,该信息是 ULEB128 编码的(作为 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 源代码 drop 中的 cuda-tdep.c。
2.3. ABI 支持
有关更多信息,请参阅标题为“Fermi ABI: Application Binary Interface”的 ABI 文档。
2.4. 异常报告
异常报告仅在 Fermi (sm_20 或更高版本) 上受支持。
2.5. 附加和分离
调试客户端必须采取以下步骤来附加到正在运行的 CUDA 应用程序
-
附加到与 CUDA 应用程序对应的 CPU 进程。应用程序的 CPU 部分此时将被冻结。
-
检查 CUDBG_IPC_FLAG_NAME 变量是否可以从应用程序的内存空间访问。如果不能,则意味着应用程序尚未加载 CUDA 驱动程序,并且附加到应用程序已完成。
-
对函数 cudbgApiInit() 进行动态(下级)函数调用,参数为 "2",即 "cudbgApiInit(2)",例如使用 Linux 上的 ptrace(2)。这会导致从应用程序中 fork 出一个辅助进程,该进程有助于附加到 CUDA 进程。
-
确保 CUDA 调试 API 的初始化已完成,或等到 API 初始化成功(即,调用 "initialize()" API 方法直到成功)。
-
调用 "initializeAttachStub()" API 方法以初始化先前从应用程序 fork 出的辅助进程。
-
从应用程序的内存空间读取 CUDBG_RESUME_FOR_ATTACH_DETACH 变量的值
-
如果该值为非零值,则恢复 CUDA 应用程序,以便可以收集有关应用程序的更多数据并将其发送到调试器。当应用程序恢复时,调试客户端可以期望从 CUDA 应用程序接收各种 CUDA 事件。收集完所有状态后,调试客户端将收到事件 CUDBG_EVENT_ATTACH_COMPLETE。
-
如果该值为零,则没有更多要收集的附加数据。将应用程序进程空间中的 CUDBG_IPC_FLAG_NAME 变量设置为 1,这将启用来自 CUDA 应用程序的更多事件。
-
-
此时,附加到 CUDA 应用程序已完成,并且属于 CUDA 应用程序的所有 GPU 都将被暂停。
调试客户端必须采取以下步骤才能从正在运行的 CUDA 应用程序中分离
-
检查 CUDBG_IPC_FLAG_NAME 变量是否可以从应用程序的内存空间访问,以及 CUDA 调试 API 是否已初始化。如果这些条件中的任何一个不满足,则将应用程序视为仅 CPU 应用程序并从应用程序中分离。
-
接下来,调用 "clearAttachState" API 方法以准备 CUDA 调试 API 进行分离。
-
在应用程序的内存空间中对函数 cudbgApiDetach() 进行动态(下级)函数调用,例如使用 Linux 上的 ptrace(2)。这会导致 CUDA 驱动程序设置分离状态。
-
从应用程序的内存空间读取 CUDBG_RESUME_FOR_ATTACH_DETACH 变量的值。如果该值为非零值,则调用 "requestCleanupOnDetach" API 方法。
-
将应用程序内存空间中的 CUDBG_DEBUGGER_INITIALIZED 变量设置为 0。这确保了如果调试客户端在将来重新附加到应用程序,调试器将从头开始重新初始化。
-
如果在步骤 4 中发现 CUDBG_RESUME_FOR_ATTACH_DETACH 变量的值为非零值,则删除所有断点并恢复 CUDA 应用程序。这允许 CUDA 驱动程序在调试客户端从中分离之前执行清理。清理完成后,调试客户端将收到事件 CUDBG_EVENT_DETACH_COMPLETE。
-
将应用程序内存空间中的 CUDBG_IPC_FLAG_NAME 变量设置为零。这可以防止 CUDA 应用程序向调试器发送更多回调。
-
然后,客户端必须完成 CUDA 调试 API。
-
最后,从 CUDA 应用程序的 CPU 部分分离。此时,属于 CUDA 应用程序的所有 GPU 都将恢复。