I have a large work space which has many source files of C code. Although I can see the functions called from a function in MS VS2005 using the Object browser, and in MSVC 6.0 also, this only shows functions called from a particular function in a non-graphical kind of display. Additionally, it does not show the function called starting from say main()
, and then the functions called from it, and so on, deeper inside to the leaf level function.
I need a tool which will give me a function call graph pictorially with functions callee
and caller
connected by arrows or something like that, starting from main()
to the last level of function, or at least showing a call graph of all functions in one C source file pictorially. It would be great if I could print this graph.
Any good tools to do that (need not be free tools)?
pycallgraph : a call graph generator for Python programs that uses Graphviz. pyan : a static call graph generator for Python programs that uses Graphviz. gprof2dot : A call graph generator written in Python that converts profiling data for many languages/runtimes to a Graphviz callgraph.
On the bottom right, select the "Call graph" tab. This shows an interactive call graph that correlates to performance metrics in other windows as you click the functions. To export the graph, right click it and select "Export Graph".
Function call graphs, or call trees, are a standard tool often found in reverse engineering tools, code browsers, IDEs, and even file editors. They provide fundamental information about the control flow dependencies for the functions you're examining in your tool.
You have to set HAVE_DOT , CALL_GRAPH and CALLER_GRAPH to YES . Also make sure the path to dot is in your PATH variable. If that still doesn't work, you might have to set EXTRACT_ALL and/or EXTRACT_STATIC , depending on your functions. Apologies, I do have HAVE_DOT, CALL_GRAPH, and CALLER_GRAPH set to YES.
Egypt (free software)
ncc
KcacheGrind (GPL)
Graphviz (CPL)
CodeViz (GPL)
Dynamic analysis methods
Here I describe a few dynamic analysis methods.
Dynamic methods actually run the program to determine the call graph.
The opposite of dynamic methods are static methods, which try to determine it from the source alone without running the program.
Advantages of dynamic methods:
Disadvantages of dynamic methods:
KcacheGrind
https://kcachegrind.github.io/html/Home.html
Test program:
int f2(int i) { return i + 2; } int f1(int i) { return f2(2) + i + 1; } int f0(int i) { return f1(1) + f2(2); } int pointed(int i) { return i; } int not_called(int i) { return 0; } int main(int argc, char **argv) { int (*f)(int); f0(1); f1(1); f = pointed; if (argc == 1) f(1); if (argc == 2) not_called(1); return 0; }
Usage:
sudo apt-get install -y kcachegrind valgrind # Compile the program as usual, no special flags. gcc -ggdb3 -O0 -o main -std=c99 main.c # Generate a callgrind.out.<PID> file. valgrind --tool=callgrind ./main # Open a GUI tool to visualize callgrind data. kcachegrind callgrind.out.1234
You are now left inside an awesome GUI program that contains a lot of interesting performance data.
On the bottom right, select the "Call graph" tab. This shows an interactive call graph that correlates to performance metrics in other windows as you click the functions.
To export the graph, right click it and select "Export Graph". The exported PNG looks like this:
From that we can see that:
_start
, which is the actual ELF entry point, and contains glibc initialization boilerplatef0
, f1
and f2
are called as expected from one anotherpointed
is also shown, even though we called it with a function pointer. It might not have been called if we had passed a command line argument.not_called
is not shown because it didn't get called in the run, because we didn't pass an extra command line argument.The cool thing about valgrind
is that it does not require any special compilation options.
Therefore, you could use it even if you don't have the source code, only the executable.
valgrind
manages to do that by running your code through a lightweight "virtual machine". This also makes execution extremely slow compared to native execution.
As can be seen on the graph, timing information about each function call is also obtained, and this can be used to profile the program, which is likely the original use case of this setup, not just to see call graphs: How can I profile C++ code running on Linux?
Tested on Ubuntu 18.04.
gcc -finstrument-functions
+ etrace
https://github.com/elcritch/etrace
-finstrument-functions
adds callbacks, etrace parses the ELF file and implements all callbacks.
I couldn't get it working however unfortunately: Why doesn't `-finstrument-functions` work for me?
Claimed output is of format:
\-- main | \-- Crumble_make_apple_crumble | | \-- Crumble_buy_stuff | | | \-- Crumble_buy | | | \-- Crumble_buy | | | \-- Crumble_buy | | | \-- Crumble_buy | | | \-- Crumble_buy | | \-- Crumble_prepare_apples | | | \-- Crumble_skin_and_dice | | \-- Crumble_mix | | \-- Crumble_finalize | | | \-- Crumble_put | | | \-- Crumble_put | | \-- Crumble_cook | | | \-- Crumble_put | | | \-- Crumble_bake
Likely the most efficient method besides specific hardware tracing support, but has the downside that you have to recompile the code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With