../πš‚πšƒπš‡
source

adhoc executable patching on nixos

Published 2020-03-14, last edit 2021-01-03

’one weird trick’ for getting that ELF executable someone just handed you working soonβ„’

Create a shell.nix file that looks like:

let
  pkgs = import <nixpkgs> {};
in pkgs.mkShell {
  nativeBuildInputs =
    (with pkgs; [
      gcc

      # example libraries you might want:
      zlib
      SDL2
    ]);
}

Launch the nix-shell, patch the linker so you can call the executable and see what’s up:

; nix-shell shell.nix
; patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" /path/to/executable

Strategies to find out what you might need:

; ldd /path/to/executable
; strace /path/to/executable

Update the shell.nix file with the dependencies/linked libraries your executable might need, and then patch the executable from the derived nix-shell based on that:

; nix-shell ./shell.nix
; # you may want to peek at new_rpath here or mangle NIX_LDFLAGS yourself
; new_rpath=$(echo "$NIX_LDFLAGS" | tr ' ' $'\n' | grep "^-L" | sed -E 's/^-L/:/' | tr -d $'\n')
; patchelf --set-rpath "$new_rpath" /path/to/executable

Then you should be able to run the executable by calling it normally (in or out of a nix-shell context).

βˆ— βˆ— βˆ—

That’s all well and good for right now, but what if we lose the nix store references we just patched in! Plus, we can’t exactly carry this around. Let’s note how to do the above as a build definition (the example here is a dynamically-linked release of babashka):

babashka = stdenv.mkDerivation rec {
    name = "babashka";
    # this is the way to coerce a string into a path
    # src = /. + "/home/neeasade/temps/2020-04-20_07:21:05/bb";

    # showing a few different ways:
    # src = /. + "/home/neeasade/temps/2020-04-20_07:21:05/babashka-0.0.88-linux-amd64.zip";
    src = (fetchurl {
        url = "https://github.com/borkdude/babashka/releases/download/v0.1.3/babashka-0.1.3-linux-amd64.zip";
        sha256 = "0nldq063a1sfk0qnkd37dpw8jq43p4divn4j4qiif6dy1qz9xdcq";
    });

    unpackPhase = "unzip $src";
    # if src is a path to the executable:
    # unpackPhase = "cp $src bb";

    # note: the chmod is only needed when using direct path to local executable
    patchPhase = ''
        chmod u+w ./bb
        patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" ./bb
        new_rpath=$(echo "$NIX_LDFLAGS" | tr ' ' $'\n' | grep "^-L" | sed -E 's/^-L/:/' | tr -d $'\n')
        patchelf --set-rpath "$new_rpath" ./bb
    '';

    dontBuild = true;

    installPhase = ''
        mkdir -p $out/bin
        cp bb $out/bin
    '';

    nativeBuildInputs = (with pkgs; [
        gcc-unwrapped.lib zlib unzip
    ]);
};

And bam! now we can integrate our sins into our package definitions.

<2020-08-07> There is also the very cool looking autopatchelf, which appears to try and do our first attempt automatically.

<2020-11-25> Another solution has come up: on demand linked libraries for nix

<2021-01-01> This popped up on my feed today and is also a cool solution to this problem: nix-autobahn