Debugging GCC

GCC is composed of a driver program (gcc, g++, gfortran, etc.) that calls the compiler itself (cc1, cc1plus, f951, etc.), the assembler (as) and the linker (ld or collect2). There is a compiler for each language. Most often, one wants to debug the compiler itself instead of the driver.

First, generate preprocessed source. Second, build a debuggable compiler. Now you can simply do

$ gcc <parameters> -wrapper gdb,--args

This will invoke all subprograms of gcc under gdb --args, thus the invocation of cc1 will be gdb --args cc1 <...>. The argument to -wrapper is the name of a binary that should execute cc1 under its control. Other popular examples are valgrind, strace, etc.


For example, to check for memory leaks and other memory-related problems you would execute the following command. Note that to suppress benign Valgrind errors GCC needs to be configured with the --enable-valgrind-annotations option on a system with the <valgrind.h> header installed (on common Linux distributions this can be accomplished by installing the valgrind-devel package).

$ gcc <parameters> -wrapper valgrind

... or directly invoke valgrind --leak-check=full [...]/lto1 [...] etc.

Note that there are a few memory leaks that nobody has bothered yet to plug.

Also note that even with --enable-valgrind-annotations, Valgrind doesn't understand that at program end, uncollected GC-allocated memory isn't actually leaking (it simply has not been collected). This means that Valgrind --show-leak-kinds=all is very noisy. This can be mitigated as follows (log being the Valgrind log file):

$ < log sed -e 's%^==[0-9]\+== %%' | csplit -n4 - '/^$/' '{*}'
$ grep -L 'by 0x[0-9A-F]\+: ggc_internal_alloc' xx* | xargs cat | less

See also


You can also call gdb directly, if you have the invocation to the cc1, cc1plus, etc compiler:

gdb --args <location of cc1, cc1plus, or whatever compiler
   for the language the preprocessed source file is in> <flags passed to compiler>

This will enable you to debug the compiler itself, instead of the driver.

You can also use the driver's -### option which writes the commands that the driver would execute. For example,

gdb --args $(./xgcc -### <parameters to the driver> 2>&1 | fgrep cc1)

There are scripts that automate all of this for you here that make debugging a front-end much simpler.

Building a Debuggable Compiler

GCC itself is normally compiled at -O2 which makes stepping through code a bit difficult. You should use GDB 6.3 (or a newer version), which can work properly with location lists generated by newer GCCs that help in debugging in such cases. To build a debuggable compiler, configure the compiler normally and then

make STAGE1_CXXFLAGS="-g -O0" all-stage1

Some people like to use -g3, which makes macros debuggable, instead of -g.

By default stage1 only includes the C++ compiler. If you need to work on a different frontend, you can configured with --enable-stage1-languages=LANGUAGES to build other languages as part of stage1.

If you have a fully bootstrapped build and you want to recompile only some files, you can do for example:

 $ touch $GCC_SRC_DIR/gcc/c-family/c-common.c
 $ cd gcc/ && make CXXFLAGS="-g3 -O0"

If you have a release source and you want to enable internal checking, configure the compiler with --enable-checking (this option is already enabled if you downloaded the source from SVN). See Installing GCC: Configuration for description of available types of checks.

Tips and tricks


You may want to read up on Randomization and disable it if you would like reproducible results.

Debugging within (x)emacs

Basically you're editing one of the files of the compiler, say tree-foo.c, when suddenly you want to debug its code. The following commands should get it right:

C-x 2
M-x gdb
C-x o
C-x <space>
C-x o
(gdb) run -O2 -ftree-foo ~~/foo.i

First, you split the window, then run the debugger on the compiler cc1. The C-x <space> sets a breakpoint at the cursor. Finally you run the compiler with the usual options on the preprocessed file foo.i.

Additional tips on Linux

Most linux systems (with a 2.6 kernel) have address space randomization, and you'll better disable it to have more reproducible runs (i.e. get the same pointer addresses from one run to the next). To disable it globally, do

echo 0 > /proc/sys/kernel/randomize_va_space

as root. Better yet, disable it in your shell with

setarch -R

Don't forget that disabling address space randomization may open some security issues.

Dumping the Intermediate Representation

Read for all the debugging options from -d* to -fdump-tree-*.

It is often useful to look at what the assembler made of GCC's output by writing an assembler list file. This is useful, for instance, to debug incorrectly computed insn lengths. Using gcc with the options "-dAp -Wa,-a" produces output like the following:

 267                    @ BLOCK 22 seq:20
 268                    @ PRED: 27
 269                    .L19:
 270 0298 18101BE5              ldr     r1, [fp, #-24]  @ 204   *arm_movsi_insn/5       [length = 4]
 271 029c 14201BE5              ldr     r2, [fp, #-20]  @ 205   *arm_movsi_insn/5       [length = 4]
 272 02a0 0430A0E3              mov     r3, #4  @ 206   *arm_movsi_insn/2       [length = 4]
 273 02a4 0221A0E1              mov     r2, r2, asl #2  @ 207   *arm_shiftsi3   [length = 4]
 274 02a8 022081E0              add     r2, r1, r2      @ 208   *arm_addsi3/1   [length = 4]
 275 02ac 033082E0              add     r3, r2, r3      @ 209   *arm_addsi3/1   [length = 4]
 276 02b0 003093E5              ldr     r3, [r3, #0]    @ 210   *arm_movsi_insn/5       [length = 4]
 277 02b4 38300BE5              str     r3, [fp, #-56]  @ 211   *arm_movsi_insn/6       [length = 4]
 278 02b8 38301BE5              ldr     r3, [fp, #-56]  @ 212   *arm_movsi_insn/5       [length = 4]
 279 02bc 000053E3              cmp     r3, #0  @ 213   *arm_cmpsi_insn/3       [length = 4]
 280                    @ SUCC: 23 (fallthru) 26
 281 02c0 1500000A              beq     .L17    @ 214   *arm_cond_branch        [length = 4]

Debugging the Ada Compiler

If you run the debugger on ../../objdir/gnat1, you get an error message:

fatal error, run-time library not installed correctly
cannot locate file
compilation abandoned

For correcting this error, simply invoke the following command at the gdb prompt:

(gdb) set env ADA_INCLUDE_PATH path_to_objdir/gcc/ada/rts

If you have a testsuite failure, in the logfile testsuite/gcc/ada/acats/acats.log you can find lines like

gnatmake --GCC="/abuild/rguenther/obj2/gcc/xgcc -B/abuild/rguenther/obj2/gcc/"
  -gnatws -O2 -I/abuild/rguenther/obj2/gcc/testsuite/ada/acats/support
  ca11c01.adb -largs --GCC="/abuild/rguenther/obj2/gcc/xgcc -B/abuild/rguenther/obj2/gcc/"

that you can nearly literally use to re-generate the testcase binary. Use find to find the ca11c01.adb source file and use the gnatmake built in the gcc/ directory. For the gnatmake command to function properly you need to set the following environment variables:

export ADA_INCLUDE_PATH=/abuild/rguenther/obj2/gcc/ada/rts
export ADA_OBJECTS_PATH=/abuild/rguenther/obj2/gcc/ada/rts

Debugging LTO

See this thread

Reproducing testsuite failures

When you get a testsuite failure, you can reproduce it using the repro_fail script in the contrib directory.

This script will search a line starting with 'spawn' that includes the pattern you are looking for (typically a source file name).

Once it finds that pattern, it re-executes the whole command in the spawn line. If the pattern matches more than one spawn command, it asks which one you want.

For instance, suppose that we spotted this failure:

FAIL: gcc.dg/tree-ssa/vrp47.c scan-tree-dump-times dom1 "x[^ ]* & y" 1

We want to call gdb on this compilation so we can debug what's going on:

$ ~/gcc/contrib/repro_fail vrp47.c gcc.log -wrapper gdb,--args
[1] <bld>/gcc/xgcc -B<bld>/gcc/ ~/gcc/gcc/testsuite/gcc.dg/tree-ssa/vrp47.c -O2 -fdump-tree-vrp -fdump-tree-dom -S -o vrp47.s -wrapper gdb,--args
+ <bld>/gcc/xgcc -B<bld>/gcc/ ~/gcc/gcc/testsuite/gcc.dg/tree-ssa/vrp47.c -O2 -fdump-tree-vrp -fdump-tree-dom -S -o vrp47.s -wrapper gdb,--args
GNU gdb (GDB)
[ ... ]

Rerunning Failed Tests

Let's assume you've done a regression run and some tests failed. You've then addressed what you think is the cause and you initially just want to confirm that those failing tests now pass.

Is there a quick and easy way to just run those tests without having to grab all their names and invoke them manually within a RUNTESTFLAGS argument?

The answer is yes and it comes in the form of the test_recheck script in the contrib directory.

The script is self-documenting, but here is an example of how I used it after already doing a full overnight run and rebuilding my modified compiler.

$ ~ gcc/contrib/test_recheck /work/src/build/obj/gcc2/gcc/testsuite

Obviously, you still need to do a full regression run before you consider your work to be regression-free (your "fix" might have broken something else!), but this is a handy way to determine if the broken tests now work.

One other valuable use of this script is when you occasionally have tests timing out due to high load on your test machines. In this case, you might wish to employ test_recheck at the end of your run to reduce the noise when comparing with a previous set of results.

WARNING: Running this script will blow away the logs (the .sum and .log files) of your full runs, so you might wish to back them up before invoking it.

None: DebuggingGCC (last edited 2021-08-06 15:14:47 by tschwinge)