Section C.3. Debugging

C 3 Debugging

The compiler can locate and describe syntax errors. The linker can reveal the existence of inconsistencies among program components and give some help as to how to locate them. One of the most challenging aspects to using C++ is learning how to find and fix various kinds of run-time errors.

Run-time errors are logical errors that can exist in a program that is syntactically correct and contains no undefined objects or functions. Effective use of a debugger, a program specifically designed for tracking down runtime errors, can greatly reduce the amount of time spent dealing with these kinds of errors.

A debugger permits the stepwise execution of your code, as well as the inspection of object values. Since debuggers work with compiled code, the early versions could only be used by programmers who were familiar with assembly language. Modern debuggers are able to step concurrently through the compiled machine code and the original source code. The GNU family of developer tools includes gdb, the source-level GNU debugger, which we can use for C/C++ applications. gdb has been designed with a command-line interface that is quite powerful but not particularly user-friendly. Fortunately, there are several open-source graphical facades for gdb, one of which we will discuss below. Commercial C++ IDEs (e.g., Visual Studio) generally have built-in source-level debuggers.

C.3.1. Building a Debuggable Target

For gdb to work, debugging symbols must be built into the code at compile time. Otherwise, the machine instructions will not be mapped to locations in C++ source files. This is easily accomplished by using the appropriate command-line switch (-g) when invoking the compiler:

g++ -g filename.cpp


This often results in a significantly larger executable file. Generally, the growth is proportional to the size and complexity of the source code files. The expanded executable contains symbol table information that the debugger can use to find source code that corresponds to machine instructions. To get qmake to generate makefiles with the -g switch passed along to g++, add the following line to your qmake project file:

CONFIG += debug


When the Qt library has been built with debugging symbols, you can step through the Qt source code just as easily as your own code. You may need to build Qt with debugging symbols to debug certain programs that contain code directly called from the Qt library.

Building Qt With Debugging Symbols

In Win32, its a menu choice you can click on. On *nix platforms, after unpacking the source code tarball, pass a parameter to the configure script before building and your Qt library will be built with debug symbols.

./configure --enable-debug
make
make install



Exercise: Building a Debuggable Target

Compare the size of an executable file created with and without the CONFIG += debug line in the project file.

Make a mental note to try this again later with a more complex application.

C.3.2. gdb Quickstart

Imagine you are running a program and, for some mysterious reason, it crashes.

[lazarus] app> ./playlistmgr
Segmentation fault
[lazarus] app>


When your app aborts, or crashes, it is helpful to know (as quickly as possible) exactly where it happened. We can use gdb to locate the trouble spot quickly and easily.

[lazarus] app> gdb playlistmgr
GNU gdb 6.3-debian
Copyright 2004 Free Software Foundation, Inc.
This GDB was configured as "i386-linux"...Using host libthread_db
library "/lib/tls/libthread_db.so.1".

(gdb) r[4]
Starting program: ftgui/app/playlistmgr
[Thread debugging using libthread_db enabled]
[New Thread -1227622176 (LWP 17021)]
Qt: gdb: -nograb added to command-line options.
 Use the -dograb option to enforce grabbing.
This is a debug message

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1227622176 (LWP 17021)]
0xb7f03320 in FormDialog::createActions (this=0x80ae2a0) at
formdialog.cpp:53
53 delete m_OkAction;
(gdb)


[4] r is the command for "run."

gdb shows you not only the filename, and line number, but also the corresponding line in the source code. However, we still might want to get some context for this error. The command list shows you the surrounding source code for the current file:

(gdb) list
51 void FormDialog::createActions() {
52
53 delete m_OkAction;
54 delete m_CancelAction;
55 m_OkAction = new OkAction(m_Model, m_View);
56 m_CancelAction = new CancelAction(m_Model, m_View);
57 QHBoxLayout *buttons = new QHBoxLayout(0);
(gdb)


The command where shows you the stack trace, or how we got there.

(gdb) where
#0 0xb7f03320 in FormDialog::createActions (this=0x80ae2a0) at
formdialog.cpp:53
#1 0xb7f03058 in FormDialog::setModel (this=0x80ae2a0,
fmodel=0x80c80d0)
 at formdialog.cpp:34
#2 0x080664bd in SettingsDialog (this=0x80ae2a0, parent=0x0) at
settingsdialog.cpp:14
#3 0x0805f313 in MainWindow (this=0xbfffdec8) at
mainwindow.cpp:42
#4 0x08066f14 in Controller (this=0xbfffdec0, argc=1,
argv=0xbfffdfe4) at controller.cpp:25
#5 0x0805a8a4 in main (argc=1, argv=0xbfffdfe4) at main.cpp:7
(gdb)


Most open-source IDEs use gdb under the hood. They each offer a user interface that makes certain features easier to learn and use. Four open-source apps that provide a front-end for gdb are: Eclipse, kdevelop, kdbg, and ddd.

Viewing Qstrings Inside the Debugger

QStrings are hard to see inside some debuggers because they are indirect pointers to Unicode data. The debugger needs to know extra things about a QString in order to display it properly.

Download these Qt 4 helper macros from the KDE subversion repository[5] and put this in your ~/.gdbinit:

[5] http://websvn.kde.org/*checkout*/trunk/KDE/kdesdk/scripts/kde-devel-gdb

source /path/to/kde/kde-devel-gdb
define pqs
 printq4string $arg0
end


Now you should be able to print qstrings with the pqs macro.


C.3.3. Finding Memory Errors

Memory errors are very difficult to track down without the aid of a run-time analysis tool. A program that analyzes the running performance of a program is called a profiler. valgrind is an open-source profiling tool for Linux that tracks the memory and CPU usage of your code and detects a variety of errors. These include

  • Memory leaksmemory that is no longer accessible but which has not been deleted
  • Invalid pointer use for heap memory, such as

    • Out of bounds index
    • Mismatches between allocation and deallocation syntax (e.g., allocating with new[] but deallocating with delete)
  • Use of uninitialized memory

Each of the errors just listed can cause catastrophic results in a piece of software. Profilers can also be used for performance tuning and determining which code is responsible for slowing down a program (i.e., finding bottlenecks).

Example C.4 shows a short program that contains a deliberate memory usage error.

Example C.4. src/debugging/wrongdelete.cpp

void badpointer1(int* ip, int n) {
 ip = new int[n];
 delete ip; <-- 1
}

int main() {
 int* iptr;
 int num(4);
 badpointer1(iptr, num);
}

(1)wrong delete syntax

For the output to be human readable, we compile with debugging symbols (-g).

debugging/wrongdelete> g++ -g -pedantic -Wall wrongdelete.cpp
debugging/wrongdelete> ./a.out
debugging/wrongdelete>


The compiler didn complain, and even after running the program, no error behavior is exhibited. However, memory is corrupted by this program.

Here is a (slightly abbreviated) look at valgrinds analysis of our program. We have removed the process id of the valgrind job from the beginning of each line. The process id will, of course, be different each time you run valgrind.

src/debugging> valgrind a.out
--3332-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--3332-- DWARF2 CFI reader: unhandled CFI instruction 0:50
 Mismatched free() / delete / delete []
 at 0x401C1CB: operator delete(void*) (vg_replace_malloc.c:246)
 by 0x80484BD: badpointer1(int*, int) (wrongdelete.cpp:3)
 by 0x80484F4: main (wrongdelete.cpp:9)

 Address 0x4277028 is 0 bytes inside a block of size 16 allocd
 at 0x401BBF4: operator new[](unsigned)
 (vg_replace_malloc.c:197)
 by 0x80484AC: badpointer1(int*, int) (wrongdelete.cpp:2)
 by 0x80484F4: main (wrongdelete.cpp:9)


valgrind found the errors and, with debugging symbols, could point us to the location of the problem code. Example C.5 is a little more interesting because it contains memory leaks and array index errors.

Example C.5. src/debugging/valgrind-test.cpp

#include 

int badpointer2(int k) {
 int* ip = new int[3];
 ip[0] = k;
 return ip[3]; <-- 1
} <-- 2

int main() {
 using namespace std;
 int* iptr;
 int num(4), k; <-- 3
 /* what is the state of iptr? */
 cout << iptr[num-1] << endl;
 cout << badpointer2(k) << endl;
}

(1)out of bounds index

(2)memory leak

(3)k is uninitialized.

Running Example C.5 through valgrind shows us the exact locations of some errors.

For more details, rerun with: -v

--2164-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--2164-- DWARF2 CFI reader: unhandled CFI instruction 0:50
 Use of uninitialised value of size 4
 at 0x80486AF: main (valgrind-test.cpp:17)
68500558

 Invalid read of size 4
 at 0x804867C: badpointer2(int) (valgrind-test.cpp:8)
 by 0x80486DD: main (valgrind-test.cpp:18)

 Address 0x4277034 is 0 bytes after a block of size 12 allocd
 at 0x401BBF4: operator new[](unsigned) (vg_replace_malloc.c:197)
 by 0x8048667: badpointer2(int) (valgrind-test.cpp:6)
 by 0x80486DD: main (valgrind-test.cpp:18)

0

 ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 19 from 1)
 malloc/free: in use at exit: 12 bytes in 1 blocks.
 malloc/free: 1 allocs, 0 frees, 12 bytes allocated.
 For counts of detected errors, rerun with: -v
 searching for pointers to 1 not-freed blocks.
 checked 120,048 bytes.

 LEAK SUMMARY:
 definitely lost: 12 bytes in 1 blocks.
 possibly lost: 0 bytes in 0 blocks.
 still reachable: 0 bytes in 0 blocks.
 suppressed: 0 bytes in 0 blocks.
 Use --leak-check=full to see details of leaked memory.


If this is not enough information to find where the memory leak is, we can rerun valgrind with the switch --leak-check=full.



Part I: Introduction to C++ and Qt 4

C++ Introduction

Classes

Introduction to Qt

Lists

Functions

Inheritance and Polymorphism

Part II: Higher-Level Programming

Libraries

Introduction to Design Patterns

QObject

Generics and Containers

Qt GUI Widgets

Concurrency

Validation and Regular Expressions

Parsing XML

Meta Objects, Properties, and Reflective Programming

More Design Patterns

Models and Views

Qt SQL Classes

Part III: C++ Language Reference

Types and Expressions

Scope and Storage Class

Statements and Control Structures

Memory Access

Chapter Summary

Inheritance in Detail

Miscellaneous Topics

Part IV: Programming Assignments

MP3 Jukebox Assignments

Part V: Appendices

MP3 Jukebox Assignments

Bibliography

MP3 Jukebox Assignments



An Introduction to Design Patterns in C++ with Qt 4
An Introduction to Design Patterns in C++ with Qt 4
ISBN: 0131879057
EAN: 2147483647
Year: 2004
Pages: 268

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net