Understand Weak Symbols by Examples
本文转载自Understand Weak Symbols by Examples。
Wikipedia defines the weak symbols:
In computing, a weak symbol is a symbol definition in an object file or dynamic library that may be overridden by other symbol definitions, its value will be zero if no definition found by loader.
In other words, we can define a symbol that doesn’t need to be resolved at link time. It is a very well-known feature and used a lot in Linux Kernel, Glibc, and so on.
Take a look at the example, we are not able to compile it due to the ‘undefined reference’ error.1
2
3
4
5
6
7
8
9
10
11$ cat err.c
int main(void)
{
f();
return 0;
}
$ gcc err.c
/tmp/ccYx7WNg.o: In function `main':
err.c:(.text+0x12): undefined reference to `f'
collect2: ld returned 1 exit status
Try to declare ‘f’ as an weak symbol, and we can compile it without error.1
2
3
4
5
6
7
8
9$ cat weak.c
void __attribute__((weak)) f();
int main(void)
{
if (f)
f();
return 0;
}
$ gcc weak.c
Note that the function ‘f’ is called inside an if statement. If not calling ‘f’ this way, we will get a ‘Segmentation fault’ error. In the weak.c example, ‘f’ is actually not invoked. It is because ‘f’ is an un-defined weak symbol and therefore will be zero when the loader cannot find it.1
2
3
4
5
6$ ./a.out
$ nm a.out
...
w f
08048324 T main
...
Let’s define the function ‘f’ in another file, and link the objects together. This time, ‘f’ will be correctly called. (Note that puts is the optimization of printf by gcc)
1 | $ cat f.c |
We may even override the original weak symbol (type ‘W’) with a strong symbol (type ‘T’).1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41$ cat orig.c
void __attribute__((weak)) f()
{
printf("original f..\n");
}
int main(void)
{
f();
return 0;
}
$ gcc orig.c
$ ./a.out
original f..
$ cat ovrd.c
void f(void)
{
printf("overridden f!\n");
}
$ gcc -c orig.c ovrd.c
$ gcc -o ovrd orig.o ovrd.o
$ ./ovrd
overridden f!
$ nm orig.o
00000000 W f
00000014 T main
U puts
$ nm ovrd.o
00000000 T f
U puts
$ nm ovrd
...
0804838c T f
08048368 T main
U puts@@GLIBC_2.0
...
And of course, we can also override a weak object (type ‘V’) with a strong object (type ‘D’).1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41$ cat orig-obj.c
int __attribute__((weak)) x = 1;
int __attribute__((weak)) y = 1;
int main(void)
{
printf("x = %d, y = %d\n", x, y);
return 0;
}
$ gcc orig-obj.c
$ ./a.out
x = 1, y = 1
$ cat ovrd-obj.c
int x = 2;
void f(void)
{
}
$ gcc -c orig-obj.c ovrd-obj.c
$ gcc -o ovrd-obj orig-obj.o ovrd-obj.o
$ ./ovrd-obj
x = 2, y = 1
$ nm orig-obj.o
00000000 T main
U printf
00000000 V x
00000004 V y
$ nm ovrd-obj.o
00000000 T f
00000000 D x
$ nm ovrd-obj
...
08048394 T f
08048354 T main
U printf@@GLIBC_2.0
080495c8 D x
080495c4 V y
...
What if there are multiple symbols? Linker’s symbol rules tell us that:
- Multiple strong symbols are not allowed
- Given a strong symbol and multiple weak symbols –> choose the strong symbol
- Given multiple weak symbols –> choose any of those weak symbols
1 | $ cat mul.c |
Hope these examples help!
References: