Imma start including a song[a] with my posts, just for fun and to share some beauty of the world and have a record

[a]

Note the following works with specifically modern ELF binaries which is basically the only thing you’ll ever see anyways though

Okay, so there’s been a surprising number of people that have no idea about this, even some friends that otherwise have quite deep knowledge of linux.

If you want to run a downloaded binary on Guix or Nix, you might sometimes come across this message:

bash: ./<name-of-file>: No such file or directory

This is caused by an interpreter not being found.

Interpreter???

Now you might be confused why a binary would be interpreted, I’ll explain a bit.

For scripts it’s usually set through the shebang, which is actually handled by the kernel not the shell you run it from. But that’s not the only way, dynamic executables have one too, and it happens to point to the linux dynamic linker.

This linker is statically linked and can just be loaded straight away, which solves the question of “do you need to invoke the linker to invoke the linker?”.

This usually lies somewhere in /lib/ld-linux-x86_64.so or so, but since Guix and Nix don’t have a /lib the interpreter can’t be found.

Patchelf

This is where I suggest getting this great utility from your nearest package manager, it allows you to check and set all the dynamic linking things that an executable needs. You can start by running patchelf --print-interpeter <some-binary> and see where that points and that it’s just a direct path.

Also notice that if you do that on a Guix binary, the interpreter will point into the store. So you can just set the interpreter with patchelf <bin> --set-interpreter <path>, to the one that’s in the live system profile /run/current-system/profile/lib/ld-linux-x86-64.so.2 and that fixes the first problem.

RPath

Okay so now you might get something like this:

./<binary-file>: error while loading shared libraries: libgtk-3.so.0: cannot open shared object file: No such file or directory

This signifies that the interpreter got found successfully, however some libraries are missing. Where does the system search for libraries anyways? I presumed for quite some while, that the /lib/ dir was somehow special but that turns out not to be the case, with patchelf --print-rpath <bin> you can see for yourself that there’s a path embedded in the binary, this is where the dynamic linker searches. As with classic $PATH the directories are separated with a :. So you need to set that to some place where the libs you have reside, in Guix’s case it’s your profile.

$ORIGIN

Sometimes you might see $ORIGIN among the RPath directories this refers to the directory where the binary resides, so if there’s a lib directory next to the binary with libraries that shipped with your program it might contain $ORIGIN/lib:....

LDD

Okay, now you might want to just see the list of required libraries, since you’ll probably be installing some manually if you’re downloading binaries.

Ldd is a neat little program to view what dynamically linking a binary would end up with, and should probably already be installed on your machine.

It also lets you verify if your RPath is set correctly.

This is what it might look like when the lib is downloaded, with something maybe getting found, but there’ll be a bunch of libs that don’t get found at all.

$ ldd <bin>

    linux-vdso.so.1 (0x00007ffdab2de000)
    libdl.so.2 => /gnu/store/ayc9r7162rphy4zjw8ch01pmyh214h82-glibc-2.33/lib/libdl.so.2 (0x00007fa58e816000)
    libpthread.so.0 => /gnu/store/ayc9r7162rphy4zjw8ch01pmyh214h82-glibc-2.33/lib/libpthread.so.0 (0x00007fa58e7f6000)
    libgtk-3.so.0 => not found
    libgdk-3.so.0 => not found

After changing the RPath most of them should show up:

$ ldd <bin>

    linux-vdso.so.1 (0x00007ffe753b1000)
    libdl.so.2 => /run/current-system/profile/lib/libdl.so.2 (0x00007f9ddedfa000)
    libpthread.so.0 => /run/current-system/profile/lib/libpthread.so.0 (0x00007f9ddedda000)
    libgtk-3.so.0 => /run/current-system/profile/lib/libgtk-3.so.0 (0x00007f9dde000000)
    libgdk-3.so.0 => /run/current-system/profile/lib/libgdk-3.so.0 (0x00007f9dde8fb000)

The List of Libs

The list of required libraries seems to just be made up of string names (the part you see on the left when running ldd), and you can use --{add,remove,replace,print}-needed in patchelf to play with that. Sadly most libraries I’ve tried patching out like this, include versioning symbols, that basically completely prevent you from easily switching versions, if your distro happens to package a different one.

Epilogue

After setting this up the binary should “just work”, I’ve ran many programs extensively, that were fixed up with this method, and it works about 75% of the time.

My solution

I wrote up a script a very long time ago to just run on a file so that it’d work. It has one flag -o that adds $ORIGIN to the RPath, otherwise you just pass a bunch of binaries as arguments and it should fix them right up for you. It adds all the profiles on the system to the RPath and sets the interpreter, having extra dirs there doesn’t matter so it just adds everything I can think of to make it as general as possible.

I think it should work on libs as well, but I haven’t done much testing on that.

#!/bin/sh
exec guix repl -- "$0" "$@"
!#

(use-modules (srfi srfi-26) (srfi srfi-1) (ice-9 getopt-long))

(define option-spec
  '((origin (single-char #\o) (value #f))))

(define options (getopt-long (command-line) option-spec))

(define (main . args)
  (map (lambda (target)
     (format #t "Target: ~a~%" target)
     (format #t "Patching out rpath...~%")
     (system* "patchelf" target "--set-rpath"
          ((if (option-ref options 'origin #f) (lambda (q) (string-append "$ORIGIN:" q)) identity)
           (string-append
            "/run/current-system/profile/lib:/home/"
            (getlogin)
            "/.guix-home/profile/lib"
            (apply string-append
               (map (cut string-append ":" <> "/lib")
                (drop-right (string-split
                         ((@ (ice-9 textual-ports) get-string-all)
                          ((@ (ice-9 popen) open-pipe*)
                           OPEN_READ "guix" "package" "--list-profiles"))
                         #\newline) 1))))))

     (format #t "Patching out interpreter...~%")
     (system*
      "patchelf" target
      "--set-interpreter"
      "/run/current-system/profile/lib/ld-linux-x86-64.so.2")

     (format #t "done~%")
     (display (format #t "RPATH: ~aLD: ~a"
              ((@ (ice-9 textual-ports) get-string-all)
               ((@ (ice-9 popen) open-pipe*)
                OPEN_READ "patchelf" target "--print-rpath"))
              ((@ (ice-9 textual-ports) get-string-all)
               ((@ (ice-9 popen) open-pipe*)
                OPEN_READ "patchelf" target "--print-interpreter")))))
       args))

(apply main (option-ref options '() '()))