[HN Gopher] Rpath, or why lld doesn't work on NixOS
       ___________________________________________________________________
        
       Rpath, or why lld doesn't work on NixOS
        
       Author : ingve
       Score  : 44 points
       Date   : 2022-03-15 18:11 UTC (4 hours ago)
        
 (HTM) web link (matklad.github.io)
 (TXT) w3m dump (matklad.github.io)
        
       | slabity wrote:
       | How does an article on NixOS talk about the `rpath` issue without
       | also mentioning the `patchelf` utility that NixOS developers
       | created to solve this issue? It's a small tool that lets you
       | modify ELF executables and binaries. It's also the recommended
       | way for NixOS users to modify binaries to work properly.
       | 
       | https://github.com/NixOS/patchelf
        
         | matklad wrote:
         | `patchelf` solves a different problem: it fixes up an already
         | built binary. Here, I am building the binary myself, so I'd
         | rather make that just work without any extra build steps.
        
           | slabity wrote:
           | Ah, my bad. I was confused because I never run into linker
           | issues when I build my rust (or any other type of) binaries
           | on a NixOS system.
           | 
           | In fact, I am running the `evdev` example and I don't get any
           | linker errors at all even when I change the linker to LLVM. I
           | am using a nightly version of rust though.
        
         | kazinator wrote:
         | I'm pretty sure NixOS isn't the only one doing this hack. The
         | Yocto build system does something similar. It contains its own
         | build-time binary glibc, and patches its tools to point to its
         | own internal library installation. Or something like that. In
         | effect, Yocto has its own build-time distro, which has to run
         | from any filesystem location.
        
       | pvtmert wrote:
       | interestingly macOS has a really nice solution called @rpath and
       | install_name_tool
       | 
       | basically you build your binary and set a rpath, let's say it is
       | /usr/local/lib in your machine
       | 
       | mach-o binary stores: @rpath = /usr/local/lib libxyz =
       | @rpath/libxyz.dylib.1
       | 
       | when I want to install those to /opt/something only thing I need
       | to do is install_name_tool -add_rpath /opt/something
       | 
       | This will add search directories to binary itself. There are some
       | DYLD_* environment variables too but I'm not sure about them...
       | (Some are SIP protected by the way)
       | 
       | PS: It may invalidate signed binaries. Again, not tested such use
       | cases.
        
         | kazinator wrote:
         | That's a pretty poor solution compared to just finding
         | libraries relative to the root of the program's installation.
        
           | whateveracct wrote:
           | Yep - Windows of all things handles this the best.
           | 
           | You can try to do this with rpath of $ORIGIN. But then you'll
           | probably still run into libc issues -.-
           | 
           | Not hard to see why a lot of games focus on Windows and let
           | wine/proton handle Linux.
        
       | elikoga wrote:
       | Wow, great investigation article
        
       | fanf2 wrote:
       | Because I am stubborn, when I am linking with a library installed
       | in a nonstandard place, I usually try to get the configure script
       | to do the right thing, even though it is not always easy. But,
       | just in case I lose the battle, I keep in mind the existence of
       | _chrpath_ , a little utility for changing the rpath in an ELF
       | binary. Because you use it on the final build artefact, there is
       | no way for autoconf or libtool to screw it up.
        
       | kazinator wrote:
       | rpath is braindamaged; no program should be using that, and a
       | distro build should almost never be inserting such a thing into
       | executables.
       | 
       | I suspect that NixOS is playing with this in order to have a
       | relocatable install: so that is to say, so that user can install
       | NixOS in some subdirectory of a system running some existing
       | distro. Any subdirectory, yet so that programs can find their
       | libraries.
       | 
       | If I were in this predicament, rather than perpetrating hacks to
       | patch the rpaths in binaries, I'd fix the dynamic linker to have
       | a better way of locating shared libraries. The linker would
       | determine the path from which the executable is being run,
       | calculate the sysroot location dynamically, then look for
       | libraries in that tree. E.g. /path/to/usr/bin/program would look
       | under /path/to/usr/lib and related places.
       | 
       | A possibly nice hack would be to extend the meaning of the rpath
       | variable. Give it a syntax, like say that if it starts with @,
       | then the rest of it denotes a relative sysroot path fragment.
       | 
       | E.g. the program that gets installed as /path/to/usr/bin/program
       | would be built with an rpath of "@/usr/bin". So then the dynamic
       | linker sees the @ and does a sysroot calculation. First it strips
       | off the basename to get just the directory part
       | "/path/to/usr/bin". Then it sees, hey, the suffix of
       | "/path/to/usr/bin" matches the "/usr/bin" in the rpath. The
       | suffix is stripped to produce "/path/to" and that path is then
       | used as the root for the library searching. Instead of searching
       | literally in /lib or /usr/lib or whatnot, the "/path/to" part is
       | prefixed to every search place to look in /path/to/lib and
       | /path/to/usr/lib.
       | 
       | Patching binaries is very poor; it changes their cryptographic
       | hash like SHA-256. You want your distro to be installing bit-
       | exact stuff from the packages, and treating it as immutable.
        
         | eptcyka wrote:
         | nixOS is built around hashing the outputs of its builds, so you
         | can verify if a build produces the expected output given the
         | same inputs. The reason nixOS patches binaries is so that they
         | can actually find the shared objects they expect when they are
         | not stored in /usr/lib. Since every build output gets its own
         | unique path, this allows for a binary to link against two
         | slightly different versions of the same library, which in
         | practice is never an issue one needs to resolve. However, a
         | more practical issue this solves is that you can have a single
         | sysroot with two binaries that need two slightly different
         | versions of the same library.
        
         | matklad wrote:
         | > I suspect that NixOS is playing with this in order to have a
         | relocatable install
         | 
         | Kinda the opposite: NixOS doesn't really have a sysroot.
        
         | geraldcombs wrote:
         | > A possibly nice hack would be to extend the meaning of the
         | rpath variable. Give it a syntax, like say that if it starts
         | with @, then the rest of it denotes a relative sysroot path
         | fragment.
         | 
         | This sounds a lot like dyld's @executable_path variable on
         | macOS.
        
       | stabbles wrote:
       | Linux usually (by convention) provides 1 file and 2 symlinks per
       | lib: liba.so -> liba.so.x -> liba.so.j.k.l.
       | 
       | The first one is to make the linker (ld) happy: -la will look for
       | liba.so. The linker puts the SONAME (liba.so.x) in DT_NEEDED.
       | 
       | The second symlink's filename corresponds to the SONAME, so that
       | the runtime linker (ld.so) can locate the library by SONAME in
       | rpaths.
       | 
       | The third one is the actual library, which can be updated while
       | keeping the same soname & same abi.
       | 
       | Now, it would be great if the linker had an option to not only
       | copy the SONAME into DT_NEEDED, but also register the path in
       | which the library was located as an rpath.
       | 
       | Cause the situation on Linux is absurd! You pass some flags -L
       | and -l to the compiler/linker, the linker links _something_ and
       | nobody knows what. Then when you run your executable it has to
       | locate this _something_ again, and you can only pray that your
       | libc and binutils /llvm agree on search paths & order. In most
       | cases this does not work, and you must manually pass
       | -Wl,-rpath,/some/path to add a search path. Nobody guarantees
       | that what the linker links is what the runtime linker uses.
       | 
       | Of course there are many edge cases:
       | 
       | - linking during make without relinking during make install will
       | make your executables register rpaths to build directories
       | instead of install dirs
       | 
       | - sometimes you link to a stub lib that should not be used at
       | runtime
       | 
       | But still, some guarantee that what you build with is what you
       | run with would be a major user experience improvement for linux.
        
         | jcranmer wrote:
         | > Cause the situation on Linux is absurd! You pass some flags
         | -L and -l to the compiler/linker, the linker links something
         | and nobody knows what. Then when you run your executable it has
         | to locate this something again, and you can only pray that your
         | libc and binutils/llvm agree on search paths & order. In most
         | cases this does not work, and you must manually pass
         | -Wl,-rpath,/some/path to add a search path. Nobody guarantees
         | that what the linker links is what the runtime linker uses.
         | 
         | The issue you're missing is that the build directory is usually
         | not the location of the final binary objects. The actual
         | absolute path of libfoo.so may well be in /builds/runner/foo-
         | package-4df78af0/build/prefix/lib/libfoo.so, which is unlikely
         | to exist on anyone other than the CI's machine (and even on the
         | CI machine itself for too much longer). The actual location
         | will usually be /usr/lib64/libfoo.so, but the library that is
         | linked against may well not be there at the time of linking
         | (particularly in the case where a package is building both a
         | library and an executable that depends on said library in the
         | same package).
         | 
         | What you really want is for the _relative_ path to the library
         | to stored in the executable. Unless what you want is to
         | actually use the globally-installed library and not one you 're
         | building at the same time. There's no single solution that fits
         | every use case!
        
           | SAI_Peregrinus wrote:
           | Of course you can't guarantee that the library will get
           | installed to the directory you think it'll get installed to,
           | since there's no unified installer system for all Linux
           | distributions. So even a relative path doesn't necessarily
           | work. The best you can do is hope that the Freedesktop
           | filesystem hierarchy is being followed, but that forces
           | installing software for all users at once instead of per
           | user, despite Linux supposedly being a multiuser OS.
        
       | setheron wrote:
       | I actually wrote somethijg recently related to RPATH in Nix you
       | might find interesting
       | 
       | https://fzakaria.com/2022/03/14/shrinkwrap-taming-dynamic-sh...
        
       | higherhalf wrote:
       | Speaking of significantly faster linkers, I personally use
       | mold[1], including when writing in Rust.
       | 
       | [1]: https://github.com/rui314/mold
        
       ___________________________________________________________________
       (page generated 2022-03-15 23:01 UTC)