如何将 LTO 与符号版本控制相结合
How to combine LTO with symbol versioning
我想使用符号版本控制和link时间优化 (LTO) 编译共享库。但是,一旦我打开 LTO,一些导出的符号就会消失。这是一个最小的例子:
首先定义一个函数的两个实现 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;
}
创建一个版本脚本,确保只导出fun:
$ cat versionscript
v1 {
global:
fun;
local:
*;
};
v2 {
global:
fun;
} v1;
第一次尝试,不使用 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
..应该是这样。但是如果我用 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
..不再导出符号。
我做错了什么?
看来我的 externally_visible
修复有效。这是:
#define DLLEXPORT __attribute__((visibility("default"),externally_visible))
DLLEXPORT int fun1(void);
另见:https://gcc.gnu.org/onlinedocs/gccint/WHOPR.html
但我认为你的版本是错误的。
如果我删除可见性覆盖并通过添加 fun1
和 fun2
更改您的版本脚本,那么它就可以工作。喜欢:
v1 {
global:
fun; fun1;
local:
*;
};
v2 {
global:
fun; fun2;
} v1;
符号别名目标必须与别名一样可见。
WHOPR Driver Design 对正在发生的事情给出了一些强烈的提示。函数定义 fun1
和 fun2
未根据版本脚本导出。 LTO 插件能够使用此信息,并且由于 GCC 不查看 asm
指令,它对 .symver
指令一无所知,因此删除函数定义。
目前,添加 __attribute__ ((externally_visible))
是解决此问题的方法。您还需要使用 -flto-partition=none
进行构建,这样 .symver
指令就不会意外地出现在与函数定义不同的中间汇编程序文件中(它不会产生预期的效果)。
GCC PR 48200 跟踪在编译器级别对符号版本控制的增强请求,这也可能会解决此问题。
我刚遇到同样的问题 - 谢谢你提出这个问题。但是我发现使用 __attribute__((used))
更干净。由于 gcc 没有扫描顶级汇编程序,它无法确定 fun1
和 fun2
正在被使用......所以它删除了它们。所以在我看来,将定义更改为:
__asm__(".symver fun1,fun@v1");
int __attribute__((used)) fun1() {
printf("fun1 called\n");
return 1;
}
应该足够了。
我想使用符号版本控制和link时间优化 (LTO) 编译共享库。但是,一旦我打开 LTO,一些导出的符号就会消失。这是一个最小的例子:
首先定义一个函数的两个实现 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;
}
创建一个版本脚本,确保只导出fun:
$ cat versionscript
v1 {
global:
fun;
local:
*;
};
v2 {
global:
fun;
} v1;
第一次尝试,不使用 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
..应该是这样。但是如果我用 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
..不再导出符号。
我做错了什么?
看来我的 externally_visible
修复有效。这是:
#define DLLEXPORT __attribute__((visibility("default"),externally_visible))
DLLEXPORT int fun1(void);
另见:https://gcc.gnu.org/onlinedocs/gccint/WHOPR.html
但我认为你的版本是错误的。
如果我删除可见性覆盖并通过添加 fun1
和 fun2
更改您的版本脚本,那么它就可以工作。喜欢:
v1 {
global:
fun; fun1;
local:
*;
};
v2 {
global:
fun; fun2;
} v1;
符号别名目标必须与别名一样可见。
WHOPR Driver Design 对正在发生的事情给出了一些强烈的提示。函数定义 fun1
和 fun2
未根据版本脚本导出。 LTO 插件能够使用此信息,并且由于 GCC 不查看 asm
指令,它对 .symver
指令一无所知,因此删除函数定义。
目前,添加 __attribute__ ((externally_visible))
是解决此问题的方法。您还需要使用 -flto-partition=none
进行构建,这样 .symver
指令就不会意外地出现在与函数定义不同的中间汇编程序文件中(它不会产生预期的效果)。
GCC PR 48200 跟踪在编译器级别对符号版本控制的增强请求,这也可能会解决此问题。
我刚遇到同样的问题 - 谢谢你提出这个问题。但是我发现使用 __attribute__((used))
更干净。由于 gcc 没有扫描顶级汇编程序,它无法确定 fun1
和 fun2
正在被使用......所以它删除了它们。所以在我看来,将定义更改为:
__asm__(".symver fun1,fun@v1");
int __attribute__((used)) fun1() {
printf("fun1 called\n");
return 1;
}
应该足够了。