下面是我的实验。正文和结尾有4个结论。
简洁版本
一般来说,要成功覆盖一个函数,你必须考虑:
长版
我有这些源文件。
.
├── decl.h
├── func3.c
├── main.c
├── Makefile1
├── Makefile2
├── override.c
├── test_target.c
└── weak_decl.h
主程序
#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();
}
函数3.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");
}
生成文件1:
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
生成文件2:
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中,func2()
and func3()
inoverride.o
覆盖了all_weak.a
. 我尝试了这两个.o
文件,但它不起作用。
对于GCC,您不需要将函数拆分为单独的文件.o
,如Visual Studio toolchain中所述。我们可以在上面的示例中看到,(在与 相同的文件中)和(在单独的文件中)都可以被覆盖。func2()
func1()
func3()
要覆盖一个函数,在编译其消费者的翻译单元时,您需要将该函数指定为弱。这将在consumer.o
. 在上面的示例中,当编译test_target.c
消耗func2()
and的 , 时func3()
,您需要添加-include weak_decl.h
, 将func2()
and声明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()
.func3.c
我仍然使用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
,我从中删除了func2
实现ovrride.c
。但这一次没有段错误。
覆盖.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
与. 所以总是带进来。所以链接器总是可以解析,无论是从or 。func1
func2
func1
func2
test_target.c
override.c
但是对于func3
,它是在单独的文件/翻译单元(func3.c) 中定义的。如果声明为弱,消费者test_target.o
仍将记录func3()
为弱。但不幸的是,GCC 链接器不会检查.o
同一.a
文件中的其他文件以查找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 更新:
这个线程可能会解释这种行为,希望如此。)
所以进一步的结论是:
- 如果您将某个符号声明为弱,则最好提供所有弱函数的覆盖版本。否则,除非原始版本位于调用者/消费者的同一文件/翻译单元中,否则无法解析原始版本。