Jump to content

Guidelines and HOWTOs/Debugging/Debugging with GDB: Difference between revisions

From KDE Community Wiki
mNo edit summary
Nmariusp (talk | contribs)
Example: How to start gdb in TUI mode
 
(22 intermediate revisions by 6 users not shown)
Line 1: Line 1:
This is a short tutorial on debugging KDE applications. Throughout this
tutorial I will use "kwrite" as an example application.


This is a short tutorial on debugging KDE applications. Throughout this
If you would like to learn more about using gdb and each step shown here, the video [https://www.youtube.com/watch?v=YzIBwqWC6EM CppCon 2022 Back to Basics: Debugging] by Mike Shah serves as a good introduction.
tutorial I will use "kedit" as an example application.


==Debugging with GDB==
==Debugging with GDB==
The recommended version of gdb to use is version 4.95 or higher; older
versions have problems generating proper backtraces.


There are three ways to debug an application with gdb:
There are three ways to debug an application with gdb:
Line 19: Line 17:


<pre>
<pre>
> gdb kedit
$ gdb kwrite
GNU gdb 4.95.0
GNU gdb (GDB) 12.0.90.20220320-git
Copyright 2000 Free Software Foundation, Inc.
Copyright (C) 2022 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
welcome to change it and/or distribute copies of it under certain conditions.
This is free software: you are free to change and redistribute it.
Type "show copying" to see the conditions.
There is NO WARRANTY, to the extent permitted by law.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
Type "show copying" and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
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)
(gdb)
</pre>
</pre>


You can now set the command line arguments that you want to pass to kedit with
You can now set the command line arguments that you want to pass to kwrite with
the gdb command "<tt>set args</tt>":
the gdb command "<tt>set args</tt>":


Line 38: Line 45:
</pre>
</pre>


gdb has loaded the kedit executable on startup but it hasn't loaded any of
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
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
libraries yet. The easiest way to do that is to set a breakpoint in the
Line 45: Line 52:
<pre>
<pre>
(gdb) break main
(gdb) break main
Breakpoint 1 at 0x804855c
Breakpoint 1 at 0x2ee6: file /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp, line 26.
(gdb) run
(gdb) run
Starting program: /ext/kde2.0/bin/kedit myfile.txt
Starting program: /home/n/kde/usr/bin/kwrite myfile.txt
Breakpoint 1 at 0x4002cf18: file kedit.cpp, line 1595.
[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=0xbffff814) at kedit.cpp:1595
Breakpoint 1, main (argc=2, argv=0x7fffffffdcc8) at /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp:26
1595            bool have_top_window = false;
26      {
Current language:  auto; currently c++
(gdb)  
(gdb)
</pre>
</pre>


You can now set breakpoints everywhere. For example lets set a breakpoint
You can now set breakpoints everywhere. For example lets set a breakpoint
in the KApplication constructor. Unfortunately, gdb is not very good in
in the KAboutData constructor.
handling C++ names, so it is not really possible to specify the constructor
directly after the break command. Instead we look up a line of source
code where we want to place the breakpoint. 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:


<pre>
<pre>
(gdb) list kapp.cpp:220
(gdb) break KAboutData::KAboutData
215    parseCommandLine( argc, argv );
Breakpoint 2 at 0x555555556570 (8 locations)
216 }
217
218 KApplication::KApplication( bool allowStyles, bool GUIenabled ) :
219  QApplication( *KCmdLineArgs::qt_argc(), *KCmdLineArgs::qt_argv(),
220                GUIenabled ),
221  KInstance( KCmdLineArgs::about),
222  d (new KApplicationPrivate)
223 {
224    if (!GUIenabled)
(gdb) break 224
Breakpoint 2 at 0x4048aa7e: file kapp.cpp, line 224.
(gdb)
(gdb)
</pre>
</pre>


We can now continue the execution of kedit. Execution will stop when it hits
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
a breakpoint or when the program exits. In this case execution will stop
in the first line of the KApplication constructor:
in the first line of the KAboutData constructor:


<pre>
<pre>
(gdb) continue
(gdb) continue
Continuing.
Continuing.
Qt: gdb: -nograb added to command-line options.
[New Thread 0x7ffff0787640 (LWP 174356)]
        Use the -dograb option to enforce grabbing.
[New Thread 0x7fffef3bd640 (LWP 174357)]


Breakpoint 2, KApplication::KApplication (this=0xbffff6a8, allowStyles=true,
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 ()
    GUIenabled=true) at kapp.cpp:224
224        if (!GUIenabled)
(gdb)
(gdb)
</pre>
</pre>


{{Note|
You can set a breakpoint on a given source code line. An external editor is of great
Important: many applications use "KUniqueApplication" to ensure that only one instance can exist at a given time in a given KDE session. This is the case for kwin, kontact, konsole, plasma, etc. To debug those applications, attach to them while they're running (see next session) or use <tt>set args --nofork</tt>
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:
 
<pre>
(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)
</pre>


==Attaching gdb to already running applications==
==Attaching gdb to already running applications==
Line 105: Line 119:
Sometimes it is not practical to start an application from within gdb.
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
E.g. in those cases where you didn't know the application was about to
crash :-) When you get the friendly DrKonqi dialog informing you about
crash and you get the friendly DrKonqi dialog informing you about
a crash you are just in time to start your debugger.
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).
First lets attach gdb to an application that hasn't crashed (yet).


You start with finding the process of the application with e.g. <tt>ps -aux</tt>:
You start with finding the process of the application with e.g. <tt>ps aux</tt>:


<pre>
<pre>
> ps -aux | grep kedit
$ ps aux | grep kwrite
bastian 21570 15.1 6.8 13740 8800 pts/6    S    15:34   0:01 kedit
n  175939 0.9 0.4 1948832 134148 pts/8  Sl  04:00   0:00 kwrite myfile.txt
bastian 21582 0.0  0.3 1132  412 pts/6   R    15:34   0:00 grep kedit
n 176073 0.0  0.0  9076 2172 pts/8   S+  04:01   0:00 grep --color=auto kwrite
</pre>
</pre>


From this you learn that kedit has process id 21570. Now you can start gdb as
From this you learn that kwrite has process id 175939. Now you can start gdb as
follows:
follows:


<pre>
<pre>
> gdb kedit 21570
$ gdb kwrite 175939
GNU gdb 4.95.0
GNU gdb (GDB) 12.0.90.20220320-git
Copyright 2000 Free Software Foundation, Inc.
Copyright (C) 2022 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
welcome to change it and/or distribute copies of it under certain conditions.
This is free software: you are free to change and redistribute it.
Type "show copying" to see the conditions.
There is NO WARRANTY, to the extent permitted by law.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
Type "show copying" and "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
This GDB was configured as "x86_64-pc-linux-gnu".
/home1/bastian/21570: No such file or directory.
Type "show configuration" for configuration details.
Attaching to program: /ext/kde2.0/bin/kedit, Pid 21570
For bug reporting instructions, please see:
Reading symbols from /ext/kde2.0/lib/kedit.so.0...done.
<https://www.gnu.org/software/gdb/bugs/>.
Loaded symbols for /ext/kde2.0/lib/kedit.so.0
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]
...
...
Reading symbols from /lib/ld-linux.so.2...done.
[New LWP 175996]
Loaded symbols for /lib/ld-linux.so.2
[Thread debugging using libthread_db enabled]
Reading symbols from /lib/libnss_compat.so.2...done.
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Loaded symbols for /lib/libnss_compat.so.2
0x00007fcb25abad7f in poll () from /lib/x86_64-linux-gnu/libc.so.6
Reading symbols from /lib/libnsl.so.1...done.
Loaded symbols for /lib/libnsl.so.1
0x40c3d88e in __select () from /lib/libc.so.6
(gdb)
(gdb)
</pre>
</pre>


You will usually end up in the middle of a select() call from the event-loop.
You will usually end up in the middle of a poll() call from the event-loop.
This is the place where a KDE application spends most of its time, waiting
This is the place where a KDE application with graphical user interface (GUI app) spends most of its time, waiting for things to happen.
for things to happen.


A backtrace will typically look something like this:
A backtrace will typically look something like this:
Line 153: Line 170:
<pre>
<pre>
(gdb) bt
(gdb) bt
#0  0x40c3d88e in __select () from /lib/libc.so.6
#0  0x00007fcb25abad7f in poll () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x40a22844 in __DTOR_END__ () at fam.c++:356
#1  0x00007fcb238d5696 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#2  0x407293bf in QApplication::enter_loop (this=0xbffff6e8)
#2  0x00007fcb2387e3c3 in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
    at kernel/qapplication.cpp:2552
#3  0x00007fcb261090a8 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /lib/x86_64-linux-gnu/libQt5Core.so.5
#3  0x406b1d7b in QApplication::exec (this=0xbffff6e8)
#4  0x00007fcb260ae74b in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /lib/x86_64-linux-gnu/libQt5Core.so.5
    at kernel/qapplication_x11.cpp:2217
#5  0x00007fcb260b6ce4 in QCoreApplication::exec() () from /lib/x86_64-linux-gnu/libQt5Core.so.5
#4  0x4002d500 in main (argc=1, argv=0xbffff854) at kedit.cpp:1662
#6  0x000056310e5abc06 in main (argc=2, argv=0x7fffd12fdaf8) at /home/n/kde/src/utilities/kate/apps/kwrite/main.cpp:194
#5  0x40bbba5e in __libc_start_main (main=0x8048568 &lt;main&gt;, argc=1,
    argv=0xbffff854, init=0x8048514 &lt;_init&gt;, fini=0x80486cc &lt;_fini&gt;,
    rtld_fini=0x4000aa20 &lt;_dl_fini&gt;, stack_end=0xbffff84c)
    at ../sysdeps/generic/libc-start.c:92
(gdb)
(gdb)
</pre>
</pre>
Line 169: Line 182:
==Debugging core files with GDB==
==Debugging core files with GDB==


Debugging process requires much memory. If you have to inspect crash, you can debug core files. It's much faster and requires less memory. First limit the maximum size of core files and run the application:
If you have to inspect a crash, you can debug core files.
E.g. generate a core file:
 
<pre>
<pre>
ulimit -c 100000
$ ulimit -c 100000
kedit --nocrashhandler
$ 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
</pre>
</pre>
Do not forget to use <tt>--nocrashhandler</tt> option. Core file would be created if the application crashed, so you can use gdb with created core file:
 
You can use gdb with the core file:
 
<pre>
<pre>
gdb kedit ./core-file #in my system it is core.PID
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)
</pre>
</pre>


==Improving your gdb experience for KDE/Qt applications==
==Improving your gdb experience for KDE/Qt applications==


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 [http://commits.kde.org/kdevelop?path=debuggers/gdb/printers KDevelop git repository]. Download the scripts and add following lines to your ~/.gdbinit to load the scripts automatically as start:
By default GDB cannot pretty print basic Qt types (e.g. QString):
 
<pre>
$ 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,
</pre>


python
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 [https://invent.kde.org/kdevelop/kdevelop/-/tree/master/plugins/gdb/printers KDevelop git repository]. Download this directory and add following lines to your ~/.gdbinit to load the scripts automatically at gdb start:
import sys
sys.path.insert(0, '/folder/where/you/downloaded/the/scripts')
from qt import register_qt_printers
from kde import register_kde_printers


register_qt_printers (None)
<pre>
register_kde_printers (None)
$ cat ~/.gdbinit
end
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


set print pretty on
register_qt_printers (None)
register_kde_printers (None)
end


Note that the pretty printers are written for Qt 4, it is only partially compatible with Qt 5. See also https://bugs.kde.org/show_bug.cgi?id=331044
set print pretty on
</pre>


If you want to go even further, you can apply those [http://developer.kde.org/documentation/other/gdb-patches patches to the gdb source],
Run gdb:
to fix a few annoyancies in gdb:


* source.c: don't try to open a directory in "." that has the same name as the executable we want to open (not needed for gdb-6.0 and above)
<pre>
* symfile.c: no prompting at end of page while opening shared libraries (not needed for gdb-6.2 and above)
$ gdb kwrite
* solib.c: less output when opening shared libraries
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)]


Those patches are maintained by [mailto:faure@kde.org David Faure].
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


Have fun with gdb! Hmm, ok, the definition of 'fun' is very relative...
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)
</pre>
 
== Example ==
 
<pre>
echo '#include <iostream>
#include <vector>
 
void SieveOfEratosthenes(int n)
{
    std::vector<bool> prime(n + 1, true);
 
    for (int p = 2; p * p <= n; p++) {
        if (prime[p] == true) {
            for (int i = p * p; i <= n; i += p) {
                prime[i] = false;
            }
        }
    }
 
    for (int p = 2; p <= n; p++) {
        if (prime[p]) {
            std::cout << p << " ";
        }
    }
}
 
int main()
{
    int n = 30;
    std::cout << "The prime numbers smaller than or equal to " << n << ":" << std::endl;
    SieveOfEratosthenes(n);
    std::cout << std::endl;
    return 0;
}
' > sieve.cpp
g++ -g sieve.cpp -o sieve # '-g' builds without build optimizations and with debug symbols.
./sieve
man gdb
gdb sieve
run
# If it asks about debuginfod, reply no.
# Says '[Inferior 1 (process 759754) exited normally]'.
start
# Pauses and says 'Temporary breakpoint 1, main () at sieve.cpp:25'.
next # Or 'n', step over.
# Press n until the process finishes.
start
n 2
# Says '27          SieveOfEratosthenes(n);'. The integer at the start of the source code line, i.e. 27 is the source code line number.
list . # Shows 10 source code lines centered at the source code line where the debugger is paused.
help list
set listsize 20
list .
set listsize 10
 
step # Step into.
# Says 'SieveOfEratosthenes (n=30) at sieve.cpp:5
# 5      {'.
list .
help
# Says 'running -- Running the program.'.
help running
# Says that step out is 'finish'.
finish # Step out.
# Says 'Run till exit from #0  SieveOfEratosthenes (n=30) at sieve.cpp:6
# main () at sieve.cpp:28
# 28          std::cout << std::endl;'
help text-user # Press tab keyboard key. Autocompletion of commands works at the gdb command prompt.
# Autocompletes 'help text-user-interface'.
# Says 'tui enable', 'tui disable'.
tui enable
# If gdb looks strange, Ctrl+L.
# Finish program.
finish
n 4
# In Text User Interface (TUI) mode, do the previous run starting from the previous 'start' line.
start
n 2
step
# TUI windowing. When the 'src' window is focused, keyboard key up arrow scrolls the source code up one line.
info win
# Says 'src has focus
# cmd'
tui focus cmd # Or 'focus cmd'. There is also 'focus src'.
# Now the 'cmd' window has focus, keyboard key up arrow navigates through the gdb command history.
break 11 # Or 'b'. Sets breakpoint.
continue
help breakpoints
help info
# Says 'info breakpoints'.
info breakpoints # Lists breakpoints.
info locals
print p
print prime[i]
print prime.back() # You can also print the dereference of a pointer (e.g '*p').
backtrace # Or 'bt'.
whatis prime # Sometimes you also want 'ptype prime'.
watch prime[i]
watch i
info breakpoints
clear sieve.cpp:11 # Removes breakpoint.
finish # Run 'finish' multiple times because the watches are actually breakpoints (watchpoints).
Ctrl+D # quit
gdb --silent --tui sieve
</pre>

Latest revision as of 19:17, 25 December 2024

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:

  1. You can start the application from within gdb.
  2. You can attach gdb to an already running application.
  3. 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)

Example

echo '#include <iostream>
#include <vector>

void SieveOfEratosthenes(int n)
{
    std::vector<bool> prime(n + 1, true);

    for (int p = 2; p * p <= n; p++) {
        if (prime[p] == true) {
            for (int i = p * p; i <= n; i += p) {
                prime[i] = false;
            }
        }
    }

    for (int p = 2; p <= n; p++) {
        if (prime[p]) {
            std::cout << p << " ";
        }
    }
}

int main()
{
    int n = 30;
    std::cout << "The prime numbers smaller than or equal to " << n << ":" << std::endl;
    SieveOfEratosthenes(n);
    std::cout << std::endl;
    return 0;
}
' > sieve.cpp
g++ -g sieve.cpp -o sieve # '-g' builds without build optimizations and with debug symbols.
./sieve
man gdb
gdb sieve
run
# If it asks about debuginfod, reply no.
# Says '[Inferior 1 (process 759754) exited normally]'.
start
# Pauses and says 'Temporary breakpoint 1, main () at sieve.cpp:25'.
next # Or 'n', step over.
# Press n until the process finishes.
start
n 2
# Says '27          SieveOfEratosthenes(n);'. The integer at the start of the source code line, i.e. 27 is the source code line number.
list . # Shows 10 source code lines centered at the source code line where the debugger is paused.
help list
set listsize 20
list .
set listsize 10

step # Step into.
# Says 'SieveOfEratosthenes (n=30) at sieve.cpp:5
# 5       {'.
list .
help
# Says 'running -- Running the program.'.
help running
# Says that step out is 'finish'.
finish # Step out.
# Says 'Run till exit from #0  SieveOfEratosthenes (n=30) at sieve.cpp:6
# main () at sieve.cpp:28
# 28          std::cout << std::endl;'
help text-user # Press tab keyboard key. Autocompletion of commands works at the gdb command prompt.
# Autocompletes 'help text-user-interface'.
# Says 'tui enable', 'tui disable'.
tui enable
# If gdb looks strange, Ctrl+L.
# Finish program.
finish
n 4
# In Text User Interface (TUI) mode, do the previous run starting from the previous 'start' line.
start
n 2
step
# TUI windowing. When the 'src' window is focused, keyboard key up arrow scrolls the source code up one line.
info win
# Says 'src has focus
# cmd'
tui focus cmd # Or 'focus cmd'. There is also 'focus src'.
# Now the 'cmd' window has focus, keyboard key up arrow navigates through the gdb command history.
break 11 # Or 'b'. Sets breakpoint.
continue
help breakpoints
help info
# Says 'info breakpoints'.
info breakpoints # Lists breakpoints.
info locals
print p
print prime[i]
print prime.back() # You can also print the dereference of a pointer (e.g '*p').
backtrace # Or 'bt'.
whatis prime # Sometimes you also want 'ptype prime'.
watch prime[i]
watch i
info breakpoints
clear sieve.cpp:11 # Removes breakpoint.
finish # Run 'finish' multiple times because the watches are actually breakpoints (watchpoints).
Ctrl+D # quit
gdb --silent --tui sieve