Search code examples
clinkershared-librariesaptpackage-managers

What causes shared object dependency to change after WSL shutdown/restart?


In an Ubuntu 22.04.3 WSL instance, I'm compiling a shared library that links to libuv.

Ubuntu 22.04.3 WSL, straight out of the Microsoft Store, doesn't include libuv. Installing it via the apt package manager installs version 1.43.0-1 at /usr/lib/x86_64-linux-gnu/libuv.so:

$ find /usr -name libuv.so 2>/dev/null
$
$ sudo apt-get install -y libuv1-dev
# ...
$ find /usr -name libuv.so 2>/dev/null
/usr/lib/x86_64-linux-gnu/libuv.so
$
$ apt list | grep libuv1

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

libuv1-dev/jammy,now 1.43.0-1 amd64 [installed]
libuv1/jammy,now 1.43.0-1 amd64 [installed,automatic]
$

The code for the shared library is a simplification/MCVE of a larger project. It requires libuv symbols not available in version 1.43.0-1 (specifically, uv_timespec64_t and function uv_clock_gettime). Therefore, I install a newer version that includes these symbols. Because no newer version is available to the apt package manager, I follow the build/install instructions at its README.md. Note that this appears to install the new version at /usr/local/lib/libuv.so, i.e. it does not overwrite the version installed with the apt-get package manager:

$ git clone https://github.com/libuv/libuv.git && cd libuv && git checkout v1.45.0 && mkdir -p build && (cd build && cmake .. -DBUILD_TESTING=ON) && cmake --build build && (cd build && ctest -C Debug --output-on-failure) && sudo cmake --install build
# ...
$ 
$ find /usr -name libuv.so 2>/dev/null
/usr/lib/x86_64-linux-gnu/libuv.so
/usr/local/lib/libuv.so
$

Now, I have two versions of libuv in my Ubuntu WSL instance: version 1.43.0-1 at /usr/lib/x86_64-linux-gnu/libuv.so and version 1.45.0 at /usr/local/lib/libuv.so.

This is the shared lib that I'm compiling:

#include <uv.h>

int func() {
  uv_timespec64_t now;

  uv_close(NULL, NULL);
  uv_clock_gettime(UV_CLOCK_REALTIME, &now);

  return 0;
}

I compile as follows:

$ cc --version && cc -fpic -ggdb -Wall -c -o func.o func.c
cc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cc -shared -o libfunc.so func.o  -fpic -ggdb -luv
$

The shared library dependencies, as reported by ldd, are below. Note that it is linking against version 1.43.0-1, located at /usr/lib/x86_64-linux-gnu/libuv.so (/lib is a symlink to /usr/lib):

$ ldd ./libfunc.so
        linux-vdso.so.1 (0x00007ffc74bdb000)
        libuv.so.1 => /lib/x86_64-linux-gnu/libuv.so.1 (0x00007f323f071000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f323ee49000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f323f0b1000)
$
$ ls -ld /lib
lrwxrwxrwx 1 root root 7 Nov 22 13:36 /lib -> usr/lib
$

But, if I shut down (wsl --shutdown) and restart my Ubuntu WSL instance, then without (knowingly) changing the environment or touching the compiled shared lib, ldd now reports that it links against version 1.45.0, located at /usr/local/lib/libuv.so:

$ # I've started this new Ubuntu WSL instance after shutting down the last instance with `wsl --shutdown`
$ 
$ ldd ./libfunc.so
        linux-vdso.so.1 (0x00007ffdd655d000)
        libuv.so.1 => /usr/local/lib/libuv.so.1 (0x00007fe8db267000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe8db03f000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe8db2b8000)
$

My question is: why does the libuv, that is "chosen" to be linked against, change after the shutdown/restart of the WSL instance?

Is this WSL-specific, where something is adjusted in the environment with each new instance (and what is the "something" that is adjusted)? Or would I observe similar behavior on any Linux PC after a physical shutdown/restart?


From How to print the ld(linker) search path, I know how to query the linker's ordered search paths:

$ ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\012
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SEARCH_DIR("=/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib")

...this implies that the linker would prefer /lib/x86_64-linux-gnu/libuv.so.1 (which, as noted before, links to /usr/lib/x86_64-linux-gnu/libuv.so.1) over /usr/local/lib/libuv.so.1. This explains the pre-shutdown output from ldd, but does not explain the post-restart output of ldd.


One more possibly-related thing I'm aware of is /etc/ld.so.conf and /etc/ld.so.conf.d/. However, there is no change to /etc/ld.so.conf or any file in /etc/ld.so.conf.d/ after shutting down/restarting the Ubuntu WSL instance.


Update: @EmployedRussian's comment appears to essentially be the answer to this question. I'm updating this question with relevant findings:

$ # After starting a new instance of Ubuntu WSL:
$ ldconfig -Np | grep libuv
236 libs found in cache `/etc/ld.so.cache'
        libuv.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libuv.so.1
$ # No change after running `sudo apt-get install -y libuv1-dev`
$ # After running `sudo apt-get install -y libsdl2-dev`:
$ ldconfig -Np | grep libuv
        libuv.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libuv.so.1
        libuv.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libuv.so
$ # After manually installing libuv v1.45.0 (acknowledged, this is a Really Bad Idea (TM)):
$ # No change
$ # After manually installing libuv 1.45.0 and running `sudo ldconfig`:
$ ldconfig -Np | grep libuv
        libuv.so.1 (libc6,x86-64) => /usr/local/lib/libuv.so.1
        libuv.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libuv.so.1
        libuv.so (libc6,x86-64) => /usr/local/lib/libuv.so
        libuv.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libuv.so

The same pattern can be observed by shutting down/restarting Ubuntu WSL (instead of running sudo ldconfig). Moreover, the difference in the output of ldd ./libfunc.so can also be reproduced before/after running sudo ldconfig. I can therefore infer that shutting down/restart of Ubuntu WSL triggers invocation of ldconfig, which explains the difference in behavior of where ldd resolves the libuv dependency before and after the Ubuntu WSL shutdown/restart.


Solution

  • Because no newer version is available to the apt package manager, I follow the build/install instructions at its README.md. Note that this appears to install the new version at /usr/local/lib/libuv.so.

    This is a Really Bad IdeaTM.

    You can download the 1.43 source package and check how it is configured; then configure 1.45 the same way. That should result in putting the binaries into the same location. However, that is also a bad idea, as it screws up package manager and dependencies, and sooner of later leads to a broken system.


    A better idea is to configure and install non-default packages into a non-default location, with something like ./configure --prefix /usr/local/libuv-1.45.

    This library will then not be used by a random build; you would have to specifically point to it in order to use it. Typically you would do something like:

    make LVTOP=/usr/local/libuv-1.45 CFLAGS='-I${LVTOP}/include' \
      LDFLAGS='-L${LVTOP}/lib -rpath=${LVTOP}/lib'
    

    to direct compiler to use ${LVTOP}/include for the headers, and the linker to use ${LVTOP}/lib for both static (ld) and dynamic (ld-linux.so) linking.

    When used that way, the extra package doesn't interfere with the system in any way, doesn't affect your other builds, and doesn't magically change configuration across reboots.


    P.S. This doesn't directly answer the question of "why did reboot change something?", but see my comment above for my best guess of why that happened.