c++ldfmtspdloggold-linker

Linking error in static linking with -gc-sections option


I was trying to produce a simple, fully statically linked binary so that it can easily run on distributions such as Alpine without having to install anything else. In my use case, I'm only using a couple of header only libraries, namely fmt and spdlog.

Let's say I want to compile the following file into a fully-static binary:

#define FMT_HEADER_ONLY
#define SPDLOG_FMT_EXTERNAL

#include <fmt/core.h>
#include <spdlog/spdlog.h>

int main(int argc, char* argv[]) {
  fmt::print("Hello, {}!\n", "fmt");
  spdlog::info("Hello, {}!", "spdlog");
  return 0;
}

If I use the following command, I get the expected result:

g++ test.cpp -o out -O3 -fuse-ld=gold -static # Compiles correctly
ldd out    # not a dynamic executable
./out      # Prints the expected output

But looking at some practices to reduce the binary size, and since it is what the Bazel optimized build does by default, I would like to like to pass the -gc-sections flag to the linker. The problem is that, when I do

g++ test.cpp -o out -O3 -fuse-ld=gold -Wl,-gc-sections -static

I get the following error:

/usr/lib/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../lib/libc.a(pthread_create.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
/usr/lib/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../lib/libc.a(pthread_create.o)(.note.stapsdt+0x74): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
collect2: error: ld returned 1 exit status

Playing around a bit with different combinations (trying different linkers, adding and removing flags) I obtained the following table:

-fuse-ld -Wl,-gc-sections Binary size
bfd Yes 2.0M
bfd No 2.5M
gold Yes ERROR
gold No 2.5M

So, it is clear that I could either remove the flag and still use the gold linker or just use another linker. I could also just give up on statically linking everything.

What I'm interested in is understanding why I get that error. Is it something I'm doing wrong, I'm missing something or is it a bug in gold?

Additional information:


Solution

  • What I'm interested in is understanding why I get that error. Is it something I'm doing wrong, I'm missing something or is it a bug in gold?

    You aren't missing anything (at least nothing relevant to the linkage failure) and aren't doing anything wrong. There is a corner-case bug in ld.gold.


    Repro

    I have your progam source in test.cpp. I haven't installed the header-only libraries spdlog or fmt; I've just cloned the repos for the present purpose.

    $ g++ --version | head -n1
    g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
    
    $ ld.gold --version | head -n1
    GNU gold (GNU Binutils for Ubuntu 2.42) 1.16
    
    $ export CPATH=$HOME/develop/spdlog/include:$HOME/develop/fmt/include
    $ g++ -c test.cpp
    

    Link without -gc-sections:

    $ g++ test.o -fuse-ld=gold -static; echo Done
    Done
    

    And with -gc-sections:

    $ g++ test.o -fuse-ld=gold -static -Wl,-gc-sections; echo Done
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x74): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    collect2: error: ld returned 1 exit status
    Done
    

    Trying other linkers

    Besides gold, -fuse-ld recognises three other ELF linkers. Let's try them all at that linkage:

    ld.bfd (the default GNU linker)

    $ ld.bfd --version | head -n1
    GNU ld (GNU Binutils for Ubuntu) 2.42
    
    $ g++ test.o -fuse-ld=bfd -static -Wl,-gc-sections
    $ ./a.out
    Hello, fmt!
    [2025-05-06 18:17:10.378] [info] Hello, spdlog!
    

    ld.lld (the LLVM linker)

    $ ld.lld --version | head -n1
    Ubuntu LLD 18.1.6 (compatible with GNU linkers)
    
    $ g++ test.o -fuse-ld=lld -static -Wl,-gc-sections
    $ ./a.out
    Hello, fmt!
    [2025-05-06 18:18:21.994] [info] Hello, spdlog!
    

    ld.mold (the Modern linker)

    $ ld.mold --version | head -n1
    mold 2.30.0 (compatible with GNU ld)
    
    $ g++ test.o -fuse-ld=mold -static -Wl,-gc-sections
    $ ./a.out
    Hello, fmt!
    [2025-05-06 18:22:47.597] [info] Hello, spdlog!
    

    So gold is the only one that can't link this program.


    What is gold doing wrong?

    The first diagnostic:

    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x14): \
    error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    

    is reporting that the relocation target at offset 0x14 in section .note.stapsdt of object file libc.a(pthread_create.o) refers to the local symbol .text, which is symbol #1 in that object file, and that this relocation can't be carried out because the section in which that symbol is defined has been discarded.

    The second diagnostic is the just same, except that the relocation target this time is at offset 0x74, so we'll just pursue the first diagnostic.

    Let's check that it's true.

    First get that object file:

    $ ar -x $(realpath /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a) pthread_create.o
    

    Check out the relocations for its note.stapsdt section:

    $ readelf -rW pthread_create.o
    ...[cut]...
    Relocation section '.rela.note.stapsdt' at offset 0x46f8 contains 4 entries:
        Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
    0000000000000014  0000000100000001 R_X86_64_64            0000000000000000 .text + 40e
    000000000000001c  0000003000000001 R_X86_64_64            0000000000000000 _.stapsdt.base + 0
    0000000000000074  0000000100000001 R_X86_64_64            0000000000000000 .text + c7b
    000000000000007c  0000003000000001 R_X86_64_64            0000000000000000 _.stapsdt.base + 0
    ...[cut]...
    

    Yes, it has relocation targets at offset 0x14 and 0x74. The first one is to be patched using the address of symbol # 1 ( = Info >> 32) in the symbol table (which we're told is .text) + 0x40e. Symbol #1 in pthread_create.o is

    $ readelf -sW pthread_create.o | grep ' 1:'
         1: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 .text
         
    

    indeed local symbol .text, (a section name) and it is defined in section #2 of the file which of course is:

    $ readelf -SW pthread_create.o | grep ' 2]'
      [ 2] .text             PROGBITS        0000000000000000 000050 001750 00  AX  0   0 16
      
    

    the .text section.

    So the diagnostic reports that gold has binned the .text section of pthread_create.o. Let's ask gold to tell us what sections of pthread_create.o it discarded.

    $ g++ test.o -fuse-ld=gold -static -Wl,-gc-sections,-print-gc-sections 2>&1 | grep pthread_create.o
    /usr/bin/ld.gold: removing unused section from '.text' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.bss' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.rodata.str1.1' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.rodata.str1.8' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.text.unlikely' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.rodata.str1.16' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.rodata.cst4' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.rodata' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.gold: removing unused section from '.rodata.cst8' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x74): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    

    It discarded 10 of the:

    $ readelf -SW pthread_create.o | head -n1
    There are 23 section headers, starting at offset 0x48c0:
    

    23 sections in the file, including .text, as compared with:

    $ g++ test.o -fuse-ld=bfd -static -Wl,-gc-sections,-print-gc-sections 2>&1 | grep pthread_create.o
    /usr/bin/ld.bfd: removing unused section '.group' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.bfd: removing unused section '.stapsdt.base[.stapsdt.base]' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.bfd: removing unused section '.rodata.cst4' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    /usr/bin/ld.bfd: removing unused section '.rodata' in file '/usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)'
    

    the 4 sections discarded by ld.bfd, excluding .text. gold also retains 2 sections (.group,stapsdt.base) that bfd discards, but the outcome says that gold has chucked out a baby with the bathwater.


    The linkage error is a (all but) a false alarm

    The retention of section .note.stapsdt from pthread_create.o sets it off. This section is retained because any output .note.* section will be a GC-root section for any linker: .note sections are conventionally reserved for special information to be consumed by other programs, and as such are unconditionaally retained in the same way as ones defining external symbols. note.stapsdt sections in particular are emitted to expose patch points for the runtime insertion of Systemtap instrumentation hooks.

    Presumably, you don't care if this program has Systemtap support. You've just got it because it's compiled into pthread_create.o (and elsewhere in GLIBC). The enabling .note.stapsdt section it a GC-root section in pthread_create.o that references its .text section. But your program has no functional need for that .text section. We can observe this by just blowing through the linkage failure with:

    $ rm a.out
    $ g++ test.o -fuse-ld=gold -static -Wl,-gc-sections,--noinhibit-exec
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x74): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    

    --noinhibit-exec tells the linker to output a viable image if it can make one, notwithstanding errors. And in this case:

    $ ./a.out
    Hello, fmt!
    [2025-05-07 10:51:57.987] [info] Hello, spdlog!
    

    The .text section of pthread_create.o is garbage-collected; the linkage errors, but the program is perfectly fine.

    So we'd expect a clean linkage if we yank .note.stapsdt out of pthread_create.o and interpose the modified object file in the link, and so we do:

    $ objcopy --remove-section='.note.stapsdt' pthread_create.o pthread_create_nostap.o
    $ g++ test.o pthread_create_nostap.o -fuse-ld=gold -static -Wl,-gc-sections
    $ ./a.out
    Hello, fmt!
    [2025-05-07 11:08:27.647] [info] Hello, spdlog!
    

    The program is fine without the .note.stapsdt and/or the .text section of pthread_create.o, but Systemtap would not be fine with the program. That's the cash value of the linkage failure.


    The linkage error has nothing to do with your particular program.

    Check out this deranged linkage:

    $ cat main.c
    int main(void)
    {
        return 0;
    }
    
    $ gcc main.c -static -Wl,-gc-sections,--whole-archive,-lc,--no-whole-archive
    /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(dso_handle.o):(.data.rel.ro.local+0x0): multiple definition of `__dso_handle'; /usr/lib/gcc/x86_64-linux-gnu/13/crtbeginT.o:(.data+0x0): first defined here
    /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(rcmd.o): in function `__validuser2_sa':
    (.text+0x5e8): warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
    /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(rcmd.o): note: the message above does not take linker garbage collection into account
    /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(dl-reloc-static-pie.o): in function `_dl_relocate_static_pie':
    (.text+0x0): multiple definition of `_dl_relocate_static_pie'; /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/crt1.o:(.text+0x30): first defined here
    collect2: error: ld returned 1 exit status
    

    where I'm trying and failing to make a garbage-collected static linkage of the whole of GLIBC into a do-nothing program, with the default linker.

    Now let's repeat the failure with gold:

    $ gcc main.c -fuse-ld=gold -static -Wl,-gc-sections,--whole-archive,-lc,--no-whole-archive
    /usr/bin/ld.gold: error: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(dso_handle.o): multiple definition of '__dso_handle'
    /usr/bin/ld.gold: /usr/lib/gcc/x86_64-linux-gnu/13/crtbeginT.o: previous definition here
    /usr/bin/ld.gold: error: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(dl-reloc-static-pie.o): multiple definition of '_dl_relocate_static_pie'
    /usr/bin/ld.gold: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/crt1.o: previous definition here
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_cond_destroy.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_cond_init.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_create.o)(.note.stapsdt+0x74): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_join_common.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_join_common.o)(.note.stapsdt+0x5c): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_mutex_destroy.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_mutex_init.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_mutex_timedlock.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_mutex_timedlock.o)(.note.stapsdt+0x68): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_mutex_timedlock.o)(.note.stapsdt+0xbc): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_mutex_timedlock.o)(.note.stapsdt+0x11c): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(pthread_rwlock_destroy.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(____longjmp_chk.o)(.note.stapsdt+0x14): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libc.a(____longjmp_chk.o)(.note.stapsdt+0x64): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    collect2: error: ld returned 1 exit status
    

    Now we're sprayed with:

    libc.a(???.o)(.note.stapsdt+???): error: relocation refers to local symbol ".text" [1], which is defined in a discarded section
    

    errors that weren't there before, the great majority of the ???.o being pthread_???.o.


    How does gold come to disregard the note.stapsdt references into .text in pthread_create.o?

    To understand that I had to get the binutils-gdb source code, study the gold source and debug a build of it on the problem linkage with ad-hoc diagnostics added. Here is the gist.

    gold's GC algorithm initally reserves a set of GC-root sections in the pre-GC linkage to be retained unconditionally. These include the section that contains the _start symbol (or other non-default program entry symbol), plus all sections that match a hard-coded set of prefixes or names, including all .note.* sections. So pthread_create.o(.note.stapsdt) is one of them.

    For each section src_object.o(.src_sec) of each object file linked - provided it is a type-ALLOC section - GC maps that section to list of the relocations ( = references) from src_object.o(.src_sec) into any other input section dest_object.o(.dest_sec), so that if src_object.o(.src_sec) is retained then dest_object.o(.dest_sec) will also be retained. An ALLOC section here means one that will occupy space in the process image, as indicated by by flag SHF_ALLOC set in the section header. This property can be taken to mean that the section would be worth garbage collecting. The algorithm discovers the relocations by reading the corresponding relocations section src_object.o(.rel[a].src_sec).

    Then, starting with the GC-root sections, the algorithm recursively determines for each retained section what other sections it refers to, as per its associated relocations, and adds the sections referred to to the retained list. Finally, all sections not retained are discarded.

    This is all as should be, except for the winnowing out of sections that are not type ALLOC from relocations gathering. That is a flaw, because a .note.* section, depending on its kind, might be type ALLOC (e.g. .note.gnu.property, .note.ABI-tag in this linkage) or it might not (e.g. .note.gnu.gold-version, note.stapsdt in this linkage), and being non-ALLOC does not preclude it having relocations into ALLOC sections. The bug will sleep soundly as long as a non-ALLOC .note.* section that is winnowed out of GC relocations processing does not contain relocations.

    Section pthread_create.o(.note.stapsdt) is non-ALLOC:

    $ readelf -SW pthread_create.o | egrep '(.note.stapsdt|Section|Flg)'
    Section Headers:
      [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
      [ 8] .note.stapsdt     NOTE            0000000000000000 001928 0000c8 00      0   0  4
      [ 9] .rela.note.stapsdt RELA            0000000000000000 0046f8 000060 18   I 20   8  8
      
    

    (Flg A not set), but does have relocations. So the bug bites. The GC algorithm never sees the associated relocations in rela.note.stapsdt that refer to pthread_create.o(.text). When it finds that pthread_create.o(.note.stapsdt) is non-ALLOC it just skips over .pthread_create.o(.rela.note.stapsdt) without further ado.

    Thus GC never records that pthread_create.o(.note.stapsdt) - retained - refers to pthread_create.o(.text), and since nothing else refers to pthread_create.o(.text), it is discarded. When time comes to apply relocations to pthread_create.o(.note.stapsdt), the section they refer to is no longer in the linkage.

    A comment in file binutils-gdb/gold/reloc.cc explaining the flawed winnowing test:

      // We are scanning relocations in order to fill out the GOT and
      // PLT sections.  Relocations for sections which are not
      // allocated (typically debugging sections) should not add new
      // GOT and PLT entries.  So we skip them unless this is a
      // relocatable link or we need to emit relocations.  FIXME: What
      // should we do if a linker script maps a section with SHF_ALLOC
      // clear to a section with SHF_ALLOC set?
      
    

    illuminates how .note.stapsdt sections fall through the cracks. It is unclear to me why this a priori logic should be allowed to prevail over contrary evidence that a non-ALLOC .somesec section does have relocations as provided by the existence of a .rel[a].somesec section. If such non-ALLOC sections were acknowledged they would need to be deferred for special "inverted" GC-handling: Instead of taking their retention to entail the retention of any sections that they transitively refer to, GC would need to determine what other sections are to be discarded without reference to the non-ALLOC ones and then also discard all the non-ALLOC ones that refer only to already discarded sections. The open FIXME is pointed in our context because it foresees the a priori logic coming unstuck, but not in quite the way that we observe.


    Is there a gold workaround?

    That code comment kindles hope that we might dodge the bug if we were either to:-

    or:-

    But gold will not play with either of these desperate ruses. The first one:

    $ g++ -o bigobj.o test.o -fuse-ld=gold -static -Wl,-r,--entry=_start,-gc-sections
    /usr/bin/ld.gold: error: cannot mix -r with --gc-sections or --icf
    /usr/bin/ld.gold: internal error in do_layout, at ../../gold/object.cc:1939
    collect2: error: ld returned 1 exit status
    

    And the second one:

    $ g++ test.o -fuse-ld=gold -static -Wl,-q,-gc-sections
    /usr/bin/ld.gold: internal error in do_layout, at ../../gold/object.cc:1939
    collect2: error: ld returned 1 exit status
    

    Both of them work with ld.bfd, where they're not needed (They also work with mold, and both fail with lld). AFAICS the only remedies that work for gold are the ones we've already seen: either link with --noinhibit-exec, or else use objcopy to make santitised copies of the problem object files from which redundant note.stapsdt sections are deleted. At a stretch these might be called workarounds, but hardly gold workarounds. Obviously a reasonable person would give up on gold and use one of the other linkers that just works (as indeed you are resigned to do).

    Reporting the bug will likely be thankless because gold is moribund, as @Eljay commented.


    Something you are maybe missing (though not relevantly to the linkage failure).

    The linkage option -gc-sections is routinely used in conjunction with the compiler options -ffunction-sections and -fdata-sections. These respectively direct the compiler to emit each function definition or data object definition in a section by itself, and that empowers GC to work unhandicapped by facing unreferenced definitions that it cannot discard because they are located in sections that also contain referenced definitions.

    In code from which template instantiations are altogether absent or not prevalent, omitting -ffunction-sections, -fdata-sections at compilation will normally render the pay-off of -gc-sections considerably sub-optimal. If template instantiations are prevalent the handicap is mitigated pro rata to their prevalence by the fact that the C++ compiler for technical reasons places template instantiations in their own sections anyway. The handicap is further mitigated by optimisation level, so for a C++ program made almost entirely of template instantiations such as yours, with -O3 optimisation, -ffunction-sections, fdata-sections at compilation may have little to no benefit on GC. But as a rule they will produce a GC benefit and the only effect they can have is for the better.