Im Folgenden finden Sie meine Experimente. Es gibt 4 Schlussfolgerungen im Körper und am Ende.
Kurzfassung
Um eine Funktion erfolgreich außer Kraft zu setzen, müssen Sie im Allgemeinen einige Dinge beachten:
- schwaches Attribut
- Anordnung der Übersetzungseinheit
Lange Version
Ich habe diese Quelldateien.
.
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");
}
deklarieren.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);
override.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
Ausgabe für Makefile1 Ergebnis:
in original func1()
in mock func2()
in mock func3()
Ausgabe für 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
Die Symboltabelle:
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
In beiden Fällen ist die override.o
Symbole:
0000000000000000 T func2 <=== func2 is strong symbol
0000000000000013 T func3 <=== func3 is strong symbol
U _GLOBAL_OFFSET_TABLE_
U puts
Demontage:
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
Die Schlussfolgerung lautet also:
-
Eine Funktion, die in .o
Datei kann die gleiche Funktion überschreiben, die in .a
Datei. In der obigen Makefile1 le func2()
y func3()
en override.o
übersteuert die Gegenstücke in all_weak.a
. Ich habe es mit beiden versucht .o
Dateien, aber es funktioniert nicht.
-
Für GCC Sie brauchen die Funktionen nicht in separate Funktionen aufzuteilen. .o
Dateien wie gesagt in aquí para Visual Studio-Toolchain . Wie wir im obigen Beispiel sehen können, sind beide func2()
(in derselben Datei wie func1()
) und func3()
(in einer separaten Datei) kann überschrieben werden.
-
Um eine Funktion außer Kraft zu setzen, muss beim Kompilieren der Verbraucher 's Übersetzungseinheit müssen Sie diese Funktion als schwach angeben. Dadurch wird die Funktion als schwach in der consumer.o
. Im obigen Beispiel wird beim Kompilieren der test_target.c
das verbraucht func2()
y func3()
müssen Sie Folgendes hinzufügen -include weak_decl.h
in der erklärt wird func2()
y func3()
als schwach. Die Website func2()
ist auch definiert in test_target.c
aber das ist in Ordnung.
Einige weitere Experimente
Immer noch mit den oben genannten Quelldateien. Aber ändern Sie die override.c
ein wenig:
override.c
#include <stdio.h>
void func2 (void)
{
printf("in mock func2()\n");
}
// void func3 (void)
// {
// printf("in mock func3()\n");
// }
Hier habe ich die Überschreibungsversion von func3()
. Ich habe dies getan, weil ich auf das Original zurückgreifen möchte func3()
Implementierung im func3.c
.
Ich benutze immer noch Makefile1
zu bauen. Der Build ist in Ordnung. Aber es tritt ein Laufzeitfehler auf wie unten:
xxx@xxx-host:~/source/override$ ./main
in original func1()
in mock func2()
Segmentation fault (core dumped)
Also habe ich die Symbole der letzten main
:
0000000000000696 T func1
00000000000006b3 T func2
w func3
So können wir die func3
hat keine gültige Adresse. Deshalb tritt der Segmentfehler auf.
Und warum? Habe ich nicht die func3.o
in die all_weak.a
Archivdatei?
ar cr all_weak.a func3.o test_target_weak.o
Ich habe das Gleiche versucht mit func2
, wo ich die func2
Implementierung von ovrride.c
. Aber dieses Mal gibt es keinen Segmentfehler.
override.c
#include <stdio.h>
// void func2 (void)
// {
// printf("in mock func2()\n");
// }
void func3 (void)
{
printf("in mock func3()\n");
}
Salida:
xxx@xxx-host:~/source/override$ ./main
in original func1()
in original func2() <====== the original func2() is invoked as a fall back
in mock func3()
Meine Vermutung ist, weil func2
ist in der gleichen Datei/Übersetzungseinheit als func1
. Also func2
wird immer mitgebracht func1
. So kann der Linker immer auflösen func2
sei es vom test_target.c
ou override.c
.
Aber für func3
wird sie in einer separaten Datei/Übersetzungseinheit (func3.c). Wenn sie als schwach deklariert ist, wird der Verbraucher test_target.o
wird weiterhin aufgezeichnet func3()
als schwach. Aber Leider überprüft der GCC-Linker nicht die anderen .o
Dateien aus demselben .a
Datei, um nach einer Implementierung von func3()
. Aber sie ist tatsächlich da.
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
Ich muss also eine Überschreibungsversion in override.c
sonst wird die func3()
nicht aufgelöst werden kann.
Aber ich weiß immer noch nicht, warum sich der GCC so verhält. Wenn jemand erklären kann, bitte.
(Update 9:01 AM 8/8/2021: este Thread kann dieses Verhalten erklären, hoffentlich .)
Die weitere Schlussfolgerung lautet also:
- Wenn Sie ein Symbol als schwach deklarieren, sollten Sie besser Override-Versionen von tous die schwachen Funktionen. Andernfalls kann die ursprüngliche Version nicht aufgelöst werden, es sei denn, sie befindet sich innerhalb der gleichen Datei/Übersetzungseinheit des Anrufers/Verbrauchers.