How to combine LTO with symbol versioning
Asked Answered
J

3

7

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?

Jewess answered 19/9, 2017 at 15:47 Comment(6)
Not sure what's exactly going on, but you are aware the objects the symbols designate (function?!) might not even exist anymore after re-compilation? For a debug-build, don't use LTO and use -OgAdjoin
I don't understand your comment. What do you mean with "re-compilation"? I am compiling only once. And why should the function cease to exist? I am creating a shared object, so LTO ought to leave exported symbols well alone.Jewess
"What do you mean with "re-compilation"? I am compiling only once." Before using a feature like LTO, it is always good to know what that actually does.Adjoin
Oh, that's what you mean. Still, my question stands: why should the function cease to exist?Jewess
Maybe try # define DLLEXPORT __attribute__((visibility("default"),externally_visible))Adorno
And then DLLEXPORT int fun1(void)Adorno
S
4

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.

Suborder answered 4/1, 2019 at 20:48 Comment(0)
A
3

It looks like my externally_visible fix works. This is:

#define DLLEXPORT __attribute__((visibility("default"),externally_visible))

DLLEXPORT int fun1(void);

Also see: https://gcc.gnu.org/onlinedocs/gccint/WHOPR.html

But I think your versionscript is wrong.

If I take out the visibility overrides and change your versionscript by adding fun1 and fun2 then it works. Like:

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

The symbol alias targets have to be visible as well as the alias.

Adorno answered 20/9, 2017 at 21:9 Comment(2)
According to Drepper's "How To Write Shared Libraries" (which I consider pretty authoritative), the version script is correct as-is. It's interesting that the "externally_visible" fix works, but before I rely on it I'd like to make sure that this is actually the right solution. Is this documented anywhere? After all, changing the version script or not using LTO also makes the symbol visible, but neither of that is truly solving the problem.Jewess
I agree that not exporting the aliased definitions under the non-aliased names should be fine.Suborder
E
1

I just hit the same problem - so thank you for asking this. However I've found it to be more clean to use __attribute__((used)). Since gcc is not scanning the top level assembler, it can't figure out that fun1 and fun2 are being used ... so it removes them. So it looks to me that changing definition to:

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

should be sufficient.

Enclitic answered 10/9, 2019 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.