本页内容

调试辅助工具

Qt Creator 使用 Python 脚本将来自调试器后端(目前支持 GDB、LLDB 和 CDB)的原始内存内容和类型信息数据,转换为在“局部变量”和“表达式”视图中呈现给用户的形式。

与 GDB的“美化打印器”和 LLDB的数据格式化器不同,Qt Creator 的调试辅助工具独立于原生调试后端。 也就是说,同一段代码既可在 Linux 上的 GDB、macOS 上的 LLDB 和 Windows 上的 CDB 中使用,也可在任何其他至少支持这三种后端中的一种的平台上使用。

若要使用系统中已安装或与应用程序所用库关联的默认 GDB 格式化打印器,请转至Preferences >Debugger >GDB >Load system GDB pretty printers 。有关更多信息,请参阅GDB。

“调试器”首选项中的“GDB”选项卡

自定义内置调试辅助程序

您可以在内置调试辅助程序加载并完全初始化后执行命令。要加载额外的调试辅助程序或修改现有辅助程序,请转至Preferences >Debugger >Locals & Expressions ,并在Debugging Helper Customization 字段中输入命令。

调试器首选项中的“局部变量和表达式”选项卡

如果在使用 GDB 时收到关于接收信号的错误消息,您可以指定GDB 命令来处理这些信号。例如,如果您收到以下错误消息:The debugged process stopped because it received a signal from the operating system. Signal name: SIGSTOP ,则可以指示 GDB 忽略SIGSTOP 信号。

要阻止 GDB 处理 `SIGSTOP ` 信号,请在“Debugging Helper Customization ”字段中添加以下命令:

handle SIGSTOP nopass
handle SIGSTOP nostop

若要在调试过程中应用程序接收到信号时立即显示消息框,请依次进入“Preferences ” > “Debugger ” > “GDB ” > “Show a message box when receiving a signal ”。

添加自定义调试辅助函数

要为自定义类型添加调试助手,无需编译,只需添加几行 Python 代码即可。这些脚本可同时支持多个版本的 Qt 或您自己的库。

要为自定义类型添加调试辅助函数,请将调试辅助函数的实现添加到调试器的启动文件中(例如,~/.gdbinit~/.lldbinit ),或者直接在Preferences >Debugger >GDB 中的Additional Startup Commands 中指定它们。

若要开始为您的自定义数据类型实现调试辅助函数,可将实现代码放入 Qt Location 目录或独立安装的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"])

要添加调试辅助函数:

  1. 打开share/qtcreator/debugger/personaltypes.py 文件进行编辑。例如,在 Windows 上请查找C:\Qt\Tools\QtCreator\share\qtcreator\debugger 。在 macOS 上,请查找 Qt Creator.app/Contents/resources/debugger
  2. 将您的转储器实现添加到personaltypes.py 文件的末尾。有关实现调试辅助函数的更多信息,请参阅以下各节。
  3. 为防止在更新Qt Creator 安装(例如更新Qt安装时)时personaltypes.py 被覆盖,请将其复制到文件系统中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 的对象,该对象包含当前设置,并提供构建表示LocalsExpressions 视图部分的对象的功能。
  • value 类型为Value ,封装了gdb.Valuelldb.SBValue 之一。

qdump__* 函数必须向 Dumper 对象提供特定信息,这些信息用于在LocalsExpressions 视图中构建该对象及其子节点的显示。

示例:

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 后端的转储函数,请避免直接访问 `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 编译器会为它编译的所有泛型序列分配人工名称,例如TY1TY2 。要在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 的格式,生成所显示数据项的描述。

对于LocalsExpressions 视图中的每一行,都需要生成如下所示的字符串,并将其传递给调试器插件。

{ 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:ValueDumper:Type 抽象类。 这些提供了完整的框架,用于处理inameaddr 字段,处理简单类型的子元素、引用、指针、枚举以及已知和未知的结构体,还提供了一些用于处理常见情况的便捷函数。

当使用 CDB 作为调试器后端时,您可以通过选择“Preferences ” > “Debugger ” > “CDB ” > “Use Python dumper ”来启用 Python 转储器。

调试器首选项中的“CDB”选项卡

以下各节将介绍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) - 使用调试器后端,将函数调用func (该函数返回rettype )放置在由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='' ,除非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 可以是以下任意一种:latin1utf8utf16ucs4intfloatitemsize 指定对象基本组件的大小(如果该大小未由codec 隐含指定),而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 进行编码,并使用正确的encoding 设置调用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> 字节的二进制数据块,隐含对齐为 1
      • <typename> - 一个大小和对齐方式由名称为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 处的模板参数。若numericTrue ,则将该参数作为整数值返回。
  • 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 对象,例如 Pythonmemoryviewbytes 对象。该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) - 返回一个根据pattern 从该值的数据创建的值列表。可接受的模式与Dumper.createType 中的相同。
  • dynamicTypeName(self) - 如果该值是一个基类对象,则尝试检索其动态类型的名称。如果无法检索,则返回 `None `。

子项与 SubItem 类

若数据未初始化或已损坏,尝试创建子项可能会导致错误。为在此类情况下实现优雅恢复,请使用ChildrenSubItem 上下文管理器来创建嵌套项。

Children 的构造函数__init__(self, dumper, numChild = 1, childType = None, childNumChild = None, maxNumChild = None, addrBase = None, addrStep = None) 接受一个必填参数和多个可选参数。必填参数指向当前的Dumper 对象。可选参数可用于指定子项的数量numChild ,每个子项包含类型为childType_ 的子项和childNumChild_ 的孙项。如果指定了maxNumChild ,则仅显示该数量的子项。当导出容器内容可能耗时过长时,应使用此方法。 参数addrBaseaddrStep 可用于减少子节点转储器生成的数据量。如果第 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.