为什么在同一个 .a 文件中定义的弱符号在不同的 .o 文件中不用作回退?

Why the weak symbol defined in the same .a file but different .o file is not used as fall back?

我有以下树:

.
├── func1.c
├── func2.c
├── main.c
├── Makefile
├── override.c
└── weak.h

func1.c

#include <stdio.h>

void func2(void);

void func1 (void)
{
    func2();
}

func2.c

#include <stdio.h>

void func2 (void)
{
    printf("in original func2()\n");
}

main.c

#include <stdio.h>

void func1();

void func2();

void main()
{
    func1();
}

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in override func2()\n");
}

weak.h

__attribute__((weak))
void func2 (void); // <==== weak attribute on declaration

生成文件

ALL:
    rm -f *.a *.o
    gcc -c override.c -o override.o
    gcc -c func1.c -o func1.o -include weak.h # weak.h is used to tell func1.c that func2() is weak
    gcc -c func2.c -o func2.o
    ar cr all_weak.a func1.o func2.o
    gcc main.c all_weak.a override.o -o main

所有这些都运行良好,如下所示:

in override func2()

但是如果我从 override.c 中删除 func2() 的覆盖版本,如下所示:

#include <stdio.h>

// void func2 (void)
// {
//     printf("in override func2()\n");
// }

构建通过但最终二进制文件在运行时出现以下错误:

Segmentation fault (core dumped)

并且在./main的符号table中,func2()是一个未解析的弱符号。

000000000000065b T func1
                 w func2 <=== func2 is a weak symbol with no default implementation

为什么没有回退到原来func2.c中的func2() 毕竟 all_weak.a 已经在 func2.o:

中包含一个实现
func1.o:
0000000000000000 T func1
                 w func2 <=== func2 is [w]eak with no implementation
                 U _GLOBAL_OFFSET_TABLE_

func2.o:
0000000000000000 T func2   <=========== HERE! a strong symbol!
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

加 1

似乎翻译单元的安排也影响到回退到weak功能。

如果我将 func2() 实现放入与 func1() 相同的 file/translation 单元 中,则返回到原始 func2() 可以工作。

func1.c

#include <stdio.h>

void func2 (void)
{
    printf("in original func2()\n");
}

void func1 (void)
{
    func2();
}

all_weak.a的符号是:

func1.o:
0000000000000013 T func1
0000000000000000 W func2 <==== func2 is still [W]eak but has default imeplementation
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

如果不提供覆盖,代码可以正确回退到原始 func2()

这个link也提到要与GCC alias属性一起工作,还必须考虑翻译单元的安排。

alias (“target”) The alias attribute causes the declaration to be emitted as an alias for another symbol, which must be specified. For instance,

void __f () { /* Do something. */; } void f () attribute ((weak, alias ("__f"))); defines f to be a weak alias for __f. In C++, the mangled name for the target must be used. It is an error if __f is not defined in the same translation unit.

根据wikipedia

The nm command identifies weak symbols in object files, libraries, and executables. On Linux a weak function symbol is marked with "W" if a weak default definition is available, and with "w" if it is not.

添加 2 - 7:54 2021 年 8 月 7 日下午

(非常感谢 @n.1.8e9-where's-my-share m.

我试过这些:

现在这些文件看起来像这样:

func2.c

#include <stdio.h>

__attribute__((weak))
void func2 (void)
{
    printf("in original func2()\n");
}

生成文件:

ALL:
    rm -f *.a *.o
    gcc -c override.c -o override.o
    gcc -c func1.c -o func1.o
    gcc -c func2.c -o func2.o
    ar cr all_weak.a func1.o func2.o
    gcc main.c all_weak.a -o main_original   # <=== no override.o
    gcc main.c all_weak.a override.o -o main_override # <=== override.o

输出是这样的:

xxx@xxx-host:~/weak_fallback$ ./main_original 
in original func2() <===== successful fall back

xxx@xxx-host:~/weak_fallback$ ./main_override
in override func2() <===== successful override

所以,结论是:

还有一些来自的引述:

The linker will only search through libraries to resolve a reference if it cannot resolve that reference after searching all input objects. If required, the libraries are searched from left to right according to their position on the linker command line. Objects within the library will be searched by the order in which they were archived. As soon as armlink finds a symbol match for the reference, the searching is finished, even if it matches a weak definition. The ELF ABI section 4.6.1.2 says: "A weak definition does not change the rules by which object files are selected from libraries. However, if a link set contains both a weak definition and a non-weak definition, the non-weak definition will always be used." The "link set" is the set of objects that have been loaded by the linker. It does not include objects from libraries that are not required. Therefore archiving two objects where one contains the weak definition of a given symbol and the other contains the non-weak definition of that symbol, into a library or separate libraries, is not recommended.

添加 3 - 8:47 2021 年 8 月 8 日上午

正如@n.1.8e9-where's-my-sharem 评论的那样:

评论 1:

"weak" on a symbol which is not a definition means "do not resolve this symbol at link time". The linker happily obeys.

评论 2:

"on a symbol which is not a definition" is wrong, should read "on an undefined symbol".

我认为“在未定义的符号上”,他的意思是“当前翻译单元内的未定义符号”。就我而言,当我:

这些基本上告诉linker 不要解析翻译单元func1.c[消耗的func2() =207=]。但似乎这个“”只适用于.a文件。如果我 link 除了 .a 文件之外还有一个 .o 文件,linker 仍然愿意解析 func2()。或者如果func2()也在func1.c中定义,linker也会解析它。很微妙!

(目前所有这些结论都是基于我的实验结果,总结起来很微妙,如果谁能找到权威的出处,欢迎评论或回复,谢谢!)

(感谢 n.1.8e9-where's-my-share m. 的评论。)

和一个相关线程:

Override a function call in C

一些事后的想法 - 9:55 2021 年 8 月 8 日下午

这些微妙的行为背后没有火箭科学。这仅取决于 linker 的实现方式。有时文档含糊不清。你必须尝试并处理它。 (如有大佬指正,不胜感激。)

these subtle behaviors

这里真的没有什么微妙之处。

  1. 弱定义意味着:使用这个符号除非另一个强定义也存在,在这种情况下使用另一个符号。

    通常两个同名符号会导致多重定义的 link 错误,但是当除一个定义之外的所有定义都很弱时,不会产生多重定义的错误。

  2. A weak (unresolved) reference means: 在决定是否将定义此符号的对象从存档库中拉出时不考虑 this 符号或不是(如果对象满足不同的强未定义符号,它仍可能被拉入)。

    通常如果在所有对象都selected 后符号仍未解析,linker 将报告未解析符号错误。但如果未解析的符号是弱的,则错误被抑制。

这真的是全部

更新:

您在评论中重复不正确理解。

What makes me feel subtle is, for a weak reference, the linker doesn't pull an object from an archive library, but still check a standalone object file.

这与上面的答案完全一致。 linker 在处理归档库时,必须做出决定:select 是否将 foo.o 包含到 link 中。 那个决定受引用类型的影响。

bar.o 作为“独立目标文件”在 link 行中给出时,linker 不做任何决定 -- bar.o select编入link。

And if that object happens to contain a definition for the weak reference, will the weak reference be also resolved by the way?

是的。

Even the weak attribute tells the linker not to.

这是误解的明显根源:弱属性不会告诉linker 不要解析引用;它只告诉 linker(请原谅重复)“在决定是否将定义此符号的对象从存档库中拉出时不要考虑 this 符号”。

I think it's all about whether or not an object containing a definition for that weak reference is pulled in for linking.

正确。

Be it a standalone object or from an archive lib.

错误:独立对象总是 select编辑到link。