Free Software

Software Engineering with FOSS and Linux

Debugging and profiling your C/C++ programs using Free Software

Every developer in Linux knows (and most likely uses) the GCC, a set of compilers, tools and libraries that form the foundation of pretty much all C and C++ development in the FOSS world. However, fewer people, even among professional software developers, are aware of the superb debugging and profiling options available to them.

Before introducing new tools, I will first talk about a few gcc options. Note that several GCC options change with each release. I am using 4.3.3; if something doesn’t work for you, check the documentation for your version.

Let’s begin with a few compiling options that will help you detect errors at compile time. This means less debugging will be needed in the future, which is always a Good Thing.

-Wall

Enable most warnings. According to GCC’s man page,

“This enables all the warnings about constructions that some users consider questionable, and that are easy to avoid (or modify to prevent the warning), even in conjunction with macros.”

Note that as of version 4.3.3, two of the most powerful features enabled by -Wall, warnings about uninitialized variables use and out of bounds array checking will only work if you specify an optimization level (-O1 for uninitialized variables, -O2 for array bounds checking). This is planned to change in future GCC versions.

It is generally suggested that all your programs are compiled with the -Wall option enabled. My suggestion is to also compile your program with -O2 to let GCC test for uninitialized variables and out of bounds array use; turn it off afterwards if you wish to have full access to debuging info.

-Wshadow

Warn about shadow variables. Shadow variables (i.e. variables declared inside an inner block with the same name as a variable already existing in the scope) are valid in C/C++, but they are quite error prone. You should avoid using them; this compiler option will help you track unintended use.

-Werror

Treat warnings as errors. This will make the compiler treat all warnings as errors, interrupting compilation. Specific warnings can be exempted from this flag. A common example is using the -Wno-invalid-offsetof flag to supress warnings about using the offsetof() marco to reference member variables of uninitialized objects.

The following flags are related to debugging and profiling:

-g

Generate debugging info in the operating system’s native format. You can use the GNU debugger (gdb) to debug programs compiled with the -g option. Note that even though you can generate debugging info for programs compiled with an optimization (-On) option, debugging data will be less informative.

You should always use the -g option when developing your programs; you can turn this off in your final product.

-pg

Generate profiling info. When running a program compiled with this option, a file named gmon.out will be generated, containing profile information; you can use the GNU profiler (gprof) to analyse this file. Note that the -pg option must be specified both in compiling and in linking options. Personally I never use -pg; I prefer the profiling tool of valgrind, which I will mention later in the article.

As I mentioned, you can use the GNU debugger (gdb) to debug a program compiled with the -g option. An extended description of gdb is beyond the scope of this post; you can check one of the numerous tutorials available on the Internet. However, a few points of interest:

  • You can debug a running process of an executable. Use ps to find the process id (pid) of an executable, run gdb <executable> to begin gdb, and use the attach <pid> command to attach gdb to the running process.
  • You can debug an executable based on a core dump. Simply run gdb <executable> <core>. Note that nowadays programs will not generate a core when they crash, unless the ulimit -c option has been specified in the shell. This is done for security reasons, as an unintented core file can be used to hack into a program. Do not forget to enable this option when developing.

There are several graphical front-ends to gdb available. I prefer to work from the command line, since most of the time I’m debugging remote programs using a terminal window. However I have found kdbg to be an excellent front-end. Here is a screenshot:kdbg - A graphical front-end to gdb

Other available options include ddd, xxgdb and nemiver. Most C/C++ IDEs, such as Eclipse, Netbeans,  KDevelop and Anjuta also contain integrated front-ends to gdb. However getting familiar with the core gdb functionality outside an IDE is probably a good idea.

Valgrind, which I already mentioned earlier, is an invaluable set of tools to anyone dealing with non-trivial debugging and profiling. The platform currently contains the following tools:

  • A memory error detector
  • Two thread error detectors, including race conditions
  • Three profilers: cache and branch-prediction profiler, call-graph generating cache profiler, and heap profiler

Extended documentation on the various tools is provided on http://valgrind.org. Here I will display examples of my favotire tools: the memory error detector, and the call-graph generating cache profiler.

Consider the following trivial and utterly useless program:

#include <string.h>
#include <stdio.h>

void myfunc( const char *arg )
{
    char *p, *ptr;
    ptr = strdup( arg );
    /* process ptr here, but forget to free memory */
}

int main( int argc, char *argv[] )
{
    myfunc( "hello world" );
    return 0;
}
Checking this program with valgrind will reveal the memory leak:
xenofon@umbra:~/tmp$ valgrind --track-origins=yes --leak-check=full ./a.out |tee valgrind.out
[...]
==21882== 12 bytes in 1 blocks are definitely lost in loss record 1 of 1
==21882==    at 0x4026FDE: malloc (vg_replace_malloc.c:207)
==21882==    by 0x40BB34F: strdup (in /lib/tls/i686/cmov/libc-2.9.so)
==21882==    by 0x80483D4: myfunc (in /home/xenofon/tmp/a.out)
==21882==    by 0x80483F6: main (in /home/xenofon/tmp/a.out)
[...]

In non-trivial examples this alone can be a big saver. But the memory check of valgrind is MUCH more than this. Consider the following example:

#include <iostream>
#include <string>
using namespace std;

class Person {
private:
    string name;
    int age, sex;
public:
    enum { MALE, FEMALE };
    Person( string name, int age, int sex )
    {
        this->name = name;
        this->age = age;
        //this->sex = sex; /* we forgot this! */
    }
    void Print( )
    {
        cout << "Name: " << name << endl;
        cout << "Age : " << age << endl;
        cout << "Sex : " << ( sex == MALE ? "Male" : "Female" ) << endl;
    }
};

int main( int argc, char *argv[] )
{
    Person p( "alphamale", 32, Person::MALE );
    p.Print();
    return 0;
}

Valgrind will catch the error:

[...]
Name: alphamale
Age : 32
==22871== Conditional jump or move depends on uninitialised value(s)
==22871==    at 0x8048BCC: Person::Print() (in /home/xenofon/tmp/a.out)
==22871==    by 0x8048A5B: main (in /home/xenofon/tmp/a.out)
==22871==  Uninitialised value was created by a stack allocation
==22871==    at 0x80489D0: main (in /home/xenofon/tmp/a.out)
Sex : Female
[...]

Even though this example is also trivial, using uninitialized variables is an extremely common mistake. The -Wuninitialized compiler option of gcc (which is automatically included if you specify the -Wall flag) will help in some cases, but not all. It will not detect the above case, and it certainly cannot detect any case that can only occur at run-time. Valgrind checks all the allocated memory at bit level, and issues a warning when you attempt to use uninitialized areas.

Valgrind can also be used for profiling. I commonly use the call-graph cache profiler to generate profile data which can be analyzed by specialized tools. First, ask valgrind to use the appropriate tool:

xenofon@umbra:~/program/arctic/server$ valgrind --tool=callgrind ./server 2702

Once the program terminates, a file named callgrind.out.<pid> is generated. This file contains the profiling info of your program. If your program forks, a seperate callgrind.out file is generated for every child, distinguished by the pid extension. You can then use the amazing kcachegrind tool to inspect the info:

kcachegrind

Kcachegrind lets you inspect CPU time spent in each function and all its subroutines, navigate through the call graph, check a list of callers and callees, and is generally an invaluable tool to detect bottlenecks in the performance of your program. Furthermore, it is extremely user-friendly. Give it a try, you will love it.

Obviously there are several more tools to help you debug and profile your C/C++ programs. Furthermore, debugging and profiling, just like all programming, is more about the process than the tools. However, once you learn to make them part of your programming activities, you will happily find out that there are several superb free software tools to support your endeavours.

May 18, 2009 - Posted by | Programming | , , , , , , , ,

12 Comments »

  1. Other useful tools I use besides the one you already listed:
    – cppcheck (http://cppcheck.wiki.sourceforge.net/) static analyzer for c++ with few false positive)
    – google perftools (http://code.google.com/p/google-perftools/) memory & CPU profiler
    – gdb macros for outputing content of STL vector, map, set, etc (http://www.yolinux.com/TUTORIALS/src/dbinit_stl_views-1.01.txt)
    – cgdb (http://cgdb.sourceforge.net/) a curses interface to gdb

    Comment by dominiko | May 19, 2009 | Reply

  2. I’m not sure if this is what people are looking for here, but for C++ I found that using a good quality set of primitives (the Qt framework) prevents lots of pesky little errors and saves coding time. The Qt framework may be conceived for interface design but it has strengths that apply to pure command-line programs.

    I mean: Qt’s string class alone removes all those stupid little antics we had to pull with strings (so it’s faster and more pleasant to code in than pure C++), and it does so in a way that avoids memory allocation mistakes. And there is a nice built-in way to deal with program arguments (to search for option switches).

    A similar thing applies with object-object communication: the signal-slot paradigm is a lot easier to program and to read, and much safer to run than a lot of hand-coded pointers lying around.

    And then there is Qt’s object hierarchy that can be used to have parent objects destroy all their children when they themselves are deleted. Far fewer chances to keep leftover objects floating around.

    And then the Qt in classes for queues, stackes, and linked-lists with their built-in iterators, making for speed and ease of programming and preventing loads of array bound overruns that normally go undetected by C or C++.

    Reduce the problem at the source … and only then break out the diagnostic tools.

    Comment by Golodh | May 19, 2009 | Reply

  3. […] Debugging and profiling your C/C++ programs using Free Software « Software Engineering (tags: programming debugging tips) […]

    Pingback by links for 2009-05-20 « Donghai Ma | May 21, 2009 | Reply

  4. Thanks much for blog post. It is very good read.
    I enjoy to browse xpapad.wordpress.com.

    st petersburg teeth whitening

    Comment by jampelasp | June 19, 2009 | Reply

  5. Segments in the program like
    #if notsure
    cout << "Debug out put" << endl;
    #endif
    What would be the purpose of such code segments

    Comment by pragati | July 2, 2010 | Reply

    • It could be used for conditional compiling based on some other precompiler definition, for example:

      #define notsure 1
      #if notsure
      cout << "debug" << endl;
      #endif

      In this case the cout… line would be compiled and included in your runtime code. By using #define notsure 0, or by omitting the directive altogether, the line would not be included in your executable.

      Comment by xpapad | July 2, 2010 | Reply

  6. Great article!
    I use tool similar to valgrind but for windows – deleaker…

    Comment by Govardius | March 15, 2012 | Reply

    • Great tool similar to valgrind but for windows

      Comment by mastavalons | March 30, 2012 | Reply

      • Hmmm…. Interesting tools :)

        Comment by John Depth | June 6, 2012

  7. And now for something completely different…
    http://stackoverflow.com/a/1779343/23771

    Comment by Mike Dunlavey | July 1, 2013 | Reply

  8. Great tips! Thanks for the post, hope to make some time to see it in detail

    Comment by Wintch | May 9, 2014 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: