Backtrace trees
I did a hack the other day I thought came out well. I had a large program, and I wanted to know the call tree for those function that used the low-level I/O routines.
The call trees generated by Doxygen were kind of useless, because they had too much detail and couldn't tell you which call paths are the normal paths. I really just wanted to know the call trees of those functions called the low-level I/O routines when the program was run on typical input data.
So, GDB will allow you to load a file of commands using the "-x" option. I wrote a command file that set up some breakpoints and requested backtraces for those breakpoints. I also added commands so that GDB ran non-interactively. I captured the backtraces in a typescript. The command file looked something like this.
##################
set pagination off
set breakpoint pending on
## Print call tree when "open" is called
break open
command
bt 10
cont
end
run
quit
##################
The first line keeps GDB from running its "more" command. The second line tells GDB not to worry if the functions I'm trying to break on aren't loaded yet. The middle section between "break" and "end" tells GDB to print a backtrace 10 levels deep when "open" is called. I end up with a file that contains a lot of GDB backtraces.
In the above I only showed the "open" command, but, I added similar backtrace requests for open, close, read, lseek, and write.
From there I coerced the output from the run into a graphviz-friendly file with a little bit of awk. Text that looked like this
#1 0x12345678 in foo (...
#2 0x12345678 in bar (...
#3 0x12345678 in baz (...
became this
bar -> for
baz -> bar
Using a simple awk script like this
###################
BEGIN { parent = ""; child = ""; }
{
child = parent;
if ($1 == "#0") {
if ($3 == "in") parent = $4; else parent = $2;
} else if (match($1, /\#[0-9]/) != 0) {
if ($3 == "in") parent = $4; else parent = $2;
print parent " -> " child;
}
}
#############
Yeah I know, you could have done it in perl in 18 characters. So what.
I finished off making my graphviz file with a bit of shell. This generated the correct Graphviz file as "test.gv" and then created the plot as "test.png".
#############
echo "digraph call {" > test.gv
echo "node [rank=sink, shape=box]; open close read write lseek __libc_open64;" >> test.gv
echo "{rank=sink; open close read write lseek __libc_open64;}" >> test.gv
echo "node [shape=ellipse];" >> test.gv
cat test.out | awk -f split.awk | sort | uniq >> test.gv
echo "}" >> test.gv
dot -Tpng test.gv -o test.png
#############
And that got me my result.
