- Introduction
- Prerequisites
- Debugger
- A few words about my environment
- How to debug QGIS with plugins using GDB
- How to debug QGIS with plugins using KDevelop
- Possible problems
- The End
- Bibliography
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:
- Basic knowledge about usage of Linux console (terminal).
- You need to know how to build QGIS
- Basic knowledge what is debuger (see also What is A Debugger?) , debugging, breakpoint and what is the idea of examining data under debugger.
- You need to know how to run program under GDB
- You need to know how to get help about GDB commands
- You need to import QGIS project to KDevelop (see ["Setting Up Kdevelop For QGIS Developement"]). (see Bibliography section for useful resources)
and software we will need to debug QGIS with plugins:
- 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:
- Open Plugin Manager
- Turn on Copyright Label plugin by checking box next to the plugin on the list
- Click OK to close Plugin Manger
- Click on the Copyright label button on the plugins toolbar
- 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:
- Go to GDB tab (at the bottom of KDevelop)
- 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:
- Open Plugin Manager
- Turn on Copyright Label plugin by checking box next to the plugin on the list
- Click OK to close Plugin Manger
- Click on the Copyright label button on the plugins toolbar
- 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 '' [email protected] '' 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¶
- Program Library HOWTO by David A. Wheeler
- C++ dlopen mini HOWTO by Aaron Isotton
- Debugging with GDB
- Using GNU's GDB Debugger by Peter Jay Salzman
- "Debugging "C" And "C++" Programs Using "gdb"":http://users.actcom.co.il/~choo/lupg/tutorials/debugging/debugging-with-gdb.html
- TN2032: Getting Started with GDB by Apple