LLVM/Clang是否支持弱链接的“weak”属性?

12

简要概述:LLVM/Clang是否支持“弱”属性?

我正在学习一些Arduino库源代码(具体来说是HardwareSerial.cpp),并发现了一些有趣的属性weak,这是我以前从未使用过的:

#if defined(HAVE_HWSERIAL0)
  void serialEvent() __attribute__((weak));
  bool Serial0_available() __attribute__((weak));
#endif

我发现这很有趣,而且我读到链接器如果没有定义应该将其设置为NULL。

然而,在我使用Clang进行测试时无法使用它。

文件lib.cpp

#include "lib.h"
#include <stdio.h>

void my_weak_func() __attribute__((weak));

void lib_func() {
    printf("lib_func()\n");

    if (my_weak_func)
        my_weak_func();
}

文件 lib.h

#ifndef LIB_FUNC
#define LIB_FUNC

void lib_func();

#endif

文件 main.cpp

#include "lib.h"
#include <stdio.h>

#ifdef DEFINE_WEAK
void my_weak_func() {
    printf("my_weak_func()\n");
}
#endif

int main() {

    lib_func();

    printf("finished\n");
    return 0;
}

如果我使用g++ lib.cpp main.cpp -o main -DDEFINE_WEAK,那么我就能够使用它:

MBA-Anton:Weak_issue asmirnov$ ./main
lib_func()
my_weak_func()
finished

但如果我使用g++ lib.cpp main.cpp -o main,我将无法链接应用程序:

Undefined symbols for architecture x86_64:
  "my_weak_func()", referenced from:
      lib_func() in lib-ceb555.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

更详细地了解Clang:

MBA-Anton:Weak_issue asmirnov$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/c++/4.2.1
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix

我该怎么办?LLVM/Clang是否支持weak属性?

PS. 我已经按照苹果的说明重新编写了lib.cpp,但仍然收到相同的链接器错误:

#include "lib.h"
#include <stdio.h>

extern void my_weak_func() __attribute__((weak_import));

void lib_func() {
    printf("lib_func()\n");

    if (my_weak_func != NULL)
        my_weak_func();
}

你正在使用哪个版本的Xcode (xcodebuild -version)? 以及哪个版本的OSX (sw_vers)? - Alec
MBA-Anton:avr-clang asmirnov$ xcodebuild -version Xcode 7.2 构建版本号7C68 - 4ntoine
2个回答

9

据我所知,苹果公司对弱链接的描述是误导性的。只有在链接时定义函数才能成功将其标记为weak/weak_import。这与Linux通常的行为相反,即弱链接的符号不必在链接时定义。

例如,以下代码在Ubuntu 14.04上使用GCC 4.8.2编译,但在Mac OS X v10.9.5(Mavericks)上使用Clang无法编译:

/* test.c */
int weakfunc() __attribute__((weak));

int main()
{
    if (weakfunc) return weakfunc();
    else        return -1;
}

我发现的最简单的解决方法是显式地告诉链接器将相关符号保留为未定义状态。例如:clang test.c -Wl,-U,_myfunc。请注意,在C和C++中,符号名称会有所不同。在C中(至少对我来说,我认为这是一致的),符号名称前面有一个下划线,如此所示。在C ++中,名称会被编码,因此您会得到类似于__Z8weakfuncv的内容(不一定一致 - 我在Ubuntu上的编码名称只有一个前导下划线)。
采用这种方法,如果函数在运行时被定义(例如通过通过设置DYLD_INSERT_LIBRARIES环境变量预加载库或者运行时共享库依赖项的版本与构建时不同),则将解析该符号并调用该函数。如果在运行时未定义符号,则检查函数失败,我们继续返回所需的-1值。
一种更加复杂的解决方案是链接到一个提供所需函数实现的虚拟库。例如,如果您将以下内容编译为libdummy.dylib并放置在同一目录中:
int weakfunc()
{
    return 1;
}

您可以使用弱链接进行链接

clang test.c -weak_library ./libdummy.dylib -flat_namespace

然后该符号在链接时定义,因此链接器很高兴,并将在生成的二进制文件中标记为弱链接。通过使用-weak_library而不是标准的-l/-L链接来链接libdummy.dylib,库依赖本身是弱的,因此即使在运行时libdummy.dylib不可用,可执行文件仍将运行。

-flat_namespace参数告诉链接器使用“平面”命名空间而不是“双层”命名空间,这似乎是OS X上的默认设置。在双层命名空间中,每个链接的符号都带有它来自的库的标记,因此如果没有这个参数,链接器只会接受来自名为libdummy.dylib的库的weakfunc版本。请注意,在将符号标记为未定义的第一种情况中,由于链接器不知道它可能在运行时所在的库,因此该符号被视为来自平面命名空间。


这让我想到Clang实际上不支持弱链接或存在漏洞。 - 4ntoine
2
我倾向于认为存在一个 bug,但我认为是在 OS X 版本的 ld 而不是 clang 中。如果你使用任一这些变通方法检查生成的二进制文件 (dyldinfo -build a.out),我相信它将显示该符号为“弱引用”,这正是你想要的。问题只是 ld 在构建时未能正确处理弱引用符号,如果它在构建时未被定义。 - Alec
1
谢谢,-Wl,-U,_myfunc 很有效!只是花了我一点时间才弄清楚如何从 Xcode 进行配置,即:将 -Wl,-U,_myfunc 添加到“其他链接器标志”。 - Elist
我认为clang对"weak"属性的处理方式不同:它允许你在一个库或模块中定义一个符号,然后在另一个库或模块中重新定义它。https://clang.llvm.org/docs/AttributeReference.html#weak - undefined

7
它因为链接器缺乏足够的信息而失败。特别地,由于两个默认的链接器设置的结合,它不能正常工作:
-two_levelnamespace -two_levelnamespace指示链接器通过名称和库安装路径绑定外部符号。使用时,链接器根据在链接时给出的一组库在哪里找到它们来将符号与库关联。如果链接器找不到该符号,则不知道它来自哪个库。
您可以使用-flat_namespace关闭两级命名空间,但通常,我认为保留它是一个好习惯。
Linux的ld.so不支持两级命名空间,所以这不是问题。每个未定义的符号都被假定为有一个定义在某个库中,在运行时发现。
-undefined error -undefined设置确定如何处理在链接时没有可见定义的符号,默认值是错误。另一个明智的选项是dynamic_lookup,告诉动态链接器自行找出符号在哪里。
更改这些设置中的任何一个都将解决您的问题,但这很笨重。您还可以告诉链接器针对特定符号使用动态查找并保持默认为error,方法是通过传递-U_my_weak_func到ld或-Wl,-U,_my_weak_func到Clang(告诉它将其转发到链接器)。必须带有下划线的符号名称前缀。
您可以制作一个tbd文件,并在动态库的位置使用它来告诉链接器,如果实现了弱符号,它将被找到。Apple为其库和框架使用tbd文件,这就是使弱链接起作用的原因。但这个过程有点繁琐,因为Apple没有提供用于自动创建库的tbd文件的工具。您需要将以下格式的文件作为库传递给编译器:
--- !tapi-tbd-v3
archs:           [ $ARCH ]
uuids:           [ '$ARCH: $UUID' ]
platform:        $ARCH
install-name:    $INSTALL_PATH
current-version: $CURRENT_VERSION
objc-constraint: none
exports:         
  - archs:           [ $ARCH ]
    symbols:         [ _my_weak_func ]
...

说明:

  • $ARCH 是您要构建的对象的架构名称(例如“x86_64”,不带引号)
  • $UUID 可以通过 otool -l $path_to_your_lib | grep -A 2 LC_UUID 查询。
  • $INSTALL_PATH 和 $CURRENT_VERSION 可以通过 otool -l $path_to_your_lib | grep -A 4 LC_ID_DYLIB 查询。

这将让链接器知道哪个库应该包含您的弱符号。


4
实际上,苹果公司提供了这样的工具。它在 Xcode 中深藏不露,像大多数工具一样:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/tapi stubify <<<你的库路径>> - zepar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接