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_uninlined
if 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.