简体   繁体   English

如何根据Linux上的CPU功能进行运行时绑定

[英]How to do runtime binding based on CPU capabilities on linux

Is it possible to have a linux library (eg "libloader.so") load another library to resolve any external symbols? 是否有可能让一个linux库(例如“libloader.so”)加载另一个库来解析任何外部符号?

I've got a whole bunch of code that gets conditionally compiled for the SIMD level to be supported ( SSE2, AVX, AVX2 ). 我有一大堆代码可以有条件地编译,以支持SIMD级别(SSE2,AVX,AVX2)。 This works fine if the build platform is the same as the runtime platform. 如果构建平台与运行时平台相同,则此方法可以正常工作。 But it hinders reuse across different processor generations. 但它阻碍了不同处理器世代的重用。

One thought is to have executable which calls function link to libloader.so that does not directly implement function . 一个想法是有executable调用function链接到libloader.so ,它不直接实现function Rather, it resolves(binds?) that symbol from another loaded library eg libimpl_sse2.so , libimpl_avx2.so or so on depending on cpuflags. 相反,它根据cpuflags解析(绑定?)来自另一个加载库的符号,例如libimpl_sse2.solibimpl_avx2.so等。

There are hundreds of functions that need to be dynamically bound in this way, so changing the declarations or calling code is not practical. 有数百个函数需要以这种方式动态绑定,因此更改声明或调用代码是不切实际的。 The program linkage is fairly easy to change. 程序链接很容易改变。 The runtime environment variables could also be changed, but I'd prefer not to. 运行时环境变量也可以更改,但我不想这样做。

I've gotten as far as making an executable that builds and starts with unresolved external symbols (UES) via the ld flag --unresolved-symbols=ignore-all . 我已经通过ld标志--unresolved-symbols=ignore-all创建了一个可执行文件来构建和启动未解析的外部符号(UES)。 But subsequent loading of the impl lib does not change the value of the UES function from NULL. 但后续加载impl lib并不会将UES函数的值从NULL更改。

Edit: I found out later on that the technique described below will only work under limited circumstances. 编辑:我后来发现,下面描述的技术只能在有限的情况下使用。 Specifically, your shared libraries must contain functions only, without any global variables. 具体来说,您的共享库必须只包含函数,不包含任何全局变量。 If there are globals inside the libraries that you want to dispatch to, then you will end up with a runtime dynamic linker error. 如果要在其中分配库中的全局变量,则最终会出现运行时动态链接器错误。 This occurs because global variables are relocated before shared library constructors are invoked . 发生这种情况是因为在调用共享库构造函数之前重定位全局变量 Thus, the linker needs to resolve those references early, before the dispatching scheme described here has a chance to run. 因此,链接器需要在此处描述的调度方案有机会运行之前尽早解析这些引用。


One way of accomplishing what you want is to (ab)use the DT_SONAME field in your shared library's ELF header. 实现所需内容的一种方法是(ab)在共享库的ELF头中使用DT_SONAME字段。 This can be used to alter the name of the file that the dynamic loader ( ld-linux-so* ) loads at runtime in order to resolve the shared library dependency. 这可以用于更改动态加载程序( ld-linux-so* )在运行时加载的文件的名称,以便解析共享库依赖项。 This is best explained with an example. 最好用一个例子来解释。 Say I compile a shared library libtest.so with the following command line: 假设我使用以下命令行编译共享库libtest.so

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

This will create a shared library whose filename is libtest.so , but its DT_SONAME field is set to libtest_dispatch.so . 这将创建一个文件名为libtest.so的共享库,但其DT_SONAME字段设置为libtest_dispatch.so Let's see what happens when we link a program against it: 让我们看看当我们将程序链接到它时会发生什么:

g++ testprog.cc -o test -ltest

Let's examine the runtime library dependencies for the resulting application binary test : 让我们检查生成的应用程序二进制test的运行时库依赖关系:

> 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 instead wants to load libtest_dispatch.so instead. 请注意,动态加载器不是要查找libtest.so ,而是要加载libtest_dispatch.so You can exploit this to implement the dispatching functionality that you want. 您可以利用它来实现所需的调度功能。 Here's how I would do it: 我是这样做的:

  • Create the various versions of your shared library. 创建共享库的各种版本。 I assume that there is some "generic" version that can always be used, with other optimized versions utilized at runtime as appropriate. 我假设有一些“通用”版本可以随时使用,其他优化版本在运行时适当使用。 I would name the generic version with the "plain" library name libtest.so , and name the others however you choose (eg libtest_sse2.so , libtest_avx.so , etc.). 我将使用“普通”库名称libtest.so命名通用版本,并将其他名称命名为(例如libtest_sse2.solibtest_avx.so等)。

  • When linking the generic version of the library, override its DT_SONAME to something else, like libtest_dispatch.so . 链接库的通用版本时,将其DT_SONAME覆盖为其他内容,例如libtest_dispatch.so

  • Create a dispatcher library called libtest_dispatch.so . 创建一个名为libtest_dispatch.so的调度程序库。 When the dispatcher is loaded at application startup, it is responsible for loading the appropriate implementation of the library. 在应用程序启动时加载调度程序时,它负责加载库的相应实现。 Here's pseudocode for what the implementation of libtest_dispatch.so might look like: 这里是libtest_dispatch.so实现的伪代码:

     #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 an application against your library, link it against the "vanilla" libtest.so , the one that has its DT_SONAME overridden to point to the dispatcher library. 将应用程序与库链接时,将其链接到“vanilla” libtest.so ,将其DT_SONAME重写为指向调度程序库的那个。 This makes the dispatching essentially transparent to any application authors that use your library. 这使得调度对使用您的库的任何应用程序作者基本上都是透明的。

This should work as described above on Linux. 这应该如上所述在Linux上工作。 On Mac OS, shared libraries have an "install name" that is analogous to the DT_SONAME used in ELF shared libraries, so a process very similar to the above could be used instead. 在Mac OS上,共享库具有“安装名称”,类似于ELF共享库中使用的DT_SONAME ,因此可以使用与上述非常类似的过程。 I'm not sure about whether something similar could be used on Windows. 我不确定在Windows上是否可以使用类似的东西。

Note: There is one important assumption made in the above: ABI compatibility between the various implementations of the library. 注意:上面有一个重要的假设:库的各种实现之间的ABI兼容性。 That is, your library should be designed such that it is safe to link against the most generic version at link time while using an optimized version (eg libtest_avx.so ) at runtime. 也就是说,您的库应该设计为在运行时使用优化版本(例如libtest_avx.so )时,在链接时链接最通用的版本是安全的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM