Package: elf-diff
elf_diff - A Tool to Compare Elf BinariesIntroductionThis tool compares pairs of ELF binary files and provides information about differences in the contained symbols with respect to the space that they occupy in program memory (functions and global data) and in RAM (global data). Binary pairs that are passed toelf_diffare typically two versions of the same program/library/firmware.elf_diffcan help you to find out about the impact of your changes on your code's resource consumption.The differences between the binaries are summarized in tables that contain information about persisting, disappeared and new symbols.elf_diffalso attempts to find pairs of matching symbols that might have been subject to renaming or signature changes (modified function arguments). Please be warned that the means to determine such symbol relations are very limited when working with binaries. False positives will result.For all those symbols that have been subject to changes and also for the new and disappeared symbols, the tool provides diff-like comparisons of the disassembly.The following types of output files are currently supported: HTML, PDF, YAML, JSON, XML, TXT.HTML documents are cross-linked to conveniently allow jumping back and forth between bits of information, e.g. tabular information and symbol disassemblies. Due to the potentially large amount of information, some parts of the HTML reports are ommitted in the pdf files.elf_diffhas two modes of operation, pair-reports and mass-reports. While the former compares two binaries, the latter generates an overview-report for a set of binary-pairs. Such overview-reports list only the changes in terms of symbol sizes and the amount of symbols, no disassembly is provided to gain feasible document sizes.ExampleAssume you have two compiled versions of a software and you might be interested in the most prominent differences (and possibly the similarities) between both.One way of comparing binaries is looking at the containedsymbols. This is whatelf_diffdoes.Let's start with exploring how differences in source code reflect in the symbols being created.For example, the following two C++ code snippets come with some subtle differences:Version 1 (old)Version 2 (new)intfunc(inta){return42;}intvar=17;classTest{public:staticintf(inta,intb);intg(floata,floatb);protected:staticintm_;};intTest::f(inta,intb){return42;}intTest::g(floata,floatb){return1;}intTest::m_=13;intpersisting1(inta){return43;}intpersisting2(inta){return43;}intfunc(doublea){return42;}intvar=17;classTest1{public:staticintf(inta,intb);intg(floata,floatb);protected:staticintm_;};intTest1::f(inta,intb){return42;}intTest1::g(floata,floatb){return1;}intTest1::m_=13;intpersisting1(inta){return42;}intpersisting2(inta){return42;}Compiled and linked version of the two above code snippets can be found in the plaform specific subdirectories of thetestssubdirectory ofelf_diffgit repository. To generate a multi page pair report from these files, please install theelf_diffPython packages as described in the installation section of this document. Then enter the following in a console on a Linux system. Please replace the placeholder<elf_diff sandbox>with the absolute path of your localelf_diffsandbox.python3-melf_diff--html_dirreport<elf_diffsandbox>/tests/x86_64/libelf_diff_test_debug_old.a<elf_diffsandbox>/tests/x86_64/libelf_diff_test_debug_new.aBy means of its self contained HTML reportself_diffallows for conveniently analyzing the similarities and differences between the symbols contained
inelffiles.Please click on the table headers to proceed to the HTML pages thatelf_diffgenerated based on the above code example.Multi PageSingle PageTo allow for convenient exchange and archiving, single page reports may also be generated inpdfformat.Pleasenote: If you are familiar withelffiles, the termssymbolandnamemangling, you know how compilers and linkers transform
high level language code into binary code and how this code is stored in elf files, you might want to skip the remaining parts of the introduction section.Content of ReportsNow, after you had a look at the different types of reports thatelf_diffgenerates, you might be interested in how the contained information is established.As already mentioned,elf_diffcompares binaries based on the contained symbols.SymbolsSymbols resulting from functions and variables likeintTest1::f(inta,intb){return42;}doublea=0.3;have the following properties:a unique name (possibly including a namespace), e.g.Test1::f,a signature (functions), e.g.(int a, int b)->int,a symbol type (variable/constant/function and subtle variants of those),an extension (the amount of RAM or program memory that the symbol occupies, the latter only when compiled forharvard architectures) andassociated code/data.Please note:There are several other properties but those are not important for understanding whatelf_diffdoes.A Brief Excursion About Name ManglingYou might be surprised that thedatatypeof variables is not part of the above list.This is because the variable data type is actually not stored in elf files. They are simply no more required after
the end of the compile process. At that point all variables have become named addresses of a memory areas of known extension.It is simply sufficient that the compiled code knows what to do with such addresses and memory extensions.Next, you might frown and ask why the same argument apparently does not apply to function signatures? Those are very well listed above.
Can't the compiler treat function parameters in the same way as variables?There are several answers to that question. We will concentrate on the one that is, subjectively, most related to the current topic. The answer is:functionoverloading.Many high level languages (as e.g. C++) allow functions with idential names but different call signatures
to be used in the same program, e.g.voidf(double)andvoidf(int)To avoid name clashes, compilers and linkers use an approach calledname manglingto convert names and signatures into so calledmangledsymbolnames.The mangled names are what is actually stored in the elf files (unlessstripped.Name mangling is, however, a reversible process. Compilers typically come with utilities that allow restoring name and signature from mangled names,
a process commonly calleddemangling(e.g. by means of the toolc++filtthat is part of the GNU binutils suite).We still haven't answered the question how symbols, or rather their properties can be used to find the differences between compiled binaries. So let's get back on track.Comparing SymbolsWhen comparing two binaries one may group symbols based on their names and signatures (or their mangled names) assymbols present in both versions (persisting),symbols that are only found in version 2 (new) andthose that are only present in version 1 (disappeared).The code snippets initially presented are deliberatly written in a way that eases identifying related pairs of symbols in both versions.Typically, software is subject to incremental transitions that affect only a quite limited number of symbols.Symbols might berenamed ormoved to another namespace (which includes being turned from a free function to a class method and vice versa).Also, theirsignature (functions),implementation (functions) ordata type (variables/constants)might be changed.Symbol Similaritieself_diffautomatically detects and visualizes pairs of similar symbols.Unfortunatelly, in some cases the relations between symbols are not unique.To help the user finding the most relevant symbol relations,elf_diffdisplays the level of lexicographic similarity for every pair of similar symbols.
For functions the level of lexicographic similarity of the two implementations is also displayed.Highlighted DifferencesTo allow for conveniently analyzing implementation changes at the assembly level, the disassembled code is displayed side-by-side with differences being highlighted.If debug information is contained in the binaries (flag-gof the gcc compiler),
the original high level language code annotates the assembly.If you want to find out about other applications ofelf_diff, please keep on reading.Don't forget to have a look at the examples section at the end of this document.Purposeresource/performance optimizationdebugginglearning/teachingThe main purpose ofelf_diffis to determine how specific changes to a piece of software affect resource consumption and performance. The tool may also serve to compare two independent change sets or just to have fun and learn how changes reflect in the generated assembly code.The following information is part ofelf_diff's report pages:differences in the amount of program storage and static RAM usagesymbols that are only present in one of the two versionssymbols whose naming or call signature is similar in both versions, e.g. as a result of symbol renaming or subtly changing call signaturesassembly code discrepancies of functions with identical names and call signaturesAself_diffoperates on elf-files, it is fairly language and platform agnostic. All it requires to work is a suitable set ofGNU Binutilsfor the target platform.Requirementself_diffis a Python script. It mostly uses standard libraries but also some non-standard packages (see the filerequirements.txt) for more information.elf_diffworks and is automatically tested with Python 3.The following setup guide assumes Python 3 to be installed on your computer. Python setup tutorials can easily be found by googling "install python 3".InstallingInstall the elf_diff Python package via one of the following commandspython3 -m pip install elf_diff(Linux)py -m pip install elf_diff(Windows)Please note:PyPIthe Python package index traditionally uses hyphens instead of underscores in package names.pip, however, happily supports both versionself_diffandelf-diff.Alternatively when developingelf_diff, the following steps are required:Clone theelf_diffrepo from github.Install any required packages via one of the following commandspython3 -m pip install -r requirements.txt(Linux)py -m pip install -r requirements.txt(Windows)Add thebinsubdirectory of theelf_diffrepo to your platform search path (environment variable, e.g.PATH)To runelf_difffrom the local git-sandbox, please use the scriptbin/elf_diffthat is part of the source code repo, e.g. asbin/elf_diff -hto display the help string.UsageThere is a small difference between running Python on Linux and Windows. While the command to run Python 3 from a console window under Linux ispython3, on Windows there is a so calledPython runner(commandpy) that invokes the most suitable Python interpreter installed.To displayelf_diff's help page in a console window, type the following in a Linux consolepython3-melf_diff-horpy-melf_diff-hin a Windows console.In the examples provided below, we prefer the Linux syntax. Please replace the keywordpython3withpywhen running the respective examples in a Windows environment.Generating Pair-ReportsTo generate a pair report, two binary files need to be passed toelf_diffvia the command line. Let's assume those files are namedmy_old_binary.elfandmy_new_binary.elf.The following command will generate a multipage html report in a subdirectory of your current working directory.python3-melf_diffmy_old_binary.elfmy_new_binary.elfGenerating Mass-ReportsPlease note:Mass reports have been deprecated and are likely removed from further versions of the software.Mass reports require a driver file (yaml syntax) that specifies a list of binaries to compare pair-wise.Let's assume you have two pairs of binaries that reside in a directory/home/my_user.binary_a_old.elf <-> binary_a_new.elf
binary_b_old.elf <-> binary_b_new.elfA driver file (namedmy_elf_diff_driver.yaml) would then contain the following information:binary_pairs:-old_binary:"/home/my_user/binary_a_old.elf"new_binary:"/home/my_user/binary_a_new.elf"short_name:"Ashortname"-old_binary:"/home/my_user/binary_b_old.elf"new_binary:"/home/my_user/binary_b_new.elf"short_name:"Bshortname"Theshort_nameparameters are used in the result tables to reference the respective binary pairs.By using the driver file, we can now run a mass-report aspython3-melf_diff--mass_report--driver_filemy_elf_diff_driver.yamlThis will generate a HTML fileelf_diff_mass_report.htmlin your current working directory.Generating pdf-FilesThe generation of pdf reports withelf_diffrequires the Python packageweasyprint. See theweasyprint installation guidefor more information.Please note:elf_diffgenerates both types of html reports even without weasyprint being installed.pdf files are generated by supplying the output file name using the parameterpdf_fileeither at the command linepython3-melf_diff--pdf_filemy_pair_report.pdfmy_old_binary.elfmy_new_binary.elfor from within a driver file, e.g.pdf_file:"my_pair_report.pdf"Specifying an Alternative HTML File LocationSimilar to specifying an explicit filename for pdf files, the same can be done for our HTML output files, either via the command linepython3-melf_diff--html_filemy_pair_report.hmtlmy_old_binary.elfmy_new_binary.elfor from within a driver file, e.g.html_file:"my_pair_report.html"this will create a single file HTML report (with the exact same content as generated pdf files).Specifying an Alternative HTML DirectoryTo generate a multi-page HTML report use the command line flag--html_dirto generate the HTML files e.g. in directorymy_target_dir.python3-melf_diff--html_dirmy_target_dirmy_pair_report.hmtlmy_old_binary.elfmy_new_binary.elfUsing Driver FilesThe driver files that we already met when generating mass-reports can also generally be used to runelf_diff. Any parameters that can be passed as command line arguments toelf_diffcan also occur in a driver file, e.g.python3-melf_diff--mass_report--pdf_filemy_file.pdf...Inmy_elf_diff_driver.yamlmass_report:Truepdf_file:my_file.pdf...Supplying a Project TitleA project title could e.g. be a short name that summarizes the changes that you applied between the old and the new version of the compared binaries. Supply a title via the parameterproject_title.Adding Background InformationAdditional information about the compared binaries can be added to pair-reports. Use the parametersold_info_fileandnew_info_fileto supply filenames of text files whose content is supposed to be added to the report.It is also possible to add general information to reports, e.g. about programming language or compiler version or about the build-system. This is supported through thebuild_infoparameter which enables supplying a string that is added to the report. For longer strings, this can be conveniently done via the driver-file.Everything that follows afterbuild_info: >in the example will be added to the report.build_info:>This buildinfo is added to the report.The whitespaces in front of these lines are removed, the line breaks arepreserved.Using Alias StringsIf you want to obtain anonymized reports, it is not desirable to reveile details about your user name (home directory) or the directory structure. In such a case, the binary filenames can be replaced by alias wherever they would appear in the reports.Supply alias names using theold_aliasandnew_aliasparameters for the old or the new version of the binaries, respectively.Working with Cross-Build BinariesWhen working on firmware projects for embedded devices, you typically will be using a cross-build environment. If based on GNU gcc, such an environment usually not only ships with the necessary compilers but also with a set of additional tools calledGNU Binutils.elf_diffuses some of these tools to inspect binaries, namelynm,objdumpandsize. Although some information about binaries can be determined even with the host-version of these tools, it is e.g. not possible to retreive disassemblies.In a cross-build environment, Binutils executable are usually bundled in a specific directory. They also often have a platform-specific prefix, to make them distinguishabel from their host-platform siblings. For theavr-version of Binutils e.g., that is shipped with theArduinodevelopment suite, the prefixavr-is used. The respective commands are, thus, namedavr-nm,avr-objdumpandavr-size.To make those dedicated binaries known toelf_diff, please add the binutils directory to the PATH environment variable, use the parametersbin_dirandbin_prefixor explicitly define the
commands e.g.objdump_command(see command help).A pair-report generation command for the avr-plattform would e.g. readpython3-melf_diff--bin_dir<path_to_avr_binaries>--bin_prefix"avr-"my_old_binary.elfmy_new_binary.elfThe string<path_to_avr_binaries>in the above example would of course be replaced by the actual directory path where the binaries live.Generating a Template Driver FileTo generate a template driver file that can serve as a basis for your own
driver files, just runelf_diffwith thedriver_template_fileparameter, e.g. aspython3-melf_diff--driver_template_filemy_template.yamlTemplate files contain the default values of all available parameters, or - if the temple file is generated in the same session where a report was created - the template file will contain the actual settings used for the report generation.Selecting and Excluding SymbolsBy means of the command line argumentssymbol_selection_regexandsymbol_exclusion_regex, symbols can be explicitly selected and excluded.
The specified regular expressions are applied to both the old and the old binary. For more fine grained selection, please used the*_oldand*_newversions of the
respective command line arguments.Skip Similar Symbols DetectionSimilar symbol detection can be a very useful tool but it is a quite costly operation as it requires comparing all symbol names from one binary with all symbols from the other.
Assuming that both binaries containnsymbols this is aO(n^2)operation. Therefore it is up to the user to disabe similar symbol detection and output via the command
line argument--skip_symbol_similarities.Assembly CodeFor most developers who are used to program in high level languages, assembly code is a mystery.
Still, there is some information that an assembly-novice can gather from observing assembly code. Starting with the number of assembly code statements. Normally less means good. The more assembly statements there are representing a high level language statement, the more time the processor needs to process them. On the contrary, sometimes there may be a suspiciously low number of assembly statements which might indicate that the compiler has optimized away something that it shouldn't have.All this, of course, relies on the knowledge about what assembly code is associated with which line of source.
This information is not included in compiled binaries by default. The compiler must explicitly be told to export additional debugging information. For the gcc-compiler the flag-g, e.g., will cause this information to be emitted. But careful, some build systems when building debug versions replace optimization flags like-O3with the debug flag-g. This is not what you want when looking at the performance of your code. Instead you want to add the-gflag and keep the optimization flag(s) in place. CMake, e.g. has a configuration variableCMAKE_BUILD_TYPEthat can be set to the valueRelWithDebInfoto enable a release build (with optimization enabled) that also comes with debug symbols.For binaries with debug symbols included, elf_diff will annotate the assembly code by adding the high level language statements that it was generated from.Dwarf Debug InfoIf compiled with appropriate compiler flags (e.g. gcc's-g) generated binaries contain debug information in Dwarf-format that can be extracted
by using GNU binutils. If present, this debug information enableself_diffto e.g. determine the location of definition of symbols in the
source code (file, line, column).Migrated SymbolsDebugging information available in elf files' Dwarf debug sections can be used to identify migrated symbols, i.e. those symbols that have been moved from one source file to another.A symbol is identified asmigratedif it is a persisting symbol and if it's source file changed when comparing old and new binary.
If both binaries where compiled from different source trees, all persisting symbols will be identified as migrated. This is becauseelf_diffdoes a lexicographic comparison of source file paths.
In that case the configuration parameterssource_prefix,old_source_prefixornew_source_prefixmay be used to eliminate erroneously identified migratred symbols. This works by stripping path prefix from source file paths.Example:A symbol with new and old source files/dir1/some/source_file.cppand/dir2/some/source_file.cppis identified as migrated unless
the path prefix/dir1/and/dir2/are stripped off.Document Structure and Plugin SystemWhen analyzing elf binaries and processing output,elf_diffrelies on a intermediate datastructure that it establishes after all symbols have been parsed
from the elf files. This data structure, calledelf_diffdocument, is the basis for the actual file export.File export relies on dedicated data export plugins for (html, pdf, yaml, json, txt, ...). Plugins receive theelf_diffdocument and can easily extract
and process its data to generate output of arbitrary type.User Defined Pluginself_diff's plugin system enables developing user plugins, e.g. for custom output based on theelf_diffdocument.
Custom plugins are registered via the command line flag--load_plugin, specifying the plugin's Python module path and the name of the plugin class
that is supposed to be loaded. Optionally the loaded plugin object can be parametrized by supplying parameter name value pairs.The following example demonstrates how to load a plugin classMyPluginClassfrom a used defined modulemy_plugin_module.py.python3-melf_diff--load_plugin"~/some/dir/my_plugin_module.py;MyPluginClass;my_arg1=42;my_arg2=bla"libfoo_old.alibfoo_new.aThis example of course assumes that the user plugin knows how to interpret the two parametersmy_arg1andmy_arg2.Plugin classes must be derived from one of the plugin classes defined in elf_diff's moduleplugin.py. Please see elf_diff's default plugins
in the subdirectories of<elf_diff_sandbox>/src/elf_diff/pluginsas a reference on how to implement custom plugins.Running the Testself_diffcomes with a number of tests in thetestssubdirectory of its git repository.
Some tests are unit tests others integration tests that runelf_diffthrough
its CLI by supplying different command line parameter sets.To run the entire test bench do the following.cd<reporoot>
python3./tests/test_main.pyRunning Individual Test CasesTest cases reside in the directorytests/test_casesofelf_diff's git repository.To run individual tests, run the test driver and submit one or more tests using the command line arguments-t. To run e.g. the test casetest_command_line_args, do as follows:cd<reporoot>
python3./tests/test_main.py-ttest_command_line_argsExamplesExamples PageSee theexamples page.libstdc++Comparison of two versions of libstdc++shipping with gcc 4.8 and 5. There are vast differences between those two library versions which result in a great number of symbols being reported. The following command demonstrates how report generation can be resticted to a subset of symbols by using regular expressions.
In the example we select only those symbols related to classstd::string.## Generated on Ubuntu 20.04 LTSpython3-melf_diff\--symbol_selection_regex"^std::string::.*"# select any symbol name starting with std::string:: \--pdf_filelibstdc++_std_string_diff.pdf# generate a pdf file \/usr/lib/gcc/x86_64-linux-gnu/4.8/libstdc++.a# path to old binary \/usr/lib/gcc/x86_64-linux-gnu/5/libstdc++.a# path to new binaryContributingPlease readCONTRIBUTING.mdfor details on our code of conduct, and the process
for submitting pull requests to us.VersioningWe useSemVerfor versioning. For the versions available,
see the tags on this repository.Authorsnoseglasses - Initial workLicenseThis project is licensed under the GNU General Public License Version 3
see the LICENSE.md file for details