Author: Mateusz ?oskot - mateusz AT loskot DOT net - for comments and suggestions to this document

Introduction

The purpose behind this document is to explain how to debug QGIS plugins and why it is so tricky. Debuger, after compiler, is the most important weapon programmer has to fight with bugs in his code. (This is obvious there is no source code free of bugs). This document is a supplement to the DevelopingPlugins. Please, be quite understanding while reading this article. I'm not a GDB guru so all I wrote is based on my own experience and knowledge learned from online resources during two nights of hacking the QGIS + Plugins debugging process.

QGIS uses dynamically loaded Libraries (DL) to implement and manage plugins. Please not I use the term dynamically loaded, not shared. There is a small difference between dynamically loaded and shared libraries. The first type - DL - is what we use in QGIS as plugins. Plugins are loaded and linked during execution time using using the dlopen facilities. Libraries of the second type - shared libraries - are loaded and linked at the start of the program by special loader (on Linux sytems loader can be found here /lib/ld-linux.so.X where X is a version number). Loader searches and loads only those shared libraries required by program being started (at the start time). For more details about programming and using libraries under Linux see Bibliography section at the bottom of this document.

Prerequisites

Here is the list of skills:

  • Linux box configured for development (installed gcc, development libraries, terminal, etc.)
  • Installed GNU Project Debugger >=6.3 + dependencies
  • Installed KDevelop + dependencies

Debugger

This document discuss usage of gdb. GDB, the GNU Project Debugger, is the most popular debugger used on Linux and other Unixes. It provides console based user interface but you can find many GUI frontends. KDevelop includes such a frontend, so when you run debugging session from KDevelop, most likely, you are using gdb (you can also use third-party debuggers from KDevelop, but that's not subject of this document). So, I will discuss debugging with gdb - using its console based interface, then I will discuss KDevelop usage.

A few words about my environment

I wrote and tested this HOWTO on "Ubuntu 5.10 "Breezy Badger"":http://www.ubuntu.com.

Prompt on my Linux box looks like this: mloskot@dog

I checkouted QGIS HEAD to following location:

 mloskot@dog:~/dev/qgis/_cvs$ ls
 CVS      debian  documentation_source  plugins  tools
 CVSROOT  design  mdrweb                qgis

So, qgis main module is here: /home/mloskot/dev/qgis/_cvs/qgis

and the main src directory is here: /home/mloskot/dev/qgis/_cvs/qgis/src

and plugins directory is - obviously - here:

 mloskot@dog:~/dev/qgis/_cvs/qgis/plugins$ ls
 community_reg_plugin  geoprocessing  Makefile     north_arrow        scale_bar
 copyright_label       georeferencer  Makefile.am  plugin_builder.pl  spit 
 CVS                   gps_importer   Makefile.in  plugins.pro
 delimited_text        grass          maplayer     plugin_template
 example               grid_maker     matrix1.xpm  qgisplugin.h

I install QGIS to my local ~/usr directory, so I use --prefix=$HOME/usr then I have following structure of QGIS installation:

  • ~/usr/bin/qgis - main QGIS executable
  • ~/usr/lib/qgis - plugins directory
  • ~/usr/share/qgis - rest of QGIS stuff (icons, images, themes, docs, etc.)

How to debug QGIS with plugins using GDB

In this section I will explain how to debug QGIS and QGIS plugins under plain gdb, withouth any GUI frontent like KDevelop. The main trick about debugging DL libraries is behind setting breakpoints and configuring debugging session properly. In this section I will try to explain following subjects:

  • how to run simple debugging session with QGIS under gdb
  • how to configure gdb environment in order to examine current debugging context in sources
  • how to debug QGIS plugins - dynamically loaded libraries - under gdb

Preparation

Note: As Tim pointed, following explanation of -g flag and --enable-debug option is not correct in terms of QGIS compilation. I'm investigating how does it work and I'll update this section ASAP. Please, let me know if you know how exactly -g and --enable-debug work in QGIS.

First, compile QGIS with -g flag to include debugging information and symbols into QGIS executable. It can be achived by specifying --enable-debug*=yes flag as an *autogen parameter:

 mloskot@dog:~/dev/qgis/_cvs/qgis$ ./configure --help | grep debug
  --enable-debug          Enable debuging messages [default=no]

Second, install QGIS uder location specified by --prefix flag

Running simple QGIS debugging session under gdb

Now, to start debugging session we invoke gdb with QGIS executable as a parameter:

 mloskot@dog:~$ gdb ~/usr/bin/qgis

Then you will see a few lines of banner with license information and finally gdb prompt:


 (gdb)

Now, we have debugger launched and ready to run our QGIS program. We can also give command line parameters here. In example below I run QGIS with specifying project file to open:


 (gdb) run --project /home/mloskot/dev/qgis/data/cewice/cewice.qgs
 Starting program: /home/mloskot/usr/bin/qgis --project /home/mloskot/dev/qgis/data/cewice /cewice.qgs

Before executing run command we have a few possibilities. We can set breakpoints, watchpoints, catchpoints, etc. Simply, before running program under debugger we should make a plan whatparts of we will be examining.

Note: I'm not going to provide complete guide about debugging with gdb. I will only explain required minimum of information, a background.

So, let's go on.

Here I set breakpoint on the main function (There are many ways to set breakpoints, see gdb documentation and bibliography at the bottom of this document):

 (gdb) break main
 Breakpoint 1 at 0x457b60: file main.cpp, line 203.

gdb displays address of main function, source file and line. This useful information (file, line) is included during compilation with -g flag.

Note, that I've not gave gdb any reference to main.cpp file and note that qgis binary is placed in different directory as QGIS sources. So, this information is included into compiled and linked binary executable.

So, now we can run QGIS:

 (gdb) run

After a few seconds debugger stops QGIS at given breakpoint, on main function:

 Breakpoint 1, main (argc=1, argv=0x7ffffff6c3b8) at main.cpp:203
 203     main.cpp: No such file or directory.
 in main.cpp
 (gdb)

QGIS execution stopped on main function but gdb also reports it can not find source file containing source code of main function. And here we come to the main problem about browsing source code of debugging context. We can examine values of variables, set breakpoints etc. but we can not access source code of the context.

So, how to solve this problem?

We have to tell gdb where live sources of debugging programs or libraries. We can do it using directory command to set source directory path:

 (gdb) directory /home/mloskot/dev/qgis/_cvs/qgis/src
 Source directories searched: /home/mloskot/dev/qgis/_cvs/qgis/src:$cdir:$cwd

To print all directories gdb knows use show command (see documentation):

 (gdb) show directories
 Source directories searched: /home/mloskot/dev/qgis/_cvs/qgis/src:$cdir:$cwd

Now we can print lines from main.cpp source file in which we have our breakpoint set using list command:

 (gdb) list
 198           abort();                    // deliberately core dump
 199       }
 200     }
 201
 202
 203     int main(int argc, char *argv[])
 204     {
 205
 206       // Set up the custom qWarning/qDebug custom handler
 207       qInstallMsgHandler( myMessageOutput );

There are many ways to print source lines, see 7.1 Printing source lines chapter of gdb manual.

Next, you can browse sources and set new breakpoints. Let's exercise:

 (gdb) break 336
 Breakpoint 2 at 0x457d4c: file main.cpp, line 336.

Now, continue running QGIS under gdb:

 (gdb) continue
 Continuing.
 Files specified on command line: 1
 Breakpoint 2, main (argc=1, argv=0x7ffffff6c3b8) at main.cpp:336
 336       if (!myUseGuiFlag)

Again, gdb stopped QGIS on our new breakpoint.

Next, you can follow this scheme, list-break-print-continue, and debug QGIS under gdb. Note, as I said before, you can follow your own plan of debugging session. There are also many advanced gdb features and techniques of debugging. I recommend you to reference gdb documentation and related bibliography.

Debugging QGIS plugins

Here we come to our main section - degugging QGIS plugins.

First, I'd like to give a little background. Shared and dynamically loaded libraries are "visible" to debugger after they are loaded, not before. Here is quotation from gdb manual which I believe explains it well:

If a specified breakpoint location cannot be found, it may be due to the fact that the location is in a shared library that is yet to be loaded. In such a case, you may want GDB to create a special breakpoint (known as a pending breakpoint) that attempts to resolve itself in the future when an appropriate shared library gets loaded.
Pending breakpoints are useful to set at the start of your GDB session for locations that you know will be dynamically loaded later by the program being debugged. When shared libraries are loaded, a check is made to see if the load resolves any pending breakpoint locations. If a pending breakpoint location gets resolved, a regular breakpoint is created and the original pending breakpoint is removed.
Let's start new gdb session. Our plan is to debug QGIS plugin Copyright Label. Here I repeat well known steps.

Run QGIS under gdb:

 mloskot@dog:~$ gdb ~/usr/bin/qgis

Set path to source directory of the plugin we will debug:

 (gdb) directory /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label
 Source directories searched: /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label:$cdir:$cwd

Next, I set up some breakpoint(s). You may decide your own plan of debugging. I searched (use Vim, KDevelop or any your favourite editor) /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label/plugin.cpp file and decided to walk step-by-step through QgsCopyrightLabelPlugin::renderLabel function. So, I set breakpoint in file plugin.cpp, line 163:

 (gdb) break plugin.cpp:163
 No source file named plugin.cpp.
 Make breakpoint pending on future shared library load? (y or [n]) y
 Breakpoint 1 (plugin.cpp:163) pending.

Note, this is what I was talking about - "visibility" of objects and pending breakpoints. At this point gdb is not able to resolve given file or function because plugin (DL library) has not been loaded by QGIS yet. We know about that, so we should accept this breakpoint as a pending breakpoint.

Next, I tell gdb to run te QGIS:

 (gdb) run
 Starting program: /home/mloskot/usr/bin/qgis
 ...

After that gdb runs QGIS and you see QGIS debug output sending to the gdb console, as usually.

At this point, I don't have Copyright Label plugin loaded by QGIS, because I turn it off before I started to debugging to make this example more clear.

So, now I have QGIS launached. I remember that my breakpoint is set on the QgsCopyrightLabelPlugin::renderLabel method and I know this method is called to draw Copyright Label on the map canvas. So, I need to trigger this rendering event:

  1. Open Plugin Manager
  2. Turn on Copyright Label plugin by checking box next to the plugin on the list
  3. Click OK to close Plugin Manger
  4. Click on the Copyright label button on the plugins toolbar
  5. Set plugin properties as you wish and click OK

After that gdb should stop QGIS execution on our breakpoint set in the plugin (dynamically loaded library):

 Breakpoint 2, QgsCopyrightLabelPlugin::renderLabel (this=0xa5a090, theQPainter=0xa7a560)
 at plugin.cpp:163
 163     void QgsCopyrightLabelPlugin::renderLabel(QPainter * theQPainter)
 (gdb)

Now, we can print lines around our breakpoint in line 163:

 (gdb) list
 158     void QgsCopyrightLabelPlugin::refreshCanvas()
 159     {
 160         qGisInterface->getMapCanvas()->refresh();
 161     }
 162
 163     void QgsCopyrightLabelPlugin::renderLabel(QPainter * theQPainter)
 164     {
 165         //Large IF statement to enable/disable copyright label
 166         if (mEnable)
 167         {

Running list command once more will give us next part of lines of plugin.cpp file:

 (gdb) list
 168             //@todo softcode this!myQSimpleText.height()
 169             // need width/height of paint device
 170             QPaintDeviceMetrics myMetrics( theQPainter->device() );
 171             int myHeight = myMetrics.height();
 172             int myWidth = myMetrics.width();
 173             //hard coded cludge for getting a colorgroup.  Needs to be replaced
 174             QButton * myQButton =new QButton();
 175             QColorGroup myQColorGroup = myQButton->colorGroup();
 176
 177             QSimpleRichText myQSimpleText(mLabelQString, mQFont);

Let's go to inspect values of variables in lines 171 and 172. So, I put new breakpoint in line 175 (this time I don't have to specify file becaue I set breakpoint in current context):

 (gdb) break 175
 Breakpoint 3 at 0x2aaab0d9ca21: file plugin.cpp, line 175.

Let gdb to continue QGIS execution to reach new breakpoint (note, here I use continue command abbreviation):

 (gdb) cont
 Continuing.
 Breakpoint 3, QgsCopyrightLabelPlugin::renderLabel (this=0x9ebfa0, theQPainter=0xa246c0)
 at plugin.cpp:175
 175             QColorGroup myQColorGroup = myQButton->colorGroup();

Now, we passed lines with initialization of variables we want to examine so they contains some values. Let's check those values:

 (gdb) print myHeight
 $1 = 439
 (gdb) print myWidth
 $2 = 566

Pretty nice, isn't it?

Finally, close QGIS to quit debugging session.

Summary

In the first part of this document I tried to present how to debug QGIS plugins, shared and dynamically loaded libraries in general. Now we know it is possible to browse source code of binaries being debugged and how to do it only using gdb. In the next section I will show you how to do the same using KDevelop. I believe my explanations are clear enough.

How to debug QGIS with plugins using KDevelop

Debugging in KDevelop is a bit simpler than using GDB. In KDevelop you can browse your code and set breakpoints. Then you can start debugging session and step through the code line-by-line straight in the editor, not in the console as it was when we used gdb. This makes debugging simpler because you don't have to know your so well. In the KDevelop, you can access your code through the Classes browser, File Tree or Bookmarks. Important thing is that you can still execute GDB commands manually from KDevelop.

Note: Be sure you have enabled GDB plugin in KDevelop: menu Project -> Project Options -> Plugins tab.

I assume you have imported QGIS project to KDevelop as described here ["Setting Up Kdevelop For QGIS Developement"]. During this part I will use the same plugin as with GDB - Copyright Label. Let's start fighting with bugs in KDevelop.

First, launch KDevelop and open QGIS project from /home/mloskot/dev/qgis/_cvs/qgis/qgis.kdevelop.

Next, we need to set breakpoints in places I want to inspect:

  • Open /home/mloskot/dev/qgis/_cvs/qgis/plugins/plugin.cpp file in KDevelop editor.
  • Scroll to the QgsCopyrightLabelPlugin::renderLabel method.
  • Set breakpoints where you wish - right-click in line in which you want to set it and select Toggle Breakpoint option. I will set in the same lines as before: 163 and 175. After you set a breakpoint in the plugin - shared library - it is's status is marked as Pending (Add) (on the Breakpoints tab in KDevelop). Certainly, we know why it is marked this way
    Now, we are ready to start debugging session. But first we should remember about something: we need to set path to plugin sources directory. Do you remember? We will do it almost the same way as we did when using GDB but here is a little trick. Here is short explanation.

For now, I only know how to set source directory path only during debugging session. I don't know how to set it using i.e. Project Options. The problem is that when you start debugging session (menu Debug > Start) then debugger is launched and runs QGIS immedietely. Here GDB launched by KDevelop does now wait for run command as it did when we used GDB directly. So, after we start debugging session we won't have any time to set source directories and other session settings. We have to stop GDB in some way before it will reach our breakpoints ;) The trick is to place another breakpoin somewhere before we have our "main" breakpoints. I suggest you to set that extra breakpoint at main function. So, let's do it. Go to main.cpp, and Toggle Breakpoint in following line:

 int main(int argc, char *argv[])

So, now we have 3 breakpoints: at main and in lines 163 and 175.

OK, no we are ready to start debugging session: menu Debug -> Start.

Shortly, debugger will stop at main function where we have our extra breakpoint. You can check Breakpoints tab and see that this breakpoint is marked as Active in status field. Also you can go to GDB tab (this is from the GDB plugin I mentioned before) and you will see a message we know well:

 (gdb) frame 0
 #0  main (argc=1, argv=0x7fffffe00b08) at main.cpp:203
 203     in main.cpp

In the line with main function you will see a red mark on the left bar. So, you won't overlook moment when GDB reach breakpoint.

Now, when we stopped at main function, we have a time to configure our debugging session. We need to set directory path to sources of Copyright Label plugin. In order to do it follow these steps:

  1. Go to GDB tab (at the bottom of KDevelop)
  2. In the GDB cmd: text box put well-known command and hit ENTER:
 GDB cmd: directory /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label

You will see confirmation in the GDB output window:

 (gdb) directory /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label

To be sure for 100% the source directory path is set you can use following command:

 GDB cmd: show directories

You will see confirmation again:

 (gdb) show directories
 Source directories searched: /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label:$cdir:$cwd

Now, we are ready to continue QGIS execution by GDB, so click Run button on the Debugger Toolbar and QGIS should launch. At this point we will repeat the same steps as in the first section - enable Copyright Label plugin and trigger QgsCopyrightLabelPlugin::renderLabel execution:

  1. Open Plugin Manager
  2. Turn on Copyright Label plugin by checking box next to the plugin on the list
  3. Click OK to close Plugin Manger
  4. Click on the Copyright label button on the plugins toolbar
  5. Set plugin properties as you wish and click OK
    After that debugger (gdb) should stop QGIS execution on our breakpoint set in the plugin (dynamically loaded library) in line 163. You can see see on Breakpoints tab that our both breakpoints, line 163 and 175, are in Active state. Also, in the line 163 - our first breakpoint in plugin, you can see green triangle (line cursor) on the left bar, next to the line with:
 void QgsCopyrightLabelPlugin::renderLabel(QPainter * theQPainter)

You can click Run to move to the next breakpoint in the line 175. Green triangle move along, right? It should. Now, you can step through your the plugin code and you will see moving line cursor in the editor. Success! That's what we wanted to do, isn't it!.

Summary

Using KDevelop you can debug QGIS and achive the same aims as using GDB: stepping through the code, setting breakpoints, inspecting variables and expressions values, etc.

Remember: the trick is in proper configuration of your debugging session and setting source directory or directories path(s).

Unfortunately, as I said, I don't know how to do set those paths in the KDevelop using Project Options or something more automatic than GDB commands. There is an option I thought would be usefull but after a few test I revealed it isn't. This is located in Project -> Project Options -> Configure Options -> and here you will see Top source directory text box. I've tested it and this is not what we need. I've also wrote to the KDevelop list '' '' asking how to configure source directories paths in the KDevelop but without any response. Nobody on IRC channel #kdevelop was able to help me too. So, I suppose there is no such option. GDB tries to find sources in current directory from which GDB is launched (see $cdir and $cwd in the output of show directories command, but I'm not sure for 100% what are those values).

There is GDB configuration file - .gdbinit - usually searched by GDB in $HOME or current directory from which GDB is launched, so there source directories may be specified to automation this process. But for QGIS plugins we can not set there all plugins sources directories because it causes some problems, see section below.

Possible problems

I'd like to point out some problem I noticed. All QGIS plugins contains plugin.cpp file in its sources. So, when you want to debug more than one plugin at once and you set more than one source directories paths i.e.:

/home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label

and

/home/mloskot/dev/qgis/_cvs/qgis/plugins/north_arrow

then GDB gets confused a bit and stops on your breakpoints in plugin.cpp twice, for both plugins, even if you set breakpoint only in one file i.e. /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label/plugin.cpp Possible solution is to rename all copies of plugin.cpp in every plugin to something more unique i.e.:

/home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label/copyright_label_plugin.cpp

and

/home/mloskot/dev/qgis/_cvs/qgis/plugins/north_arrow/north_arrow_plugin.cpp

Another, less revolutionary, solution is to create .gdbinit file in directory from which you run GDB i.e. /home/mloskot/dev/qgis/_cvs/qgis:

 #
 # .gdbinit - gdb initialization file for QGIS project
 #
 # Here set path to source directory of plugin you want to debug
 directory /home/mloskot/dev/qgis/_cvs/qgis/plugins/copyright_label

Then after we run GDB debugging session will be initialized to debug Copyright Plugin just as we wish:

 mloskot@dog:~/dev/qgis/_cvs/qgis$ gdb ~/usr/bin/qgis
 GNU gdb 6.3-debian
 Copyright 2004 Free Software Foundation, Inc.
 GDB is free software, covered by the GNU General Public License, and you are
 welcome to change it and/or distribute copies of it under certain conditions.
 Type "show copying" to see the conditions.
 There is absolutely no warranty for GDB.  Type "show warranty" for details.
 This GDB was configured as "x86_64-linux-gnu"...Using host libthread_db library   "/lib/libthread_db.so.1".
 (gdb) show directories
 Source directories searched: /home/mloskot/dev/qgis/_cvs/qgis/plugins /copyright_label:$cdir:$cwd(gdb)

If you will want to debug North Arrow plugin, or any other QGIS plugin, then edit /home/mloskot/dev/qgis/_cvs/qgis/.gdbinit file and put there path to source directory of that plugin.

The End

I believe this article will be helpful and will be a small contribution to increase QGIS quality. If you have any comments or questions, please give me a message on my mateusz AT loskot DOT net - Mateusz ?oskot.

Bibliography