How to perform runtime binding based on CPU capabilities on Linux

Is it possible for the linux library (for example, "libloader.so") to load another library to resolve any external characters?

I have a whole bunch of code that is conditionally compiled for a supported SIMD level (SSE2, AVX, AVX2). This works great if the build platform is the same as the runtime platform. But this prevents reuse in different generations of processors.

One thought is to have an executable that calls a function reference to libloader.so , which does not directly implement function . Rather, it allows (links?) This symbol from another loaded library, for example. libimpl_sse2.so , libimpl_avx2.so or so on depending on cpuflags.

There are hundreds of functions that need to be dynamically linked in this way, so changing ads or calling code is not practical. Communication with the program is quite easy to change. Runtime environment variables can also be changed, but I would prefer not to.

I got to the --unresolved-symbols=ignore-all creating an executable file that builds and runs unresolved external characters (UES) through the ld flag --unresolved-symbols=ignore-all . But the subsequent loading of impl lib does not change the value of the UES function with NULL.

+6
source share
1 answer

Edit: I found out later that the technique described below will only work in limited conditions. In particular, your shared libraries should only contain functions, without any global variables. If the libraries you want to send have global variables, then you will get a dynamic runtime linker error. This happens because global variables are moved before calling shared library constructors . Therefore, the linker needs to resolve these links early before the dispatch scheme described here can start.


One way to achieve what you want is to (ab) use the DT_SONAME field in the ELF header of your shared library. This can be used to change the name of the file that the dynamic loader ( ld-linux-so* ) loads at runtime to resolve the dependency of the shared library. This is best explained by example. Let's say I am compiling the libtest.so shared library with the following command line:

 g++ test.cc -shared -o libtest.so -Wl,-soname,libtest_dispatch.so 

This will create a shared library with the file name libtest.so , but the DT_SONAME field DT_SONAME set to libtest_dispatch.so . Let's see what happens when we associate the program with it:

 g++ testprog.cc -o test -ltest 

Let's look at the runtime library dependencies for the resulting binary test application:

 > ldd test linux-vdso.so.1 => (0x00007fffcc5fe000) libtest_dispatch.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd1e4a55000) /lib64/ld-linux-x86-64.so.2 (0x00007fd1e4e4f000) 

Note that instead of looking for libtest.so , the dynamic loader wants to load libtest_dispatch.so . You can use this to implement the required dispatch functions. Here's how I do it:

  • Create different versions of your shared library. I assume that there is some β€œgeneric” version that can always be used, with other optimized versions used at runtime, if necessary. I would name the generic version with the name "plain" library libtest.so and name the rest, but you chose (for example, libtest_sse2.so , libtest_avx.so , etc.).

  • When linking a generic version of a library, override its DT_SONAME with something else, such as libtest_dispatch.so .

  • Create a dispatcher library called libtest_dispatch.so . When the dispatcher boots at application startup, it is responsible for loading the appropriate library implementation. Here's the pseudocode that the libtest_dispatch.so implementation might look like:

     #include <dlfcn.h> #include <stdlib.h> // the __attribute__ ensures that this function is called when the library is loaded __attribute__((constructor)) void init() { // manually load the appropriate shared library based upon what the CPU supports // at runtime if (avx_is_available) dlopen("libtest_avx.so", RTLD_NOW | RTLD_GLOBAL); else if (sse2_is_available) dlopen("libtest_sse2.so", RTLD_NOW | RTLD_GLOBAL); else dlopen("libtest.so", RTLD_NOW | RTLD_GLOBAL); // NOTE: this is just an example; you should check the return values from // dlopen() above and handle errors accordingly } 
  • When linking the application to your library, link it to the "vanilla" libtest.so , which has its own DT_SONAME , redefined to point to the dispatcher library. This makes sending essentially transparent to any application authors who use your library.

This should work as described above on Linux. On Mac OS, shared libraries have a "name for installation", which is similar to DT_SONAME used in ELF shared libraries, so instead you can use a process very similar to the one above. I'm not sure if something like this can be used on Windows.

Note. . The above makes one important assumption: ABI compatibility between different library implementations. That is, your library should be designed so that you can safely communicate with the most common version at connection time using an optimized version (e.g. libtest_avx.so ) at runtime.

+4
source

Source: https://habr.com/ru/post/984866/


All Articles