I would like to compile a shared library using both symbol versioning and link-time optimization (LTO). However, as soon as I turn on LTO, some of the exported symbols vanish. Here is a minimal example:
Start by defining two implementations of a function fun:
$ cat fun.c
#include <stdio.h>
int fun1(void);
int fun2(void);
__asm__(".symver fun1,fun@v1");
int fun1() {
printf("fun1 called\n");
return 1;
}
__asm__(".symver fun2,fun@@v2");
int fun2() {
printf("fun2 called\n");
return 2;
}
Create a version script to ensure that only fun is exported:
$ cat versionscript
v1 {
global:
fun;
local:
*;
};
v2 {
global:
fun;
} v1;
First attempt, compile without LTO:
$ gcc -o fun.o -Wall -Wextra -O2 -fPIC -c fun.c
$ gcc -o libfun.so.1 -shared -fPIC -Wl,--version-script,versionscript fun.o
$ nm -D --with-symbol-versions libfun.so.1 | grep fun
00000000000006b0 T fun@@v2
0000000000000690 T fun@v1
..exactly as it should be. But if I compile with LTO:
$ gcc -o fun.o -Wall -Wextra -flto -O2 -fPIC -c fun.c
$ gcc -o libfun.so.1 -flto -shared -fPIC -Wl,--version-script,versionscript fun.o
$ nm -D --with-symbol-versions libfun.so.1 | grep fun
..no symbols exported anymore.
What am I doing wrong?
WHOPR Driver Design gives some strong hints to what is going on. The function definitions fun1
and fun2
are not exported according to the version script. The LTO plugin is able to use this information, and since GCC does not peek into the asm
directives, it knows nothing about the .symver
directive, and therefore removes the function definition.
For now, adding __attribute__ ((externally_visible))
is the workaround for this. You also need to build with -flto-partition=none
, so that the .symver
directives do not land by accident in a different intermediate assembler file than the function definition (where it will not have the desired effect).
GCC PR 48200 tracks an enhancement request for symbol versioning at the compiler level, which would likely address this issue as well.