$ cat foo.c void foo() {} int main() { return 0; } $ cat ver FOO { global: foo; }; $ gcc -o foo foo.c -Wl,--version-script,ver -fuse-ld=gold -rdynamic $ objdump -T foo | grep -e '\b\(main\|foo\)$' 000000000000085a g DF .text 0000000000000007 Base foo 0000000000000861 g DF .text 000000000000000b Base main The above is actually confusing because of bug 23267. $ readelf -V foo Version symbols section '.gnu.version' contains 20 entries: Addr: 00000000000005dc Offset: 0x0005dc Link: 4 (.dynsym) 000: 0 (*local*) 2 (GLIBC_2.2.5) 2 (GLIBC_2.2.5) 0 (*local*) 004: 0 (*local*) 0 (*local*) 1 (*global*) 1 (*global*) 008: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) 00c: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) 010: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) Version definition section '.gnu.version_d' contains 1 entry: Addr: 0x0000000000000604 Offset: 0x000604 Link: 5 (.dynstr) 000000: Rev: 1 Flags: none Index: 1 Cnt: 1 Name: FOO Version needs section '.gnu.version_r' contains 1 entry: Addr: 0x0000000000000620 Offset: 0x000620 Link: 5 (.dynstr) 000000: Version: 1 File: libc.so.6 Cnt: 1 0x0010: Name: GLIBC_2.2.5 Flags: none Version: 2 So the problem here is that both exported symbols end up with the same version, named FOO. What BFD ld does is: $ objdump -T foo | grep -e '\b\(main\|foo\)$' 000000000000084a g DF .text 0000000000000007 FOO foo 0000000000000851 g DF .text 000000000000000b Base main $ readelf -V foo Version symbols section '.gnu.version' contains 20 entries: Addr: 00000000000005ba Offset: 0x0005ba Link: 5 (.dynsym) 000: 0 (*local*) 0 (*local*) 3 (GLIBC_2.2.5) 0 (*local*) 004: 0 (*local*) 3 (GLIBC_2.2.5) 1 (*global*) 1 (*global*) 008: 2 (FOO) 1 (*global*) 1 (*global*) 2 (FOO) 00c: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) 010: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) Version definition section '.gnu.version_d' contains 2 entries: Addr: 0x00000000000005e8 Offset: 0x0005e8 Link: 6 (.dynstr) 000000: Rev: 1 Flags: BASE Index: 1 Cnt: 1 Name: foo 0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: FOO Version needs section '.gnu.version_r' contains 1 entry: Addr: 0x0000000000000620 Offset: 0x000620 Link: 6 (.dynstr) 000000: Version: 1 File: libc.so.6 Cnt: 1 0x0010: Name: GLIBC_2.2.5 Flags: none Version: 3 That is, it places main in the Base version, and foo in the FOO version. Please note that gold actually does that when linking libraries: $ gcc -shared -o libfoo.so foo.c -Wl,--version-script,ver -fuse-ld=gold $ objdump -T libfoo.so | grep -e '\b\(main\|foo\)$' 000000000000069a g DF .text 0000000000000007 FOO foo 00000000000006a1 g DF .text 000000000000000b Base main And more than skipping the base version, it actually attributes all symbols to the first version in executables: $ cat ver2 FOO { global: foo; }; BAR { global: main; }; $ gcc -o foo foo.c -Wl,--version-script,ver2 -fuse-ld=gold -rdynamic $ LANG=C objdump -T foo | grep -e '\b\(main\|foo\)$' 000000000000089a g DF .text 0000000000000007 Base foo 00000000000008a1 g DF .text 000000000000000b Base main $ readelf -V foo Version symbols section '.gnu.version' contains 21 entries: Addr: 00000000000005f8 Offset: 0x0005f8 Link: 4 (.dynsym) 000: 0 (*local*) 3 (GLIBC_2.2.5) 3 (GLIBC_2.2.5) 0 (*local*) 004: 0 (*local*) 0 (*local*) 1 (*global*) 1 (*global*) 008: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) 00c: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) 010: 1 (*global*) 1 (*global*) 1 (*global*) 1 (*global*) 014: 1 (*global*) Version definition section '.gnu.version_d' contains 2 entries: Addr: 0x0000000000000624 Offset: 0x000624 Link: 5 (.dynstr) 000000: Rev: 1 Flags: none Index: 1 Cnt: 1 Name: FOO 0x001c: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: BAR Version needs section '.gnu.version_r' contains 1 entry: Addr: 0x000000000000065c Offset: 0x00065c Link: 5 (.dynstr) 000000: Version: 1 File: libc.so.6 Cnt: 1 0x0010: Name: GLIBC_2.2.5 Flags: none Version: 3 Here, main is not even associated to version BAR.
As for what kind of subtle problem this can cause: $ cat foo.c #include <stdlib.h> void* malloc_hook(size_t s) { abort(); } void* (*__malloc_hook)(size_t s) = malloc_hook; int main() { malloc(1);} $ cat ver hidden { local: main; }; $ gcc -o foo foo.c -rdynamic -Wl,--version-script,ver # Aborted means we got in the hook. $ ./foo Aborted $ gcc -o foo foo.c -rdynamic -Wl,--version-script,ver -fuse-ld=gold $ ./foo No output in this case, because the hook is not found by ld.so: $ LD_DEBUG=bindings ./foo 2>&1 | grep malloc_hook 1531: binding file /lib/x86_64-linux-gnu/libc.so.6 [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `__malloc_hook' [GLIBC_2.2.5] The reason being that since __malloc_hook has a symbol version (that thanks to bug 23267, neither objdump nor readelf show), ld.so doesn't pick it up because the symbol version doesn't match GLIBC_2.2.5. (Yes, the version script is a little contrived, for $reasons ; but this would happen with a version script that exports other symbols with a symbol version too)
You're using --version-script when linking an executable? As far as I know, that's not supported (and probably should be disallowed). Version scripts are for shared libraries. What happens if you remove the version script from your executable?
I'm using --version-script to hide symbols (because --exclude-symbols is PE only, and I don't think it takes globs anyways). Of course, when not using one, it works.
FWIW, it works with both BFD ld and lld.
The master branch has been updated by Cary Coutant <ccoutant@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=ebb1332297da904a4adab0d3696a5512604f5edd commit ebb1332297da904a4adab0d3696a5512604f5edd Author: Cary Coutant <ccoutant@gmail.com> Date: Wed Jun 20 01:24:11 2018 -0700 Fix problem where gold does not create base version for executables. gold/ PR gold/23268 * dynobj.cc (Versions::Versions): Change init for needs_base_version_. (Versions::record_version): Add verdefs for both shared objects and executables. (Versions::add_def): Likewise for base version. (Versions::add_need): Don't add base version for executables. (Versions::version_index): Look up version for both shared objects and executables. * testsuite/Makefile.am (ver_test_14): New test case. * testsuite/Makefile.in: Regenerate. * testsuite/ver_test_14.script: New version script. * testsuite/ver_test_14.sh: New test script.
Thanks. Is this going to make the cut for 2.31?
Fixed for binutils 2.31.