Why is patchelf needed for interpreter when ldd shows no problems?

I’m trying to create a haskell executable on an ubuntu machine with Nix installed that can be run on another ubuntu system that does not have nix installed. Running the executable on the system without Nix installed gives me a “No such file or directory” error.

Running ldd on the executable indicates, among other things:

/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib/ld-linux-x86-64.so.2 
    => /lib64/ld-linux-x86-64.so.2 (0x00007fe6c6684000)

I understood that to mean that the executable was dynamically linking to an interpreter in the nix store (which would not be present on this system that does not have nix installed), but that the interpreter was nevertheless found on the local system at /lib64/ld-linux-x86-64.so.2, so there shouldn’t be any problem (since ldd didn’t give a not found message).

But since I was getting a “No such file or directory” error when running my executable, I tried running ~patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 my-executable-exe on the executable. After that, it ran fine.

Can you help me understand why patchelf helps here? I must be misunderstanding something about the initial ldd command’s response, since I thought that indicated it could find the interpreter, but it seems that it couldn’t since updating the path to the interpreter fixed my problem.

Thanks for your help.

1 Like

Also, if you have any hints on how best to incorporate this patchelf command into my flake, I’d appreciate it. I’m still figuring out nix and flakes, and this is giving me some trouble. This is my current flake:

{ inputs = {
    nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable;
    utils.url = github:numtide/flake-utils;
  };

  outputs = { nixpkgs, utils, ... }:
    utils.lib.eachDefaultSystem (system:
      let
        config = { };

       compiler = "ghc924";

       overlay = pkgsNew: pkgsOld: {
          my-server =
            pkgsNew.haskell.lib.justStaticExecutables
              pkgsNew.haskell.packages.${compiler}.my-server;

          haskellPackages = pkgsOld.haskell.packages.${compiler}.override (old: {
            overrides =
              let
                oldOverrides = old.overrides or (_: _: {});
                manualOverrides = haskellPackagesNew: haskellPackagesOld: {
                  http2 = pkgsNew.haskell.lib.dontCheck haskellPackagesOld.http2;
                };
                sourceOverrides = pkgsNew.haskell.lib.packageSourceOverrides {
                  my-server = ./.;
                };
              in
                pkgsNew.lib.fold pkgsNew.lib.composeExtensions oldOverrides
                  ( [ sourceOverrides ] ++ [ manualOverrides ] );
          });
        };

        pkgs = import nixpkgs { inherit config system; overlays = [ overlay ]; };

      in rec {
        packages.default = pkgs.haskellPackages.my-server;

        apps.default = {
          type = "app";
        };
      }
    );
}

If you look at ldd source you’ll see how it extracts libraries: it runs encoded there ld-linux.so instead of using binary’s interpreter. I suggest using lddtree from pax-utils to list needed libraries.

2 Likes

There’s also readelf:

$ nix-build -A stack
...
$ readelf -d ./result/bin/stack 
Dynamic section at offset 0x168a3c8 contains 36 entries:
  Tag        Type                         Name/Value
 0x0000000000000003 (PLTGOT)             0x1a8b670
 0x0000000000000002 (PLTRELSZ)           7272 (bytes)
 0x0000000000000017 (JMPREL)             0x404f80
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000007 (RELA)               0x404ef0
 0x0000000000000008 (RELASZ)             144 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000006 (SYMTAB)             0x400350
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000005 (STRTAB)             0x402a50
 0x000000000000000a (STRSZ)              5364 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x403f48
 0x0000000000000004 (HASH)               0x403f98
 0x0000000000000001 (NEEDED)             Shared library: [libsqlite3.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libz.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libgmp.so.10]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [librt.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libffi.so.8]
 0x000000000000000c (INIT)               0x406be8
 0x000000000000000d (FINI)               0x19698a4
 0x000000000000001a (FINI_ARRAY)         0x1a8a608
 0x000000000000001c (FINI_ARRAYSZ)       24 (bytes)
 0x0000000000000019 (INIT_ARRAY)         0x1a8a620
 0x000000000000001b (INIT_ARRAYSZ)       912 (bytes)
 0x000000000000001d (RUNPATH)            Library runpath: [/nix/store/2infxahfp9lj084xn3q9ib5ajks8447i-libffi-3.4.4/lib:/nix/store/4n2j62ghfgsk5amycf3zss01b3wp0846-gmp-with-cxx-6.2.1/lib:/nix/store/b81m03by2l7qq2ajzlipjd7q3h01n5fy-sqlite-3.40.0/lib:/nix/store/zaflwh2nwzj1f0wngd7hqm3nvlf3yhsx-zlib-1.2.13/lib:/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib]
 0x000000000000001e (FLAGS)              BIND_NOW
 0x000000006ffffffb (FLAGS_1)            Flags: NOW
 0x000000006ffffff0 (VERSYM)             0x404a3c
 0x000000006ffffffe (VERNEED)            0x404d7c
 0x000000006fffffff (VERNEEDNUM)         3
 0x0000000000000000 (NULL)               0x0

And patchelf itself:

$ nix-shell -p patchelf --command 'patchelf --print-interpreter ./result/bin/stack'
/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib/ld-linux-x86-64.so.2
$ nix-shell -p patchelf --command 'patchelf --print-needed ./result/bin/stack'
libsqlite3.so.0
libpthread.so.0
libz.so.1
libgmp.so.10
libc.so.6
libm.so.6
librt.so.1
libdl.so.2
libffi.so.8
$ nix-shell -p patchelf --command 'patchelf --print-rpath ./result/bin/stack'
/nix/store/2infxahfp9lj084xn3q9ib5ajks8447i-libffi-3.4.4/lib:/nix/store/4n2j62ghfgsk5amycf3zss01b3wp0846-gmp-with-cxx-6.2.1/lib:/nix/store/b81m03by2l7qq2ajzlipjd7q3h01n5fy-sqlite-3.40.0/lib:/nix/store/zaflwh2nwzj1f0wngd7hqm3nvlf3yhsx-zlib-1.2.13/lib:/nix/store/ayfr5l52xkqqjn3n4h9jfacgnchz1z7s-glibc-2.35-224/lib

Although if you want to make a Haskell binary you can run on other distributions, it may just be easiest to build a statically-linked binary:

$ nix-build -A pkgsStatic.haskellPackages.stack
...
$ file ./result/bin/stack 
./result/bin/stack: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

That should hopefully be more portable than the patchelf’d binary.

2 Likes