为什么在同一个 .a 文件但不同的 .o 文件中定义的弱符号不用作后备? [英] Why the weak symbol defined in the same .a file but different .o file is not used as fall back?

查看:43
本文介绍了为什么在同一个 .a 文件但不同的 .o 文件中定义的弱符号不用作后备?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有下面的树:

<预><代码>.├── func1.c├── func2.c├── main.c├── 生成文件├── override.c└── 弱.h

  • main.c 调用 func1().
  • func1() 调用 func2().
  • weak.h 将 func2() 声明为弱.
  • override.c 提供了 func2() 的覆盖版本.

func1.c

#include void func2(void);void func1 (void){func2();}

func2.c

#include void func2 (void){printf("在原来的 func2()\n");}

ma​​in.c

#include 无效功能1();void func2();无效主(){func1();}

override.c

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

weak.h

__attribute__((弱))void func2 (void);//<==== 声明时的弱属性

Makefile

所有:rm -f *.a *.ogcc -c override.c -o override.ogcc -c func1.c -o func1.o -include weak.h #weak.h 用于告诉 func1.c func2() 是弱的gcc -c func2.c -o func2.oar cr all_weak.a func1.o func2.ogcc main.c all_weak.a override.o -o main

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

 覆盖 func2()

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

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

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

分段错误(核心转储)

而在./main的符号表中,func2()是一个未解析的弱符号.

000000000000065b T func1w func2 <=== func2 是一个没有默认实现的弱符号

为什么不回退到原来的func2.c中的func2()?毕竟 all_weak.a 已经包含在 func2.o 中的实现:

func1.o:0000000000000000 T func1w func2 <=== func2 是 [w]eak 没有实现你_GLOBAL_OFFSET_TABLE_func2.o:0000000000000000 T func2 <=========== 这里!强大的象征!你_GLOBAL_OFFSET_TABLE_看跌期权

加 1

看来翻译单元的安排也会影响回退到weak功能.

如果我将 func2() 实现放入与 func1() 相同的文件/翻译单元,如下所示,回退到原来的 func2() 可以工作.

func1.c

#include void func2 (void){printf("在原来的 func2()\n");}void func1 (void){func2();}

all_weak.a 的符号为:

func1.o:0000000000000013 T func10000000000000000 W func2 <==== func2 仍然是 [W]eak 但有默认实现你_GLOBAL_OFFSET_TABLE_看跌期权

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

这个 link 还提到了工作使用 GCC alias 属性,还必须考虑翻译单元的排列.

<块引用>

alias (target") alias 属性导致声明为作为另一个符号的别名发出,必须指定.为了例如,

void __f () {/* 做某事.*/;} void f() 属性((弱,别名(__f")));将 f 定义为 __f 的弱别名.在 C++ 中,必须使用目标的重整名称.如果 __f 不是,这是一个错误在同一个翻译单元中定义.

根据维基百科:

<块引用>

nm 命令识别目标文件、库和可执行文件.在 Linux 上,弱函数符号标有W";如果一个弱默认定义可用,并带有w";如果不是.

添加 2 - 2021 年 8 月 7 日晚上 7:54

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

我试过这些:

  • __attribute__((weak)) 添加到 func2.c 中的 func2() 定义.

  • 从 Makefile 中删除 -include weak.h.

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

func2.c

#include __attribute__((弱))void func2 (void){printf("在原来的 func2()\n");}

Makefile:

所有:rm -f *.a *.ogcc -c override.c -o override.ogcc -c func1.c -o func1.ogcc -c func2.c -o func2.oar cr all_weak.a func1.o func2.ogcc main.c all_weak.a -o main_original # <=== no override.ogcc main.c all_weak.a override.o -o main_override # <=== override.o

输出是这样的:

xxx@xxx-host:~/weak_fallback$ ./main_original在原始 func2() <====== 成功回退xxx@xxx-host:~/weak_fallback$ ./main_override在覆盖 func2() <====== 成功覆盖

所以,结论是:

  • 如果弱函数声明(就像我在weak.h中所做的那样),它本质上是告诉链接器不要解析它.

  • 如果弱函数定义(就像我在func2.c中所做的那样),它本质上是告诉链接器在没有强函数的情况下将其用作后备找到版本.

  • 如果弱函数声明,你最好在.o文件中向链接器提供一个覆盖版本(就像我在中所做的那样>override.o).在这种情况下,链接器似乎仍然愿意解析 .o 文件.当您无法修改源但仍想覆盖某些功能时,就会出现这种情况.

以及来自此处的一些引文:

<块引用>

链接器只会搜索来解析引用如果在搜索所有输入对象之后无法解析该引用.如果需要,根据库从左到右搜索到它们在链接器命令行上的位置.内的对象图书馆将按存档顺序进行搜索.作为一旦 armlink 找到与引用匹配的符号,搜索完成,即使它匹配弱定义.ELF ABI 部分4.6.1.2 说:弱定义不会改变从库中选择目标文件的规则.但是,如果链接集包含弱定义和非弱定义,将始终使用非弱定义."链接集"是一组链接器加载的对象.它不包括库中不需要的对象.因此归档两个其中一个包含给定符号的弱定义的对象和另一个包含该符号的非弱定义,放入一个库或单独的库,不推荐使用.

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

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

评论 1:

<块引用>

弱"在不是定义的符号上意味着不解析链接时的这个符号".链接器愉快地服从.

评论 2:

<块引用>

在不是定义的符号上"是错误的,应该读on an未定义符号".

我认为在一个未定义的符号上",他的意思是一个未定义的符号在当前翻译单元内".就我而言,当我:

  • 在单独的func2.c文件中定义了func2()
  • 并用weak.h
  • 编译func1.c

这些本质上告诉链接器不解析翻译单元func1.c中使用的func2().但似乎这"仅适用于 .a 文件.如果我链接除 .a 文件之外的另一个 .o 文件,链接器仍然愿意解析 func2().或者如果 func2() 也定义在 func1.c 中,链接器也会解析它.很微妙!

(到目前为止,所有这些结论都是基于我的实验结果.所有这些总结起来很微妙.如果有人能找到一些权威来源,请随时发表评论或回复.谢谢!)

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

和一个相关的线程:

在 C 中覆盖函数调用

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

这些微妙的行为背后没有火箭科学.这仅取决于链接器的实现方式.有时文件是模糊的.你必须尝试并处理它.(如果所有这些背后有什么大想法,请纠正我,我将不胜感激.)

解决方案

这些微妙的行为

这里没有任何微妙之处.

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

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

  2. 弱(未解析)引用意味着:在决定是否从存档库中提取定义此符号的对象时,不要考虑这个符号(一个对象可能仍然是如果它满足不同的强未定义符号,则被拉入).

    通常,如果在所有对象都被选中后符号未解析,链接器将报告未解析的符号错误.但是如果未解析的符号很弱,错误就会被抑制.

这就是全部.

更新:

您在评论中重复了错误的理解.

<块引用>

让我感到微妙的是,对于弱引用,链接器不会从存档库中提取对象,但仍会检查独立的对象文件.

这与上面的答案完全一致.当链接器处理归档库时,它必须做出决定:是否选择包含的 foo.o 到链接中.那个决定会受到引用类型的影响.

bar.o 作为独立目标文件"在链接行中给出时,链接器不做任何决定 -- bar.o 选入链接.

<块引用>

如果那个对象恰好包含弱引用的定义,那么弱引用也会被顺便解析吗?

是的.

<块引用>

即使是weak 属性也告诉链接器不要这样做.

这显然是误解的根源:弱属性不会告诉链接器不要解析引用;它只告诉链接器(请原谅重复)在决定是否从存档库中提取定义此符号的对象时,不要考虑这个符号".

<块引用>

我认为这完全取决于包含弱引用定义的对象是否被拉入链接.

正确.

<块引用>

无论是独立对象还是来自存档库.

错误:一个独立的对象总是被选择到链接中.

I have below tree:

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

  • main.c invokes func1().
  • func1() invokes func2().
  • weak.h declares func2() as weak.
  • override.c provides an override version of func2().

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

Makefile

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

All these runs well as below:

in override func2()

But if I remove the override version of func2() from override.c as below:

#include <stdio.h>

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

The build pass but the final binary gives below error at runtime:

Segmentation fault (core dumped)

And in the symbol table of ./main, the func2() is an unresolved weak symbol.

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

Why didn't it fall back to the func2() in the original func2.c? After all the all_weak.a already contains an implementation in 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

ADD 1

It seems the arrangement of translation unit also affects the fall back to the weak function.

If I put the func2() implementation into the same file/translation unit as func1() as below, the fall back to the original func2() can work.

func1.c

#include <stdio.h>

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

void func1 (void)
{
    func2();
}

The symbols of all_weak.a is:

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

The code can fall back to the original func2() correctly if no override is provided.

This link also mentioned that the to work with the GCC alias attribute, translation unit arrangement must also be considered.

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.

According to the 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.

ADD 2 - 7:54 PM 8/7/2021

(Huge thanks to @n. 1.8e9-where's-my-share m. )

I tried these:

  • Add the __attribute__((weak)) to the func2() definition in func2.c.

  • Remove the -include weak.h from the Makefile.

Now these files look like this:

func2.c

#include <stdio.h>

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

Makefile:

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

The output is this:

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

So, the conclusion is:

  • If weak a function declaration (like what I did in weak.h), it essentially tells the linker not to resolve it.

  • If weak a function definition (like what I did in func2.c), it essentially tells the linker to use it as a fallback if no strong version found.

  • If weak a function declaration, you'd better provide an override version in a .o file to the linker (like what I did in override.o). It seems linker is still willing to resolve .o file in this situation. This is the case when you cannot modify the source but still want to override some function.

And some quotation from here:

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.

ADD 3 - 8:47 AM 8/8/2021

As @n.1.8e9-where's-my-sharem commented:

Comment 1:

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

Comment 2:

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

I think by "on an undefined symbol", he means "an undefined symbol within current translation unit". In my case, when I:

  • defined the func2() in a separated func2.c file
  • and compiled func1.c with weak.h

These essentially tell the linker do not resolve the func2() consumed in the translation unit func1.c. But it seems this "do not" only applies to .a file. If I link another .o file besides the .a file, the linker is still willing to resolve the func2(). Or if the func2() is also defined in the func1.c, linker will also resolve it. Subtle it is!

(So far, all these conclusions are based on my experiment result. It's subtle to summarize all these. If anyone can find some authoritative source, please feel free to comment or reply. Thanks!)

(Thanks to n. 1.8e9-where's-my-share m.'s comment.)

And a related thread:

Override a function call in C

Some afterthought - 9:55 PM 8/8/2021

There's no rocket science behind these subtle behaviors. It just depends on how the linker is implemented. Sometimes document is vague. You have to try it and deal with it. (If there's some big idea behind all these, please correct me and I will be more than grateful.)

解决方案

these subtle behaviors

There isn't really anything subtle here.

  1. A weak definition means: use this symbol unless another strong definition is also present, in which case use the other symbol.

    Normally two same-named symbols result in a multiply-defined link error, but when all but one definitions are weak, no multiply-defined error is produced.

  2. A weak (unresolved) reference means: don't consider this symbol when deciding whether to pull an object which defines this symbol out of archive library or not (an object may still be pulled in if it satisfies a different strong undefined symbol).

    Normally if the symbol is unresolved after all objects are selected, the linker will report unresolved symbol error. But if the unresolved symbol is weak, the error is suppressed.

That's really all there is to it.

Update:

You are repeating incorrect understanding in comments.

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.

This is entirely consistent with the answer above. When a linker deals with archive library, it has to make a decision: to select contained foo.o into the link or not. It is that decision that is affected by the type of reference.

When bar.o is given on the link line as a "standalone object file", the linker makes no decisions about it -- bar.o will be selected into the link.

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

Yes.

Even the weak attribute tells the linker not to.

This is the apparent root of misunderstanding: the weak attribute doesn't tell the linker not to resolve the reference; it only tells the linker (pardon repetition) "don't consider this symbol when deciding whether to pull an object which defines this symbol out of archive library".

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

Correct.

Be it a standalone object or from an archive lib.

Wrong: a standalone object is always selected into the link.

这篇关于为什么在同一个 .a 文件但不同的 .o 文件中定义的弱符号不用作后备?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆