gcc - 为什么在同一个 .a 文件但不同 .o 文件中定义的弱符号不用作后备?
问题描述
我有下面的树:
.
├── func1.c
├── func2.c
├── main.c
├── Makefile
├── override.c
└── weak.h
- main.c 调用 func1()。
- func1() 调用 func2()。
- weak.h 将 func2() 声明为弱。
- override.c 提供了 func2() 的覆盖版本。
函数1.c
#include <stdio.h>
void func2(void);
void func1 (void)
{
func2();
}
函数2.c
#include <stdio.h>
void func2 (void)
{
printf("in original func2()\n");
}
主程序
#include <stdio.h>
void func1();
void func2();
void main()
{
func1();
}
覆盖.c
#include <stdio.h>
void func2 (void)
{
printf("in override func2()\n");
}
弱.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()
func2()
但是,如果我删除from的覆盖版本,override.c
如下所示:
#include <stdio.h>
// void func2 (void)
// {
// printf("in override func2()\n");
// }
构建通过,但最终的二进制文件在运行时给出以下错误:
Segmentation fault (core dumped)
而在 的符号表中./main
,func2() 是一个未解析的弱符号。
000000000000065b T func1
w func2 <=== func2 is a weak symbol with no default implementation
为什么没有回到func2()
原来的样子func2.c
?
毕竟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()
中,则可以退回到原始文件func2()
。
函数1.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()
如果没有提供覆盖,代码可以正确回退到原始代码。
该链接还提到要使用 GCCalias
属性,还必须考虑翻译单元的排列。
alias (“target”) alias 属性导致声明作为另一个符号的别名发出,必须指定。例如,
void __f () { /* 做某事。*/; } void f()属性((weak, alias ("__f"))); 将 f 定义为 __f 的弱别名。在 C++ 中,必须使用目标的错位名称。如果 __f 未在同一个翻译单元中定义,则会出错。
根据维基百科:
nm 命令识别目标文件、库和可执行文件中的弱符号。在 Linux 上,如果弱默认定义可用,则弱函数符号标记为“ W ”,如果不可用,则标记为“ w ”。
添加 2 - 2021 年 8 月 7 日下午 7:54
(非常感谢@n。1.8e9-where's-my-share m。)
我试过这些:
在 func2.c
__attribute__((weak))
的func2()
定义中添加。-include weak.h
从 Makefile 中删除。
现在这些文件看起来像这样:
函数2.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
所以,结论是:
如果弱函数声明(就像我在 中所做的那样
weak.h
),它本质上是告诉链接器不要解析它。如果弱函数定义(就像我在 中所做的那样
func2.c
),它本质上是告诉链接器在没有找到强版本时将其用作后备。如果弱函数声明,您最好在
.o
文件中向链接器提供覆盖版本(就像我在 中所做的那样override.o
)。在这种情况下,链接器似乎仍然愿意解析.o
文件。当您无法修改源但仍想覆盖某些功能时,就会出现这种情况。
以及这里的一些引文:
如果在搜索所有输入对象后无法解析引用,则链接器将仅搜索库以解析引用. 如果需要,将根据它们在链接器命令行上的位置从左到右搜索这些库。库中的对象将按它们的归档顺序进行搜索。一旦 armlink 找到与引用匹配的符号,搜索就完成了,即使它与弱定义匹配。ELF ABI 第 4.6.1.2 节说:“弱定义不会改变从库中选择目标文件的规则。但是,如果链接集同时包含弱定义和非弱定义,则非弱定义将永远被使用。” “链接集”是链接器已加载的对象集。它不包括不需要的库中的对象。因此,不建议将其中一个包含给定符号的弱定义而另一个包含该符号的非弱定义的两个对象归档到一个库或单独的库中。
2021 年 8 月 8 日上午 8 点 47 分添加
正如@n.1.8e9-where's-my-sharem 评论的:
评论1:
不是定义的符号上的“弱”表示“在链接时不解析此符号”。链接器高兴地服从。
评论 2:
“在一个不是定义的符号上”是错误的,应该读作“在一个未定义的符号上”。
我认为“在一个未定义的符号上”,他的意思是“当前翻译单元中的一个未定义的符号”。就我而言,当我:
func2()
在单独的func2.c文件中定义- 并编译func1.c
weak.h
这些本质上告诉链接器不要解析func2()
翻译单元func1.c中的消耗。但似乎这个“不”只适用于.a
文件。如果我链接.o
文件之外的另一个文件.a
,链接器仍然愿意解析func2()
. 或者,如果func1.cfunc2()
中也定义了,链接器也会解析它。很微妙!
(到目前为止,所有这些结论都是基于我的实验结果。总结这些很微妙。如果有人能找到一些权威来源,请随时评论或回复。谢谢!)
(感谢n. 1.8e9-where's-my-share m.的评论。)
和一个相关的线程:
事后诸葛亮 - 2021 年 8 月 8 日晚上 9:55
这些微妙的行为背后没有火箭科学。这仅取决于链接器的实现方式。有时文件是模糊的。你必须尝试并处理它。(如果所有这些背后有什么大想法,请纠正我,我将不胜感激。)
解决方案
这些微妙的行为
这里真的没有什么微妙之处。
弱定义意味着:除非还存在另一个强定义,否则使用这个符号,在这种情况下使用另一个符号。
通常两个同名符号会导致多重定义链接错误,但当除一个定义之外的所有定义都很弱时,不会产生多重定义错误。弱(未解析)引用意味着:在决定是否将定义此符号的对象从存档库中拉出时,不要考虑此符号(如果对象满足不同的强未定义符号,则仍可能被拉入)。
通常如果在选择所有对象后符号未解析,链接器将报告未解析符号错误。但是如果未解析的符号很弱,则错误会被抑制。
这就是它的全部。
更新:
您在评论中重复不正确的理解。
让我感到微妙的是,对于弱引用,链接器不会从存档库中提取对象,但仍会检查独立的对象文件。
这与上面的答案完全一致。当链接器处理归档库时,它必须做出决定:选择是否包含foo.o
在链接中。正是该决定受参考类型的影响。
当bar.o
在链接行上作为“独立目标文件”给出时,链接器不会做出任何决定——bar.o
将被选择到链接中。
而如果那个对象恰好包含弱引用的定义,那么弱引用是否也会被解析呢?
是的。
即使是弱属性也告诉链接器不要这样做。
这是误解的明显根源:weak 属性并没有告诉链接器不要解析引用;它只告诉链接器(请原谅重复)“在决定是否将定义此符号的对象拉出存档库时不要考虑此符号”。
我认为这完全取决于是否将包含该弱引用定义的对象拉入以进行链接。
正确的。
无论是独立对象还是来自存档库。
错误:始终将独立对象选择到链接中。
推荐阅读
- javascript - 我们可以在对字符串数组进行排序时实现以下行为吗?
- node.js - 为什么要在复制 package.json 后复制 dot dot?
- javascript - 为什么只有单击两次按钮才写入js cookie?
- python - 创建一个新列,该列取决于熊猫中其他列的值范围
- ejs - “chat.ejs”文件显示在“index.ejs”文件之前
- css - 在 NextJS 组件中使用 css 而不将其作为对象导入
- sql - BIgQuery/SQL 中的条件选择
- asp.net-core - 如何使用 FusionAuth API 密钥对 HotChocolate Graph API 进行身份验证/授权
- javascript - 单击刷新按钮时,chrome显示ajax响应
- mysql - 将规范化的逗号分隔字段插入新表