Guidelines and HOWTOs/Debugging/Debugging with GDB
This is a short tutorial on debugging KDE applications. Throughout this tutorial I will use "kwrite" as an example application.
If you would like to learn more about using gdb and each step shown here, the video CppCon 2022 Back to Basics: Debugging by Mike Shah serves as a good introduction.
Debugging with GDB
There are three ways to debug an application with gdb:
- You can start the application from within gdb.
- You can attach gdb to an already running application.
- You can run gdb after an application has crashed using a core file.
Starting applications from within gdb
To start an application with gdb you can start gdb as follows:
$ gdb kwrite GNU gdb (GDB) 12.0.90.20220320-git Copyright (C) 2022 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-pc-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from kwrite... (gdb)
You can now set the command line arguments that you want to pass to kwrite with the gdb command "set args":
(gdb) set args myfile.txt (gdb)
gdb has loaded the kwrite executable on startup but it hasn't loaded any of the libraries yet. This means that you can't set any breakpoints in the libraries yet. The easiest way to do that is to set a breakpoint in the first line of main and then start the program:
(gdb) break main Breakpoint 1 at 0x2ee6: file /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp, line 26. (gdb) run Starting program: /home/n/kde/usr/bin/kwrite myfile.txt [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=2, argv=0x7fffffffdcc8) at /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp:26 26 { (gdb)
You can now set breakpoints everywhere. For example lets set a breakpoint in the KAboutData constructor.
(gdb) break KAboutData::KAboutData Breakpoint 2 at 0x555555556570 (8 locations) (gdb)
We can now continue the execution of kwrite. Execution will stop when it hits a breakpoint or when the program exits. In this case execution will stop in the first line of the KAboutData constructor:
(gdb) continue Continuing. [New Thread 0x7ffff0787640 (LWP 174356)] [New Thread 0x7fffef3bd640 (LWP 174357)] Thread 1 "kwrite" hit Breakpoint 2, 0x0000555555556570 in KAboutData::KAboutData(QString const&, QString const&, QString const&, QString const&, KAboutLicense::LicenseKey, QString const&, QString const&, QString const&, QString const&)@plt () (gdb)
You can set a breakpoint on a given source code line. An external editor is of great use at this point. With the list command we can select the source file we are interested in and verify that we have found the correct source line:
(gdb) list main.cpp:200 file: "/home/n/kde/src/frameworks/ki18n/src/i18n/main.cpp", line number: 200, symbol: "???" Line number 195 out of range; /home/n/kde/src/frameworks/ki18n/src/i18n/main.cpp has 77 lines. (gdb) list /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp:115 110 /** 111 * bugzilla 112 */ 113 aboutData.setProductName(QByteArray("kate/kwrite")); 114 115 /** 116 * set and register app about data 117 */ 118 KAboutData::setApplicationData(aboutData); 119 (gdb) break 118 Breakpoint 2 at 0x55555555732b: file /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp, line 118. (gdb) continue Continuing. Thread 1 "kwrite" hit Breakpoint 2, main (argc=1, argv=0x7fffffffdce8) at /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp:118 118 KAboutData::setApplicationData(aboutData); (gdb)
Attaching gdb to already running applications
Sometimes it is not practical to start an application from within gdb. E.g. in those cases where you didn't know the application was about to crash and you get the friendly DrKonqi dialog informing you about a crash. At that point, you are just in time to start your debugger.
First lets attach gdb to an application that hasn't crashed (yet).
You start with finding the process of the application with e.g. ps aux:
$ ps aux | grep kwrite n 175939 0.9 0.4 1948832 134148 pts/8 Sl 04:00 0:00 kwrite myfile.txt n 176073 0.0 0.0 9076 2172 pts/8 S+ 04:01 0:00 grep --color=auto kwrite
From this you learn that kwrite has process id 175939. Now you can start gdb as follows:
$ gdb kwrite 175939 GNU gdb (GDB) 12.0.90.20220320-git Copyright (C) 2022 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-pc-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from kwrite... Attaching to program: /home/n/kde/usr/bin/kwrite, process 175939 [New LWP 175940] ... [New LWP 175996] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 0x00007fcb25abad7f in poll () from /lib/x86_64-linux-gnu/libc.so.6 (gdb)
You will usually end up in the middle of a poll() call from the event-loop. This is the place where a KDE application with graphical user interface (GUI app) spends most of its time, waiting for things to happen.
A backtrace will typically look something like this:
(gdb) bt #0 0x00007fcb25abad7f in poll () from /lib/x86_64-linux-gnu/libc.so.6 #1 0x00007fcb238d5696 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0 #2 0x00007fcb2387e3c3 in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0 #3 0x00007fcb261090a8 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /lib/x86_64-linux-gnu/libQt5Core.so.5 #4 0x00007fcb260ae74b in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /lib/x86_64-linux-gnu/libQt5Core.so.5 #5 0x00007fcb260b6ce4 in QCoreApplication::exec() () from /lib/x86_64-linux-gnu/libQt5Core.so.5 #6 0x000056310e5abc06 in main (argc=2, argv=0x7fffd12fdaf8) at /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp:194 (gdb)
Debugging core files with GDB
If you have to inspect a crash, you can debug core files. E.g. generate a core file:
$ ulimit -c 100000 $ ulimit -a core file size (blocks, -c) 100000 $ # E.g. sudo sysctl -w kernel.core_pattern=core.%u.%p.%t # to enable core generation $ # E.g. cat /proc/sys/kernel/core_pattern core.%u.%p.%t $ KCRASH_DUMP_ONLY=1 kwrite& $ killall -SEGV kwrite [1]+ Segmentation fault (core dumped) KCRASH_DUMP_ONLY=1 kwrite $ ls core.1001.178247.1653874173
You can use gdb with the core file:
gdb kwrite ./core.1001.178247.1653874173 GNU gdb (GDB) 12.0.90.20220320-git ... [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Core was generated by `kwrite'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00007f250647fd7f in poll () from /lib/x86_64-linux-gnu/libc.so.6 [Current thread is 1 (Thread 0x7f2500e1ce80 (LWP 178247))] (gdb)
Improving your gdb experience for KDE/Qt applications
By default GDB cannot pretty print basic Qt types (e.g. QString):
$ gdb kwrite GNU gdb (GDB) 12.0.90.20220320-git ... Reading symbols from kwrite... (gdb) break KAboutData::KAboutData Breakpoint 1 at 0x2570 (gdb) run Starting program: /home/n/kde/usr/bin/kwrite [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff0787640 (LWP 180173)] [New Thread 0x7fffef3bd640 (LWP 180174)] Thread 1 "kwrite" hit Breakpoint 1, 0x0000555555556570 in KAboutData::KAboutData(QString const&, QString const&, QString const&, QString const&, KAboutLicense::LicenseKey, QString const&, QString const&, QString const&, QString const&)@plt () (gdb) step Single stepping until exit from function _ZN10KAboutDataC1ERK7QStringS2_S2_S2_N13KAboutLicense10LicenseKeyES2_S2_S2_S2_@plt, which has no line number information. KAboutData::KAboutData (this=0x555555559b40 <main::{lambda()#2}::operator()() const::qstring_literal>, _componentName=..., _displayName=..., _version=..., _shortDescription=..., licenseType=KAboutLicense::Unknown, _copyrightStatement=..., text=..., homePageAddress=..., bugAddress=...) at /home/n/kde/src/frameworks/kcoreaddons/src/lib/kaboutdata.cpp:507 507 KAboutData::KAboutData(const QString &_componentName, (gdb) step Thread 1 "kwrite" hit Breakpoint 1, KAboutData::KAboutData (this=0x7fffffffd9e0, _componentName=..., _displayName=..., _version=..., _shortDescription=..., licenseType=KAboutLicense::LGPL, _copyrightStatement=..., text=..., homePageAddress=..., bugAddress=...) at /home/n/kde/src/frameworks/kcoreaddons/src/lib/kaboutdata.cpp:507 507 KAboutData::KAboutData(const QString &_componentName,
Since version 7 GDB supports Python scripting for pretty printers. There are such scripts for basic Qt types (QString, QList, QMap, QHash, QDateTime and many others) in KDevelop git repository. Download this directory and add following lines to your ~/.gdbinit to load the scripts automatically at gdb start:
$ cat ~/.gdbinit python import sys sys.path.insert(0, '/home/n/.local/misc/kdevelop-plugins-gdb-printers') from qt import register_qt_printers from kde import register_kde_printers register_qt_printers (None) register_kde_printers (None) end set print pretty on
Run gdb:
$ gdb kwrite GNU gdb (GDB) 12.0.90.20220320-git ... Reading symbols from kwrite... (gdb) break KAboutData::KAboutData Breakpoint 1 at 0x2570 (gdb) run Starting program: /home/n/kde/usr/bin/kwrite [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff0787640 (LWP 179991)] [New Thread 0x7fffef3bd640 (LWP 179992)] Thread 1 "kwrite" hit Breakpoint 1, 0x0000555555556570 in KAboutData::KAboutData(QString const&, QString const&, QString const&, QString const&, KAboutLicense::LicenseKey, QString const&, QString const&, QString const&, QString const&)@plt () (gdb) step Single stepping until exit from function _ZN10KAboutDataC1ERK7QStringS2_S2_S2_N13KAboutLicense10LicenseKeyES2_S2_S2_S2_@plt, which has no line number information. KAboutData::KAboutData (this=0x555555559b40 <main::{lambda()#2}::operator()() const::qstring_literal>, _componentName="", _displayName="", _version="", _shortDescription="", licenseType=KAboutLicense::Unknown, _copyrightStatement="", text="", homePageAddress="", bugAddress="") at /home/n/kde/src/frameworks/kcoreaddons/src/lib/kaboutdata.cpp:507 507 KAboutData::KAboutData(const QString &_componentName, (gdb) step Thread 1 "kwrite" hit Breakpoint 1, KAboutData::KAboutData (this=0x7fffffffd9e0, _componentName="kwrite", _displayName="KWrite", _version="22.07.70", _shortDescription="KWrite - Text Editor", licenseType=KAboutLicense::LGPL, _copyrightStatement="(c) 2000-2022 The Kate Authors", text="<img height=\"362\" width=\"512\" src=\":/kate/mascot.png\"/>", homePageAddress="https://kate-editor.org", bugAddress="[email protected]") at /home/n/kde/src/frameworks/kcoreaddons/src/lib/kaboutdata.cpp:507 507 KAboutData::KAboutData(const QString &_componentName, (gdb)