以下是我的实验。正文和结尾有4个结论。
短版
一般来说,要成功覆盖一个函数,你必须考虑:
加长版
我有这些源文件。
.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h
main.c
#include <stdio.h>
void main (void)
{
func1();
}
test_target.c
#include <stdio.h>
void func3(void);
void func2 (void)
{
printf("in original func2()\n");
}
void func1 (void)
{
printf("in original func1()\n");
func2();
func3();
}
func3.c
#include <stdio.h>
void func3 (void)
{
printf("in original func3()\n");
}
decl.h
void func1 (void);
void func2 (void);
void func3 (void);
weak_decl.h
void func1 (void);
__attribute__((weak))
void func2 (void);
__attribute__((weak))
void func3 (void);
覆盖.c
#include <stdio.h>
void func2 (void)
{
printf("in mock func2()\n");
}
void func3 (void)
{
printf("in mock func3()\n");
}
Makefile1:
ALL:
rm -f *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_weak.o -include weak_decl.h
ar cr all_weak.a test_target_weak.o func3.o
gcc main.c all_weak.a override.o -o main -include decl.h
Makefile2:
ALL:
rm -f *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # HERE -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h
Makefile1 结果的输出:
in original func1()
in mock func2()
in mock func3()
Makefile2 的输出:
rm *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h
override.o: In function `func2':
override.c:(.text+0x0): multiple definition of `func2' <===== HERE!!!
all_strong.a(test_target_strong.o):test_target.c:(.text+0x0): first defined here
override.o: In function `func3':
override.c:(.text+0x13): multiple definition of `func3' <===== HERE!!!
all_strong.a(func3.o):func3.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile4:2: recipe for target 'ALL' failed
make: *** [ALL] Error 1
符号表:
all_weak.a:
test_target_weak.o:
0000000000000013 T func1 <=== 13 is the offset of func1 in test_target_weak.o, see below disassembly
0000000000000000 W func2 <=== func2 is [W]eak symbol with default value assigned
w func3 <=== func3 is [w]eak symbol without default value
U _GLOBAL_OFFSET_TABLE_
U puts
func3.o:
0000000000000000 T func3 <==== func3 is a strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
all_strong.a:
test_target_strong.o:
0000000000000013 T func1
0000000000000000 T func2 <=== func2 is strong symbol
U func3 <=== func3 is undefined symbol, there's no address value on the left-most column because func3 is not defined in test_target_strong.c
U _GLOBAL_OFFSET_TABLE_
U puts
func3.o:
0000000000000000 T func3 <=== func3 is strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
在这两种情况下,override.o 符号:
0000000000000000 T func2 <=== func2 is strong symbol
0000000000000013 T func3 <=== func3 is strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
反汇编:
test_target_weak.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <func2>: <===== HERE func2 offset is 0
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <func2+0xb>
b: e8 00 00 00 00 callq 10 <func2+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
0000000000000013 <func1>: <====== HERE func1 offset is 13
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
17: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1e <func1+0xb>
1e: e8 00 00 00 00 callq 23 <func1+0x10>
23: e8 00 00 00 00 callq 28 <func1+0x15>
28: e8 00 00 00 00 callq 2d <func1+0x1a>
2d: 90 nop
2e: 5d pop %rbp
2f: c3 retq
所以结论是:
-
.o 文件中定义的函数可以覆盖.a 文件中定义的相同函数。在上面的 Makefile1 中,override.o 中的 func2() 和 func3() 覆盖了 all_weak.a 中的对应项。我尝试了两个 .o 文件,但它不起作用。
-
对于 GCC,您不需要像 Visual Studio 工具链 的 here 中所述将函数拆分为单独的 .o 文件。我们可以在上面的例子中看到,func2()(与func1()在同一个文件中)和func3()(在一个单独的文件中)都可以被覆盖。
-
要覆盖一个函数,在编译它的consumer的translation unit时,你需要将该函数指定为weak。这将在consumer.o 中将该功能记录为弱。在上面的例子中,当编译test_target.c,它消耗func2()和func3(),你需要添加-include weak_decl.h,它声明func2()和func3()是弱的。 func2() 也在 test_target.c 中定义,但没关系。
一些进一步的实验
还是上面的源文件。但是稍微改变一下override.c:
覆盖.c
#include <stdio.h>
void func2 (void)
{
printf("in mock func2()\n");
}
// void func3 (void)
// {
// printf("in mock func3()\n");
// }
这里我删除了func3() 的覆盖版本。 我这样做是因为我想回退到 func3.c 中的原始 func3() 实现。
我仍然使用Makefile1 来构建。构建是好的。但运行时错误如下:
xxx@xxx-host:~/source/override$ ./main
in original func1()
in mock func2()
Segmentation fault (core dumped)
所以我检查了最后main的符号:
0000000000000696 T func1
00000000000006b3 T func2
w func3
所以我们可以看到func3 没有有效地址。这就是发生段错误的原因。
那为什么?我不是将func3.o 添加到all_weak.a 存档文件中了吗?
ar cr all_weak.a func3.o test_target_weak.o
我用func2 尝试了同样的事情,我从ovrride.c 中删除了func2 实现。但这次没有段错误。
覆盖.c
#include <stdio.h>
// void func2 (void)
// {
// printf("in mock func2()\n");
// }
void func3 (void)
{
printf("in mock func3()\n");
}
输出:
xxx@xxx-host:~/source/override$ ./main
in original func1()
in original func2() <====== the original func2() is invoked as a fall back
in mock func3()
我的猜测是,因为func2 与func1 在相同的文件/翻译单元中定义。所以func2 总是与func1 一起引入。所以链接器总是可以解析func2,无论是来自test_target.c还是override.c。
但对于func3,它是在一个单独的文件/翻译单元 (func3.c) 中定义的。如果声明为弱,消费者test_target.o 仍会将func3() 记录为弱。但不幸的是,GCC 链接器不会检查同一 .a 文件中的其他 .o 文件以查找 func3() 的实现。虽然它确实存在。
all_weak.a:
func3.o:
0000000000000000 T func3 <========= func3 is indeed here!
U _GLOBAL_OFFSET_TABLE_
U puts
test_target_weak.o:
0000000000000013 T func1
0000000000000000 W func2
w func3
U _GLOBAL_OFFSET_TABLE_
U puts
所以我必须在override.c 中提供覆盖版本,否则func3() 无法解析。
但我仍然不知道为什么 GCC 会这样。如果有人能解释一下,请。
(2021 年 8 月 8 日上午 9:01 更新:
this 线程可能会解释这种行为,希望。)
所以进一步的结论是:
- 如果您将某个符号声明为weak,则最好提供allweak 函数的覆盖版本。否则,原始版本无法解析,除非它位于调用者/消费者的同一文件/翻译单元中。