调试助手

Qt Creator 使用 Python 脚本将调试器后端(目前支持 GDB、LLDB 和 CDB)的原始内存内容和类型信息数据转换成LocalsExpressions视图中的形式呈现给用户。

与 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

调试器首选项中的 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"])

添加调试助手:

  1. 打开share/qtcreator/debugger/personaltypes.py 文件进行编辑。例如,如果您的 Qt 安装位于 Windows 上的Qt5 目录中,请在C:\Qt5\Tools\QtCreator\share\qtcreator\debugger 中查找。在 macOS 上,请在 Qt5/Qt Creator.app/Contents/resources/debugger.
  2. personaltypes.py 文件末尾添加您的 dumper 实现。有关实施调试助手的更多信息,请参阅以下章节。
  3. 为防止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 Expressions
  • value 类型为 的对象,封装了一个Valuegdb.Value或一个lldb.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 后端使用的 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 编译器会为其编译的所有通用序列指定人工名称,如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 作为调试器后端时,可以通过选择首选项>Debugger >CDB >Use Python dumper 来启用 Python dumper。

调试器首选项中的 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) - 使用调试器后台将返回 的函数调用 放在 指定的值上,并输出结果项。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='' type putType 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) - 编码 并调用具有正确 设置的 。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> 字节的 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 对象,如 Pythonmemoryview ,或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

子项和子项类

如果数据未初始化或已损坏,尝试创建子项目可能会导致错误。为了在这种情况下从容恢复,请使用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.