~/git/blog

My brain-dump of random code/configuration.

31 Dec 2022

Nix-ld: A clean solution for issues with pre-compiled executables on NixOS

No such file or directory: How I stopped worrying and started loving binaries on NixOS.

In this article, I will discuss the technical issue of running pre-compiled executables on NixOS, and how we can improve the user experience by making these binaries work seamlessly using nix-ld.

One of the key benefits of NixOS is its focus on purity and reproducibility. The operating system is designed to ensure that the system configuration and installed software are always in a known and predictable state. This is achieved through the use of the Nix package manager, which allows users to declaratively specify their system configuration and software dependencies.

However, this focus on purity can make it difficult for users to run pre-compiled executables that were not specifically designed for NixOS. These executables may have dependencies on libraries that are not available in the Nix package manager, or may require patching or modification to work correctly on the operating system.

The problem

If you have used NixOS for a while, you may have encountered an issue when attempting to run a pre-compiled executable. You probably saw something like this:

$ ./masterpdfeditor5
bash: ./masterpdfeditor5: No such file or directory

However, the file clearly exists:

$ ls -la ./masterpdfeditor5
-rwxr-xr-x 1 joerg users 27160344 Jul  4 16:22 ./masterpdfeditor5

To understand what is going on, we need to look at what happens when an executable is run on a Linux operating system. When the shell attempts to run a program, it uses an execve system call to request the operating system to run the program. We can use the tool strace to visualize this:

$ strace -f ./masterpdfeditor5
execve("./masterpdfeditor5", ["./masterpdfeditor5"], 0x7fff70350ef8 /* 188 vars */) = -1 ENOENT (No such file or directory)
strace: exec: No such file or directory
+++ exited with 1 +++

Strace prints out the system call and its arguments, as well as the return code from the operating system. In this case, we can see that bash derived its error message (No such file or directory) from the execve system call.

To understand why the operating system is reporting this error, we need to analyze the executable file further. The file command from the binutils package provides more information about the executable file:

$ file ./masterpdfeditor5
masterpdfeditor5: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=406f865023e33cc6a0f9d179cc14a939c4b29fbe, stripped

We can see that the executable is a dynamically linked ELF binary that depends on libraries found on the system to function. It uses a link-loader program, also known as an interpreter to locate and load these libraries.

Commonly these programs are provided in your system libc, which in most cases is glibc, and are in a fixed location (/lib64/ld-linux-x86-64.so.2 if your CPU is x86-based).

On NixOS, the issue with running pre-compiled executables arises because it allows users to mix different libraries, including the glibc package. Unlike Linux, it does not provide a fixed path such as /lib64/ld-linux-x86-64.so.2 for the link-loader program. Executables packaged with Nix are linked against a specific version of glibc. The patchelf command can be used to find out exactly which version is being used.

$ patchelf --print-interpreter /run/current-system/sw/bin/ls
/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib/ld-linux-x86-64.so.2

When the operating system tries to run an executable, it parses the binary and looks for the specified link-loader. If it cannot find it, it returns the generic error code ENOENT, which results in an unhelpful error message.

The current solution

To work around this issue when packaging programs that do not have the source code available, such as masterpdfeditor, Nix uses a build function called autoPatchelfHook to analyze the binary and resolve any missing dependencies.

This function rewrites the interpreter path /lib64/ld-linux-x86-64.so.2 to a specific version of the glibc package, and populates the RPATH field in the executable with paths to all necessary libraries for the program to run. The link-loader uses this field to locate the libraries at runtime.

We can use the patchelf program to see the effect of autoPatchelfHook on the masterpdfeditor program. By using nix-shell to load a shell with masterpdfeditor and then printing the RPATH of the program, we can see the paths to the necessary libraries encoded in the program.

First, we load up a shell with masterpdfeditor in it.

$ nix-shell -p masterpdfeditor

Next, we get the nix path to the program

[nix-shell]$ which masterpdfeditor5
/nix/store/zmdjwbizg4a6cja4darcn2qy9imr336k-masterpdfeditor-5.8.70/bin/masterpdfeditor5

The next command prints the RPATH encoded in the program.

[nix-shell]$ patchelf --print-rpath "/nix/store/zmdjwbizg4a6cja4darcn2qy9imr336k-masterpdfeditor-5.8.70/bin/.masterpdfeditor5-wrapped"

It gives this result:

/nix/store/y4k2206qhks30wspxx1nkmgfqfdmxp0j-sane-backends-1.1.1/lib:/nix/store/zaflwh2nwzj1f0wngd7hqm3nvlf3yhsx-zlib-1.2.13/lib:/nix/store/dgxn688wq7whsvs2fycygq0wn888xnsv-qtsvg-5.15.7/lib:/nix/store/9lcgwnc70f4wj1czklczql7a
wcv24mi-qtbase-5.15.7/lib:/nix/store/lgfp5762m5qzby9syd21kj04l5qmjg4h-qtdeclarative-5.15.7/lib:/nix/store/ykjcsxdh9c1w664g6v38d86gph8m6mq7-libglvnd-1.5.0/lib:/nix/store/wprxx5zkkk13hpj6k1v6qadjylh3vq9m-gcc-11.3.0-lib/lib

While autoPatchelfHook is a useful tool for making many programs usable in Nix, there are a few cases where it may not be possible or practical to use it. These include:

  • Using binary executables downloaded with third-party package managers (e.g. vscode, npm, or pip). With autoPatchelfHook, these would have to be patched on every update
  • Executables hidden inside other programs or archives, for example Java JARs might contain executables unpacked at runtime.
  • Running a game or proprietary software that verifies its integrity and will not start if the binary has been modified.
  • Programs that are too large to be copied to the Nix store (e.g. FPGA IDEs).

Nix-ld to the rescue!

To address these cases, nix-ld was created as an alternative to autoPatchelfHook. It allows users to run pre-compiled executables on NixOS without the need to modify the binaries or copy them to the Nix store. This improves the user experience by allowing users to easily run binaries downloaded from third-party sources and proprietary software without patching or modification.

It is installed in the same location as the link-loader on other Linux distributions (i.e. /lib64/ld-linux-x86-64.so.2), and it loads the actual link-loader as specified in the NIX_LD environment variable. It also accepts a comma-separated list of library lookup paths in NIX_LD_LIBRARY_PATH and rewrites this variable to LD_LIBRARY_PATH before passing execution to the link-loader. This allows users to specify additional libraries that the executable needs to run.

On a system configured with nix-ld, the error message when attempting to run an unpatched binary will be more informative and provide guidance on how to address the issue:

$ ./masterpdfeditor5
cannot execute ./masterpdfeditor5: You are trying to run an unpatched binary on nixos, but you have not configured NIX_LD or NIX_LD_x86_64-linux. See https://github.com/Mic92/nix-ld for more details

To further improve the user experience, a new feature is available in the latest unstable version of NixOS and the upcoming 23.05 release. It allows the most common libraries to be included in the NixOs configuration as follows:

{ config, pkgs, ... }: {
  # Enable nix ld
  programs.nix-ld.enable = true;

  # Sets up all the libraries to load
  programs.nix-ld.libraries = with pkgs; [
    stdenv.cc.cc
    zlib
    fuse3
    icu
    zlib
    nss
    openssl
    curl
    expat
    # ...
  ];
}

For a more extensive version of this configuration, see my dotfiles.

By including the most common libraries in the configuration, nix-ld can provide a more seamless experience for users running pre-compiled executables on NixOS. They will not need to manually specify the necessary libraries for each executable and can simply run them as they would on other Linux distributions.

Conclusion

In conclusion, nix-ld is a useful tool for running pre-compiled executables on NixOS without the need for patching or modification. It provides a shim layer that allows users to specify the necessary libraries for each executable and improves the user experience by allowing users to easily run binaries from third-party sources and proprietary software. By including the most common libraries in the NixOS configuration, nix-ld can provide an even more seamless experience for running pre-compiled executables on NixOS.

In my next article, I’ll be looking at a similar issue to the one encountered when working with executable binaries. Scripts that are hardcoded to point to /usr/bin can also cause a problem on NixOS, and I will address this by introducing envfs

Categories

comments powered by Disqus