调试助手
Qt Creator 使用 Python 脚本将调试器后端(目前支持 GDB、LLDB 和 CDB)的原始内存内容和类型信息数据转换成Locals和Expressions视图中的形式呈现给用户。
与 GDB 的漂亮打印机和 LLDB 的数据格式化器不同,Qt Creator 的调试助手独立于本机调试后端。也就是说,同样的代码可以用于 Linux 上的 GDB、macOS 上的 LLDB 和 Windows 上的 CDB,也可以用于至少有三种支持后端之一的任何其他平台。
要使用系统中安装的默认 GDB pretty printers 或链接到应用程序使用的库的默认 GDB pretty printers,请访问Preferences>Debugger >GDB >Load system GDB pretty printers 。更多信息,请参阅GDB。
自定义内置调试助手
可以在加载并完全初始化内置调试辅助工具后执行命令。要加载其他调试助手或修改现有助手,请访问首选项>Debugger >Locals & Expressions ,然后在Debugging Helper Customization 字段中输入命令。
如果在使用 GDB 时收到关于接收信号的错误信息,可以指定GDB 命令来处理信号。例如,如果收到以下错误信息,可以告诉 GDB 忽略SIGSTOP
信号:The debugged process stopped because it received a signal from the operating system. Signal name: SIGSTOP
.
要阻止 GDB 处理SIGSTOP
信号,请在Debugging Helper Customization 字段中添加以下命令:
handle SIGSTOP nopass handle SIGSTOP nostop
要在调试过程中一旦收到信号就显示消息框,请转到首选项>Debugger >GDB >Show a message box when receiving a signal 。
添加自定义调试辅助工具
要为自己的类型添加调试帮助程序,无需编译,只需添加几行 Python 代码即可。脚本可以同时处理多个版本的 Qt 或您自己的库。
要为自定义类型添加调试助手,可在调试器的启动文件(如~/.gdbinit
或~/.lldbinit
)中添加调试助手实现,或直接在Additional Startup Commands 的偏好设置>Debugger >GDB 中指定。
要开始为自己的数据类型实现调试帮助程序,可以将它们的实现放到 Qt XML 安装或单机版Qt Creator 安装中的文件share/qtcreator/debugger/personaltypes.py
中。在 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
文件末尾添加您的 dumper 实现。有关实施调试助手的更多信息,请参阅以下章节。 - 为防止
personaltypes.py
在更新Qt Creator 安装(例如更新 Qt 安装时)时被覆盖,请将其复制到文件系统中Qt Creator 安装之外的安全位置,并在Preferences>Debugger >Locals & Expressions >Extra Debugging Helper 中指定该位置。
当你在Qt Creator 中启动调试会话或从调试器日志视图的上下文菜单中选择Reload Debugging Helpers 时,自定义调试辅助程序将自动从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
Locals Expressionsvalue
类型为 的对象,封装了一个Value
gdb.Value或一个lldb.SBValue。
qdump__*
函数必须向 Dumper 对象提供某些信息,这些信息用于在Locals 和Expressions 视图中建立对象及其子对象的显示。
示例
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])
注意: 要创建可在 LLDB 和 GDB 后端使用的 dumper 函数,应避免直接访问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 的格式创建显示数据项的描述。
对于Locals 和Expressions 视图中的每一行,都需要创建如下字符串,并将其导入调试器插件。
{ 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
字段的值显示在视图的Name 列中。如果没有指定,则使用括号中的简单数字代替。
由于无法保证格式的稳定性,强烈建议不要直接生成导线格式,而是使用 Python Dumper 类的抽象层,特别是Dumper
类本身,以及Dumper:Value
和Dumper:Type
抽象。它们提供了一个完整的框架来处理iname
和addr
字段,处理简单类型、引用、指针、枚举、已知和未知结构体的子代,以及一些处理常见情况的方便函数。
使用 CDB 作为调试器后端时,可以通过选择首选项>Debugger >CDB >Use Python dumper 来启用 Python dumper。
下面几节描述了qtcreator\share\qtcreator\debugger\dumper.py
中指定的一些广泛使用的 Dumper 类和成员。
dumper 类
Dumper
类具有通用簿记、底层和便利函数:
putItem(self, value)
- 主函数直接处理基本类型、引用、指针和枚举,迭代基类和复合类型的类成员,并在适当的时候调用 函数。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)
- 使用调试器后台将返回 的函数调用 放在 指定的值上,并输出结果项。rettype
func
value本地调用功能非常强大,例如可以利用调试进程中现有的调试或日志记录设施。但是,只有在受控环境下,而且只有在没有其他方法访问数据的情况下,才能使用本地调用,原因如下:
- 直接执行代码很危险。它以被调试进程的权限运行本地代码,不仅有可能破坏被调试进程,还有可能访问磁盘和网络。
- 检查核心文件时不能执行调用。
- 在调试器中设置和执行调用的成本很高。
putArrayData(self, address, itemCount, type)
- 创建由 指定的子对象个数,该子对象的类型为 ,位于 。itemCount
type
address
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=''
typeputType
priority
putBetterType(self, type)
- 覆盖最后记录的 。type
putExpandable(self)
- 宣布当前值是否存在子项目。默认情况下没有子项。putNumChild(self, numchild)
- 宣布当前值是否存在子项 ( > 0)。numchild
putValue(self, value, encoding = None)
- 附加文件 ,可选择在其后附加字段 。 需要转换为完全由字母数字值组成的字符串。 参数可用于指定编码,以防实际值必须以某种方式编码才能满足只包含字母数字的要求。参数 是一个形式为 的字符串,其中 是 , , , , , 或 中的任意一个。 给出对象基本组件的大小(如果 没有暗示), 指定显示值时是否应使用引号。value=''
valueencoding=''
value
encoding
encoding
codec:itemsize:quote
codec
latin1
utf8
utf16
ucs4
int
float
itemsize
codec
quote
示例
# Safe transport of quirky data. Put quotes around the result. d.putValue(d.hexencode("ABC\"DEF"), "utf8:1:1")
putStringValue(self, value)
- 编码 并调用具有正确 设置的 。QStringencoding
putValue
putByteArrayValue(self, value)
- 编码 并以正确的 设置调用 。QByteArrayencoding
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>
- 一个具有合适大小和合适对齐方式的 blob,由 决定,其名称为Dumper.Type
typename
- 如果
Dumper.Type 类
Dumper.Type
类描述了数据类型,通常是 C++ 类或结构体、结构体指针或原始类型(如积分或浮点类型)。
类型对象,也就是Dumper.Type
类的实例,可以由调试器后端创建,通常是通过评估调试二进制文件中内置或随附的调试信息,或者由调试辅助程序即时创建。
Qt Creator 调试助手为大多数 Qt 类即时提供了类型信息,从而消除了使用调试构建 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 类
Dumper.Field
类描述类型对象的基类或数据成员:
isBaseClass
- 区分基类和数据成员。fieldType(self)
- 返回该基类或数据成员的类型。parentType(self)
- 返回所属类型。bitsize(self)
- 以位为单位返回该字段的大小。bitpos(self)
- 以位为单位返回该字段在所属类型中的偏移量。
Dumper.Value 类
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)
- 将此值的数据作为 Python 对象返回。bytes
split(self, pattern)
- 返回根据 从该值的数据创建的值列表。可接受的模式与 相同。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)
Copyright © The Qt Company Ltd. and other contributors. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.