Bug 22677 - ld -r --gc-section does not KEEP .init_array/.fini_array
Summary: ld -r --gc-section does not KEEP .init_array/.fini_array
Status: RESOLVED FIXED
Alias: None
Product: binutils
Classification: Unclassified
Component: ld (show other bugs)
Version: 2.30
: P2 normal
Target Milestone: 2.30
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-01-05 09:38 UTC by David Leonard
Modified: 2018-03-31 12:43 UTC (History)
2 users (show)

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


Attachments
Proposed patch (674 bytes, patch)
2018-01-11 17:02 UTC, Nick Clifton
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description David Leonard 2018-01-05 09:38:38 UTC
"ld -r --gc-collect" drops the .init_array/.fini_array sections, although it
preserves .init, .fini and the .preinit_array sections. It should probably
KEEP() the .init_array/.fini_array sections too? (And others?)

There doesn't seem to be a clean indirect way to make --gc-collect keep the
.{init,fini}_array sections. A (noisy) workaround is to add this linker
script as an argument:

  SECTIONS {
    .init_array : { KEEP (*(.init_array)) }
    .fini_array : { KEEP (*(.fini_array)) }
  }

My use case is using ld -r as part of a post-processing step to reduce shared
library size in a firmware image. The gc root set is passed into ld as -u
options via a @file.

GNU ld (GNU Binutils for Ubuntu) 2.29.1
gcc version 7.2.0 (Ubuntu 7.2.0-8ubuntu3) 
Target: x86_64-linux-gnu

The KEEP() for these arrays appears to have been lost in commit
3cff7cc71fc25ffa83e7d5ce8f4aeb308fb656db ("Don't combine .init_array.*
or .fini_array.* when do relocatable linking.")
Comment 1 David Leonard 2018-01-05 09:52:33 UTC
Submitted too early, sorry. I mean --gc-section not --gc-collect.


Example

$ cat L.c
int L4var = 4;
int L5var;
int L5init() {
	L5var = 5;
}
__attribute__((constructor))
static void Lconstructor() {
	L5init();
}

$ gcc -ffunction-sections -fdata-sections -c -o L.o L.c
$ ld -r --gc-section L.o -u L4var -o libL.o
$ nm libL.o
0000000000000000 D L4var


With workaround:

$ ld -r --gc-section L.o -u L4var -o libL.o fixup.ldscript 
ld: warning: fixup.ldscript contains output sections; did you forget -T?
$ nm libL.o
0000000000000000 D L4var
0000000000000000 T L5init
0000000000000004 C L5var
0000000000000000 t Lconstructor
Comment 2 Nick Clifton 2018-01-08 12:15:08 UTC
(In reply to David Leonard from comment #0)

Hi David,

> "ld -r --gc-collect" drops the .init_array/.fini_array sections, although it
> preserves .init, .fini and the .preinit_array sections. It should probably
> KEEP() the .init_array/.fini_array sections too? (And others?)

Indeed it should.

> There doesn't seem to be a clean indirect way to make --gc-collect keep the
> .{init,fini}_array sections. A (noisy) workaround is to add this linker
> script as an argument:
> 
>   SECTIONS {
>     .init_array : { KEEP (*(.init_array)) }
>     .fini_array : { KEEP (*(.fini_array)) }
>   }

This appears to be an artifact of the linker that you are using.  If you
build an x86_64 linker using the FSF sources and run "ld --verbose" you
should see this as part of the output:

  .init_array     :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }

So the input sections should survive garbage collection.

I think however that the ubuntu version of the linker that you are using may
have its own, different built-in linker script, without the KEEP statements.
Please could you check ?  If I am right, then you need to complain to Ubuntu
and not us...

Cheers
  Nick
Comment 3 David Leonard 2018-01-11 00:24:29 UTC
Hi Nick,

> This appears to be an artifact of the linker that you are using.  If you
> build an x86_64 linker using the FSF sources and run "ld --verbose" you
> should see this as part of the output

You are right that KEEPs for init_array/fini_array are present when -r is not supplied. But they are lost with -r.

> I think however that the ubuntu version of the linker that you are using may
> have its own, different built-in linker script, without the KEEP statements.
> Please could you check ?

Checked. I found the same issue with a build from clean sources:

$ wget http://mirror.aarnet.edu.au/pub/gnu/binutils/binutils-2.29.1.tar.xz
$ tar xf binutils-2.29.1.tar.xz
$ mkdir build-clean /tmp/usr
$ cd build-clean
$ ../binutils-2.29.1/configure --prefix=/tmp/usr && make && make install
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking target system type... x86_64-pc-linux-gnu
[...]

$ /tmp/usr/bin/ld -r --verbose
GNU ld (GNU Binutils) 2.29.1
  Supported emulations:
   elf_x86_64
   elf32_x86_64
   elf_i386
   elf_iamcu
   i386linux
   elf_l1om
   elf_k1om
using internal linker script:
==================================================
/* Script for ld -r: link without relocation */
/* Copyright (C) 2014-2017 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
	      "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
[...]

$ /tmp/usr/bin/ld -r --verbose | grep -C1 KEEP
  {
    KEEP (*(SORT_NONE(.init)))
  }
--
  {
    KEEP (*(SORT_NONE(.fini)))
  }
--
  .eh_frame_hdr : { *(.eh_frame_hdr)  }
  .eh_frame     0 : ONLY_IF_RO { KEEP (*(.eh_frame))  }
  .gcc_except_table 0 : ONLY_IF_RO { *(.gcc_except_table
--
  /* Exception handling  */
  .eh_frame     0 : ONLY_IF_RW { KEEP (*(.eh_frame))  }
  .gnu_extab    0 : ONLY_IF_RW { *(.gnu_extab) }
--
  {
    KEEP (*(.preinit_array))
  }
  .jcr          0 : { KEEP (*(.jcr)) }
  .dynamic      0 : { *(.dynamic) }
--
  .debug_addr     0 : { *(.debug_addr) }
  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
}


Re-verified that gc closure over .init_array still fails until explicit KEEPs added.

mklibs$ gcc -ffunction-sections -fdata-sections -c -o L.o L.c
mklibs$ /tmp/usr/bin/ld -r --gc-section L.o -u L4var -o libL.o
mklibs$ nm libL.o
0000000000000000 D L4var

mklibs$ cat fixup.ldscript 
SECTIONS {
  .init_array    : { KEEP (*(.init_array)) }
  .fini_array    : { KEEP (*(.fini_array)) }
}
mklibs$ /tmp/usr/bin/ld -r --gc-section L.o -u L4var -o libL.o fixup.ldscript
/tmp/usr/bin/ld: warning: fixup.ldscript contains output sections; did you forget -T?
mklibs$ nm libL.o
0000000000000000 D L4var
0000000000000000 T L5init
0000000000000004 C L5var
0000000000000000 t Lconstructor
Comment 4 Nick Clifton 2018-01-11 17:02:37 UTC
Created attachment 10728 [details]
Proposed patch

Hi David,

  Sorry - I missed the "-r" option in your initial bug report.

  This does pose an interesting problem however as your linker script 
  fragment is only a partial solution.  The .init_array and .fini_array 
  sections can have a priority value as a suffix to their name, and this 
  suffix must be preserved when linking with -r.  (See 
  ld/testsuite/ld-elf/init-fini-arrays.[sd] for an example of this). Hence 
  using:

     .init_array { KEEP (.init_array.*) }

  will not work as it looses the section suffixes.  So instead I am proposing
  the attached patch which adds a special case for init and fini arrays to 
  the garbage collection code in the BFD library.  Please could you try it
  out and let me know if it works for you.

Cheers
  Nick
Comment 5 Sourceware Commits 2018-01-11 17:46:18 UTC
The master branch has been updated by H.J. Lu <hjl@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=8b6f4cd34fdde524ea035c65f7d48aaa3fb449b5

commit 8b6f4cd34fdde524ea035c65f7d48aaa3fb449b5
Author: H.J. Lu <hjl.tools@gmail.com>
Date:   Thu Jan 11 09:42:12 2018 -0800

    ld: Keep PREINIT_ARRAY/INIT_ARRAY/FINI_ARRAY sections for -r --gc-sections
    
    We must keep all PREINIT_ARRAY, INIT_ARRAY as well as FINI_ARRAY sections
    for ld -r --gc-sections.
    
    bfd/
    
    	PR ld/22677
    	* elflink.c (bfd_elf_gc_sections): Keep all PREINIT_ARRAY,
    	INIT_ARRAY as well as FINI_ARRAY sections for ld -r --gc-sections.
    
    ld/
    
    	PR ld/22677
    	* scripttempl/elf.sc (PREINIT_ARRAY): New.
    	Don't add .preinit_array for ld -r.
    	* testsuite/ld-elf/pr22677.d: New file.
    	* testsuite/ld-elf/pr22677.s: Likewise.
Comment 6 H.J. Lu 2018-01-11 17:57:11 UTC
Fixed for 2.30.
Comment 7 David Leonard 2018-01-12 01:16:05 UTC
> Please could you try it out and let me know if it works for you.
Confirmed. My tests pass without workaround. I used current master ld (89a7f793f1). Thanks!