Some notes on computer stuff

Skipping standard C++ library during debug session in gdb

May 26, 2016
[c++] [gdb] [programming] [howto]

UPDATE: if you have new gdb (7.12.1), scroll to the bottom.

Ability to inline functions and methods in C++ is one of the reasons programs in C++ are so fast. As such code provided by libraries effectively becomes part of its client application and the great and mighty gdb knows what inline functions are, this might cause inconveniences during debugging. The problem is that while stepping into a method one can get into one of many inlined functions that aren't interesting.

This issue is quite evident in case of template-based libraries like standard C++ library itself. Consider an example in C++11:

class Config
{
    // ...
    void setRolesChain(std::vector<std::string> roles);
    // ...
};

// ...
cfg.setRolesChain({ "a", "b" }); // <-- current line
// ...

step command will take you to allocator constructor and then to one of std::vector<std::string> constructors (std::string constructors are skipped, not sure why). The way out is to repeat finish and then step commands until you reach Config::setRolesChain(), which can be boring and error prone (one off breakpoints is a solution, but not very convenient one).

gdb doesn't treat standard headers as anything special, so stepping into methods can be quite annoying.

The skip file command

The good news is that there is skip file [<path>] command since gdb 7.4. It can also ignore functions (skip function <function> or skip <function>). It's easy to use skip in debug session, but what we want is to permanently ignore whole directory tree (e.g. by pre-configuring it via ~/.gdbinit), this unfortunately isn't that straightforward.

Limitations of the command

skip doesn't accept patterns. This means that one can't easily ignore everything system related. Moreover, files being ignored should be mentioned in debug information of the inferior process, which doesn't even exist when ~/.gdbinit is executed. We'll need a combination of several gdb features to overcome such limitations.

Solution components

First, we need command post hooks for run, start and attach commands. The hooks are executed after gdb command and can do anything you want.

Second, we need list of files to skip. At first, I generated them via find /usr/include/c++/5.3.0 -type f, but it's not extensible. So the list will be present, but it's better to generate it dynamically.

Third, as gdb can be extended via python, we'll use it to make the list and instruct gdb to skip those files. Here, using python bindings allows us to easily ignore error output of skip file command (to_string parameter), which prevents hundreds of useless output cluttering our screen (two lines for each header unused by process being debugged). Mind that because python is indentation based (bad idea) you can't indent its code relative to other commands properly. By the way, gdb can also be extended via Guile, which would be more native, but I'm barely familiar with Lisp dialects.

Solution

Putting it all together we get this simple solution ready to be inserted into ~/.gdbinit (path to headers might need correction in some cases):

define skipstdcxxheaders
python
def skipAllIn(root):
    import os
    for root, dirs, files in os.walk(root, topdown=False):
        for name in files:
            path = os.path.join(root, name)
            gdb.execute('skip file %s' % path, to_string=True)
# do this for C++ only
if 'c++' in gdb.execute('show language', to_string=True):
    skipAllIn('/usr/include/c++')
end
end

define hookpost-run
    skipstdcxxheaders
end
define hookpost-start
    skipstdcxxheaders
end
define hookpost-attach
    skipstdcxxheaders
end

Troubleshooting

(Derived from discussion with Dhiraj Reddy in comments.)

info skip can be used too see what's being ignored.

Language check might not work in IDEs, so one might need to replace

if 'c++' in gdb.execute('show language', to_string=True):
    skipAllIn('/usr/include/c++')

with

skipAllIn('/usr/include/c++')

Solution for new GDB (7.12.1)

(See comments.)

Something like this (didn't try it myself) in ~/.gdbinit should do:

skip -gfi /usr/include/c++/*/*/*
skip -gfi /usr/include/c++/*/*
skip -gfi /usr/include/c++/*