Bug 23268 - gold doesn't handle symbol versions like BFD ld when linking executable with both -E and --version-script
Summary: gold doesn't handle symbol versions like BFD ld when linking executable with ...
Status: RESOLVED FIXED
Alias: None
Product: binutils
Classification: Unclassified
Component: gold (show other bugs)
Version: unspecified
: P2 normal
Target Milestone: 2.31
Assignee: Cary Coutant
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-06-07 04:36 UTC by Mike Hommey
Modified: 2018-06-20 20:33 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Mike Hommey 2018-06-07 04:36:15 UTC
$ 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.
Comment 1 Mike Hommey 2018-06-07 04:49:02 UTC
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)
Comment 2 Cary Coutant 2018-06-07 05:42:47 UTC
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?
Comment 3 Mike Hommey 2018-06-07 05:52:09 UTC
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.
Comment 4 Mike Hommey 2018-06-07 05:52:49 UTC
FWIW, it works with both BFD ld and lld.
Comment 5 Sourceware Commits 2018-06-20 08:28:13 UTC
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.
Comment 6 Mike Hommey 2018-06-20 09:02:41 UTC
Thanks. Is this going to make the cut for 2.31?
Comment 7 Cary Coutant 2018-06-20 20:33:07 UTC
Fixed for binutils 2.31.