Home > Articles > Operating Systems, Server > Linux/UNIX/Open Source

  • Print
  • + Share This

Debugging Options

Bugs, alas, are as inevitable as death and taxes. To accommodate this inescapable reality, you can use GCC's -g and -ggdb options to insert debugging information into your compiled programs to facilitate debugging sessions. In addition, GCC also has a number of options to make code-profiling sessions easier and more productive.

The -g option can be qualified with a 1, 2, or 3 to specify how much debugging information to generate. The default level is 2 (-g2), which generates extensive symbol tables, line numbers, and information about local and external variables. All of this information is stored inside the binary. Level 3 debugging information includes all of the level 2 information plus all of the macros defined in the source code. Level 1, in contrast, generates just enough information to create backtraces and stack dumps. A backtrace is the history of the function calls that a program makes. A stack dump is a listing, usually in raw hexadecimal format, of the contents of a program's execution environment, primarily the CPU registers and the memory allocated to it. Note that level 1 debugging information does not generate debugging information for local variables or line numbers.

If you intend to use the GNU Debugger, gdb (covered in Chapter 8, "Debugging"), using the -ggdb option creates extra information that eases the debugging chore under gdb. However, it will also likely make the program impossible to debug using other debuggers, such as the DBX debugger common on the Solaris operating system. -ggdb accepts the same level specifications as -g, and they have the same effects on the debugging output.

Using either of the two debug-enabling options will, however, dramatically increase the size of your binary. Simply compiling and linking the simple hello.c program I used earlier in this chapter resulted in a binary of 4089 bytes on my system. The resulting sizes when I compiled it with the -g and -ggdb options may surprise you:

$ gcc -g hello.c -o hello
$ ls -l hello
-rwxr-xr-x  1 kwall  users    10275 May 21 23:27 hello

$ gcc -ggdb hello.c -o hello
$ ls -l hello
-rwxr-xr-x  1 kwall  users    8135 May 21 23:28 hello

As you can see, the -g option increased the binary's size nearly three times, while the -ggdb option doubled its size! Despite the size increase, I recommend shipping binaries with standard debugging symbols (created using -g) in them in case someone encounters a problem and wants to try to debug your code for you.

Additional debugging options include the -p and -pg options, which embed profiling information into the binary. This information is useful for tracking down performance bottlenecks in your code and for developing a general picture of a program's performance. -p adds profiling symbols that the prof program can read, and -pg adds symbols that the GNU project's prof incarnation, gprof, can interpret. The -a option counts how many times each block of code (such as functions) is entered.

-save-temps saves the intermediate files, such as the object and assembler files, generated during compilation. These files can be useful if you suspect that the compiler is doing something unusual with your code or if you want to examine the generated code to see if it can be hand-tuned for better performance.

If you are interested in seeing how long the compiler takes to do its work, consider using the -Q option, which causes GCC to display each function as it compiles along with some statistics about how long each compiler pass takes. For example, here is the output when compiling the trusty hello.c program:

$ gcc hello.c
time in parse: 0.020000
time in integration: 0.000000
time in jump: 0.000000
time in cse: 0.000000
time in loop: 0.000000
time in cse2: 0.000000
time in branch-prob: 0.000000
time in flow: 0.000000
time in combine: 0.000000
time in regmove: 0.000000
time in sched: 0.000000
time in local-alloc: 0.000000
time in global-alloc: 0.000000
time in sched2: 0.000000
time in shorten-branch: 0.000000
time in stack-reg: 0.000000
time in final: 0.000000
time in varconst: 0.000000
time in symout: 0.000000 
time in dumpt: 0.000000

The displayed times may vary on your system. This information is mostly of interest to compiler writers, but, if you ever get curious about what the compiler is doing, you know how to find out.

Finally, as mentioned at the beginning of this chapter, GCC allows you simultaneously to optimize your code and to insert debugging information. Optimized code presents a debugging challenge, however, because variables you declare and use may not be used in the optimized program, flow control may branch to unexpected places, statements that compute constant values may not execute, and statements inside loops will execute elsewhere because the loop was unrolled. My personal preference, though, is to debug a program thoroughly before worrying about optimization. Your mileage may vary.

Tip - "Optimize later" does not mean "ignore efficiency during the design process." Optimization, in the context of this chapter, refers to the compiler magic I have discussed in this section. Good design and efficient algorithms have a far greater impact on overall performance than any compiler optimization ever will. A highly optimized bubble sort, for example, will never be as fast as a quick sort, except for very small data sets. If you take the time up front to create a clean design and use fast algorithms, you may not need to optimize, although it never hurts to try.

  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.