第三方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;
    .....
    .....
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章