第三方IDE使用gdb调试Qt实现pretty print

  直接使用gdb调试Qt应用时,Qt的一些数据类型没法友好的显示出来,而qtcreator可以很好的展示出来,qtcreator也是通过gdb来调试的,在展示数据时,其实是gdb通过python脚本来处理后显示的,这些python脚本位于/usr/share/qtcreator/debugger这个位置(ubuntu20.04)。

gdb在启动时,会自动在某些目录寻找初始化脚本,一般为:

 ~/etc/gdb/gdbinit
 ~/.gdbinit
 ./.gdbinit

也可以在gdb启动后,手动指定加载初始化脚本source your_gdbinit。按照/usr/share/qtcreator/debugger里面的README.txt,我在~/.gdbinit加了加载命令后,发现不能正常工作。在网络上搜索到kde项目用的kdevelop gdb printers,kde是使用Qt作为GUI开发框架的一个东西,与之对应的是gnome。也可以直接在这里下载
 
  首先将连接里面的3个py文件保存到~/.gdb/qt5prettyprints目录下,其中kde.py可以不用。然后将gdbinit文件保存为~/.gdbinit。需要稍微改下这个文件:

python

#新增下面两行
import sys, os
sys.path.insert(0, "/home/a/.gdb/qt5prettyprinters")

from qt import register_qt_printers
register_qt_printers (None)

#这两句看你是否需要做kde开发
from kde import register_kde_printers
register_kde_printers (None)

end

新增的两行的意思是,给第三方的模块(qt.py, kde.py, helper.py)设置搜索路径,因为这3个python文件保存在我自定义的位置。现在就可以在gdb,甚至是第三方IDE中通过gdb来直观的观察变量了。
 

具体支持哪些Qt内置类型,可以看qt.py脚本的build_dictionary函数,当前为20个。
 
  在CLion中使用这个qt pretty print的python脚本时,遇到一个bug,就是当QDateTime类型作为参数传递时,如果在这在函数上下断点会引起qt.py这个脚本执行异常,导致程序直接退出。例如下面这个例子

如果仅下2,3号断点,不进使用QDateTime作为参数的foo函数,不会有异常,但是如果下了1号断点,程序进入foo直接退出。一开始我使用CLion调试,总是莫名其妙的退出,折腾了1个小时才发现这个规律。但是不知道原因。无意中抛开CLion直接用gdb调试,才发现是python脚本出了问题,如下图:

查看下qt.py脚本里面打印QDateTime类型的处理函数:

class QDateTimePrinter:

    def __init__(self, val):
        self.val = val

    def to_string(self):
        time_t = gdb.parse_and_eval("reinterpret_cast<const QDateTime*>(%s)->toSecsSinceEpoch()" % self.val.address)
        return time.ctime(int(time_t))

  gdb.parse_and_eval很容易看得懂,就是gdb的eval命令,然后结果返回给time_t,再通过time.ctime将time_t转为字符串,返回,也就是print一个QDateTime变量时,显示的字符串。

  再分析下不用qt.py时,直接执行eval,看得到什么结果,先注释~/.gdbinit里面初始化语句,不加载qt.py。然后在foo函数下断点,执行eval。

可以看到eval执行时就报了内存访问错误了。只能改qt.py里面打印QDateTime的逻辑了。经过调试我改为下面的样子:

class QDateTimePrinter:

    def __init__(self, val):
        self.val = val

    def to_string(self):
        sec = self.val['d']['data']['msecs'] / 1000
        localsec = time.localtime(sec + time.timezone)
        return time.strftime("%Y-%m-%d %H:%M:%S", localsec)

  首先它拿到毫秒时间戳,取秒,然后根据本地时区,转为字符串格式。至于为什么self.val['d']['data']['msecs']可以拿到毫秒时间戳,可以看下class QDateTime的定义:

class QDateTime
{
    .....
    .....
  struct ShortData
  {
    .....
    .....
    quintptr status : 8;
    qintptr msecs : sizeof(void *) * 8 - 8;
  }

  union Data
  {
    .....
    .....
      QDateTimePrivate *d;
      ShortData data;
    .....
    .....
  }
  private:
  Data d;
    .....
    .....
}
posted @ 2023-10-13 18:08  thammer  阅读(361)  评论(0编辑  收藏  举报