【问题标题】:How to combine LTO with symbol versioning如何将 LTO 与符号版本控制相结合
【发布时间】:2017-09-19 15:47:10
【问题描述】:

我想同时使用符号版本控制和链接时优化 (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

..不再导出符号。

我做错了什么?

【问题讨论】:

  • 不确定到底发生了什么,但您知道符号指定的对象(函数?!)在重新编译后甚至可能不再存在?对于调试构建,不要使用 LTO 并使用 -Og
  • 我不明白你的评论。 “重新编译”是什么意思?我只编译一次。为什么该功能应该不复存在?我正在创建一个共享对象,因此 LTO 应该单独保留导出的符号。
  • ""重新编译"是什么意思?我只编译一次。"在使用 LTO 之类的功能之前,最好了解它的实际作用。
  • 哦,这就是你的意思。不过,我的问题仍然存在:为什么该功能不复存在?
  • 也许可以试试# define DLLEXPORT __attribute__((visibility("default"),externally_visible))

标签: c gcc linker lto


【解决方案1】:

WHOPR Driver Design 对正在发生的事情给出了一些强有力的提示。函数定义fun1fun2不根据版本脚本导出。 LTO 插件能够使用这些信息,并且由于 GCC 不查看 asm 指令,它对 .symver 指令一无所知,因此删除了函数定义。

目前,添加__attribute__ ((externally_visible)) 是解决此问题的方法。您还需要使用-flto-partition=none 进行构建,这样.symver 指令就不会意外出现在与函数定义不同的中间汇编程序文件中(不会产生预期的效果)。

GCC PR 48200 在编译器级别跟踪符号版本控制的增强请求,这也可能解决此问题。

【讨论】:

    【解决方案2】:

    看起来我的externally_visible 修复有效。这是:

    #define DLLEXPORT __attribute__((visibility("default"),externally_visible))
    
    DLLEXPORT int fun1(void);
    

    另见:https://gcc.gnu.org/onlinedocs/gccint/WHOPR.html

    但我认为你的版本脚本是错误的。

    如果我去掉可见性覆盖并通过添加 fun1fun2 来更改您的版本脚本,那么它可以工作。喜欢:

    v1 {
        global:
            fun; fun1;
        local:
            *;
    };
    v2 {
        global:
            fun; fun2;
    } v1;
    

    符号别名目标必须和别名一样可见。

    【讨论】:

    • 根据 Drepper 的“如何编写共享库”(我认为它非常权威),版本脚本是正确的。有趣的是,“externally_visible”修复有效,但在我依赖它之前,我想确保这实际上是正确的解决方案。这在任何地方都有记录吗?毕竟,更改版本脚本或不使用 LTO 也会使符号可见,但这都不能真正解决问题。
    • 我同意不导出非别名下的别名定义应该没问题。
    【解决方案3】:

    我也遇到了同样的问题 - 感谢您提出这个问题。但是我发现使用__attribute__((used)) 更干净。由于 gcc 没有扫描顶级汇编程序,它无法确定 fun1fun2 正在被使用......所以它删除了它们。所以在我看来,将定义更改为:

    __asm__(".symver fun1,fun@v1");
    int __attribute__((used)) fun1() {
        printf("fun1 called\n");
        return 1;
    }
    

    应该足够了。

    【讨论】:

      猜你喜欢
      • 2016-04-29
      • 2011-06-28
      • 1970-01-01
      • 1970-01-01
      • 2015-09-16
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 2016-09-26
      相关资源
      最近更新 更多