调试助手
Qt Creator 使用 Python 脚本来将调试器后端(目前支持 GDB、LLDB 和 CDB)从调试器获取的原始内存内容和类型信息数据翻译成用户在 局部变量 和 表达式 视图中查看到的格式。
与 GDB 的 美化和打印选项 以及 LLDB 的 数据格式化工具 不同,Qt Creator 的调试助手独立于本地的调试后端。也就是说,相同的代码可以在 Linux 上的 GDB、macOS 上的 LLDB 和 Windows 上的 CDB,或至少在三个支持的后端中有一个可用的任何其他平台上使用。
要使用您的系统或应用程序使用的库中安装的默认 GDB 美化和打印选项,请转到 首选项 > 调试器 > GDB > 加载系统 GDB 美化和打印选项。有关更多信息,请参阅 GDB。
自定义内置调试助手
您可以在内置调试助手加载并完全初始化后执行命令。要加载额外的调试助手或修改现有的调试助手,请转到 首选项 > 调试器 > 局部变量和表达式,并在 调试助手自定义 字段中输入命令。
如果您在使用 GDB 时收到有关接收信号的错误消息,您可以指定用于处理信号的 GDB 命令。例如,如果您收到以下错误消息:`The inferior stopped because it received a signal from the operating system. Signal name: SIGSTOP`,您可以告诉 GDB 忽略 `SIGSTOP
` 信号。
要停止 GDB 处理 `SIGSTOP
` 信号,请将以下命令添加到 调试助手自定义 字段
handle SIGSTOP nopass handle SIGSTOP nostop
在调试过程中,当你的应用程序收到信号时立即显示消息框,请转到首选项 > 调试器 > GDB > 接收信号时显示消息框。
添加自定义调试助手
为您的自定义类型添加调试助手时,无需编译,只需添加几行Python代码。脚本是针对Qt的多个版本或您自己的库同时使用的。
为自定义类型添加调试助手,请将调试助手实现添加到调试器的启动文件中(例如,~/.gdbinit
或 ~/.lldbinit
),或在首选项 > 调试器 > GDB 中的其他启动命令中直接指定。
要开始实现自定义数据类型的调试助手,您可以将实现放入您Qt安装中的share/qtcreator/debugger/personaltypes.py
文件,或单独的Qt Creator安装中的该文件。在macOS上,该文件捆绑在Qt Creator应用程序包中,位于Contents/resources/debugger
文件夹中。
personaltypes.py
文件有一个示例实现
# def qdump__MapNode(d, value): # d.putValue("This is the value column contents") # d.putExpandable() # if d.isExpanded(): # with Children(d): # # Compact simple case. # d.putSubItem("key", value["key"]) # # Same effect, with more customization possibilities. # with SubItem(d, "data") # d.putItem("data", value["data"])
要添加调试助手
- 打开用于编辑的
share/qtcreator/debugger/personaltypes.py
文件。例如,如果您的Qt安装位于Windows上的Qt5
目录中,请查看C:\Qt5\Tools\QtCreator\share\qtcreator\debugger
。在macOS上,请查看Qt5/Qt Creator.app/Contents/resources/debugger
。 - 将您的反序列化实现添加到
personaltypes.py
文件的末尾。有关实现调试助手的更多信息,请参阅下面的章节。 - 要防止在更新您的Qt Creator安装(例如,在更新您的Qt安装时)时覆盖
personaltypes.py
,请将其复制到文件系统中的Qt Creator安装之外的安全位置,并在首选项 > 调试器 > 局部变量和表达式 > 额外的调试助手中指定位置。
在Qt Creator中启动调试会话或在调试日志视图的上下文菜单中选择重新加载调试助手时,将自动从personaltypes.py
中选取自定义调试助手。
调试助手概述
调试助手的实现通常由一个Python函数组成,该函数的名称需要为qdump__NS__Foo
,其中NS::Foo
是要检查的类或类模板。请注意,作用域解析运算符::
被双下划线:__
所代替。可以创建嵌套命名空间。在函数名称构造中不使用模板参数。
示例
- 为类型
namespace Project { template<typename T> struct Foo {... } }
实现调试助手的函数名为qdump__Project__Foo
。 - 为类型
std::__1::vector<T>::iterator
实现调试助手的函数名为qdump__std____1__vector__iterator
。
Qt Creator的调试插件在您想要显示此类型对象的任何时候都会调用此函数。函数将传递以下参数
d
,类型为Dumper
,它是一个具有当前设置并提供构建表示局部变量和表达式视图中的对象的部分的支持的对象。value
的类型,封装了 gdb.Value 或 lldb.SBValue。
qdump__*
函数需要向 Dumper 对象提供某些信息,用于构建对象及其子对象在 局部变量 和 表达式 视图中的显示。
示例
def qdump__QFiniteStack(d, value): alloc = value["_alloc"].integer() size = value["_size"].integer() d.putItemCount(size) if d.isExpanded(): d.putArrayData(value["_array"], size, value.type[0])
注意:为了使 dumper 函数适用于 LLDB 和 GDB 后端,请避免直接访问 gdb.*
和 lldb.*
命名空间,并使用 Dumper
类的函数。
要在调试辅助程序中获取对象的基实例,请使用 value.base()
函数或以下示例代码
def qdump__A(d, value): t = value.members(True)[0].type dptr, base_v = value.split('p{%s}' % t.name) d.putItem(base_v)
调试辅助程序可以配置为在类型名称与正则表达式匹配时调用。为此,调试辅助程序的函数名必须以 qdump__
开头(包含两个下划线)。此外,该函数还需要一个名为 regex
的第三个参数,其默认值指定类型名称应匹配的正则表达式。
例如,Nim 0.12 编译器为所有编译的泛型序列分配人工名称,例如 TY1
和 TY2
。要使用 Qt Creator 可视化这些名称,可以使用以下调试辅助程序
def qdump__NimGenericSequence__(d, value, regex = "^TY.*$"): size = value["Sup"]["len"] base = value["data"].dereference() typeobj = base.dereference().type d.putArrayData(base, size, typeobj)
调试辅助程序实现
调试辅助程序创建的显示数据项描述格式类似于 GDB/MI 和 JSON。
对于 局部变量 和 表达式 视图中每一行,需要创建并传输类似以下字符串到调试插件。
{ iname='some internal name', # optional address='object address in memory', # optional name='contents of the name column', # optional value='contents of the value column', type='contents of the type column', numchild='number of children', # zero/nonzero is sufficient children=[ # only needed if item is expanded in view {iname='internal name of first child', }, {iname='internal name of second child', }, ]}
iname
字段的值是对象的内部名称,由一系列点分隔的标识符组成,对应于视图中对象表示的位置。如果不存在,则通过连接父对象的 iname
、一个点和一个顺序号生成。
name
字段的值在视图中显示在 名称 列中。如果没有指定,则使用括号中的简单数字代替。
由于格式可能不稳定,因此强烈建议不要直接生成线格式,而是使用 Python Dumper 类的抽象层,特别是 Dumper
类本身,以及 Dumper:Value
和 Dumper:Type
抽象。它们提供了一个完整的框架,用于处理 iname
和 addr
字段,以及处理简单类型、引用、指针、枚举和已知和未知的结构,同时还提供了一些方便的函数来处理常见情况。
使用 CDB 作为调试后端时,您可以通过选择 首选项 > 调试器 > CDB > 使用 Python dumper 启用 Python dumper。
以下各节描述了 qtcreator\share\qtcreator\debugger\dumper.py
中定义的一些常用 Dumper 类和成员。
Dumper 类
Dumper
类具有通用的、低级的和方便的函数
putItem(self, value)
- 处理基本类型、引用、指针和枚举 directly,遍历复杂数据类型的基类和类成员,并在适当的情况下调用qdump__*
函数。putIntItem(self, name, value)
- 等价于with SubItem(self, name): self.putValue(value) self.putType("int")
putBoolItem(self, name, value)
- 等价于with SubItem(self, name): self.putValue(value) self.putType("bool")
putCallItem(self, name, rettype, value, func, *args)
- 使用调试器后端将函数调用func
返回rettype
置于由 value 指定的值上,并输出结果项。本地调用非常强大,可以利用被调试进程中的现有调试或日志功能,例如。但是,它们只能在受控环境中使用,而且只有在没有其他访问数据的方式时才使用,以下是一些原因:
- 直接执行代码是危险的。它以调试进程的权限运行本地代码,可能会不仅损坏调试进程,还能访问磁盘和网络。
- 在检查核心文件时无法执行调用。
- 在调试器中设置和执行调用很昂贵。
putArrayData(self, address, itemCount, type)
- 在地址address
处创建具有指定类型type
和数量itemCount
的类似数组的对象的孩子。putSubItem(self, component, value)
- 等同于with SubItem(self, component): self.putItem(value)
嵌套函数调用抛出的异常将被捕获,并且
putItem
生成的所有输出都将被except RuntimeError: d.put('value="<invalid>",type="<unknown>",numchild="0",')
put(self, value)
- 一个低级函数,用于直接向输出字符串追加。这也是追加输出最快的方式。putField(self, name, value)
- 追加一个name='value'
字段。childRange(self)
- 返回当前Children
范围内指定的孩子范围。putItemCount(self, count)
- 向输出追加字段value='<%d items>'
。putName(self, name)
- 追加name=''
字段。putType(self, type, priority=0)
- 追加字段type=''
,除非 type 与父级的默认孩子类型相同,或者putType
已经对当前项目使用更高的priority
值进行了调用。putBetterType(self, type)
- 覆盖最后记录的type
。putExpandable(self)
- 宣称当前值存在孩子物品。默认情况是没有孩子。putNumChild(self, numchild)
- 宣称当前值存在或不存在孩子物品(numchild
> 0)。putValue(self, value, encoding = None)
- 追加文件value=''
,可选地随后跟字段valueencoding=''
。需要将value
完全转换为仅由字母数字值组成的字符串。可以使用encoding
参数在需要的情况下指定编码以满足仅字母数字的要求。参数encoding
是一个形如codec:itemsize:quote
的字符串,其中codec
可以是latin1
、utf8
、utf16
、ucs4
、int
或float
之一的任何内容。《code translate="no">itemsize 给出对象的基组件的大小,如果它不由codec
暗示,则《code translate="no">quote 指定是否在显示中用引号包围值。示例
# Safe transport of quirky data. Put quotes around the result. d.putValue(d.hexencode("ABC\"DEF"), "utf8:1:1")
putStringValue(self, value)
- 对 QString 进行编码,并使用正确的encoding
设置调用putValue
。putByteArrayValue(self, value)
- 对QByteArray进行编码,并使用正确的编码设置调用putValue
。isExpanded(self)
- 检查当前项是否在视图中展开。createType(self, pattern, size = None)
- 创建一个Dumper.Type
对象。具体操作取决于pattern
。- 如果
pattern
与知名类型的名称匹配,则返回描述此类型的Dumper.Type
对象。 - 如果
pattern
是与本地后端已知的类型的名称,则返回的类型描述了本地类型。 - 否则,使用以下方式通过解释描述结构体字段的项目的序列来构建类型描述:
pattern
。字段描述由以下字符组成:q
- 有符号8字节整数值Q
- 无符号8字节整数值i
- 有符号4字节整数值I
- 无符号4字节整数值h
- 有符号2字节整数值H
- 无符号2字节整数值b
- 有符号1字节整数值B
- 无符号1字节整数值d
- 8字节IEEE 754浮点值f
- 4字节IEEE 754浮点值p
- 指针,即根据目标架构适当大小的无符号整数@
- 适当的填充。大小由前一个字段和后一个字段以及目标架构确定<n>s
- <n>字节的blob,隐含对齐为1<typename>
- 根据具有名称typename
的Dumper.Type
的大小和对齐来确定大小的blob
- 如果
Dumper.Type 类
Dumper.Type
类描述数据片段的类型,通常是C++类或结构体、结构体的指针或基础类型,如整数或浮点类型。
类型对象,即Dumper.Type
类的实例,可以由调试器后端创建,通常是通过评估构建在或与调试的二进制文件一起分发的调试信息,或者在实时由调试辅助函数创建。
Qt Creator为大多数Qt类提供即时类型信息,从而消除了使用Debug构建Qt以进行对象内省的需要。
Dumper.Type
类具有以下广泛使用的成员函数
name
- 作为字符串的此类型的名称,或者在类型是匿名的时为None
。size(self)
- 返回该类型对象的字节大小。bitsize(self)
- 返回该类型对象的位大小。alignment(self)
- 返回此类型对象所需的对齐字节。deference(self)
- 对于指针类型返回解引用类型,否则返回None
。pointer(self)
- 返回可以解引用到此类型的指针类型。target(self)
- 返回数组类型的项类型和指针或引用类型的解引用类型的一个便利函数。stripTypedefs(self)
- 如果此类型是别名,则返回基础类型。templateArgument(self, position, numeric = False)
- 如果这是模板类型,则返回位于position
的模板参数。如果numeric
为True
,则将参数作为整数值返回。fields(self)
- 返回描述该类型的基本类和数据成员的Dumper:Fields
列表。
거버너 필드 클래스
Dumper.Field
类描述类型对象的基本类或数据成员
isBaseClass
- 区分基本类和数据成员。fieldType(self)
- 返回此基本类或数据成员的类型。parentType(self)
- 返回拥有类型。bitsize(self)
- 返回此字段的位大小。bitpos(self)
- 返回此字段在拥有型中的偏移(以位为单位)。
거버너 가치 클래스
Dumper.Value
类描述数据块,如 C++ 类的实例或原始数据类型。它还可以用来描述在内存中没有直接表示的人造项,如文件内容、非连续对象或集合。
一个 Dumper.Value
总是有相关的 Dumper.Type
。值的实际数据的两个主要表示形式为:
- Python 对象,遵循 Python 缓冲区协议,如 Python 的
memoryview
或bytes
对象。其size()
应匹配此值类型的尺寸。 - 一个表示当前地址空间中对象开始的指针的整数值。对象的尺寸由其类型的
size()
给出。
在为 Dumper.Value
创建调试辅助工具时,通常不需要了解其内部表示。
Dumper.Value
类的成员函数和属性如下
integer(self)
- 以合适大小的有符号整数值返回对此值的解释。pointer(self)
- 以当前地址空间中的指针解释此值。members(self, includeBases)
- 返回表示此值的基对象和数据成员的Dumper.Value
对象的列表。dereference(self)
- 对于描述指针的值,返回取消引用的值,否则返回None
。cast(self, type)
- 返回一个值,它的数据与此值相同,但有type
类型。address(self)
- 如果此值由当前地址空间中的一个连续区域组成,则返回此值的地址,否则返回None
。data(self)
- 返回此值的 Pythonbytes
对象中的数据。split(self, pattern)
- 根据模式在此值的数据中创建值,并返回值列表。可接受的模式与Dumper.createType
相同。dynamicTypeName(self)
- 如果这是一个基类对象,则尝试检索此值的动态类型名称。如果不可能,则返回None
。
子项和子项类
如果数据未初始化或损坏,则尝试创建子项可能会导致错误。要在这种情况下优雅地恢复,请使用 Children
和 SubItem
上下文管理器 创建嵌套项。
《Children》构造函数 __init__(self, dumper, numChild = 1, childType = None, childNumChild = None, maxNumChild = None, addrBase = None, addrStep = None)
使用一个必选参数和多个可选参数。必选参数是指当前的 Dumper
对象。可选参数可以用来指定子节点数量 numChild
,每个子节点可以指定子子节点类型 childType_
和 childNumChild_
。如果指定了 maxNumChild
,则只显示这么多子节点。这通常用于需要在其他情况下执行过度长时间的容器内容的反向工程时。参数 addrBase
和 addrStep
可以用来减少子节点反向工程生成的数据量。如果第 n 个子节点项的地址与 addrBase + n * addrStep 相等,则将抑制地址打印。
示例
if d.isExpanded(): with Children(d): with SubItem(d): d.putName("key") d.putItem(key) with SubItem(d): d.putName("value") d.putItem(value)
请注意,这可以更方便地写成
d.putNumChild(2) if d.isExpanded(): with Children(d): d.putSubItem("key", key) d.putSubItem("value", value)
©2024 The Qt Company Ltd. 本文档中包含的文档贡献是各自所有者的版权。提供的文档根据 Free Software Foundation 发布的 GNU Free Documentation License 版本 1.3 的条款进行许可。Qt 及其相应标志是芬兰和/或全世界 The Qt Company Ltd 的商标。所有其他商标均为各自所有者的财产。