patchelf is a utility developed by the NixOS community to facilitate modifications of some parts of an ELF binary.
The features offered by patchelf include, but are not limited to:
1# Change the interpreter path
2$ patchelf --set-interpreter /lib/my-ld-linux.so.2 my-program
3
4# Add a new dependency
5$ patchelf --add-needed libfoo.so.1 my-program
6
7# Change RUN/RPATH values
8$ patchelf --set-rpath /opt/my-libs/lib:/other-libs my-program
In addition to its use in NixOS, many projects leverage patchelf to perform
post-compilation modifications. For example, pypa/auditwheel
uses patchelf
to change the rpath value and/or change the names of DT_NEEDED
libraries.
In comparison to LIEF, patchelf is not a library, and the current version (0.18.0)
can only be accessed via the command line.
The source code of patchelf is relatively small (around 3,000 lines of code)
and is focused on specific modifications such as renaming/removing DT_NEEDED
entries and changing/removing/adding DT_RPATH/DT_RUNPATH
.
When it comes to design decisions for modifying ELF binaries, there is a significant difference between patchelf and LIEF:
Section vs Segments
Patchelf relies on the section layout to modify and rewrite ELF binaries, while LIEF uses segments representation. This distinction makes patchelf less reliable for ELF binaries that have a non-standard section layout, whereas the representation used by LIEF corresponds to how binaries are loaded, making it less prone to errors during modifications.
LIEF already had (almost) all the internal features to provide the same functionalities as
patchelf
. However, LIEF does not offer a comprehensive command-line tool like
patchelf does. To create a drop-in replacement for patchelf using the LIEF backend,
we had to ensure compatibility with the current state of patchelf, including:
zsh
(at least)Thanks to the recent LIEF’s Rust bindings, we can leverage the Rust ecosystem to simplify the development of this command-line tool:
The source code is available here: lief-project/LIEF/tools/lief-patchelf
and it is worth mentioning that this LIEF-based implementation has the exact same command
line interface as the original patchelf
:
1$ lief-patchelf --help
2Patchelf based on LIEF
3
4Usage: lief-patchelf [OPTIONS] [filenames]...
5
6Arguments:
7 [filenames]...
8
9Options:
10 --set-interpreter <INTERPRETER>
11 Change the dynamic loader ('ELF interpreter') of executable given to INTERPRETER.
12
13 --page-size <SIZE>
14 Uses the given page size instead of the default
15
16 --print-interpreter
17 Prints the ELF interpreter of the executable. (e.g. `/lib64/ld-linux-x86-64.so.2`)
18
19 --print-os-abi
20 Prints the OS ABI of the executable (`EI_OSABI` field of an ELF file).
Moreover, it passes1 all the original patchelf’s test suite except one
test related to the IA-64
architecture support:
PASS: set-interpreter-same.sh
PASS: no-rpath-alpha.sh
PASS: no-rpath-amd64.sh
PASS: no-rpath-armel.sh
PASS: no-rpath-armhf.sh
PASS: no-rpath-hurd-i386.sh
PASS: no-rpath-i386.sh
FAIL: no-rpath-ia64.sh
PASS: no-rpath-kfreebsd-amd64.sh
PASS: no-rpath-kfreebsd-i386.sh
PASS: no-rpath-mips.sh
PASS: no-rpath-mipsel.sh
PASS: no-rpath-powerpc.sh
PASS: no-rpath-s390.sh
PASS: no-rpath-sh4.sh
PASS: no-rpath-sparc.sh
============================================================================
Testsuite summary for patchelf 0.18.0 (based on LIEF 0.17.0)
============================================================================
# TOTAL: 58
# PASS: 55
# SKIP: 2
# XFAIL: 0
# FAIL: 1
# XPASS: 0
# ERROR: 0
If you want to test this new LIEF-based implementation, you can download the compiled version here:
lief-tools-aarch64-apple-darwin.zip
lief-tools-aarch64-pc-windows-msvc.zip
lief-tools-aarch64-unknown-linux-gnu.zip
lief-tools-aarch64-unknown-linux-musl.zip
lief-tools-i686-unknown-linux-musl.zip
lief-tools-x86_64-apple-darwin.zip
lief-tools-x86_64-pc-windows-msvc.zip
lief-tools-x86_64-unknown-linux-gnu.zip
lief-tools-x86_64-unknown-linux-musl.zip
Since LIEF performs in-depth modifications and can raise different error messages, I had to make minor changes on some checks: patchelf-test-lief.diff ↩︎