弱关联vs" - 按需"

时间:2017-07-07 09:20:51

标签: gcc linker jack weak-linking

我在使用包含弱符号和--as-needed链接器标记的库时遇到了问题。

实施例

(这使用Jack库)

$ cat <<EOF >myjack.c
#include <jack/weakjack.h>
#include <jack/jack.h>
int main() {
  if (jack_client_opent)
     jack_client_open("foobar", JackNoStartServer, 0, 0);
  else return 1;
  return 0;
}
EOF

$ gcc -o myjack myjack.c  -Wl,--no-as-needed -ljack

$ ./myjack && echo "ok" || echo "K.O."
ok

$ ldd myjack | grep jack
    libjack.so.0 => /usr/lib/x86_64-linux-gnu/libjack.so.0 (0x00007f16f615f000)

$ gcc -o myjack myjack.c  -Wl,--as-needed -ljack

$ ./myjack && echo "ok" || echo "K.O."
K.O.

$ ldd myjack | grep jack

$

(示例代码被编辑为不再发生段错误,因为段错误不是我的实际问题)

问题

似乎问题是:

  • Jack将所有符号声明为 weak (如果我包含<jack/weakjack.h>)。这对我很好;我确实希望我的符号保持弱势。 ESP。我的程序与OSX上的jack(-weak_framework Jackmp)弱连接,需要包含<jack/weakjack.h>

  • --as-needed链接时,链接器会排除任何未引用至少一个非弱符号的库。从联机帮助页:

  

- 根据需要导致仅发出DT_NEEDED标记              在链接中的那个点满足非弱的库              来自常规对象文件的未定义符号引用

  • 某些操作系统(例如Ubuntu-16.04LTS)默认启用--as-needed

现在我认为--as-needed是一个很好的链接器功能,可以摆脱许多真正不需要的运行时依赖项。

但是,我不明白为什么依赖关系被视为根本没有依赖。对我来说,依赖关系是启用可选功能。我确实希望在可能的情况下启用这些功能,并且决定是否可以这样做应该是运行时决定。使用当前行为,它成为编译时决策。 (如果我想要,我会通过一些预处理器魔术禁用相关代码。)

显然,一个解决方案就是将--no-as-needed添加到链接器标志中。 我不想要这样:如果我的发行版(或编译我的二进制文件的人)认为这是可以做的事情,我确实想摆脱过度链接。

所以我可能在我的已知弱库中链接后启用as-needed

  gcc -o myjack myjack.c  -Wl,--no-as-needed -ljack -Wl,--as-needed ...

但这也感觉不对,因为在我强制需要的库之后的所有库突然被迫--as-needed(这可能是我的发行版或编译我的二进制文件的人认为这是要做的事情)。它似乎也为构建链添加了很多东西,只是因为某些库恰好只输出弱符号。我不想手动跟踪执行此操作的所有库。

我当然也可以包括<jack/weakjack.h>。包含它的原因是因为应用程序也适用于OSX,我希望可选地依赖在JACK框架上(所以我链接到-weak_framework Jackmp),并保持我的程序可运行在没有该框架的情况下。

由于各种平台上的链接器之间的细微差别,我真的不想弄乱我的应用程序代码。这可能是我所有这一切的主要问题:为什么将平台特定的代码添加到我的应用程序以满足不同的链接器细节 - 我可能没问题添加特定于功能的代码,例如如果编译器没有weakjack.h-weak_library的等效项,则不包括-weak_framework;但目前似乎我能得到的最接近的是#ifdef __APPLE__,这让我在这种情况下不寒而栗。)

所以我真的很喜欢一些选项来强制只有弱符号的库才能被打包。

有这样的事吗?

1 个答案:

答案 0 :(得分:7)

  

我在使用包含弱符号和--as-needed链接器标志的库时遇到了问题。

不,你不是。

找出libjack.so的位置,例如

$ locate libjack
/usr/lib/x86_64-linux-gnu/libjack.so
/usr/lib/x86_64-linux-gnu/libjack.so.0
/usr/lib/x86_64-linux-gnu/libjack.so.0.1.0
...

然后使用nm检查libjack.so中JACK API的符号类型:

$ nm -C -D /usr/lib/x86_64-linux-gnu/libjack.so | grep jack_
000000000000e7e0 T jack_acquire_real_time_scheduling
000000000000d530 T jack_activate
000000000002ccf0 T jack_client_close
000000000000e820 T jack_client_create_thread
....
....
000000000000f340 T jack_uuid_empty
000000000000f320 T jack_uuid_parse
000000000000f2e0 T jack_uuid_to_index
000000000000f330 T jack_uuid_unparse

您会发现它们都是T的类型(=文本部分中的普通全局符号:man nm)。 库中的一些弱符号:

$ nm -C -D /usr/lib/x86_64-linux-gnu/libjack.so | egrep ' (w|W) '
                 w __cxa_finalize
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
0000000000025410 W std::ctype<char>::do_widen(char) const
0000000000014c10 W void std::vector<unsigned short, std::allocator<unsigned short> >::_M_emplace_back_aux<unsigned short const&>(unsigned short const&)
0000000000014b10 W std::pair<std::_Rb_tree_iterator<unsigned short>, bool> std::_Rb_tree<unsigned short, unsigned short, std::_Identity<unsigned short>, std::less<unsigned short>, std::allocator<unsigned short> >::_M_insert_unique<unsigned short>(unsigned short&&)
0000000000014ad0 W std::_Rb_tree<unsigned short, unsigned short, std::_Identity<unsigned short>, std::less<unsigned short>, std::allocator<unsigned short> >::_M_erase(std::_Rb_tree_node<unsigned short>*)

但它们都不在JACK API中。没有什么比重建你的libjack.so还能改变的那样。 表征问题的正确方法是:

我在将库与--as-needed链接器标志链接到一个程序时遇到问题 我决定削弱我对该库的所有引用

libjack.so中JACK API的定义符号引用都很强大。你有 编写了一个程序,指示编译器在你的目标代码中发出符号 对JACK API的弱未定义引用,您发现,使用 as-needed 链接, 这些弱引用无法强制libjack.so的链接以提供它们缺少的定义。

  

似乎问题是:

     

jack将所有符号声明为弱(如果我包括)。

     

与--as-needed链接时,链接器会排除任何不引用至少一个非弱符号的库。

     

某些操作系统(例如Ubuntu-16.04LTS)默认启用了--as-needed。

最后两点是正确的。连接共享库的发行版之间的分裂 as-needed 默认情况下和发行版不会再回到2013年的Debian Wheezy, went over to as-needed。 从那时起,Debian派生的发行家族也纷纷效仿RedHat / Fedora 战队坚持现状

第一点很困惑。正如我们已经指出的那样,libjack.so导出了一个强烈定义的 通过编写和编译新代码无法改变的JACK API。 如果您在其中一个源文件中包含<jack/weakjack.h>,那么就是 在代码中声明所有JACK API符号都很弱,编译器会 给你一个只包含对JACK API的弱引用的目标文件。 <jack/weakjack.h> 只定义具有该效果的宏。

如果像libjack这样的旧的和主要的linux库,那将会令人惊讶 已经拙劣地适应了所需的分裂。我怀疑你忽略了some of the small print about jack/weakjack.h

  

详细说明

     

开发人员面临的一个挑战是利用新功能   在[JACK]的新版本中引入,同时仍然支持旧版本的   系统。通常,如果应用程序使用库/ API中的新功能,   它无法在不支持的早期版本的库/ API上运行   那个功能。当这样的应用程序无法启动或崩溃时   尝试使用该功能。这个问题可以通过使用来解决   弱联系的符号。

     

...

     

一个具体的例子会有所帮助。假设有人使用JACK版本   客户我们将致电&#34; Jill&#34;。 Jill与包含的JACK版本相关联   API的一个较新的部分(比如jack_set_latency_callback())并且想要使用   它是否可用。

     

当Jill在具有适当&#34; new&#34;的系统上运行时这个版本的JACK   功能将完全正常。但如果吉尔在一个系统上运行   使用旧版本的JACK,该功能无法使用。

     

使用正常的符号链接,这会在有人时创建启动错误   试图用#34; old&#34; JACK的版本。但是,添加了功能   版本0.116.2之后的JACK都被宣布为&#34;弱&#34;联系这意味着   在程序启动期间,它们的绝对不会导致错误。相反,吉尔   可以测试符号jack_set_latency_callback是否为null。   如果为null,则表示此机器上安装的JACK太旧了   支持这个功能。如果它不为null,那么Jill可以像其他任何一样使用它   API中的函数。例如:

if (jack_set_latency_callback) {
    jack_set_latency_callback (jill_client, jill_latency_callback, arg);
}
     

但是,有些客户可能希望将此方法用于部分    JACK API早于0.116.2。例如,他们可能想看看是否   像jack_client_open()这样的API的基本部分在运行时存在。

     

此类客户应包括&lt; jack / weakjack.h&gt;在任何其他JACK标题之前。   这将使整个JACK API受到弱连接,因此任何和   可以在运行时检查所有函数是否存在。 非常重要   了解很少有客户需要这样做 - 如果您使用此功能   应该有明确的理由这样做。

[强调补充]

这清楚地表明,为了削弱对整个JACK API的引用而采取包括jack/weakjack.h在内的特殊步骤的程序,只有在测试定义时才能成功运行。在引用它之前的每个JACK API符号,并处理未定义它的情况。你的程序不符合。这个确实:

<强> myjack1.c

#include <jack/weakjack.h>
#include <jack/jack.h>
#include <stdio.h>

int main() {
    if (jack_client_open) { 
        jack_client_open("foobar", JackNoStartServer, 0, 0);
    } else {
        puts("`jack_client_open` is not available");
    }
    return 0;
}

并做到这一点:

<强> myjack2.c

#include <jack/weakjack.h>
#include <jack/jack.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
    jack_client_t * (*jack_client_open_fp)
        (const char *, jack_options_t,jack_status_t *,...) = jack_client_open;

    if (!jack_client_open_fp) {
        void * dsoh = dlopen("libjack.so",RTLD_LAZY);
        if (!dsoh) {
            fputs("`libjack` is not available\n",stderr);
            exit(EXIT_FAILURE);
        }
        *(void**)(&jack_client_open_fp) = dlsym(dsoh,"jack_client_open");
        if (!jack_client_open_fp) {
            fputs("`jack_client_open` is not available\n",stderr);
            exit(EXIT_FAILURE);
        }
    }
    jack_client_open_fp("foobar", JackNoStartServer, 0, 0);
    exit(EXIT_SUCCESS);
}

概述了可发现API的常用方法 - apt 对于一个程序,意味着在一个系统上安装和运行 可能根本不提供libjack。因此,您无需参考libjack即可构建它 像:

gcc -o myjack2 myjack2.c -ldl

在Ubuntu 17.04上 - 提供libjack - 它可能会像:

$ ./myjack2 
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock

因此,图书馆的T&amp; Cs在 as-needed 链接方面处于良好状态。那 似乎让你处于独立不满意 as-needed 链接工作的位置 它的方式,而不是以一种允许你削弱的不同方式 您对JACK API的所有引用,并且仍然会通过您对其的弱引用使libjack成为 API符号: -

  

我不明白为什么弱依赖被认为根本没有依赖。为了我,   弱依赖是启用可选功能。我确实想要这些功能   如果可能的话,启用,并决定是否可能这样做   运行时决定。根据当前的行为,它成为编译时的决定。

您认为弱符号引用会导致对库的链接依赖关系 定义符号没有GNU链接器的基础。一个程序 如果它的链接需要libary提供的符号定义,它依赖于库;除此以外 它并不依赖于那个图书馆:没有弱和强烈的依赖程度。 (Darwin Mach-O链接器支持同源区分)

弱符号,与默认和通常类型相反, 这是强大的 {weak | strong}符号 {weakly | strong}引用的简写 符号,因为在多个链接器输入文件中可以引用相同的符号, 有时或总是弱,有时或总是强烈。

强符号必须在链接中只有一个定义引用。

弱符号是这样的:

  • 链接器没有义务为它找到定义:它可能在输出文件中保持未定义

  • 链接器没有义务对同一符号的多个弱定义进行故障 在不同的输入文件中。如果链接中只有一个定义参考 强大的,然后选择强大的定义,忽略所有弱的定义。我摔倒 定义链接中的引用很弱,然后链接器将随机选择一个。

从第一部分开始,它是一个未定义的弱引用 符号不会在所有中产生链接依赖。定义是 不需要并且不需要定义的事实是a的结果 程序员的决定(例如#include <jack/weak_jack.h>)或者也许是由... 编译器。如果指向链接,期望链接器是不合理的 只有需要的共享库,然后应链接库以提供定义 您或编制者告诉它的符号不需要定义。

如果链接器 的行为与您的情况类似, 将构成 链接时决定冻结并启用API,包括jack/weak_jack.h, 您已表明您希望完全保留用于运行时发现。

将问题计划与-no-as-needed相关联是成功的 扼杀程序中的错误。错误是包括jack/weak_jack.h 您承诺自己运行整个API的发现,但不能实现 承诺,而是将API的可用性视为理所当然。因此 使用按需链接进行段错误。与-no-as-needed链接只会取消 包含jack/weak_jack.h的效果。包括它说你的程序没有 需要任何API定义:-no-as-needed说,无论它们是什么,您都会得到 无论如何,他们都是。

鉴于版本0.116.2的所有JACK API都很弱 如果没有诉诸jack/weak_jack.h,我认为你根本就没有 除非你确实计划一个程序,否则对此标题有任何用处 将在缺少libjack的主机上做一些有价值的事情。如果你 正在计划,那么你就别无选择 您使用的JACK API,无论链接约定如何,因为您无法链接 无论如何libjack

如果没有,则只需链接libjack,如果您只是致电jack_client_open, 您的程序将在任何主机上动态链接所有API定义,无论如何 他们在那个主持人身上,因为你提到jack_client_open(在 缺少<jack/weak_jack.h>)会使libjack 需要,无论如何 对链接进行链接是否很重要。如果你想兼容 在API版本中,您需要实现运行时检测 as documented 具有属性JACK_WEAK_EXPORT的{​​{3}}的任何API   - 而不是JACK_OPTIONAL_WEAK_EXPORT, or JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT:后者表示基本的API  只能通过<jack/weak_jack.h>强行削弱。