弱链接与“--按需”

Weak-linking vs "--as-needed"

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

例子

(这使用了 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

$

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

问题

看来问题是:

--as-needed causes a DT_NEEDED tag to only be emitted for a library that at that point in the link satisfies a non-weak undefined symbol reference from a regular object file

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

但是,我不明白为什么 依赖被认为是 完全没有依赖 。对我来说,依赖是启用可选特性。如果可能的话,我确实希望启用这些功能,而这是否可能的决定应该是 runtime 的决定。对于当前的行为,它变成了编译时的决定。 (如果我想要那样,我会通过一些预处理器魔法简单地禁用相关代码)。

一个解决方案显然是将 --no-as-needed 添加到 linker 标志。 我不想要这个:我确实想摆脱 overlinking,如果我的发行版(或任何编译我的二进制文件的人)认为这是要做的事情。

所以我 可能 在 link 在我已知的弱库中打开 as-needed:

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

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

我当然也可以简单地包括<jack/weakjack.h>。包含它的原因是因为该应用程序也适用于 OSX,我确实想 选择性地依赖 JACK 框架(所以我 link 和 -weak_framework Jackmp),并在没有该框架的情况下保持我的程序可运行。

我真的不想让我的应用程序代码混乱,因为 link 用户在不同平台上的细微差别。这可能是我遇到的主要问题:为什么 I 应该向我的应用程序添加特定于平台的代码以满足不同的 linker 细节 - 我会添加特定于功能的代码可能没问题,例如如果编译器没有 -weak_library-weak_framework 的等效项,则不包括 weakjack.h;但目前看来我能得到的最接近的是 #ifdef __APPLE__ 这样的东西,这让我在这种情况下不寒而栗。

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

有这种东西吗?

I'm having trouble with using a library that contains weak-symbols and the --as-needed linker flag.

不,你不是。

找出你的 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中JACKAPI的交易品种类型:

$ 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>*)

但其中 none 个在 JACK API 中。除了重建 libjack.so 之外,您无能为力。 描述问题的正确方法是:

我在 link 使用带有 --as-needed linker 标记的程序库时遇到问题,其中 我决定削弱对该库的所有引用

libjack.so中JACKAPI的定义符号引用都是强的。你有 编写了一个程序,指示编译器在 您的 object 代码中发出以下符号 对 JACK API 的弱未定义引用,你会发现,随着 as-needed linkage, 这些弱引用无法强制 linkage of libjack.so 提供它们缺失的定义。

it seems that the problem is:

jack declares all symbols as weak (if I include ).

when linking with --as-needed, the linker excludes any library, that does not reference at least one non-weak symbol.

some OSs (e.g. Ubuntu-16.04LTS) have --as-needed enabled by default.

最后两点是正确的。 link 共享库的发行版之间的分裂 as-needed 默认情况下和不返回到 Debian Wheezy 的发行版,2013, went over to as-needed。 从那时起,Debian-derived distro-clan 紧随其后,而 RedHat/Fedora 氏族坚持原状

第一点比较糊涂。 libjack.so,正如我们所指出的,导出一个强定义的 JACK API 你不能通过编写和编译新代码来改变。 如果您在其中一个源文件中包含 <jack/weakjack.h>,那么 you 在您的代码中声明所有 JACK API 符号 weak,编译器将 给你一个 object 文件,其中只包含对 JACK API 的弱引用。 <jack/weakjack.h> 只定义具有这种效果的宏。

如果像 libjack 这样的古老而主要的 linux 图书馆,那将是令人惊讶的 拙劣地适应了 as-needed 分裂。我怀疑你忽略了 一些 关于 jack/weakjack.h:

的小字

Detailed Description

One challenge faced by developers is that of taking advantage of new features introduced in new versions of [ JACK ] while still supporting older versions of the system. Normally, if an application uses a new feature in a library/API, it is unable to run on earlier versions of the library/API that do not support that feature. Such applications would either fail to launch or crash when an attempt to use the feature was made. This problem cane be solved using weakly-linked symbols.

...

A concrete example will help. Suppose that someone uses a version of a JACK client we'll call "Jill". Jill was linked against a version of JACK that contains a newer part of the API (say, jack_set_latency_callback()) and would like to use it if it is available.

When Jill is run on a system that has a suitably "new" version of JACK, this function will be available entirely normally. But if Jill is run on a system with an old version of JACK, the function isn't available.

With normal symbol linkage, this would create a startup error whenever someone tries to run Jill with the "old" version of JACK. However, functions added to JACK after version 0.116.2 are all declared to have "weak" linkage which means that their abscence doesn't cause an error during program startup. Instead, Jill can test whether or not the symbol jack_set_latency_callback is null or not. If its null, it means that the JACK installed on this machine is too old to support this function. If its not null, then Jill can use it just like any other function in the API. For example:

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

However, there are clients that may want to use this approach to parts of the JACK API that predate 0.116.2. For example, they might want to see if even really old basic parts of the API like jack_client_open() exist at runtime.

Such clients should include <jack/weakjack.h> before any other JACK header. This will make the entire JACK API be subject to weak linkage, so that any and all functions can be checked for existence at runtime. It is important to understand that very few clients need to do this - if you use this feature you should have a clear reason to do so.

[强调]

这清楚地表明,像您这样的程序,为了削弱其对整个 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&Cs 在 as-needed linkage 方面井井有条。那 似乎让你处于独立不满意 as-needed linkage 工作的位置 它的方式,而不是以一种不同的方式让你削弱 您对 JACK API 的所有引用,并且仍然 libjack 需要 的弱引用 API 符号:-

I fail to see why a weak dependency is considered as no dependency at all. For me, a weak dependency is to enable optional features. I do want these features to be enabled if possible, and the decision whether this is possible should be a runtime decision. With the current behavior, it becomes a compile-time decision.

您认为弱符号引用会导致对库的 link年龄依赖 定义符号的 GNU linker 没有立足点。一个程序 如果其 linkage 需要库提供的符号定义,则取决于库;否则 它不依赖于那个库:没有弱和强的依赖程度。 (达尔文 Mach-O linker 确实支持同源区分)

弱符号,与默认和通常的种类相反, 即 strong{weak|strong} 符号 是 shorthand 用于 {weakly|strongly} 引用 symbol,因为同一个符号可能在多个 linker 输入文件中被引用, 有时或总是微弱,有时或总是强烈。

强符号必须在 linkage 中有一个定义引用。

弱符号是这样的:

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

  • linker 没有义务对同一符号的多个弱定义进行错误 在不同的输入文件中。如果 linkage 中恰好有一个定义引用是 强,则选择强定义并忽略所有弱定义。我摔倒 在 link 年龄段定义引用较弱,那么 link 人将随机选择一个。

从它的第一部分开始这是一个未定义的弱引用 一个符号根本不会 link年龄相关性 。一个定义是 不需要并且不需要定义的事实是 由程序员决定(例如 #include <jack/weak_jack.h>)或者可能由 编译器。如果指向 link,期望 linker 是不合理的 只有 需要 的共享库才应该 link 库来提供定义 您或编译器告诉它不需要定义的符号的数量。

如果 linker 在你的情况下表现得像那样,that 将构成 link冻结和启用 API 的时间决定,其中包括 jack/weak_jack.h, 您已表示希望完全保留 运行时间发现。

将您的问题程序与 -no-as-needed 链接成功作为一种方式 扼杀程序中的错误。错误是通过包含 jack/weak_jack.h 您承诺 运行 时间发现整个 API,但没有实现 承诺,而是将 API 的可用性视为理所当然。因此 as-needed linkage 的段错误。链接 -no-as-needed 只是取消 包含 jack/weak_jack.h 的效果。包括它说你的程序没有 需要任何API定义:-no-as-needed说,不管它们是什么,你都会得到 反正都是。

鉴于JACKAPI的所有post版本0.116.2都弱 定义而不诉诸 jack/weak_jack.h,我认为你根本不 这个 header 有任何用处,除非你确实在计划一个程序 将在缺少 libjack 的主机上做一些有价值的事情。如果你 正在计划那个,那你别无选择运行时间发现一切 你使用的 JACK API,不管 link 年龄约定如何,因为你不能 link libjack 无论如何。

如果没有,那么就 link libjack 并且,如果你只是调用 jack_client_open, 您的程序在任何主机上都会动态 link 所有 API 定义,无论 它们在该主机上,因为您对 jack_client_open 的引用(在 缺少 <jack/weak_jack.h>) 将使 libjack 需要 ,是否 对 linker 是否执行 linking 很重要。如果你想兼容 跨 API 个版本,那么你需要实现 运行 时间检测 as documented 属于 documented 且属性为 JACK_WEAK_EXPORT 的任何 API - 与 JACK_OPTIONAL_WEAK_EXPORT, or JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT 相对:后者表示基本 API 只能通过<jack/weak_jack.h>强行削弱。