【问题标题】:__attribute__((weak)) and static libraries__attribute__((weak)) 和静态库
【发布时间】:2018-08-02 15:04:30
【问题描述】:

我想在我的代码中引入一个弱符号,但是,当使用 *.a 文件时,我无法理解它的行为。

这是我的最小示例:

文件 a.h:

void foo() __attribute__((weak));

文件交流:

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

void foo() { printf("%s\n", __FILE__); }

文件 b.c:

#include <stdio.h>

void foo() { printf("%s\n", __FILE__); }

文件 main.cpp:

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

int main() { if (foo) foo(); else printf("no foo\n"); }

现在,取决于我使用 *.o 文件(make -c a.cmake -c b.c)还是 *.a 文件(ar cr a.oar cr b.o),输出会有所不同:

1) g++ main.cpp a.o b.o 打印 b.c
2) g++ main.cpp b.o a.o 打印 b.c
3) g++ main.cpp a.a b.a 打印 no foo
4) g++ main.cpp b.a a.a 打印 no foo

1), 2) 工作得很好,但 3), 4) 的输出似乎有点意外。

我拼命尝试让这个示例与档案一起使用,所以我做了一些更改:

文件 a.h:

void foo();

文件交流:

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

void __attribute__((weak)) foo() { printf("%s\n", __FILE__); }

修改后:

1) g++ main.cpp a.a b.a 打印 a.c
2) g++ main.cpp b.a a.a 打印 b.c

所以它工作得更好一点。运行nm a.a 后显示W _Z3foov,因此没有违反ODR。但是,我不知道这是否是 weak 属性的正确用法。根据 gcc 文档:

weak 属性导致 声明 作为弱符号而不是全局符号发出。这主要用于定义可以在用户代码中覆盖的库函数,尽管它也可以与非函数声明一起使用。 ELF 目标支持弱符号,使用 GNU 汇编器和链接器时也支持 a.out 目标。

然而我在函数定义而不是声明上使用 weak 属性。

所以问题是为什么 weak 不适用于 *.a 文件?在定义而不是声明上使用 weak 属性是否正确?

更新

我突然明白,与 foo() 方法定义一起使用的 weak 属性对符号解析没有影响。没有属性最终二进制生成相同的:

1) g++ main.cpp a.a b.a 打印 a.c
2) g++ main.cpp b.a a.a 打印 b.c

所以只需使用符号的第一个定义,这与默认的 gcc 行为一致。尽管nm a.a 表明发出了一个弱符号,但它似乎并不影响静态链接。

是否可以将 weak 属性与静态链接一起使用?

我想解决的问题的描述

我有一个超过 20 个客户使用的库,我们称之为库 A。我还提供了一个库 B,其中包含 A 的测试实用程序。不知何故,我需要知道库 A 用于测试模式,所以最简单的解决方案似乎是在与 B 链接期间替换符号(因为客户端已经与 B 链接)。

我知道这个问题有更简洁的解决方案,但是我绝对不能影响客户的代码或其构建脚本(添加表明测试 A 或某些 DEFINE 以进行编译的参数是不可行的)。

【问题讨论】:

  • 我嗅到了 XY 问题。你为什么要这样做?
  • 我想在与测试框架链接时覆盖原始函数实现。以前我使用全局变量处理过类似的问题,但是,由于全局初始化的未定义顺序,这是一个脆弱的解决方案。
  • 为此使用 objcopy。从框架中替换或删除原始函数实现或使用 objcopy 将原始函数设置为弱。您不需要重新编译库,从而影响原始框架的编译过程。前任。 objcopy -N foo a.o
  • 你是如何让它输出no foo的?我无法重现它。如果缺少 foo 符号,链接器应该发出错误,foo 符号不能等于 false,除非您在某处定义 void (*foo)() = 0;。您是否使用带有 C 符号的 C++ 编译器?如果您使用 make -c ,它通常运行 C 编译器 cc,它会创建 foo 符号,而不是 C++ _Z3foov 符号。
  • 生成no foo 输出我使用了以下命令:g++ -c a.c ar cr a.a a.o g++ -c b.c ar cr b.a b.o g++ main.cpp a.a b.a 关于void (*foo)() = 0; 正如 Mike Kinghan 已经提到的那样,一个弱符号可能未定义,则其值假定为 0。我一直使用 g++。在写这篇文章时,我错误地混合了 c 和 cpp 扩展。这在我的原始项目中不是问题。

标签: c++ g++


【解决方案1】:

为了解释这里发生了什么,让我们先谈谈你的原始源文件,

a.h (1)

void foo() __attribute__((weak));

和:

交流 (1)

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

void foo() { printf("%s\n", __FILE__); }

示例代码中.c.cpp 文件的混合与 问题,所有代码都是 C,所以我们会说 main.cppmain.c 和 使用gcc进行所有编译和链接:

$ gcc -Wall -c main.c a.c b.c
ar rcs a.a a.o
ar rcs b.a b.o

首先让我们回顾一下弱声明符号之间的区别,例如 你的:

void foo() __attribute__((weak));

和一个强烈声明的符号,比如

void foo();

这是默认的:

  • 如果在程序中链接了对 foo 的弱引用(即对弱声明的 foo 的引用),则 链接器不需要在链接中的任何地方找到foo 的定义:它可能仍然存在 不明确的。如果在程序中链接了对foo 的强引用, 链接器需要找到foo 的定义。

  • 链接最多可以包含一个强定义 foo(即定义 foo 强烈声明)。否则会导致多重定义错误。 但它可能包含foo 的多个弱定义而不会出错。

  • 如果链接包含foo 的一个或多个弱定义,并且还包含一个强 定义,则链接器选择强定义而忽略弱定义 那些。

  • 如果链接只包含foo 的一个弱定义并且没有强 定义,链接器不可避免地使用一个弱定义。

  • 如果链接包含foo 的多个弱定义并且没有强 定义,然后链接器选择一个弱定义任意

接下来,让我们回顾一下在链接中输入目标文件的区别 并输入一个静态库。

静态库只是我们可能提供给目标文件的ar 存档 从中选择需要进行链接的链接器。

当一个目标文件被输入到一个链接时,链接器无条件地链接它 进入输出文件。

当静态库输入到链接时,链接器会检查存档以 在其中找到为未解析的符号引用提供定义它需要的任何目标文件 从已经链接的输入文件中产生的。如果它找到任何这样的目标文件 在存档中,它提取它们并将它们链接到输出文件中,就像 如果它们是单独命名的输入文件并且根本没有提到静态库。

考虑到这些观察结果,考虑编译和链接命令:

gcc main.c a.o b.o

幕后gcc 将其分解为编译步骤和链接 步骤,就像你已经运行一样:

gcc -c main.c     # compile
gcc main.o a.o b.o  # link

所有三个目标文件都无条件地链接到(默认)程序./a.outa.o 包含一个 foo 的弱定义,我们可以看到:

$ nm --defined a.o
0000000000000000 W foo

b.o 包含一个强定义:

$ nm --defined b.o
0000000000000000 T foo

链接器将找到这两个定义并从b.o 中选择强定义,因为我们可以 另见:

$ gcc main.o a.o b.o -Wl,-trace-symbol=foo
main.o: reference to foo
a.o: definition of foo
b.o: definition of foo
$ ./a.out
b.c

颠倒a.ob.o的链接顺序不会有任何区别:有 仍然是foo 的一个强定义,b.o 中的一个。

相比之下,考虑编译和链接命令:

gcc main.cpp a.a b.a

分解为:

gcc -c main.cpp     # compile
gcc main.o a.a b.a  # link                   

这里,只有main.o 是无条件链接的。这会产生一个未定义的弱引用 到foo进入链接:

$ nm --undefined main.o
                 w foo
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

foo 的弱引用不需要定义。所以链接器将 不要尝试在a.ab.a 的任何目标文件中找到解析它的定义,并且 如我们所见,它将在程序中未定义:

$ gcc main.o a.a b.a -Wl,-trace-symbol=foo
main.o: reference to foo
$ nm --undefined a.out
                 w __cxa_finalize@@GLIBC_2.2.5
                 w foo
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@@GLIBC_2.2.5
                 U puts@@GLIBC_2.2.5

因此:

$ ./a.out
no foo

同样,如果你颠倒a.ab.a的链接顺序也没关系, 但这次是因为它们都没有对链接做出任何贡献。

现在让我们转向您通过更改 a.ha.c 发现的不同行为 到:

a.h (2)

void foo();

a.c (2)

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

void __attribute__((weak)) foo() { printf("%s\n", __FILE__); }

再一次:

$ gcc -Wall -c main.c a.c b.c
main.c: In function ‘main’:
main.c:4:18: warning: the address of ‘foo’ will always evaluate as ‘true’ [-Waddress]
 int main() { if (foo) foo(); else printf("no foo\n"); }

看到那个警告了吗? main.o 现在包含一个强烈 声明的对foo 的引用:

$ nm --undefined main.o
                 U foo
                 U _GLOBAL_OFFSET_TABLE_

所以代码(当链接时)必须有foo 的非空地址。进行中:

$ ar rcs a.a a.o
$ ar rcs b.a b.o

那就试试联动吧:

$ gcc main.o a.o b.o
$ ./a.out
b.c

并且目标文件颠倒:

$ gcc main.o b.o a.o
$ ./a.out
b.c

和以前一样,顺序没有区别。所有的目标文件都是链接的。 b.o 提供 fooa.o 的强定义提供了弱定义,因此 b.o 获胜。

接下来尝试联动:

$ gcc main.o a.a b.a
$ ./a.out
a.c

并且库的顺序颠倒了:

$ gcc main.o b.a a.a
$ ./a.out
b.c

确实有所作为。为什么?让我们重做与诊断的联系:

$ gcc main.o a.a b.a -Wl,-trace,-trace-symbol=foo
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main.o
(a.a)a.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
main.o: reference to foo
a.a(a.o): definition of foo

忽略默认库,ours 的唯一目标文件 链接是:

main.o
(a.a)a.o

foo的定义取自a.a的归档成员a.o

a.a(a.o): definition of foo

反转库顺序:

$ gcc main.o b.a a.a -Wl,-trace,-trace-symbol=foo
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main.o
(b.a)b.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
main.o: reference to foo
b.a(b.o): definition of foo

这次链接的目标文件是:

main.o
(b.a)b.o

foo的定义取自b.a中的b.o

b.a(b.o): definition of foo

在第一个链接中,链接器有一个未解决的强引用 foo 在到达 a.a 时需要对其进行定义。所以 在档案中查找提供定义的目标文件, 并找到a.o。这个定义很弱,但这并不重要。不 已经看到了强烈的定义。 a.o 是从 a.a 中提取并链接的, 因此解决了对foo 的引用。接下来到达b.a,其中 foo 的强定义 已在 b.o 中找到,如果链接器仍需要一个 并寻找它。但它不再需要一个,也不看。联动:

gcc main.o a.a b.a

完全一样如下:

gcc main.o a.o

还有链接:

$ gcc main.o b.a a.a

完全一样:

$ gcc main.o b.o

你真正的问题...

...出现在您的帖子中的一个 cmets 中:

我想在与测试框架链接时覆盖 [the] 原始函数实现。

你想链接一个程序输入一些静态库lib1.a 其中有一些成员file1.o 定义了一个符号foo,并且你想淘汰 foo 的定义并链接在其他对象中定义的另一个 文件file2.o

__attribute__((weak)) 不适用于该问题。解决方案更多 初级。 您只需确保在输入之前输入 file2.o 到链接 lib1.a(在任何其他提供foo 定义的输入之前)。 然后链接器将使用file2.o 中提供的定义解析对foo 的引用,并且不会尝试找到任何其他 到达lib1.a 时的定义。链接器根本不会消耗lib1.a(file1.o)。它也可能不存在。

如果您将file2.o 放入另一个静态库lib2.a 会怎样?然后输入 lib2.a before lib1.a 将完成链接lib2.a(file2.o) before 达到lib1.a 并将foo 解析为file2.o 中的定义。

同样,当然,lib2.a 成员提供的每个定义都将链接到 优先于lib1.a 中提供的相同符号的定义。如果那不是什么 你想要,然后不喜欢lib2.a:链接file2.o 本身。

终于

是否可以将 [the] 弱属性与静态链接一起使用?

当然。这是一个第一原则用例:

foo.h (1)

#ifndef FOO_H
#define FOO_H

int __attribute__((weak)) foo(int i)
{
    return i != 0;
}

#endif

aa.c

#include "foo.h"

int a(void)
{
    return foo(0);
}

bb.c

#include "foo.h"

int b(void)
{
    return foo(42);
}

prog.c

#include <stdio.h>

extern int a(void);
extern int b(void);

int main(void)
{
    puts(a() ? "true" : "false");
    puts(b() ? "true" : "false");
    return 0;
}

编译所有源文件,为每个函数请求单独的 ELF 部分:

$ gcc -Wall -ffunction-sections -c prog.c aa.c bb.c

注意fooweak 定义是通过foo.h 编译成both aa.obb.o,我们可以看到:

$ nm --defined aa.o
0000000000000000 T a
0000000000000000 W foo
$ nm --defined bb.o
0000000000000000 T b
0000000000000000 W foo

现在从所有目标文件链接一个程序,请求链接器 丢弃未使用的部分(并给我们地图文件和一些诊断):

$ gcc prog.o aa.o bb.o -Wl,--gc-sections,-Map=mapfile,-trace,-trace-symbol=foo
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
prog.o
aa.o
bb.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
aa.o: definition of foo

此链接与:

$ ar rcs libaabb.a aa.o bb.o
$ gcc prog.o libaabb.a

尽管 aa.obb.o 都已加载,并且每个都包含 foo的一个定义,没有多重定义的错误结果,因为每个定义 aa.obb.o 之前加载,foo 的定义是从 aa.o 链接的。

那么bb.ofoo 的定义发生了什么变化?地图文件向我们展示了:

地图文件 (1)

...
...
Discarded input sections
...
...
 .text.foo      0x0000000000000000       0x13 bb.o
...
...

链接器丢弃了包含定义的函数部分 在bb.o

让我们颠倒aa.obb.o的链接顺序:

$ gcc prog.o bb.o aa.o -Wl,--gc-sections,-Map=mapfile,-trace,-trace-symbol=foo
...
prog.o
bb.o
aa.o
...
bb.o: definition of foo

现在相反的事情发生了。 bb.oaa.o 之前加载。这 foo 的定义是从 bb.o 链接的,并且:

地图文件 (2)

...
...
Discarded input sections
...
...
 .text.foo      0x0000000000000000       0x13 aa.o
...
...

aa.o 的定义被丢弃了。

你会看到链接器如何任意选择多个中的一个 在没有强定义的情况下,符号的弱定义。它简直 选择你给它的第一个并忽略其余的。

我们刚才所做的实际上就是 GCC C++ 编译器为我们所做的,当我们 定义一个全局内联函数。重写:

foo.h (2)

#ifndef FOO_H
#define FOO_H

inline int foo(int i)
{
    return i != 0;
}

#endif

重命名我们的源文件*.c -> *.cpp;编译链接:

$ g++ -Wall -c prog.cpp aa.cpp bb.cpp

现在在aa.obb.o 中都有foo 的弱定义(C++ 损坏):

$ nm --defined aa.o bb.o

aa.o:
0000000000000000 T _Z1av
0000000000000000 W _Z3fooi

bb.o:
0000000000000000 T _Z1bv
0000000000000000 W _Z3fooi

链接使用它找到的第一个定义:

$ g++ prog.o aa.o bb.o -Wl,-Map=mapfile,-trace,-trace-symbol=_Z3fooi
...
prog.o
aa.o
bb.o
...
aa.o: definition of _Z3fooi
bb.o: reference to _Z3fooi

然后扔掉另一个:

地图文件 (3)

...
...
Discarded input sections
...
...
 .text._Z3fooi  0x0000000000000000       0x13 bb.o
...
...

您可能知道,C++ 函数模板的每个实例化 全局范围(或​​类模板成员函数的实例化)是 内联全局函数。再次重写:

#ifndef FOO_H
#define FOO_H

template<typename T>
T foo(T i)
{
    return i != 0;
}

#endif

重新编译:

$ g++ -Wall -c prog.cpp aa.cpp bb.cpp

再次:

$ nm --defined aa.o bb.o

aa.o:
0000000000000000 T _Z1av
0000000000000000 W _Z3fooIiET_S0_

bb.o:
0000000000000000 T _Z1bv
0000000000000000 W _Z3fooIiET_S0_

aa.obb.o 中的每一个都有以下弱定义:

$ c++filt _Z3fooIiET_S0_
int foo<int>(int)

并且链接行为现在很熟悉了。一种方式:

$ g++ prog.o aa.o bb.o -Wl,-Map=mapfile,-trace,-trace-symbol=_Z3fooIiET_S0_
...
prog.o
aa.o
bb.o
...
aa.o: definition of _Z3fooIiET_S0_
bb.o: reference to _Z3fooIiET_S0_

反之:

$ g++ prog.o bb.o aa.o -Wl,-Map=mapfile,-trace,-trace-symbol=_Z3fooIiET_S0_
...
prog.o
bb.o
aa.o
...
bb.o: definition of _Z3fooIiET_S0_
aa.o: reference to _Z3fooIiET_S0_

重写后我们的程序的行为没有改变:

$ ./a.out
false
true

所以weak属性在ELF对象链接中的符号应用—— 无论是静态的还是动态的 - 启用 C++ 模板的 GCC 实现 对于 GNU 链接器。你可以说它支持现代 C++ 的 GCC 实现。

【讨论】:

  • 赞成,但是我不喜欢关于它启用现代 C++ 的 GCC 实现的最后一句话。它只允许内联函数进行多重定义,内联函数存在于 C 中并具有这种行为。只有模板(默认为内联)是 C++ 特定的。但随后他们通过内联获得这种行为,并且只能通过弱链接间接获得。
【解决方案2】:

我发现here是最好的解释:

如果在搜索所有输入对象后无法解析引用,链接器将仅搜索库以解析引用。如果需要,将根据它们在链接器命令行上的位置从左到右搜索这些库。库中的对象将按它们的归档顺序进行搜索。一旦 armlink 找到与引用匹配的符号,搜索就完成了,即使它与弱定义匹配。
ELF ABI 第 4.6.1.2 节说:
“弱定义不会改变从库中选择目标文件的规则。但是,如果链接集同时包含弱定义和非弱定义,则将始终使用非弱定义。”
“链接集”是链接器已加载的对象集。它不包括库中不需要的对象。
因此,不建议将其中一个包含给定符号的弱定义而另一个包含该符号的非弱定义的两个对象归档到一个库或单独的库中。

请注意以下事项。基本上改名为mv a.c definition.cmv b.c noweak.cmv second_a.c declaration.c

> for i in Makefile *.c; do echo "cat $i <<EOF"; cat $i; echo EOF; done
cat Makefile <<EOF
tgt=
tgt+=only_weak_1.out only_weak_2.out
tgt+=definition.out declaration.out noweak.out
tgt+=definition_static.out declaration_static.out noweak_static.out
tgt+=1.out 2.out 3.out 4.out
tgt+=5.out 6.out 7.out 8.out
tgt+=10.out 11.out 12.out
tgt+=13.out
tgt+=14.out

only_weak_1_obj= definition.o declaration.o
only_weak_2_obj= declaration.o definition.o
definition_obj= definition.o
declaration_obj= declaration.o
noweak_obj= noweak.o
definition_static_obj= definition.a
declaration_static_obj= declaration.a
noweak_static_obj= noweak.a
1_obj= declaration.o noweak.o
2_obj= noweak.o declaration.o
3_obj= declaration.a noweak.a
4_obj= noweak.a declaration.a
5_obj= definition.o noweak.o
6_obj= noweak.o definition.o
7_obj= definition.a noweak.a
8_obj= noweak.a definition.a
10_obj= noweak.a definition.a declaration.a
11_obj= definition.a declaration.a noweak.a
12_obj= declaration.a definition.a noweak.a
13_obj= all.a
14_obj= all.o


.PRECIOUS: % %.o %.c %.a
def: run
.PHONY: run
run: $(tgt)
    { $(foreach a,$^,echo "$($(a:.out=)_obj)#->#$(a)#:#$$(./$(a))";) } | { echo; column -t -s'#' -N 'objects, ,executable, ,output' -o' '; echo; }
.SECONDEXPANSION:
%.out: main.o $$(%_obj) 
    $(CC) -o $@ $^
%.o: %.c
    $(CC) -c -o $@ $^
%.a: %.o
    ar cr $@ $^
all.a: declaration.o definition.o noweak.o
    ar cr $@ $^
all.o: declaration.o definition.o noweak.o
    $(LD) -i -o $@ $^
clean:
    rm -fv *.o *.a *.out
EOF

cat declaration.c <<EOF
#include <stdio.h>
__attribute__((__weak__)) void foo();
void foo() { printf("%s\n", __FILE__); }
EOF
cat definition.c <<EOF
#include <stdio.h>
__attribute__((__weak__)) void foo() { printf("%s\n", __FILE__); }
EOF
cat main.c <<EOF
#include <stdio.h>
void foo();
int main() {
    if (foo) foo(); else printf("no foo\n");
    return 0;
}
EOF
cat noweak.c <<EOF
#include <stdio.h>
void foo() { printf("%s\n", __FILE__); }
EOF

> make
cc -c -o definition.o definition.c
cc -c -o declaration.o declaration.c
cc -c -o main.o main.c
cc -o only_weak_1.out main.o definition.o declaration.o
cc -o only_weak_2.out main.o declaration.o definition.o
cc -o definition.out main.o definition.o
cc -o declaration.out main.o declaration.o
cc -c -o noweak.o noweak.c
cc -o noweak.out main.o noweak.o
ar cr definition.a definition.o
cc -o definition_static.out main.o definition.a
ar cr declaration.a declaration.o
cc -o declaration_static.out main.o declaration.a
ar cr noweak.a noweak.o
cc -o noweak_static.out main.o noweak.a
cc -o 1.out main.o declaration.o noweak.o
cc -o 2.out main.o noweak.o declaration.o
cc -o 3.out main.o declaration.a noweak.a
cc -o 4.out main.o noweak.a declaration.a
cc -o 5.out main.o definition.o noweak.o
cc -o 6.out main.o noweak.o definition.o
cc -o 7.out main.o definition.a noweak.a
cc -o 8.out main.o noweak.a definition.a
cc -o 10.out main.o noweak.a definition.a declaration.a
cc -o 11.out main.o definition.a declaration.a noweak.a
cc -o 12.out main.o declaration.a definition.a noweak.a
ar cr all.a declaration.o definition.o noweak.o
cc -o 13.out main.o all.a
ld -i -o all.o declaration.o definition.o noweak.o
cc -o 14.out main.o all.o
{ echo "definition.o declaration.o#->#only_weak_1.out#:#$(./only_weak_1.out)"; echo "declaration.o definition.o#->#only_weak_2.out#:#$(./only_weak_2.out)"; echo "definition.o#->#definition.out#:#$(./definition.out)"; echo "declaration.o#->#declaration.out#:#$(./declaration.out)"; echo "noweak.o#->#noweak.out#:#$(./noweak.out)"; echo "definition.a#->#definition_static.out#:#$(./definition_static.out)"; echo "declaration.a#->#declaration_static.out#:#$(./declaration_static.out)"; echo "noweak.a#->#noweak_static.out#:#$(./noweak_static.out)"; echo "declaration.o noweak.o#->#1.out#:#$(./1.out)"; echo "noweak.o declaration.o#->#2.out#:#$(./2.out)"; echo "declaration.a noweak.a#->#3.out#:#$(./3.out)"; echo "noweak.a declaration.a#->#4.out#:#$(./4.out)"; echo "definition.o noweak.o#->#5.out#:#$(./5.out)"; echo "noweak.o definition.o#->#6.out#:#$(./6.out)"; echo "definition.a noweak.a#->#7.out#:#$(./7.out)"; echo "noweak.a definition.a#->#8.out#:#$(./8.out)"; echo "noweak.a definition.a declaration.a#->#10.out#:#$(./10.out)"; echo "definition.a declaration.a noweak.a#->#11.out#:#$(./11.out)"; echo "declaration.a definition.a noweak.a#->#12.out#:#$(./12.out)"; echo "all.a#->#13.out#:#$(./13.out)"; echo "all.o#->#14.out#:#$(./14.out)"; } | { echo; column -t -s'#' -N 'objects, ,executable, ,output' -o' '; echo; }

objects                                executable               output
definition.o declaration.o          -> only_weak_1.out        : definition.c
declaration.o definition.o          -> only_weak_2.out        : declaration.c
definition.o                        -> definition.out         : definition.c
declaration.o                       -> declaration.out        : declaration.c
noweak.o                            -> noweak.out             : noweak.c
definition.a                        -> definition_static.out  : definition.c
declaration.a                       -> declaration_static.out : declaration.c
noweak.a                            -> noweak_static.out      : noweak.c
declaration.o noweak.o              -> 1.out                  : noweak.c
noweak.o declaration.o              -> 2.out                  : noweak.c
declaration.a noweak.a              -> 3.out                  : declaration.c
noweak.a declaration.a              -> 4.out                  : noweak.c
definition.o noweak.o               -> 5.out                  : noweak.c
noweak.o definition.o               -> 6.out                  : noweak.c
definition.a noweak.a               -> 7.out                  : definition.c
noweak.a definition.a               -> 8.out                  : noweak.c
noweak.a definition.a declaration.a -> 10.out                 : noweak.c
definition.a declaration.a noweak.a -> 11.out                 : definition.c
declaration.a definition.a noweak.a -> 12.out                 : declaration.c
all.a                               -> 13.out                 : declaration.c
all.o                               -> 14.out                 : noweak.c

如果只使用弱符号(case only_weak_1 和 only_weak_2),则使用第一个定义。
如果只有静态库(情况 3、4、7、8、10、11、12、13),则使用第一个定义。
如果仅使用目标文件(情况 1、2、5、6、14),则忽略弱符号,仅使用来自 noweak 的符号。
从我提供的链接:

有多种方法可以保证 armlink 选择给定符号的非弱版本:
- 不要归档此类对象
- 存档前确保弱符号和非弱符号包含在同一对象中
- 使用部分链接作为替代方案。

【讨论】:

  • 感谢您的详细回复。所以现在很清楚我不能在静态库中使用弱符号。不幸的是,在不影响客户构建系统的情况下,我似乎无法达到预期的效果。
  • 您可以使用 objcopy 删除该符号,然后与您的 foo 实现链接。使用我的代码:objcopy -Nfoo noweak.a; gcc -o main.out noweak.a declaration.a main.c 将从 noweak.a 中删除 not weak 符号,然后始终使用 declaration.a 中的 foo 符号。
  • 感谢您的精彩回复 :-)
  • 感谢您的出色回答。但 ARM 链接已损坏。
  • 原始回复中的链接已损坏,我找不到新链接。有人找到了吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-18
  • 2019-03-11
  • 2014-10-26
  • 2019-08-09
相关资源
最近更新 更多