[PATCH] Support SHF_GNU_RETAIN ELF section flag

Jozef Lawrynowicz jozef.l@mittosystems.com
Wed Sep 23 12:13:48 GMT 2020
Attached a revised patch:
- Removed mmix-*-* XFAILs
- Fixed SHF_GNU_RETAIN being clobbered when using the .section directive
  without the "R" flag, after the section has previously been marked
  with SHF_GNU_RETAIN. Added tests for this behavior.

Thanks,
Jozef

On Tue, Sep 22, 2020 at 09:29:33PM +0100, Jozef Lawrynowicz wrote:
> The attached patch adds support for the new SHF_GNU_RETAIN ELF section
> flag, which was discussed on the GNU gABI mailing list here:
> https://sourceware.org/pipermail/gnu-gabi/2020q3/000429.html
> 
> The flag is GNU-specific so uses a bit in the SHF_MASKOS mask.
> Its precise definition is as follows:
> 
> =======================================================================
> Section Attribute Flags
> +-------------------------------------+
> | Name           | Value              |
> +-------------------------------------+
> | SHF_GNU_RETAIN | 0x200000 (1 << 21) |
> +-------------------------------------+
> 
> SHF_GNU_RETAIN
>   The link editor should not garbage collect the section if it is
>   unused.
> 
> =======================================================================
> 
> The overall intention for this new flag is to enable a new "retain"
> attribute to be applied to declarations of functions and data in the
> source code. This attribute can be used to ensure the definition
> associated with the declaration is present in the linked output file,
> even if linker garbage collection would normally remove the containing
> section because it is unused.
> 
> The new ".retain" assembler directive can be used to apply
> SHF_GNU_RETAIN to a section. GCC will emit this directive when
> assembling definitions of functions and data that have had the "retain"
> attribute applied.
> 
> Note that there is *not* a direct mapping of SHF_GNU_RETAIN to the BFD
> section flag SEC_KEEP. SEC_KEEP would prevent the user being able to
> explicitly remove an SHF_GNU_RETAIN section by placing it in /DISCARD/,
> which could be necessary in some situations.
> 
> I successfully regtested the patch for the Binutils, GAS and LD
> testsuites for the following CPUs, applying the "-elf" suffix when
> configuring:
>   aarch64 alpha arc arm avr bfin bpf cr16 cris crx csky d10v d30v dlx
>   epiphany fr30 frv ft32 h8300 hppa i386 ia64 ia64-vms ip2k iq2000 lm32
>   m32c m32r m68hc11 m68hc12 m68k mcore mep metag microblaze mips mmix
>   moxie msp430 mt nds32 nfp nios2 or1k pj ppc pru riscv rl78 rx s12z
>   s390 score sh sparc spu tic6x tilegx tilepro v850 vax visium wasm32
>   x86-64 x86 xc16x xgate xstormy16 xtensa z80
> 
> The new tests are passing for all targets except mmix-elf. This target
> has a lot of LD failures, particularly --gc-sections doesn't appear to
> have any effect. I don't know anything about the target, but I wonder if
> it should be added to the hard-coded list of targets that doesn't
> support --gc-sections.  I have therefore XFAIL'd the new LD tests for
> this target.
> 
> I also regtested for i386-pe, to ensure there was no spill-over of the
> new functionality into any non-ELF areas.
> 
> Ok to apply?
> 
> Thanks,
> Jozef
-------------- next part --------------
>From 862a04415e1b490b4561fe5a44c9fa4120c50825 Mon Sep 17 00:00:00 2001
From: Jozef Lawrynowicz <jozef.l@mittosystems.com>
Date: Tue, 22 Sep 2020 21:00:35 +0100
Subject: [PATCH] Support SHF_GNU_RETAIN ELF section flag

The GNU-specific SHF_GNU_RETAIN ELF section flag is defined as follows:

=======================================================================
Section Attribute Flags
+-------------------------------------+
| Name           | Value              |
+-------------------------------------+
| SHF_GNU_RETAIN | 0x200000 (1 << 21) |
+-------------------------------------+

SHF_GNU_RETAIN
  The link editor should not garbage collect the section if it is
  unused.

=======================================================================

The new ".retain" assembler directive can be used to apply
SHF_GNU_RETAIN to a section.

Note that there is not a direct mapping of SHF_GNU_RETAIN to the BFD
section flag SEC_KEEP. SEC_KEEP would prevent the user being able to
explicitly remove an SHF_GNU_RETAIN section by placing it in /DISCARD/.

bfd/ChangeLog:

2020-09-22  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* elflink.c (bfd_elf_gc_sections): gc_mark the section if
	SHF_GNU_RETAIN is set.

binutils/ChangeLog:

2020-09-22  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* NEWS: Announce SHF_GNU_RETAIN.
	* readelf.c (get_elf_section_flags): Handle SHF_GNU_RETAIN.
	* testsuite/binutils-all/readelf.exp: Run new test.
	Don't run run_dump_test when there isn't an assembler available.
	* testsuite/binutils-all/retain1.d: New test.
	* testsuite/binutils-all/retain1.s: New test.

gas/ChangeLog:

2020-09-22  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* NEWS: Announce .retain directive and SHF_GNU_RETAIN.
	* config/obj-elf.c (elf_pseudo_table): Add "retain".
	(obj_elf_retain): New.
	(obj_elf_parse_section_letters): Handle 'R' flag.
	* doc/as.texi: Document .retain directive.
	(Section): Document 'R' flag.
	* testsuite/gas/elf/elf.exp: Run new tests.
	* testsuite/gas/elf/retain1.d: New test.
	* testsuite/gas/elf/retain1.s: New test.
	* testsuite/gas/elf/retain2.d: New test.
	* testsuite/gas/elf/retain2.l: New test.
	* testsuite/gas/elf/retain2.s: New test.
	* testsuite/gas/elf/retain3.d: New test.
	* testsuite/gas/elf/retain3.s: New test.
	* testsuite/gas/elf/section10.d: Adjust test.

include/ChangeLog:

2020-09-22  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* elf/common.h (SHF_GNU_RETAIN): Define.

ld/ChangeLog:

2020-09-22  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* NEWS: Announce SHF_GNU_RETAIN.
	* ld.texi (garbage collection): Document SHF_GNU_RETAIN.
	(Output Section Discarding): Likewise.
	* testsuite/ld-elf/elf.exp: Run new tests.
	* testsuite/ld-elf/retain1.msg: New test.
	* testsuite/ld-elf/retain1.s: New test.
	* testsuite/ld-elf/retain2.d: New test.
	* testsuite/ld-elf/retain2.ld: New test.
	* testsuite/ld-elf/retain2.map: New test.
	* testsuite/ld-elf/retain3.msg: New test.
	* testsuite/ld-elf/retain3.s: New test.
	* testsuite/ld-elf/retain4.s: New test.
	* testsuite/ld-elf/retain5.s: New test.
	* testsuite/ld-elf/retain6lib.s: New test.
	* testsuite/ld-elf/retain6main.s: New test.
	* testsuite/ld-elf/retain7.msg: New test.
	* testsuite/ld-elf/retain7lib.s: New test.
	* testsuite/ld-elf/retain7main.s: New test.
---
 bfd/elflink.c                               |   3 +-
 binutils/NEWS                               |   4 +
 binutils/readelf.c                          |   4 +
 binutils/testsuite/binutils-all/readelf.exp |   6 +-
 binutils/testsuite/binutils-all/retain1.d   |  17 +++
 binutils/testsuite/binutils-all/retain1.s   | 114 ++++++++++++++++++++
 gas/NEWS                                    |   4 +
 gas/config/obj-elf.c                        |  71 +++++++++++-
 gas/doc/as.texi                             |  20 ++++
 gas/testsuite/gas/elf/elf.exp               |   7 ++
 gas/testsuite/gas/elf/retain1.d             |  24 +++++
 gas/testsuite/gas/elf/retain1.s             | 114 ++++++++++++++++++++
 gas/testsuite/gas/elf/retain2.d             |   3 +
 gas/testsuite/gas/elf/retain2.l             |   3 +
 gas/testsuite/gas/elf/retain2.s             |   7 ++
 gas/testsuite/gas/elf/retain3.d             |  24 +++++
 gas/testsuite/gas/elf/retain3.s             | 104 ++++++++++++++++++
 gas/testsuite/gas/elf/retain4.d             |   6 ++
 gas/testsuite/gas/elf/retain4.s             |  20 ++++
 gas/testsuite/gas/elf/retain5.d             |   6 ++
 gas/testsuite/gas/elf/retain5.s             |  18 ++++
 gas/testsuite/gas/elf/section10.d           |   4 +-
 include/elf/common.h                        |   1 +
 ld/NEWS                                     |   4 +
 ld/ld.texi                                  |   8 ++
 ld/testsuite/ld-elf/elf.exp                 |  33 ++++++
 ld/testsuite/ld-elf/retain1.msg             |   9 ++
 ld/testsuite/ld-elf/retain1.s               | 114 ++++++++++++++++++++
 ld/testsuite/ld-elf/retain2.d               |   6 ++
 ld/testsuite/ld-elf/retain2.ld              |   7 ++
 ld/testsuite/ld-elf/retain2.map             |  32 ++++++
 ld/testsuite/ld-elf/retain3.msg             |   9 ++
 ld/testsuite/ld-elf/retain3.s               | 104 ++++++++++++++++++
 ld/testsuite/ld-elf/retain4.s               |  19 ++++
 ld/testsuite/ld-elf/retain5.s               |  13 +++
 ld/testsuite/ld-elf/retain6lib.s            |   6 ++
 ld/testsuite/ld-elf/retain6main.s           |   5 +
 ld/testsuite/ld-elf/retain7.msg             |   1 +
 ld/testsuite/ld-elf/retain7lib.s            |  17 +++
 ld/testsuite/ld-elf/retain7main.s           |  13 +++
 40 files changed, 977 insertions(+), 7 deletions(-)
 create mode 100644 binutils/testsuite/binutils-all/retain1.d
 create mode 100644 binutils/testsuite/binutils-all/retain1.s
 create mode 100644 gas/testsuite/gas/elf/retain1.d
 create mode 100644 gas/testsuite/gas/elf/retain1.s
 create mode 100644 gas/testsuite/gas/elf/retain2.d
 create mode 100644 gas/testsuite/gas/elf/retain2.l
 create mode 100644 gas/testsuite/gas/elf/retain2.s
 create mode 100644 gas/testsuite/gas/elf/retain3.d
 create mode 100644 gas/testsuite/gas/elf/retain3.s
 create mode 100644 gas/testsuite/gas/elf/retain4.d
 create mode 100644 gas/testsuite/gas/elf/retain4.s
 create mode 100644 gas/testsuite/gas/elf/retain5.d
 create mode 100644 gas/testsuite/gas/elf/retain5.s
 create mode 100644 ld/testsuite/ld-elf/retain1.msg
 create mode 100644 ld/testsuite/ld-elf/retain1.s
 create mode 100644 ld/testsuite/ld-elf/retain2.d
 create mode 100644 ld/testsuite/ld-elf/retain2.ld
 create mode 100644 ld/testsuite/ld-elf/retain2.map
 create mode 100644 ld/testsuite/ld-elf/retain3.msg
 create mode 100644 ld/testsuite/ld-elf/retain3.s
 create mode 100644 ld/testsuite/ld-elf/retain4.s
 create mode 100644 ld/testsuite/ld-elf/retain5.s
 create mode 100644 ld/testsuite/ld-elf/retain6lib.s
 create mode 100644 ld/testsuite/ld-elf/retain6main.s
 create mode 100644 ld/testsuite/ld-elf/retain7.msg
 create mode 100644 ld/testsuite/ld-elf/retain7lib.s
 create mode 100644 ld/testsuite/ld-elf/retain7main.s

diff --git a/bfd/elflink.c b/bfd/elflink.c
index 0e339f3c1e..6d1a1c5105 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -13977,7 +13977,8 @@ bfd_elf_gc_sections (bfd *abfd, struct bfd_link_info *info)
 			|| (elf_section_data (o)->this_hdr.sh_type
 			    == SHT_FINI_ARRAY)))
 		|| (elf_section_data (o)->this_hdr.sh_type == SHT_NOTE
-		    && elf_next_in_group (o) == NULL )))
+		    && elf_next_in_group (o) == NULL)
+		|| (elf_section_flags (o) & SHF_GNU_RETAIN)))
 	  {
 	    if (!_bfd_elf_gc_mark (info, o, gc_mark_hook))
 	      return FALSE;
diff --git a/binutils/NEWS b/binutils/NEWS
index c0dc73d7d8..6c7d3f3953 100644
--- a/binutils/NEWS
+++ b/binutils/NEWS
@@ -4,6 +4,10 @@
   symbol names.  In addition the --demangle=<style>, --no-demangle,
   --recurse-limit and --no-recurse-limit options are also now availale.
 
+* Add support for the SHF_GNU_RETAIN ELF section flag.
+  This flag specifies that the section should not be garbage collected by the
+  linker if it is unused.
+
 Changes in 2.35:
 
 * Changed readelf's display of symbol names when wide mode is not enabled.
diff --git a/binutils/readelf.c b/binutils/readelf.c
index cb4208f7b9..00502f7058 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -5977,6 +5977,8 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
       /* 24 */ { STRING_COMMA_LEN ("GNU_MBIND") },
       /* VLE specific.  */
       /* 25 */ { STRING_COMMA_LEN ("VLE") },
+      /* GNU specific.  */
+      /* 26 */ { STRING_COMMA_LEN ("GNU_RETAIN") },
     };
 
   if (do_section_details)
@@ -6010,6 +6012,7 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_EXCLUDE:		sindex = 18; break;
 	    case SHF_COMPRESSED:	sindex = 20; break;
 	    case SHF_GNU_MBIND:		sindex = 24; break;
+	    case SHF_GNU_RETAIN:	sindex = 26; break;
 
 	    default:
 	      sindex = -1;
@@ -6108,6 +6111,7 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_EXCLUDE:		*p = 'E'; break;
 	    case SHF_COMPRESSED:	*p = 'C'; break;
 	    case SHF_GNU_MBIND:		*p = 'D'; break;
+	    case SHF_GNU_RETAIN:	*p = 'R'; break;
 
 	    default:
 	      if ((filedata->file_header.e_machine == EM_X86_64
diff --git a/binutils/testsuite/binutils-all/readelf.exp b/binutils/testsuite/binutils-all/readelf.exp
index 1fb36ae5c4..6dea09e305 100644
--- a/binutils/testsuite/binutils-all/readelf.exp
+++ b/binutils/testsuite/binutils-all/readelf.exp
@@ -364,8 +364,12 @@ readelf_wi_test
 readelf_compressed_wa_test
 
 readelf_dump_test
-run_dump_test "pr25543"
 
+# These dump tests require an assembler.
+if {[which $AS] != 0} then {
+    run_dump_test "pr25543"
+    run_dump_test "retain1"
+}
 
 # PR 13482 - Check for off-by-one errors when dumping .note sections.
 if {![binutils_assemble $srcdir/$subdir/version.s tmpdir/version.o]} then {
diff --git a/binutils/testsuite/binutils-all/retain1.d b/binutils/testsuite/binutils-all/retain1.d
new file mode 100644
index 0000000000..35d7373cc1
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1.d
@@ -0,0 +1,17 @@
+#source: retain1.s
+#readelf: -S --wide
+#name: readelf SHF_GNU_RETAIN
+
+#...
+  \[[ 0-9]+\] .bss.retain0.*WAR.*
+  \[[ 0-9]+\] .bss.retain1.*WAR.*
+  \[[ 0-9]+\] .data.retain2.*WAR.*
+  \[[ 0-9]+\] .bss.sretain0.*WAR.*
+  \[[ 0-9]+\] .bss.sretain1.*WAR.*
+  \[[ 0-9]+\] .data.sretain2.*WAR.*
+  \[[ 0-9]+\] .text.fnretain1.*AXR.*
+#...
+  \[[ 0-9]+\] .bss.lsretain0.*WAR.*
+  \[[ 0-9]+\] .bss.lsretain1.*WAR.*
+  \[[ 0-9]+\] .data.lsretain2.*WAR.*
+#pass
diff --git a/binutils/testsuite/binutils-all/retain1.s b/binutils/testsuite/binutils-all/retain1.s
new file mode 100644
index 0000000000..e799ff72ec
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1.s
@@ -0,0 +1,114 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, STT_OBJECT
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, STT_OBJECT
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, STT_OBJECT
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, STT_OBJECT
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, STT_OBJECT
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, STT_OBJECT
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, STT_FUNC
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"aw"
+	.type	retain0, STT_OBJECT
+	.retain	.bss.retain0
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"aw"
+	.type	retain1, STT_OBJECT
+	.retain	.bss.retain1
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"aw"
+	.type	retain2, STT_OBJECT
+	.retain	.data.retain2
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"aw"
+	.type	sretain0, STT_OBJECT
+	.retain	.bss.sretain0
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"aw"
+	.type	sretain1, STT_OBJECT
+	.retain	.bss.sretain1
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aw"
+	.type	sretain2, STT_OBJECT
+	.retain	.data.sretain2
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"ax"
+	.global	fnretain1
+	.type	fnretain1, STT_FUNC
+	.retain	.text.fnretain1
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, STT_FUNC
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"aw"
+	.type	lsretain0.2, STT_OBJECT
+	.retain	.bss.lsretain0
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aw"
+	.type	lsretain1.1, STT_OBJECT
+	.retain	.bss.lsretain1
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aw"
+	.type	lsretain2.0, STT_OBJECT
+	.retain	.data.lsretain2
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/gas/NEWS b/gas/NEWS
index 66afd0357b..0d63fad7b3 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -9,6 +9,10 @@
 
 * Configure with --enable-x86-used-note by default for Linux/x86.
 
+* Add support for the .retain directive, which applies the ELF SHF_GNU_RETAIN
+  flag to the specified section.  This flag specifies the section should not be
+  garbage collected by the linker if it is unused.
+
 Changes in 2.35:
 
 * X86 NaCl target support is removed.
diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c
index 9e39707801..5e384f2c58 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -78,6 +78,7 @@ static void obj_elf_gnu_attribute (int);
 static void obj_elf_tls_common (int);
 static void obj_elf_lcomm (int);
 static void obj_elf_struct (int);
+static void obj_elf_retain (int);
 
 static const pseudo_typeS elf_pseudo_table[] =
 {
@@ -119,6 +120,9 @@ static const pseudo_typeS elf_pseudo_table[] =
   /* A GNU extension for object attributes.  */
   {"gnu_attribute", obj_elf_gnu_attribute, 0},
 
+  /* A GNU extension for preventing linker garbage collection of sections.  */
+  {"retain", obj_elf_retain, 0},
+
   /* These are used for dwarf.  */
   {"2byte", cons, 2},
   {"4byte", cons, 4},
@@ -802,9 +806,15 @@ obj_elf_change_section (const char *name,
 		as_bad (_("changed section attributes for %s"), name);
 	    }
 	  else
-	    /* FIXME: Maybe we should consider removing a previously set
-	       processor or application specific attribute as suspicious ?  */
-	    elf_section_flags (sec) = attr;
+	    {
+	      /* Don't clobber SHF_GNU_RETAIN if it has been previously set on
+		 the section.  */
+	      attr |= (elf_section_flags (sec) & SHF_GNU_RETAIN);
+
+	      /* FIXME: Maybe we should consider removing a previously set
+		 processor or application specific attribute as suspicious ?  */
+	      elf_section_flags (sec) = attr;
+	    }
 
 	  if ((flags & SEC_MERGE) && old_sec->entsize != (unsigned) entsize)
 	    as_bad (_("changed section entity size for %s"), name);
@@ -857,6 +867,9 @@ obj_elf_parse_section_letters (char *str, size_t len,
 	case 'd':
 	  *gnu_attr |= SHF_GNU_MBIND;
 	  break;
+	case 'R':
+	  *gnu_attr |= SHF_GNU_RETAIN;
+	  break;
 	case '?':
 	  *is_clone = TRUE;
 	  break;
@@ -1986,6 +1999,58 @@ obj_elf_gnu_attribute (int ignored ATTRIBUTE_UNUSED)
   obj_elf_vendor_attribute (OBJ_ATTR_GNU);
 }
 
+/* Parse a ".retain [name]" directive.
+   The SHF_GNU_RETAIN flag should be applied to the section named "name".
+   If "name" is omitted, apply the flag to the current section being
+   assembled.  */
+static void
+obj_elf_retain (int ignored ATTRIBUTE_UNUSED)
+{
+  const char *name;
+  symbolS *secsym;
+  asection *bfdsec;
+
+  SKIP_WHITESPACE ();
+  if (*input_line_pointer == '\n')
+    {
+      /* Handle the case when "name" isn't specified.  */
+      name = now_seg->name;
+      if (name == NULL)
+	{
+	  as_bad (_("\".retain\" directive not within a section"));
+	  ignore_rest_of_line ();
+	  return;
+	}
+    }
+  else
+    {
+      /* The "name" argument has been given, validate it.  */
+      name = obj_elf_section_name ();
+      secsym = symbol_find (name);
+      if (secsym == NULL)
+	{
+	  as_bad (_("section '%s' has not been declared"), name);
+	  ignore_rest_of_line ();
+	  return;
+	}
+      else if (secsym != NULL
+	       && !symbol_section_p (secsym))
+	{
+	  as_bad (_("'%s' is not a section name, expected "
+		    "\".retain [name]\""), name);
+	  ignore_rest_of_line ();
+	  return;
+	}
+    }
+  demand_empty_rest_of_line ();
+
+  bfdsec = bfd_get_section_by_name (stdoutput, name);
+  if (bfdsec != NULL)
+    elf_section_flags (bfdsec) |= SHF_GNU_RETAIN;
+  else
+    as_bad (_("Couldn't find BFD section for %s\n"), name);
+}
+
 void
 elf_obj_read_begin_hook (void)
 {
diff --git a/gas/doc/as.texi b/gas/doc/as.texi
index 112eaf810c..68afaa6aae 100644
--- a/gas/doc/as.texi
+++ b/gas/doc/as.texi
@@ -4468,6 +4468,7 @@ Some machine configurations provide additional directives.
 * Quad::                        @code{.quad @var{bignums}}
 * Reloc::			@code{.reloc @var{offset}, @var{reloc_name}[, @var{expression}]}
 * Rept::			@code{.rept @var{count}}
+* Retain::			@code{.retain [@var{name}]}
 * Sbttl::                       @code{.sbttl "@var{subheading}"}
 @ifset COFF
 * Scl::                         @code{.scl @var{class}}
@@ -6468,6 +6469,22 @@ is equivalent to assembling
 A count of zero is allowed, but nothing is generated.  Negative counts are not
 allowed and if encountered will be treated as if they were zero.
 
+@ifset ELF
+@node Retain
+@section @code{.retain [@var{name}]}
+
+@cindex @code{retain} directive
+@cindex SHF_GNU_RETAIN
+
+Apply the @code{SHF_GNU_RETAIN} flag to the section named @var{name}.
+If @var{name} is omitted, apply the flag to the current section being
+assembled.
+
+The @code{SHF_GNU_RETAIN} flag specifies that the section should not be
+garbage collected by the linker if it is unused.
+
+@end ifset
+
 @node Sbttl
 @section @code{.sbttl "@var{subheading}"}
 
@@ -6546,6 +6563,9 @@ ignored.  (For compatibility with the ELF version)
 section is not readable (meaningful for PE targets)
 @item 0-9
 single-digit power-of-two section alignment (GNU extension)
+@item R
+retained section (apply SHF_GNU_RETAIN to prevent linker garbage
+collection, GNU ELF extension)
 @end table
 
 If no flags are specified, the default flags depend upon the section name.  If
diff --git a/gas/testsuite/gas/elf/elf.exp b/gas/testsuite/gas/elf/elf.exp
index 8520421ba3..c0dfdf504f 100644
--- a/gas/testsuite/gas/elf/elf.exp
+++ b/gas/testsuite/gas/elf/elf.exp
@@ -307,6 +307,13 @@ if { [is_elf_format] } then {
 
     run_dump_test "strtab"
 
+    # Tests for the .retain directive/SHF_GNU_RETAIN.
+    run_dump_test "retain1"
+    run_dump_test "retain2"
+    run_dump_test "retain3"
+    run_dump_test "retain4"
+    run_dump_test "retain5"
+
     run_dump_test "bignums"
     run_dump_test "section-symbol-redef"
     
diff --git a/gas/testsuite/gas/elf/retain1.d b/gas/testsuite/gas/elf/retain1.d
new file mode 100644
index 0000000000..439a60ca21
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain1.d
@@ -0,0 +1,24 @@
+#readelf: -S --wide
+#name: SHF_GNU_RETAIN 1
+
+#...
+  \[..\] .bss.discard0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .bss.discard1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .data.discard2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .bss.sdiscard0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .bss.sdiscard1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .data.sdiscard2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .text.fndiscard0[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX.*
+  \[..\] .bss.retain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.retain1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .data.retain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.sretain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.sretain1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .data.sretain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .text.fnretain1[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR.*
+  \[..\] .text.fndiscard2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX.*
+#...
+  \[..\] .bss.lsretain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.lsretain1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .data.lsretain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#pass
diff --git a/gas/testsuite/gas/elf/retain1.s b/gas/testsuite/gas/elf/retain1.s
new file mode 100644
index 0000000000..e799ff72ec
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain1.s
@@ -0,0 +1,114 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, STT_OBJECT
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, STT_OBJECT
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, STT_OBJECT
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, STT_OBJECT
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, STT_OBJECT
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, STT_OBJECT
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, STT_FUNC
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"aw"
+	.type	retain0, STT_OBJECT
+	.retain	.bss.retain0
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"aw"
+	.type	retain1, STT_OBJECT
+	.retain	.bss.retain1
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"aw"
+	.type	retain2, STT_OBJECT
+	.retain	.data.retain2
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"aw"
+	.type	sretain0, STT_OBJECT
+	.retain	.bss.sretain0
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"aw"
+	.type	sretain1, STT_OBJECT
+	.retain	.bss.sretain1
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aw"
+	.type	sretain2, STT_OBJECT
+	.retain	.data.sretain2
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"ax"
+	.global	fnretain1
+	.type	fnretain1, STT_FUNC
+	.retain	.text.fnretain1
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, STT_FUNC
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"aw"
+	.type	lsretain0.2, STT_OBJECT
+	.retain	.bss.lsretain0
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aw"
+	.type	lsretain1.1, STT_OBJECT
+	.retain	.bss.lsretain1
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aw"
+	.type	lsretain2.0, STT_OBJECT
+	.retain	.data.lsretain2
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/gas/testsuite/gas/elf/retain2.d b/gas/testsuite/gas/elf/retain2.d
new file mode 100644
index 0000000000..ca58b250b6
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain2.d
@@ -0,0 +1,3 @@
+#name: SHF_GNU_RETAIN 2
+#error_output: retain2.l
+#skip: hppa-*-*
diff --git a/gas/testsuite/gas/elf/retain2.l b/gas/testsuite/gas/elf/retain2.l
new file mode 100644
index 0000000000..5a44012567
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain2.l
@@ -0,0 +1,3 @@
+[^:]*: Assembler messages:
+[^:]*:1: Error: section '.data.foo' has not been declared
+[^:]*:7: Error: 'myvar' is not a section name, expected ".retain \[name\]"
diff --git a/gas/testsuite/gas/elf/retain2.s b/gas/testsuite/gas/elf/retain2.s
new file mode 100644
index 0000000000..75eb26d7f7
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain2.s
@@ -0,0 +1,7 @@
+.retain ".data.foo"
+.section .data,"aw"
+.global myvar
+.type myvar, STT_OBJECT
+myvar:
+	.byte 2
+.retain "myvar"
diff --git a/gas/testsuite/gas/elf/retain3.d b/gas/testsuite/gas/elf/retain3.d
new file mode 100644
index 0000000000..2d5ca68086
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain3.d
@@ -0,0 +1,24 @@
+#readelf: -S --wide
+#name: SHF_GNU_RETAIN 3 (use flags set on .section directive)
+
+#...
+  \[..\] .bss.discard0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .bss.discard1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .data.discard2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .bss.sdiscard0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .bss.sdiscard1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .data.sdiscard2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+  \[..\] .text.fndiscard0[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX.*
+  \[..\] .bss.retain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.retain1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .data.retain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.sretain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.sretain1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .data.sretain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .text.fnretain1[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR.*
+  \[..\] .text.fndiscard2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX.*
+#...
+  \[..\] .bss.lsretain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .bss.lsretain1[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+  \[..\] .data.lsretain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#pass
diff --git a/gas/testsuite/gas/elf/retain3.s b/gas/testsuite/gas/elf/retain3.s
new file mode 100644
index 0000000000..065399ec6f
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain3.s
@@ -0,0 +1,104 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, STT_OBJECT
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, STT_OBJECT
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, STT_OBJECT
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, STT_OBJECT
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, STT_OBJECT
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, STT_OBJECT
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, STT_FUNC
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"awR"
+	.type	retain0, STT_OBJECT
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"awR"
+	.type	retain1, STT_OBJECT
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"awR"
+	.type	retain2, STT_OBJECT
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"awR"
+	.type	sretain0, STT_OBJECT
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"awR"
+	.type	sretain1, STT_OBJECT
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aRw"
+	.type	sretain2, STT_OBJECT
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"Rax"
+	.global	fnretain1
+	.type	fnretain1, STT_FUNC
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, STT_FUNC
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"awR"
+	.type	lsretain0.2, STT_OBJECT
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aRw"
+	.type	lsretain1.1, STT_OBJECT
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aRw"
+	.type	lsretain2.0, STT_OBJECT
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/gas/testsuite/gas/elf/retain4.d b/gas/testsuite/gas/elf/retain4.d
new file mode 100644
index 0000000000..eacad20635
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain4.d
@@ -0,0 +1,6 @@
+#readelf: -S --wide
+#name: SHF_GNU_RETAIN 4 (switch to SHF_GNU_RETAIN section without "R" flag)
+
+#...
+  \[..\] .text.foo[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR.*
+#pass
diff --git a/gas/testsuite/gas/elf/retain4.s b/gas/testsuite/gas/elf/retain4.s
new file mode 100644
index 0000000000..48aeae2a11
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain4.s
@@ -0,0 +1,20 @@
+/* SHF_GNU_RETAIN should not be clobbered when switching to a section without
+   explicitly specifying the flag.  */
+	.section	.text.foo,"axR"
+	.global	foo
+	.type	foo, STT_FUNC
+foo:
+	.word 0
+
+	.section	.text.foo,"ax"
+	.global	bar
+	.type	bar, STT_FUNC
+bar:
+	.word 0
+
+	.section	.text.main,"ax"
+	.global	main
+	.type	main, STT_FUNC
+main:
+	.word 0
+
diff --git a/gas/testsuite/gas/elf/retain5.d b/gas/testsuite/gas/elf/retain5.d
new file mode 100644
index 0000000000..9edbfad710
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain5.d
@@ -0,0 +1,6 @@
+#readelf: -S --wide
+#name: SHF_GNU_RETAIN 5 (apply "R" flag to existing section)
+
+#...
+  \[..\] .text.foo[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR.*
+#pass
diff --git a/gas/testsuite/gas/elf/retain5.s b/gas/testsuite/gas/elf/retain5.s
new file mode 100644
index 0000000000..c2957a7db4
--- /dev/null
+++ b/gas/testsuite/gas/elf/retain5.s
@@ -0,0 +1,18 @@
+/* Applying the "R" flag to an existing section should apply SHF_GNU_RETAIN.  */
+	.section	.text.foo,"ax"
+	.global	foo
+	.type	foo, STT_FUNC
+foo:
+	.word 0
+
+	.section	.text.foo,"axR"
+	.global	bar
+	.type	bar, STT_FUNC
+bar:
+	.word 0
+
+	.section	.text.main,"ax"
+	.global	main
+	.type	main, STT_FUNC
+main:
+	.word 0
diff --git a/gas/testsuite/gas/elf/section10.d b/gas/testsuite/gas/elf/section10.d
index 554a791f1d..ef91d7d086 100644
--- a/gas/testsuite/gas/elf/section10.d
+++ b/gas/testsuite/gas/elf/section10.d
@@ -18,7 +18,7 @@
 #...
 [ 	]*\[.*\][ 	]+sec3
 [ 	]*PROGBITS.*
-[ 	]*\[.*fefff030\]: MERGE, STRINGS,.* EXCLUDE, OS \(.*ef00000\), PROC \(.*[3467]0000000\), UNKNOWN \(0+0ff000\)
+[ 	]*\[.*fefff030\]: MERGE, STRINGS,.* EXCLUDE, OS \(.*ed00000\), PROC \(.*[3467]0000000\), UNKNOWN \(0+0ff000\)
 #...
 [ 	]*\[.*\][ 	]+sec4
 [ 	]*LOOS\+0x11[ 	].*
@@ -26,7 +26,7 @@
 #...
 [ 	]*\[.*\][ 	]+sec5
 [ 	]*LOUSER\+0x9[ 	].*
-[ 	]*\[.*feff0000\]:.* EXCLUDE, OS \(.*ef00000\), PROC \(.*[3467]0000000\), UNKNOWN \(.*f0000\)
+[ 	]*\[.*feff0000\]:.* EXCLUDE, OS \(.*ed00000\), PROC \(.*[3467]0000000\), UNKNOWN \(.*f0000\)
 [ 	]*\[.*\][ 	]+.data.foo
 [ 	]*LOUSER\+0x7f000000[ 	].*
 [ 	]*\[0+003\]: WRITE, ALLOC
diff --git a/include/elf/common.h b/include/elf/common.h
index 805058146a..364c58a7de 100644
--- a/include/elf/common.h
+++ b/include/elf/common.h
@@ -554,6 +554,7 @@
 /* #define SHF_MASKOS	0x0F000000    *//* OS-specific semantics */
 #define SHF_MASKOS	0x0FF00000	/* New value, Oct 4, 1999 Draft */
 #define SHF_GNU_BUILD_NOTE    (1 << 20)	/* Section contains GNU BUILD ATTRIBUTE notes.  */
+#define SHF_GNU_RETAIN	      (1 << 21)	/* Section should not be garbage collected by the linker.  */
 #define SHF_MASKPROC	0xF0000000	/* Processor-specific semantics */
 
 /* This used to be implemented as a processor specific section flag.
diff --git a/ld/NEWS b/ld/NEWS
index 695348141b..1c3cc9d20a 100644
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -13,6 +13,10 @@
   unless you are working on a project that has its own analogue
   of symbol tables that are not reflected in the ELF symtabs.
 
+* Add support for the SHF_GNU_RETAIN ELF section flag.
+  This flag specifies that the section should not be garbage collected by the
+  linker if it is unused.
+
 Changes in 2.35:
 
 * X86 NaCl target support is removed.
diff --git a/ld/ld.texi b/ld/ld.texi
index 7d961c3bb8..5daffbeb5f 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -1781,6 +1781,10 @@ specified either by one of the options @samp{--entry},
 @samp{--undefined}, or @samp{--gc-keep-exported} or by a @code{ENTRY}
 command in the linker script.
 
+As a GNU extension, ELF input sections marked with the
+@code{SHF_GNU_RETAIN} flag will not be garbage collected if they are
+unused.
+
 @kindex --print-gc-sections
 @kindex --no-print-gc-sections
 @cindex garbage collection
@@ -5226,6 +5230,10 @@ The special output section name @samp{/DISCARD/} may be used to discard
 input sections.  Any input sections which are assigned to an output
 section named @samp{/DISCARD/} are not included in the output file.
 
+This can be used to discard input sections marked with the ELF flag
+@code{SHF_GNU_RETAIN}, which would otherwise have been saved from linker
+garbage collection when they are unused.
+
 Note, sections that match the @samp{/DISCARD/} output section will be
 discarded even if they are in an ELF section group which has other
 members which are not being discarded.  This is deliberate.
diff --git a/ld/testsuite/ld-elf/elf.exp b/ld/testsuite/ld-elf/elf.exp
index c0d67d80d2..9046bc870a 100644
--- a/ld/testsuite/ld-elf/elf.exp
+++ b/ld/testsuite/ld-elf/elf.exp
@@ -225,6 +225,39 @@ if [check_gc_sections_available] {
 	    {pr25490-6.s} \
 	    [list [list "readelf" {-SW} $pr25490_6_exp]] \
 	    "pr25490-6.exe"] \
+	[list "SHF_GNU_RETAIN 1" \
+	    "--gc-sections -e _start --print-gc-sections" "" "" \
+	    {retain1.s} \
+	    {{ ld retain1.msg }} \
+	    "retain1.exe"] \
+	[list "SHF_GNU_RETAIN 3 (use flags set on .section directive)" \
+	    "--gc-sections -e _start --print-gc-sections" "" "" \
+	    {retain3.s} \
+	    {{ ld retain3.msg }} \
+	    "retain3.exe"] \
+	[list "SHF_GNU_RETAIN 4 (keep sections referenced by retained sections)" \
+	    "--gc-sections -e _start --print-gc-sections" "" "" \
+	    {retain4.s} {} \
+	    "retain4.exe"] \
+	[list "SHF_GNU_RETAIN 5 (keep orphaned sections when not discarding)" \
+	    "--gc-sections -e _start --print-gc-sections --orphan-handling=place" "" "" \
+	    {retain5.s} {} \
+	    "retain5.exe"] \
+	[list "Build libretain6.a" \
+	    "" "" "" \
+	    {retain6lib.s} {} "libretain6.a"] \
+	[list "SHF_GNU_RETAIN 6 (don't pull SHF_GNU_RETAIN section out of lib)" \
+	    "--gc-sections -e _start --print-gc-sections" "-Ltmpdir -lretain6" "" \
+	    {retain6main.s} {} \
+	    "retain6.exe"] \
+	[list "Build libretain7.a" \
+	    "" "" "" \
+	    {retain7lib.s} {} "libretain7.a"] \
+	[list "SHF_GNU_RETAIN 7 (pull section out of lib required by SHF_GNU_RETAIN section)" \
+	    "--gc-sections -e _start --print-gc-sections" "-Ltmpdir -lretain7" "" \
+	    {retain7main.s} \
+	    {{ ld retain7.msg }} \
+	    "retain7.exe"] \
 	]
 }
 
diff --git a/ld/testsuite/ld-elf/retain1.msg b/ld/testsuite/ld-elf/retain1.msg
new file mode 100644
index 0000000000..9a265c980f
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1.msg
@@ -0,0 +1,9 @@
+.*: removing unused section '.bss.discard0' in file.*
+.*: removing unused section '.bss.discard1' in file.*
+.*: removing unused section '.data.discard2' in file.*
+.*: removing unused section '.bss.sdiscard0' in file.*
+.*: removing unused section '.bss.sdiscard1' in file.*
+.*: removing unused section '.data.sdiscard2' in file.*
+.*: removing unused section '.text.fndiscard0' in file.*
+.*: removing unused section '.text.fndiscard2' in file.*
+#pass
diff --git a/ld/testsuite/ld-elf/retain1.s b/ld/testsuite/ld-elf/retain1.s
new file mode 100644
index 0000000000..e799ff72ec
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1.s
@@ -0,0 +1,114 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, STT_OBJECT
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, STT_OBJECT
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, STT_OBJECT
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, STT_OBJECT
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, STT_OBJECT
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, STT_OBJECT
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, STT_FUNC
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"aw"
+	.type	retain0, STT_OBJECT
+	.retain	.bss.retain0
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"aw"
+	.type	retain1, STT_OBJECT
+	.retain	.bss.retain1
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"aw"
+	.type	retain2, STT_OBJECT
+	.retain	.data.retain2
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"aw"
+	.type	sretain0, STT_OBJECT
+	.retain	.bss.sretain0
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"aw"
+	.type	sretain1, STT_OBJECT
+	.retain	.bss.sretain1
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aw"
+	.type	sretain2, STT_OBJECT
+	.retain	.data.sretain2
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"ax"
+	.global	fnretain1
+	.type	fnretain1, STT_FUNC
+	.retain	.text.fnretain1
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, STT_FUNC
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"aw"
+	.type	lsretain0.2, STT_OBJECT
+	.retain	.bss.lsretain0
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aw"
+	.type	lsretain1.1, STT_OBJECT
+	.retain	.bss.lsretain1
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aw"
+	.type	lsretain2.0, STT_OBJECT
+	.retain	.data.lsretain2
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain2.d b/ld/testsuite/ld-elf/retain2.d
new file mode 100644
index 0000000000..9834d58297
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain2.d
@@ -0,0 +1,6 @@
+# Test that sections marked with SHF_GNU_RETAIN can be removed by placing them
+# in /DISCARD/.
+# source: retain1.s
+# ld: -e _start -Map=retain2.map --gc-sections --script=retain2.ld
+# map: retain2.map
+# skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
diff --git a/ld/testsuite/ld-elf/retain2.ld b/ld/testsuite/ld-elf/retain2.ld
new file mode 100644
index 0000000000..8ef982753c
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain2.ld
@@ -0,0 +1,7 @@
+SECTIONS
+{
+  /DISCARD/ :
+  {
+    *(.text.fnretain1)
+  }
+}
diff --git a/ld/testsuite/ld-elf/retain2.map b/ld/testsuite/ld-elf/retain2.map
new file mode 100644
index 0000000000..4028aa1f58
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain2.map
@@ -0,0 +1,32 @@
+# Test that .text.fnretain1, which has the SHF_GNU_RETAIN flag, can still be
+# explicitly discarded from the output file.
+
+#...
+Discarded input sections
+
+ .text.*
+#...
+ .data.*
+#...
+ .bss.*
+#...
+ .bss.discard0.*
+#...
+ .bss.discard1.*
+#...
+ .data.discard2.*
+#...
+ .bss.sdiscard0.*
+#...
+ .bss.sdiscard1.*
+#...
+ .data.sdiscard2.*
+#...
+ .text.fndiscard0.*
+#...
+ .text.fnretain1.*
+#...
+ .text.fndiscard2.*
+#...
+Memory Configuration
+#pass
diff --git a/ld/testsuite/ld-elf/retain3.msg b/ld/testsuite/ld-elf/retain3.msg
new file mode 100644
index 0000000000..9a265c980f
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.msg
@@ -0,0 +1,9 @@
+.*: removing unused section '.bss.discard0' in file.*
+.*: removing unused section '.bss.discard1' in file.*
+.*: removing unused section '.data.discard2' in file.*
+.*: removing unused section '.bss.sdiscard0' in file.*
+.*: removing unused section '.bss.sdiscard1' in file.*
+.*: removing unused section '.data.sdiscard2' in file.*
+.*: removing unused section '.text.fndiscard0' in file.*
+.*: removing unused section '.text.fndiscard2' in file.*
+#pass
diff --git a/ld/testsuite/ld-elf/retain3.s b/ld/testsuite/ld-elf/retain3.s
new file mode 100644
index 0000000000..065399ec6f
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.s
@@ -0,0 +1,104 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, STT_OBJECT
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, STT_OBJECT
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, STT_OBJECT
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, STT_OBJECT
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, STT_OBJECT
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, STT_OBJECT
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, STT_FUNC
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"awR"
+	.type	retain0, STT_OBJECT
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"awR"
+	.type	retain1, STT_OBJECT
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"awR"
+	.type	retain2, STT_OBJECT
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"awR"
+	.type	sretain0, STT_OBJECT
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"awR"
+	.type	sretain1, STT_OBJECT
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aRw"
+	.type	sretain2, STT_OBJECT
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"Rax"
+	.global	fnretain1
+	.type	fnretain1, STT_FUNC
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, STT_FUNC
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"awR"
+	.type	lsretain0.2, STT_OBJECT
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aRw"
+	.type	lsretain1.1, STT_OBJECT
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aRw"
+	.type	lsretain2.0, STT_OBJECT
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain4.s b/ld/testsuite/ld-elf/retain4.s
new file mode 100644
index 0000000000..080a5dfc2e
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain4.s
@@ -0,0 +1,19 @@
+/* The retention of bar should also prevent foo from being gc'ed, since bar
+   references foo.  */
+	.section	.text.foo,"ax"
+	.global	foo
+	.type	foo, STT_FUNC
+foo:
+	.word 0
+
+	.section	.text.bar,"axR"
+	.global	bar
+	.type	bar, STT_FUNC
+bar:
+	.long foo
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain5.s b/ld/testsuite/ld-elf/retain5.s
new file mode 100644
index 0000000000..5ffadad694
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5.s
@@ -0,0 +1,13 @@
+/* A section which doesn't match any linker script input section rules but
+   has SHF_GNU_RETAIN applied should not be garbage collected.  */
+	.section	.orphaned_section,"axR"
+	.global	foo
+	.type	foo, STT_FUNC
+foo:
+	.word 0
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain6lib.s b/ld/testsuite/ld-elf/retain6lib.s
new file mode 100644
index 0000000000..4de7adea3d
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6lib.s
@@ -0,0 +1,6 @@
+/* The link will fail if foo is included because undefined_sym is not defined.  */
+	.section	.text.foo,"axR"
+	.global	foo
+	.type	foo, STT_FUNC
+foo:
+	.long undefined_sym
diff --git a/ld/testsuite/ld-elf/retain6main.s b/ld/testsuite/ld-elf/retain6main.s
new file mode 100644
index 0000000000..7c722481e8
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6main.s
@@ -0,0 +1,5 @@
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain7.msg b/ld/testsuite/ld-elf/retain7.msg
new file mode 100644
index 0000000000..c21e3b9d75
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain7.msg
@@ -0,0 +1 @@
+.*: removing unused section '.text.discard_from_lib' in file.*
diff --git a/ld/testsuite/ld-elf/retain7lib.s b/ld/testsuite/ld-elf/retain7lib.s
new file mode 100644
index 0000000000..2aebf32bd0
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain7lib.s
@@ -0,0 +1,17 @@
+	.section	.text.bar,"ax"
+	.global	bar
+	.type	bar, STT_FUNC
+bar:
+	.word 0
+
+	.section	.text.retain_from_lib,"axR"
+	.global	retain_from_lib
+	.type	retain_from_lib, STT_FUNC
+retain_from_lib:
+	.word 0
+
+	.section	.text.discard_from_lib,"ax"
+	.global	discard_from_lib
+	.type	discard_from_lib, STT_FUNC
+discard_from_lib:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain7main.s b/ld/testsuite/ld-elf/retain7main.s
new file mode 100644
index 0000000000..d8ce70e718
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain7main.s
@@ -0,0 +1,13 @@
+/* Undefined symbol reference in retained section .text.foo requires symbol
+   definition to be pulled out of library.  */
+	.section	.text.foo,"axR"
+	.global	foo
+	.type	foo, STT_FUNC
+foo:
+	.long bar
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, STT_FUNC
+_start:
+	.word 0
-- 
2.28.0



More information about the Binutils mailing list