Post-build static analysis - pfunct and pahole
Only after writing about post-build static analysis, I found a nice suite of tools that can help to inspect binaries and gather information based on the debug Information. I found pahole and pfunct all from the dwarves project.
The Debian package of dwarfes contains many more utilities
-
codiff: diff-tool for comparing how changes in source affect resulting binaries -
ctracer: trace execution of functions -
dtagnames: lists tag names -
pahole: finds alignment holes in structs and classes -
pdwtags: pretty-prints DWARF information -
pfunct: displays information about functions, like inlines -
pglobal: lists global symbols -
prefcnt: counts DWARF tags usage -
scncopy: to copy ELF sections -
syscse: system call sign extender
Unfortunately, the documentation is not that great.
pahole
As already mentioned, it helps to find suboptimal structure layouts. As struct that have more than one member variable are normally padded, changing the order of the member variable can change its layout size.
For example, given the following structure
struct a {
short b1;
int b2;
short b3;
}; The output of pahole is (if struct a has been used somewhere)
struct a {
short int b1; /* 0 2 */
/* XXX 2 bytes hole, try to pack */
int b2; /* 4 4 */
short int b3; /* 8 2 */
/* size: 12, cachelines: 1, members: 3 */
/* sum members: 8, holes: 1, sum holes: 2 */
/* padding: 2 */
/* last cacheline: 12 bytes */
}; While the output for
struct a {
int b2;
short b1;
short b3;
}; shows that there is no wasted space
struct a {
int b2; /* 0 4 */
short int b1; /* 4 2 */
short int b3; /* 6 2 */
/* size: 8, cachelines: 1, members: 3 */
/* last cacheline: 8 bytes */
}; As all three major compilers (GCC, Clang, and MSVC) can emit warnings at compile time about padding, one would think that there is no good reason to use pahole.
For example with GCC and -Wpadded the output will be
main.cpp:17:6: warning: padding struct to align 'a::b2' [-Wpadded]
17 | int b2;
| ^~
main.cpp:15:8: warning: padding struct size to alignment boundary [-Wpadded]
15 | struct a{
| ^ It should be normally preferred to do the check at compile time and not after, also because pahole needs debugging information.
pahole also gives the possibility to diagnose how to reduce the structure packing. With -R, --reorganize and for clarity -S, --show_reorg_steps it will enlist the steps:
/* Moving 'b3' from after 'b2' to after 'b1' */
struct a {
short int b1; /* 0 2 */
short int b3; /* 2 2 */
int b2; /* 4 4 */
/* size: 8, cachelines: 1, members: 3 */
/* last cacheline: 8 bytes */
};
/* Final reorganized struct: */
struct a {
short int b1; /* 0 2 */
short int b3; /* 2 2 */
int b2; /* 4 4 */
/* size: 8, cachelines: 1, members: 3 */
/* last cacheline: 8 bytes */
}; /* saved 4 bytes! */ This will only work if using -C, --class_name=CLASS_NAME, thus it is necessary to invoke pahole for every structure we are interested in.
As it can always be useful to double-check if there is unnecessarily wasted space, it is still useful to know that tools like pahole are available.
pfunct
pfunct is a program that displays information about functions.
If invoked without arguments, it will simply list all functions, but notice that it omits information like parameters (type and numbers), overloads, and namespaces (the dwarves suite was originally intended for C source code and not C++).
nm [--extern-only] --demangle <binary file> | grep ' T ' will show all those information for all functions (only exported if invoked with --extern-only).
An interesting option of pfunct is --goto_labels, which shows the number of goto labels per function. As far as I know, GCC, Clang, and MSVC do not have any flag for diagnosing the use of goto labels (and/or statements). While the usage of goto has its valid use cases, it is often possible to use a more structured language feature to accomplish the same task, which often results in easier-to-understand code.
Given the following function
int foo(int j){
int a = 1;
here:
++a;
if(a<j){
goto here;
}
there:
return a;
} pfunct emits foo: 2, indicating that function foo has two labels: here and there. Labels inside switch statements are (correctly) not counted. If a second function named foo, like an overload or in a different namespace, is defined somewhere pfunct will analyze only one, which limits its capabilities when analyzing C++ code.
Other probably useful options are
-
-s, --sizes, as it shows the size of functions, useful for finding big functions and splitting them -
-S, --nr_variables, as it counts the number of local variables -
-G, --cc_uninlined, as it lists those functions declared inline, but that the compiler did not inline -
-H, --cc_inlined, as it lists those functions not declared inline, but that the compiler inlined anyway (probably less useful than-cc_uninlinedif compiling with optimizations)
pglobal
This tool shows global symbols (functions and variables). As other tools like readelf, nm and objdump are already capable of displaying this type of data even without debugging information, and since pglobal, unlike pahole and pfunct, does not do any analysis, I decided to not invest much time in it.
cscope
After reading the description, I realized that diffoscope from the reproducible-builds project is another tool for static post-build-analysis.
diffoscope is a much more versatile tool, with a much broader audience and scope, and does not require debug information. I tried installing it, the package manager notified me that it would have occupied 1.750 MB of additional space (YMMV). It has Java and some Android packages as dependencies on Debian, fortunately, they are all marked as optional. With apt-get install --no-install-recommends diffoscope I was able to get the tool only for 655 kB of additional disk space.
If you have questions, comments, or found typos, the notes are not clear, or there are some errors; then just contact me.