[RFC] Add support for the "retain" attribute utilizing SHF_GNU_RETAIN

Message ID 20201006111028.5agrgnq26cf72zrx@jozef-acer-manjaro
State New
Headers show
Series
  • [RFC] Add support for the "retain" attribute utilizing SHF_GNU_RETAIN
Related show

Commit Message

Jozef Lawrynowicz Oct. 6, 2020, 11:10 a.m.
Hi,

I'd like to propose a new "retain" attribute, which can
be applied to function and variable declarations.

The attribute is used to protect the function or variable declaration it
is applied to from linker garbage collection, by applying the
SHF_GNU_RETAIN section flag to the section containing it. This flag is a
GNU OSABI ELF extension.

The SHF_GNU_RETAIN flag was discussed on the GNU gABI mailing list here:
https://sourceware.org/pipermail/gnu-gabi/2020q3/000429.html

The Binutils patch for SHF_GNU_RETAIN was discussed in the following
threads:
https://sourceware.org/pipermail/binutils/2020-September/113406.html
https://sourceware.org/pipermail/binutils/2020-September/113499.html

The Binutils patch is still being iterated on, and I'd like to get some
feedback on one particular aspect of the GCC functionality before
finalizing the Binutils side of things.

When the "retain" attribute is applied to a declaration, there are three
ways to apply the SHF_GNU_RETAIN flag to the section containing the
declaration:
(1) Mark the entire section containing the declaration with the
    SHF_GNU_RETAIN flag
(2) Place the declaration in a new, uniquely named section with
    SHF_GNU_RETAIN set.
(3) Place the declaration in a new section with its default name, and
    SHF_GNU_RETAIN set.

I think that (2) is the best option, as it most closely corresponds to
the behavior the user wants to apply by using the "retain" attribute.
That is, only the declaration itself needs to be retained.

Option (3) has the same advantage, however it requires some non-standard
behavior in the assembler to support. The assembler would normally emit
an error if two input sections have the same name, but different flags
set. At the moment, SHF_GNU_RETAIN is an exception to this, but
there is no fundamental reason that this exception is required, as the
associated behavior can be fully supported by just giving the section a
unique name.

As far as I'm aware, option (1) would be tricky to support in GCC.
We'd have to examine all the declarations within a section before the
first assembler directive to create a section is created, which isn't
really compatible with the current, linear nature of the assembly output
stream. I guess there's probably something we could do in the middle-end
to set a flag somewhere to catch this without it getting too
complicated.
However, it would also lead to large portions of the program being
unnecessarily retained in the linked file, when only one declaration was
required.

If anyone has any strong opinions that option (2) isn't the best choice
for the "retain" attribute, please let me know. I plan on finalizing the
Binutils patch in the coming days, removing the added support for unique
input sections with the same name, but different states for the
SHF_GNU_RETAIN flag, which is required for option (3).

Should "used" apply SHF_GNU_RETAIN?
===================================
Another talking point is whether the existing "used" attribute should
apply the SHF_GNU_RETAIN flag to the containing section.

It seems unlikely that a user applies the "used" attribute to a
declaration, and means for it to be saved from only compiler
optimization, but *not* linker optimization. So perhaps it would be
beneficial for "used" to apply SHF_GNU_RETAIN in some way.

If "used" did apply SHF_GNU_RETAIN, we would also have to
consider the above options for how to apply SHF_GNU_RETAIN to the
section. Since the "used" attribute has been around for a while 
it might not be appropriate for its behavior to be changed to place the
associated declaration in its own, unique section, as in option (2).

However, I tested this "used" attribute modification on
x86_64-pc-linux-gnu, and there was only a small number of regressions
(27 PASS->FAIL, from 6 tests) across the GCC and G++ testsuites.

I briefly investigated these, and some failures are just due to a change
in the expected output of tests, but also some real errors from issues
with hot/cold function partitioning. I believe those would just require
some additional functional changes, and there isn't anything
fundamentally broken.

So nothing that can't be worked around, but I am more concerned about
the wider impact of changing the attribute, which is not represented by
this small subset of testing. The changes would also only affect targets
that support the GNU ELF OSABI, which would lead to inconsistent
behavior between non-GNU OS's. Perhaps this isn't an issue since we can
just document it in the description for the "used" attribute:
  As a GNU ELF extension, the declaration the "used" attribute is
  applied to will be placed in a new, uniquely named section with the
  SHF_GNU_RETAIN flag applied.

I think that unless "used" creates a new, uniquely named SHF_GNU_RETAIN
section for a declaration, there is merit to having a separate "retain"
attribute that has this behavior.

To summarize the talking points:
- Any downsides to the new "retain" attribute creating a new, uniquely
  named section for the declaration, with SHF_GNU_RETAIN applied?
- Should the "used" attribute apply SHF_GNU_RETAIN in some way?
- Is "retain" even required, could "used" be modified to create a new,
  uniquely named section for the declaration with SHF_GNU_RETAIN
  applied?

Currently, my opinion is that the best way forward is:
- New "retain" attribute places the declaration in a new, uniquely named
  section with SHF_GNU_RETAIN applied
- "used" attribute is modified so the section containing the declaration
  has SHF_GNU_RETAIN applied (option 1 from above) ..
  * .. as long as there are no negative consequences to "used" affecting
    linker behavior for the wider ecosystem of targets, and
  * the technical challenges for implementing it with this method can be
    solved.

I've attached 3 patches for reference:
- Binutils handling of SHF_GNU_RETAIN in binutils-SHF_GNU_RETAIN.patch
- New "retain" GCC attribute in gcc-retain-attribute.patch 
- "used" applies SHF_GNU_RETAIN (by creating a new section) in
  gcc-used-applies-SHF_GNU_RETAIN.patch

Thanks,
Jozef
From 84515f9caed188ebe36ac45217f491f082d9d3e9 Mon Sep 17 00:00:00 2001
From: Jozef Lawrynowicz <jozef.l@mittosystems.com>

Date: Tue, 8 Sep 2020 20:41:03 +0100
Subject: [PATCH] Add support for "retain" attribute

---
 gcc/c-family/c-attribs.c                   | 43 +++++++++++
 gcc/cfgexpand.c                            |  5 +-
 gcc/config.in                              |  6 ++
 gcc/configure                              | 36 ++++++++++
 gcc/configure.ac                           |  9 +++
 gcc/doc/extend.texi                        | 23 ++++++
 gcc/output.h                               |  3 +-
 gcc/testsuite/c-c++-common/attr-retain-1.c | 83 ++++++++++++++++++++++
 gcc/testsuite/c-c++-common/attr-retain-2.c | 70 ++++++++++++++++++
 gcc/testsuite/c-c++-common/attr-retain-3.c | 81 +++++++++++++++++++++
 gcc/testsuite/c-c++-common/attr-retain-4.c | 10 +++
 gcc/testsuite/lib/target-supports.exp      |  9 +++
 gcc/tree-core.h                            |  1 +
 gcc/tree.h                                 |  7 ++
 gcc/varasm.c                               |  8 ++-
 15 files changed, 390 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-1.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-2.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-3.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-4.c

-- 
2.28.0
From afc31b92c35faec899440c1e7c896f7da01e00a9 Mon Sep 17 00:00:00 2001
From: Jozef Lawrynowicz <jozef.l@mittosystems.com>

Date: Mon, 5 Oct 2020 18:22:10 +0100
Subject: [PATCH] used attribute applies SHF_GNU_RETAIN

---
 gcc/c-family/c-attribs.c | 11 +++++++++++
 gcc/config.in            |  6 ++++++
 gcc/configure            | 36 ++++++++++++++++++++++++++++++++++++
 gcc/configure.ac         |  9 +++++++++
 gcc/output.h             |  3 ++-
 gcc/varasm.c             | 10 +++++++++-
 6 files changed, 73 insertions(+), 2 deletions(-)

diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 70b00037d98..26161b27380 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -1416,6 +1416,17 @@ handle_used_attribute (tree *pnode, tree name, tree ARG_UNUSED (args),
 {
   tree node = *pnode;
 
+#if HAVE_GAS_SECTION_RETAIN
+  /* DECL_PRESERVE_P will place the "used" declaration in its own section if
+     SHF_GNU_RETAIN is supported.  */
+  if (!targetm_common.have_named_sections)
+    {
+      warning (OPT_Wattributes, "%qE attribute not supported by this target",
+	       name);
+      *no_add_attrs = true;
+    }
+#endif
+
   if (TREE_CODE (node) == FUNCTION_DECL
       || (VAR_P (node) && TREE_STATIC (node))
       || (TREE_CODE (node) == TYPE_DECL))
diff --git a/gcc/config.in b/gcc/config.in
index 5835ceaf669..8d2654504fe 100644
--- a/gcc/config.in
+++ b/gcc/config.in
@@ -1339,6 +1339,12 @@
 #endif
 
 
+/* Define if your assembler supports specifying the retain section flag. */
+#ifndef USED_FOR_TARGET
+#undef HAVE_GAS_SECTION_RETAIN
+#endif
+
+
 /* Define 0/1 if your assembler supports marking sections with SHF_MERGE flag.
    */
 #ifndef USED_FOR_TARGET
diff --git a/gcc/configure b/gcc/configure
index 33a3e34029f..7ce5d61320d 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -24218,6 +24218,42 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
+# Test if the assembler supports the section flag 'R' for specifying a
+# section which should not be garbage collected by the linker.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section retain flag" >&5
+$as_echo_n "checking assembler for section retain flag... " >&6; }
+if ${gcc_cv_as_section_retain_r+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  gcc_cv_as_section_retain_r=no
+  if test x$gcc_cv_as != x; then
+    $as_echo '.section foo1,"aR"
+  .byte 0' > conftest.s
+    if { ac_try='$gcc_cv_as $gcc_cv_as_flags  -o conftest.o conftest.s >&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+    then
+	gcc_cv_as_section_retain_r=yes
+    else
+      echo "configure: failed program was" >&5
+      cat conftest.s >&5
+    fi
+    rm -f conftest.o conftest.s
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_cv_as_section_retain_r" >&5
+$as_echo "$gcc_cv_as_section_retain_r" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_GAS_SECTION_RETAIN `if test $gcc_cv_as_section_retain_r = yes; then echo 1; else echo 0; fi`
+_ACEOF
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section merging support" >&5
 $as_echo_n "checking assembler for section merging support... " >&6; }
 if ${gcc_cv_as_shf_merge+:} false; then :
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 975f6d97c4b..dbf0b0fc8f5 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -3211,6 +3211,15 @@ AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_EXCLUDE,
   [`if test $gcc_cv_as_section_exclude_e = yes || test $gcc_cv_as_section_exclude_hash = yes; then echo 1; else echo 0; fi`],
 [Define if your assembler supports specifying the exclude section flag.])
 
+# Test if the assembler supports the section flag 'R' for specifying a
+# section which should not be garbage collected by the linker.
+gcc_GAS_CHECK_FEATURE([section retain flag], gcc_cv_as_section_retain_r,,,
+ [.section foo1,"aR"
+  .byte 0])
+AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_RETAIN,
+  [`if test $gcc_cv_as_section_retain_r = yes; then echo 1; else echo 0; fi`],
+[Define if your assembler supports specifying the retain section flag.])
+
 gcc_GAS_CHECK_FEATURE(section merging support, gcc_cv_as_shf_merge,
  [elf,2,12,0], [--fatal-warnings],
  [.section .rodata.str, "aMS", @progbits, 1])
diff --git a/gcc/output.h b/gcc/output.h
index eb253c50329..2a723969bd9 100644
--- a/gcc/output.h
+++ b/gcc/output.h
@@ -381,7 +381,8 @@ extern void no_asm_to_stream (FILE *);
 #define SECTION_COMMON   0x800000	/* contains common data */
 #define SECTION_RELRO	 0x1000000	/* data is readonly after relocation processing */
 #define SECTION_EXCLUDE  0x2000000	/* discarded by the linker */
-#define SECTION_MACH_DEP 0x4000000	/* subsequent bits reserved for target */
+#define SECTION_RETAIN   0x4000000	/* retained by the linker, SHF_GNU_RETAIN */
+#define SECTION_MACH_DEP 0x8000000	/* subsequent bits reserved for target */
 
 /* This SECTION_STYLE is used for unnamed sections that we can switch
    to using a special assembler directive.  */
diff --git a/gcc/varasm.c b/gcc/varasm.c
index ea0b59cf44a..3a42b78ef29 100644
--- a/gcc/varasm.c
+++ b/gcc/varasm.c
@@ -464,7 +464,10 @@ resolve_unique_section (tree decl, int reloc ATTRIBUTE_UNUSED,
   if (DECL_SECTION_NAME (decl) == NULL
       && targetm_common.have_named_sections
       && (flag_function_or_data_sections
-	  || DECL_COMDAT_GROUP (decl)))
+#if HAVE_GAS_SECTION_RETAIN
+	  || DECL_PRESERVE_P (decl))
+#endif
+	  || DECL_COMDAT_GROUP (decl))
     {
       targetm.asm_out.unique_section (decl, reloc);
       if (DECL_SECTION_NAME (decl))
@@ -6619,6 +6622,9 @@ default_section_type_flags (tree decl, const char *name, int reloc)
   if (decl && VAR_P (decl) && DECL_THREAD_LOCAL_P (decl))
     flags |= SECTION_TLS | SECTION_WRITE;
 
+  if (decl && DECL_PRESERVE_P (decl))
+    flags |= SECTION_RETAIN;
+
   if (strcmp (name, ".bss") == 0
       || strncmp (name, ".bss.", 5) == 0
       || strncmp (name, ".gnu.linkonce.b.", 16) == 0
@@ -6738,6 +6744,8 @@ default_elf_asm_named_section (const char *name, unsigned int flags,
       if (flags & SECTION_MACH_DEP)
 	*f++ = MACH_DEP_SECTION_ASM_FLAG;
 #endif
+      if (flags & SECTION_RETAIN)
+	*f++ = 'R';
       *f = '\0';
     }
 
-- 
2.28.0
From 6d43566aa02724e17636252bab019f3b279d0287 Mon Sep 17 00:00:00 2001
From: Jozef Lawrynowicz <jozef.l@mittosystems.com>

Date: Thu, 1 Oct 2020 11:49:04 +0100
Subject: [PATCH] Support SHF_GNU_RETAIN ELF section flag

The SHF_GNU_RETAIN section flag is an extension to the GNU ELF OSABI.
It 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.

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

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

bfd/ChangeLog:

2020-10-02  H.J. Lu  <hongjiu.lu@intel.com>
	Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* elf-bfd.h (enum elf_gnu_osabi): Add elf_gnu_osabi_retain.
	(struct elf_obj_tdata): Increase has_gnu_osabi to 4 bits.
	* elf.c (_bfd_elf_make_section_from_shdr): Set elf_gnu_osabi_retain
	for SHF_GNU_RETAIN.
	(_bfd_elf_final_write_processing): Report if SHF_GNU_RETAIN is
	not supported by the OSABI.
	Adjust error messages.
	* elflink.c (bfd_elf_gc_sections): gc_mark the section if
	SHF_GNU_RETAIN is set.

binutils/ChangeLog:

2020-10-02  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* NEWS: Announce SHF_GNU_RETAIN.
	* readelf.c (get_elf_section_flags): Handle SHF_GNU_RETAIN.
	Recognize SHF_GNU_RETAIN and SHF_GNU_MBIND only for supported OSABIs.
	* testsuite/binutils-all/readelf.exp: Run new test.
	Don't run run_dump_test when there isn't an assembler available.
	* testsuite/lib/binutils-common.exp (supports_gnu_osabi): Adjust
	comment.
	* testsuite/binutils-all/retain1.d: New test.
	* testsuite/binutils-all/retain1.s: New test.

gas/ChangeLog:

2020-10-02  H.J. Lu  <hongjiu.lu@intel.com>
	Jozef Lawrynowicz  <jozef.l@mittosystems.com>

	* NEWS: Announce SHF_GNU_RETAIN.
	* config/obj-elf.c (SEC_ASSEMBLER_SHF_MASK): New.
	(get_section): Use SEC_ASSEMBLER_SHF_MASK.
	(obj_elf_change_section): Update struct member name.
	(obj_elf_parse_section_letters): Handle 'R' flag.
	Handle numeric flag values within the SHF_MASKOS range.
	(obj_elf_section): Set elf_gnu_osabi_retain
	for SHF_GNU_RETAIN.
	* config/obj-elf.h (struct elf_section_match): Adjust "info" member
	name to "sh_info".  Add "sh_flags" member.
	* doc/as.texi (Section): Document 'R' flag.
	* testsuite/gas/elf/elf.exp: Run new tests.
	* testsuite/gas/elf/section10.d: Don't test SHF_GNU_RETAIN bit of
	section flag.
	* testsuite/gas/elf/section10.s: Don't set SHF_GNU_RETAIN bit of
	section flag.
	* testsuite/gas/elf/section22.d: New test.
	* testsuite/gas/elf/section22.s: New test.
	* testsuite/gas/elf/section23.s: New test.
	* testsuite/gas/elf/section23a.d: New test.
	* testsuite/gas/elf/section23b.d: New test.
	* testsuite/gas/elf/section23b.err: New test.

include/ChangeLog:

2020-10-02  Jozef Lawrynowicz  <jozef.l@mittosystems.com>

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

ld/ChangeLog:

2020-10-02  Jozef Lawrynowicz  <jozef.l@mittosystems.com>
	H.J. Lu  <hongjiu.lu@intel.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.s: New test.
	* testsuite/ld-elf/retain1a.d: New test.
	* testsuite/ld-elf/retain1b.d: 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.d: New test.
	* testsuite/ld-elf/retain3.s: New test.
	* testsuite/ld-elf/retain4.d: New test.
	* testsuite/ld-elf/retain4.s: New test.
	* testsuite/ld-elf/retain5.d: New test.
	* testsuite/ld-elf/retain5.map: New test.
	* testsuite/ld-elf/retain5lib.s: New test.
	* testsuite/ld-elf/retain5main.s: New test.
	* testsuite/ld-elf/retain6a.d: New test.
	* testsuite/ld-elf/retain6b.d: New test.
	* testsuite/ld-elf/retain6lib.s: New test.
	* testsuite/ld-elf/retain6main.s: New test.
	* testsuite/ld-elf/retain7.s: New test.
	* testsuite/ld-elf/retain7a.d: New test.
	* testsuite/ld-elf/retain7b.d: New test.
---
 bfd/elf-bfd.h                                 |   9 +-
 bfd/elf.c                                     |  17 ++-
 bfd/elflink.c                                 |   4 +-
 binutils/NEWS                                 |   4 +
 binutils/readelf.c                            |  51 ++++++++-
 .../binutils-all/readelf-maskos-1a.d          |   8 ++
 .../binutils-all/readelf-maskos-1b.d          |  10 ++
 .../testsuite/binutils-all/readelf-maskos.s   |  11 ++
 binutils/testsuite/binutils-all/readelf.exp   |   9 +-
 binutils/testsuite/binutils-all/retain1.s     | 104 ++++++++++++++++++
 binutils/testsuite/binutils-all/retain1a.d    |  18 +++
 binutils/testsuite/binutils-all/retain1b.d    |  46 ++++++++
 binutils/testsuite/lib/binutils-common.exp    |   5 +-
 gas/NEWS                                      |   6 +
 gas/config/obj-elf.c                          |  66 ++++++++---
 gas/config/obj-elf.h                          |   3 +-
 gas/doc/as.texi                               |   3 +
 gas/testsuite/gas/elf/elf.exp                 |   4 +-
 gas/testsuite/gas/elf/section10.d             |   4 +-
 gas/testsuite/gas/elf/section10.s             |   4 +-
 gas/testsuite/gas/elf/section22.d             |  19 ++++
 gas/testsuite/gas/elf/section22.s             |  34 ++++++
 gas/testsuite/gas/elf/section23.s             |  11 ++
 gas/testsuite/gas/elf/section23a.d            |  11 ++
 gas/testsuite/gas/elf/section23b.d            |   6 +
 gas/testsuite/gas/elf/section23b.err          |   2 +
 include/elf/common.h                          |   1 +
 ld/NEWS                                       |   4 +
 ld/ld.texi                                    |   8 ++
 ld/testsuite/ld-elf/elf.exp                   |  11 ++
 ld/testsuite/ld-elf/retain1.s                 | 104 ++++++++++++++++++
 ld/testsuite/ld-elf/retain1a.d                |  28 +++++
 ld/testsuite/ld-elf/retain1b.d                |  11 ++
 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.d                 |  12 ++
 ld/testsuite/ld-elf/retain3.s                 |  19 ++++
 ld/testsuite/ld-elf/retain4.d                 |  10 ++
 ld/testsuite/ld-elf/retain4.s                 |  13 +++
 ld/testsuite/ld-elf/retain5.d                 |  12 ++
 ld/testsuite/ld-elf/retain5.map               |   5 +
 ld/testsuite/ld-elf/retain5lib.s              |   6 +
 ld/testsuite/ld-elf/retain5main.s             |   5 +
 ld/testsuite/ld-elf/retain6a.d                |  14 +++
 ld/testsuite/ld-elf/retain6b.d                |  11 ++
 ld/testsuite/ld-elf/retain6lib.s              |  17 +++
 ld/testsuite/ld-elf/retain6main.s             |  13 +++
 ld/testsuite/ld-elf/retain7.s                 | 104 ++++++++++++++++++
 ld/testsuite/ld-elf/retain7a.d                |  28 +++++
 ld/testsuite/ld-elf/retain7b.d                |  11 ++
 51 files changed, 925 insertions(+), 36 deletions(-)
 create mode 100644 binutils/testsuite/binutils-all/readelf-maskos-1a.d
 create mode 100644 binutils/testsuite/binutils-all/readelf-maskos-1b.d
 create mode 100644 binutils/testsuite/binutils-all/readelf-maskos.s
 create mode 100644 binutils/testsuite/binutils-all/retain1.s
 create mode 100644 binutils/testsuite/binutils-all/retain1a.d
 create mode 100644 binutils/testsuite/binutils-all/retain1b.d
 create mode 100644 gas/testsuite/gas/elf/section22.d
 create mode 100644 gas/testsuite/gas/elf/section22.s
 create mode 100644 gas/testsuite/gas/elf/section23.s
 create mode 100644 gas/testsuite/gas/elf/section23a.d
 create mode 100644 gas/testsuite/gas/elf/section23b.d
 create mode 100644 gas/testsuite/gas/elf/section23b.err
 create mode 100644 ld/testsuite/ld-elf/retain1.s
 create mode 100644 ld/testsuite/ld-elf/retain1a.d
 create mode 100644 ld/testsuite/ld-elf/retain1b.d
 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.d
 create mode 100644 ld/testsuite/ld-elf/retain3.s
 create mode 100644 ld/testsuite/ld-elf/retain4.d
 create mode 100644 ld/testsuite/ld-elf/retain4.s
 create mode 100644 ld/testsuite/ld-elf/retain5.d
 create mode 100644 ld/testsuite/ld-elf/retain5.map
 create mode 100644 ld/testsuite/ld-elf/retain5lib.s
 create mode 100644 ld/testsuite/ld-elf/retain5main.s
 create mode 100644 ld/testsuite/ld-elf/retain6a.d
 create mode 100644 ld/testsuite/ld-elf/retain6b.d
 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.s
 create mode 100644 ld/testsuite/ld-elf/retain7a.d
 create mode 100644 ld/testsuite/ld-elf/retain7b.d

diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 140a98594d..ffb75f7919 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -1897,14 +1897,15 @@ struct output_elf_obj_tdata
   bfd_boolean flags_init;
 };
 
-/* Indicate if the bfd contains SHF_GNU_MBIND sections or symbols that
-   have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE binding.  Used
-   to set the osabi field in the ELF header structure.  */
+/* Indicate if the bfd contains SHF_GNU_MBIND/SHF_GNU_RETAIN sections or
+   symbols that have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE
+   binding.  Used to set the osabi field in the ELF header structure.  */
 enum elf_gnu_osabi
 {
   elf_gnu_osabi_mbind = 1 << 0,
   elf_gnu_osabi_ifunc = 1 << 1,
   elf_gnu_osabi_unique = 1 << 2,
+  elf_gnu_osabi_retain = 1 << 3,
 };
 
 typedef struct elf_section_list
@@ -2034,7 +2035,7 @@ struct elf_obj_tdata
   ENUM_BITFIELD (dynamic_lib_link_class) dyn_lib_class : 4;
 
   /* Whether the bfd uses OS specific bits that require ELFOSABI_GNU.  */
-  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 3;
+  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 4;
 
   /* Whether if the bfd contains the GNU_PROPERTY_NO_COPY_ON_PROTECTED
      property.  */
diff --git a/bfd/elf.c b/bfd/elf.c
index 9d7cbd52e0..dc097e825a 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -1066,9 +1066,12 @@ _bfd_elf_make_section_from_shdr (bfd *abfd,
       /* FIXME: We should not recognize SHF_GNU_MBIND for ELFOSABI_NONE,
 	 but binutils as of 2019-07-23 did not set the EI_OSABI header
 	 byte.  */
-    case ELFOSABI_NONE:
     case ELFOSABI_GNU:
     case ELFOSABI_FREEBSD:
+      if ((hdr->sh_flags & SHF_GNU_RETAIN) != 0)
+	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_retain;
+      /* Fall through */
+    case ELFOSABI_NONE:
       if ((hdr->sh_flags & SHF_GNU_MBIND) != 0)
 	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_mbind;
       break;
@@ -12464,11 +12467,17 @@ _bfd_elf_final_write_processing (bfd *abfd)
 	       && i_ehdrp->e_ident[EI_OSABI] != ELFOSABI_FREEBSD)
 	{
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_mbind)
-	    _bfd_error_handler (_("GNU_MBIND section is unsupported"));
+	    _bfd_error_handler (_("GNU_MBIND section is supported only by GNU "
+				  "and FreeBSD targets"));
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_ifunc)
-	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is unsupported"));
+	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is supported "
+				  "only by GNU and FreeBSD targets"));
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_unique)
-	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is unsupported"));
+	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is supported "
+				  "only by GNU and FreeBSD targets"));
+	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_retain)
+	    _bfd_error_handler (_("GNU_RETAIN section is supported "
+				  "only by GNU and FreeBSD targets"));
 	  bfd_set_error (bfd_error_sorry);
 	  return FALSE;
 	}
diff --git a/bfd/elflink.c b/bfd/elflink.c
index e23d189b98..742254055c 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -14103,7 +14103,9 @@ bfd_elf_gc_sections (bfd *abfd, struct bfd_link_info *info)
 			    == SHT_FINI_ARRAY)))
 		|| (elf_section_data (o)->this_hdr.sh_type == SHT_NOTE
 		    && elf_next_in_group (o) == NULL
-		    && elf_linked_to_section (o) == NULL)))
+		    && elf_linked_to_section (o) == NULL)
+		|| ((elf_tdata (sub)->has_gnu_osabi & elf_gnu_osabi_retain)
+		    && (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 9ba4e29a65..e6ec99a2cc 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)
@@ -6009,7 +6011,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_TLS:		sindex = 9; break;
 	    case SHF_EXCLUDE:		sindex = 18; break;
 	    case SHF_COMPRESSED:	sindex = 20; break;
-	    case SHF_GNU_MBIND:		sindex = 24; break;
 
 	    default:
 	      sindex = -1;
@@ -6061,10 +6062,26 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 		  if (flag == SHF_PPC_VLE)
 		    sindex = 25;
 		  break;
+		}
 
+	      switch (filedata->file_header.e_ident[EI_OSABI])
+		{
+		case ELFOSABI_GNU:
+		case ELFOSABI_FREEBSD:
+		  if (flag == SHF_GNU_RETAIN)
+		    sindex = 26;
+		  /* Fall through */
+		case ELFOSABI_NONE:
+		  if (flag == SHF_GNU_MBIND)
+		    /* We should not recognize SHF_GNU_MBIND for
+		       ELFOSABI_NONE, but binutils as of 2019-07-23 did
+		       not set the EI_OSABI header byte.  */
+		    sindex = 24;
+		  break;
 		default:
 		  break;
 		}
+	      break;
 	    }
 
 	  if (sindex != -1)
@@ -6107,7 +6124,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_TLS:		*p = 'T'; break;
 	    case SHF_EXCLUDE:		*p = 'E'; break;
 	    case SHF_COMPRESSED:	*p = 'C'; break;
-	    case SHF_GNU_MBIND:		*p = 'D'; break;
 
 	    default:
 	      if ((filedata->file_header.e_machine == EM_X86_64
@@ -6117,14 +6133,37 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 		*p = 'l';
 	      else if (filedata->file_header.e_machine == EM_ARM
 		       && flag == SHF_ARM_PURECODE)
-		  *p = 'y';
+		*p = 'y';
 	      else if (filedata->file_header.e_machine == EM_PPC
 		       && flag == SHF_PPC_VLE)
-		  *p = 'v';
+		*p = 'v';
 	      else if (flag & SHF_MASKOS)
 		{
-		  *p = 'o';
-		  sh_flags &= ~ SHF_MASKOS;
+		  switch (filedata->file_header.e_ident[EI_OSABI])
+		    {
+		    case ELFOSABI_GNU:
+		    case ELFOSABI_FREEBSD:
+		      if (flag == SHF_GNU_RETAIN)
+			{
+			  *p = 'R';
+			  break;
+			}
+		      /* Fall through */
+		    case ELFOSABI_NONE:
+		      if (flag == SHF_GNU_MBIND)
+			{
+			  /* We should not recognize SHF_GNU_MBIND for
+			     ELFOSABI_NONE, but binutils as of 2019-07-23 did
+			     not set the EI_OSABI header byte.  */
+			  *p = 'D';
+			  break;
+			}
+		      /* Fall through */
+		    default:
+		      *p = 'o';
+		      sh_flags &= ~SHF_MASKOS;
+		      break;
+		    }
 		}
 	      else if (flag & SHF_MASKPROC)
 		{
diff --git a/binutils/testsuite/binutils-all/readelf-maskos-1a.d b/binutils/testsuite/binutils-all/readelf-maskos-1a.d
new file mode 100644
index 0000000000..8dd8e330df
--- /dev/null
+++ b/binutils/testsuite/binutils-all/readelf-maskos-1a.d
@@ -0,0 +1,8 @@
+#name: Unknown SHF_MASKOS value in section
+#source: readelf-maskos.s
+#notarget: [supports_gnu_osabi] msp430-*-elf visium-*-elf
+#readelf: -S --wide
+
+#...
+  \[[ 0-9]+\] .data.retain_var.*WAo.*
+#pass
diff --git a/binutils/testsuite/binutils-all/readelf-maskos-1b.d b/binutils/testsuite/binutils-all/readelf-maskos-1b.d
new file mode 100644
index 0000000000..88e110135f
--- /dev/null
+++ b/binutils/testsuite/binutils-all/readelf-maskos-1b.d
@@ -0,0 +1,10 @@
+#name: -t (section details) for unknown SHF_MASKOS value in section
+#source: readelf-maskos.s
+#notarget: [supports_gnu_osabi] msp430-*-elf visium-*-elf
+#readelf: -S -t --wide
+
+#...
+  \[[ 0-9]+\] .data.retain_var
+       PROGBITS +0+ +[0-9a-f]+ +[0-9a-f]+ +[0-9a-f]+ +0 +0 +(1|2|4|8)
+       \[00200003\]: WRITE, ALLOC, OS \(00200000\)
+#pass
diff --git a/binutils/testsuite/binutils-all/readelf-maskos.s b/binutils/testsuite/binutils-all/readelf-maskos.s
new file mode 100644
index 0000000000..d671119bca
--- /dev/null
+++ b/binutils/testsuite/binutils-all/readelf-maskos.s
@@ -0,0 +1,11 @@
+  .section	.data.retain_var,"0x200003"
+	.global	retain_var
+	.type	retain_var, %object
+retain_var:
+	.long	2
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/binutils/testsuite/binutils-all/readelf.exp b/binutils/testsuite/binutils-all/readelf.exp
index 1fb36ae5c4..9d1d496e5c 100644
--- a/binutils/testsuite/binutils-all/readelf.exp
+++ b/binutils/testsuite/binutils-all/readelf.exp
@@ -364,8 +364,15 @@ 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 "retain1a"
+    run_dump_test "retain1b"
+    run_dump_test "readelf-maskos-1a"
+    run_dump_test "readelf-maskos-1b"
+}
 
 # 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.s b/binutils/testsuite/binutils-all/retain1.s
new file mode 100644
index 0000000000..f7716faabe
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1.s
@@ -0,0 +1,104 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, %object
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, %object
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, %object
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, %object
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, %object
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, %object
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, %function
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"awR"
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"awR"
+	.type	retain1, %object
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"awR"
+	.type	retain2, %object
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"awR"
+	.type	sretain0, %object
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"awR"
+	.type	sretain1, %object
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aRw"
+	.type	sretain2, %object
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"Rax"
+	.global	fnretain1
+	.type	fnretain1, %function
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, %function
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"awR"
+	.type	lsretain0.2, %object
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aRw"
+	.type	lsretain1.1, %object
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aRw"
+	.type	lsretain2.0, %object
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/binutils/testsuite/binutils-all/retain1a.d b/binutils/testsuite/binutils-all/retain1a.d
new file mode 100644
index 0000000000..6397ac52ae
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1a.d
@@ -0,0 +1,18 @@
+#name: readelf SHF_GNU_RETAIN
+#source: retain1.s
+#target: [supports_gnu_osabi]
+#readelf: -S --wide
+
+#...
+  \[[ 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/retain1b.d b/binutils/testsuite/binutils-all/retain1b.d
new file mode 100644
index 0000000000..12bc388ba1
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1b.d
@@ -0,0 +1,46 @@
+#name: -t (section details) for readelf SHF_GNU_RETAIN
+#source: retain1.s
+#target: [supports_gnu_osabi]
+#readelf: -S -t --wide
+
+#...
+  \[[ 0-9]+\] .bss.retain0
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.retain1
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .data.retain2
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.sretain0
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.sretain1
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .data.sretain2
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .text.fnretain1
+#...
+       \[0+200006\]: ALLOC, EXEC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.lsretain0
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.lsretain1
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .data.lsretain2
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#pass
diff --git a/binutils/testsuite/lib/binutils-common.exp b/binutils/testsuite/lib/binutils-common.exp
index b9a1e6e4bc..a43639bafb 100644
--- a/binutils/testsuite/lib/binutils-common.exp
+++ b/binutils/testsuite/lib/binutils-common.exp
@@ -195,13 +195,15 @@ proc match_target { target } {
 
 # True if the ELF target supports setting the ELF header OSABI field
 # to ELFOSABI_GNU or ELFOSABI_FREEBSD, a requirement for STT_GNU_IFUNC
-# symbol and SHF_GNU_MBIND section support.
+# symbol and SHF_GNU_MBIND or SHF_GNU_RETAIN section support.
 #
 # This generally depends on the target OS only, however there are a
 # number of exceptions for bare metal targets as follows.  The MSP430
 # and Visium targets set OSABI to ELFOSABI_STANDALONE.  Likewise
 # non-EABI ARM targets set OSABI to ELFOSABI_ARM
 #
+# Non-Linux HPPA defaults to ELFOSABI_HPUX.
+#
 # Note that some TI C6X targets use ELFOSABI_C6000_* but one doesn't,
 # so we don't try to sort out tic6x here.  (The effect is that linker
 # testcases will generally need to exclude tic6x or use a -m option.)
@@ -227,6 +229,7 @@ proc supports_gnu_osabi {} {
     }
     if { [istarget "arm*-*-*"]
 	 || [istarget "msp430-*-*"]
+	 || [istarget "hppa-unknown-elf"]
 	 || [istarget "visium-*-*"] } {
 	return 0
     }
diff --git a/gas/NEWS b/gas/NEWS
index 1107725ea6..67f5ca3e28 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -30,6 +30,12 @@
 
 * Configure with --enable-x86-used-note by default for Linux/x86.
 
+* Add support for the "R" flag in the .section directive.
+  This flag requires ELFOSABI_GNU or ELFOSABI_FREEBSD, and 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 f061ea61f3..557f2020ea 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -519,8 +519,11 @@ struct section_stack
 
 static struct section_stack *section_stack;
 
-/* Return TRUE iff SEC matches the section info INF.  */
+/* Create unique input sections for sections with the same name, but different
+   values for the flags in this mask.  */
+#define SEC_ASSEMBLER_SHF_MASK SHF_GNU_RETAIN
 
+/* Return TRUE iff SEC matches the section info INF.  */
 static bfd_boolean
 get_section_by_match (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
 {
@@ -529,9 +532,12 @@ get_section_by_match (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
   const char *group_name = elf_group_name (sec);
   const char *linked_to_symbol_name
     = sec->map_head.linked_to_symbol_name;
-  unsigned int info = elf_section_data (sec)->this_hdr.sh_info;
+  unsigned int sh_info = elf_section_data (sec)->this_hdr.sh_info;
+  bfd_vma sh_flags = (elf_section_data (sec)->this_hdr.sh_flags
+		      & SEC_ASSEMBLER_SHF_MASK);
 
-  return (info == match->info
+  return (sh_info == match->sh_info
+	  && sh_flags == match->sh_flags
 	  && ((bfd_section_flags (sec) & SEC_ASSEMBLER_SECTION_ID)
 	       == (match->flags & SEC_ASSEMBLER_SECTION_ID))
 	  && sec->section_id == match->section_id
@@ -740,7 +746,7 @@ obj_elf_change_section (const char *name,
 	type = bfd_elf_get_default_section_type (flags);
       elf_section_type (sec) = type;
       elf_section_flags (sec) = attr;
-      elf_section_data (sec)->this_hdr.sh_info = match_p->info;
+      elf_section_data (sec)->this_hdr.sh_info = match_p->sh_info;
 
       /* Prevent SEC_HAS_CONTENTS from being inadvertently set.  */
       if (type == SHT_NOBITS)
@@ -861,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;
@@ -890,8 +899,32 @@ obj_elf_parse_section_letters (char *str, size_t len,
 	      if (ISDIGIT (*str))
 		{
 		  char * end;
+		  struct elf_backend_data *bed;
+		  bfd_vma numeric_flags = strtoul (str, &end, 0);
+
+		  attr |= numeric_flags;
+
+		  bed = (struct elf_backend_data *)
+		    get_elf_backend_data (stdoutput);
+
+		  if (bed->elf_osabi == ELFOSABI_NONE
+		      || bed->elf_osabi == ELFOSABI_STANDALONE
+		      || bed->elf_osabi == ELFOSABI_GNU
+		      || bed->elf_osabi == ELFOSABI_FREEBSD)
+		    {
+		      /* Add flags in the SHF_MASKOS range to gnu_attr for
+			 OSABIs that support those flags.
+			 Also adding the flags for ELFOSABI_{NONE,STANDALONE}
+			 allows them to be validated later in obj_elf_section.
+			 We can't just always set these bits in gnu_attr for
+			 all OSABIs, since Binutils does not recognize all
+			 SHF_MASKOS bits for non-GNU OSABIs.  It's therefore
+			 possible that numeric flags are being used to set bits
+			 in the SHF_MASKOS range for those targets, and we
+			 don't want assembly to fail in those situations.  */
+		      *gnu_attr |= (numeric_flags & SHF_MASKOS);
+		    }
 
-		  attr |= strtoul (str, & end, 0);
 		  /* Update str and len, allowing for the fact that
 		     we will execute str++ and len-- below.  */
 		  end --;
@@ -1287,18 +1320,21 @@ obj_elf_section (int push)
 	      if (ISDIGIT (* input_line_pointer))
 		{
 		  char *t = input_line_pointer;
-		  match.info = strtoul (input_line_pointer,
+		  match.sh_info = strtoul (input_line_pointer,
 					&input_line_pointer, 0);
-		  if (match.info == (unsigned int) -1)
+		  if (match.sh_info == (unsigned int) -1)
 		    {
 		      as_warn (_("unsupported mbind section info: %s"), t);
-		      match.info = 0;
+		      match.sh_info = 0;
 		    }
 		}
 	      else
 		input_line_pointer = save;
 	    }
 
+	  if ((gnu_attr & SHF_GNU_RETAIN) != 0)
+	    match.sh_flags |= SHF_GNU_RETAIN;
+
 	  if (*input_line_pointer == ',')
 	    {
 	      char *save = input_line_pointer;
@@ -1390,11 +1426,12 @@ obj_elf_section (int push)
   obj_elf_change_section (name, type, attr, entsize, &match, linkonce,
 			  push);
 
-  if ((gnu_attr & SHF_GNU_MBIND) != 0)
+  if ((gnu_attr & (SHF_GNU_MBIND | SHF_GNU_RETAIN)) != 0)
     {
       struct elf_backend_data *bed;
+      bfd_boolean mbind_p = (gnu_attr & SHF_GNU_MBIND) != 0;
 
-      if ((attr & SHF_ALLOC) == 0)
+      if (mbind_p && (attr & SHF_ALLOC) == 0)
 	as_bad (_("SHF_ALLOC isn't set for GNU_MBIND section: %s"), name);
 
       bed = (struct elf_backend_data *) get_elf_backend_data (stdoutput);
@@ -1402,9 +1439,12 @@ obj_elf_section (int push)
 	bed->elf_osabi = ELFOSABI_GNU;
       else if (bed->elf_osabi != ELFOSABI_GNU
 	       && bed->elf_osabi != ELFOSABI_FREEBSD)
-	as_bad (_("GNU_MBIND section is supported only by GNU "
-		  "and FreeBSD targets"));
-      elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_mbind;
+	as_bad (_("%s section is supported only by GNU and FreeBSD targets"),
+		mbind_p ? "GNU_MBIND" : "GNU_RETAIN");
+      if (mbind_p)
+	elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_mbind;
+      if ((gnu_attr & SHF_GNU_RETAIN) != 0)
+	elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_retain;
     }
   elf_section_flags (now_seg) |= gnu_attr;
 
diff --git a/gas/config/obj-elf.h b/gas/config/obj-elf.h
index 4f29572eef..c714ba7a70 100644
--- a/gas/config/obj-elf.h
+++ b/gas/config/obj-elf.h
@@ -106,8 +106,9 @@ struct elf_section_match
 {
   const char *   group_name;
   const char *   linked_to_symbol_name;
-  unsigned int   info;
   unsigned int   section_id;
+  unsigned int   sh_info;		/* ELF section information.  */
+  bfd_vma        sh_flags;		/* ELF section flags.  */
   flagword       flags;
 };
 
diff --git a/gas/doc/as.texi b/gas/doc/as.texi
index 4d5294552a..e92432a8bd 100644
--- a/gas/doc/as.texi
+++ b/gas/doc/as.texi
@@ -6657,6 +6657,9 @@ section is a member of a section group
 section is used for thread-local-storage
 @item ?
 section is a member of the previously-current section's group, if any
+@item R
+retained section (apply SHF_GNU_RETAIN to prevent linker garbage
+collection, GNU ELF extension)
 @item @code{<number>}
 a numeric value indicating the bits to be set in the ELF section header's flags
 field.  Note - if one or more of the alphabetic characters described above is
diff --git a/gas/testsuite/gas/elf/elf.exp b/gas/testsuite/gas/elf/elf.exp
index 9d75154483..c24152c5a5 100644
--- a/gas/testsuite/gas/elf/elf.exp
+++ b/gas/testsuite/gas/elf/elf.exp
@@ -261,8 +261,10 @@ if { [is_elf_format] } then {
     run_dump_test "section19"
     run_dump_test "section20"
     run_dump_test "section21"
+    run_dump_test "section22"
+    run_dump_test "section23a"
+    run_dump_test "section23b"
     run_dump_test "sh-link-zero"
-
     run_dump_test "dwarf2-1" $dump_opts
     run_dump_test "dwarf2-2" $dump_opts
     run_dump_test "dwarf2-3" $dump_opts
diff --git a/gas/testsuite/gas/elf/section10.d b/gas/testsuite/gas/elf/section10.d
index 554a791f1d..6aa7b088b1 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\)
+[ 	]*\[.*fedff030\]: 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\)
+[ 	]*\[.*fedf0000\]:.* EXCLUDE, OS \(.*ed00000\), PROC \(.*[3467]0000000\), UNKNOWN \(.*f0000\)
 [ 	]*\[.*\][ 	]+.data.foo
 [ 	]*LOUSER\+0x7f000000[ 	].*
 [ 	]*\[0+003\]: WRITE, ALLOC
diff --git a/gas/testsuite/gas/elf/section10.s b/gas/testsuite/gas/elf/section10.s
index 29f1184523..d52b3458fb 100644
--- a/gas/testsuite/gas/elf/section10.s
+++ b/gas/testsuite/gas/elf/section10.s
@@ -7,7 +7,7 @@
 	.word 2
 
 	# Make sure that specifying further arguments to .sections is still supported
-	.section sec3, "0xfefff000MS", %progbits, 32
+	.section sec3, "0xfedff000MS", %progbits, 32
 	.word 3
 
 	# Make sure that extra flags can be set for well known sections as well.
@@ -19,7 +19,7 @@
 	.word 5
 
 	# Test both together, with a quoted type value.
-	.section sec5, "0xfeff0000", "0x80000009"
+	.section sec5, "0xfedf0000", "0x80000009"
 	.word 6
 
 	# Test that declaring an extended version of a known special section works.
diff --git a/gas/testsuite/gas/elf/section22.d b/gas/testsuite/gas/elf/section22.d
new file mode 100644
index 0000000000..27d9127745
--- /dev/null
+++ b/gas/testsuite/gas/elf/section22.d
@@ -0,0 +1,19 @@
+#readelf: -h -S --wide
+#name: SHF_GNU_RETAIN sections 22
+#notarget: ![supports_gnu_osabi]
+
+#...
+ +OS/ABI: +UNIX - GNU
+#...
+  \[..\] .text[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX.*
+#...
+  \[..\] .data[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+#...
+  \[..\] .bss[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+#...
+  \[..\] .bss[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#...
+  \[..\] .data[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#...
+  \[..\] .text[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR.*
+#pass
diff --git a/gas/testsuite/gas/elf/section22.s b/gas/testsuite/gas/elf/section22.s
new file mode 100644
index 0000000000..6e2b95c1e8
--- /dev/null
+++ b/gas/testsuite/gas/elf/section22.s
@@ -0,0 +1,34 @@
+	.section	.text,"ax",%progbits
+	.global	discard0
+	.type	discard0, %function
+discard0:
+	.word	0
+
+	.section	.data,"aw"
+	.global	discard1
+	.type	discard1, %object
+discard1:
+	.word	1
+
+	.section	.bss,"aw"
+	.global	discard2
+	.type	discard2, %object
+discard2:
+	.zero	2
+
+	.section	.bss,"awR",%nobits
+	.global	retain0
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.section	.data,"awR",%progbits
+	.type	retain1, %object
+retain1:
+	.word	1
+
+	.section	.text,"axR",%progbits
+	.global	retain2
+	.type	retain2, %function
+retain2:
+	.word	0
diff --git a/gas/testsuite/gas/elf/section23.s b/gas/testsuite/gas/elf/section23.s
new file mode 100644
index 0000000000..d671119bca
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23.s
@@ -0,0 +1,11 @@
+  .section	.data.retain_var,"0x200003"
+	.global	retain_var
+	.type	retain_var, %object
+retain_var:
+	.long	2
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/gas/testsuite/gas/elf/section23a.d b/gas/testsuite/gas/elf/section23a.d
new file mode 100644
index 0000000000..1d850d9e8e
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23a.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN set with numeric flag value in .section
+#source: section23.s
+#target: [supports_gnu_osabi]
+#readelf: -h -S --wide
+
+#...
+ +OS/ABI: +UNIX - GNU
+#...
+  \[..\] .data.retain_var[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#pass
+
diff --git a/gas/testsuite/gas/elf/section23b.d b/gas/testsuite/gas/elf/section23b.d
new file mode 100644
index 0000000000..c85200e5ff
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23b.d
@@ -0,0 +1,6 @@
+#name: SHF_GNU_RETAIN set with numeric flag value in .section for non-GNU OSABI target
+#source: section23.s
+#error_output: section23b.err
+#target: msp430-*-elf visium-*-elf
+
+# This test only runs for targets which set ELFOSABI_STANDALONE.
diff --git a/gas/testsuite/gas/elf/section23b.err b/gas/testsuite/gas/elf/section23b.err
new file mode 100644
index 0000000000..83de60c397
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23b.err
@@ -0,0 +1,2 @@
+.*: Assembler messages:
+.*:1: Error: GNU_RETAIN section is supported only by GNU and FreeBSD targets
diff --git a/include/elf/common.h b/include/elf/common.h
index 571e21af29..c01e562c78 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 e4ae43b257..72cdf0edd3 100644
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -15,6 +15,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 ee592df6c2..526b642d78 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -1787,6 +1787,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
@@ -5232,6 +5236,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 f2ff0397c7..bd06ab0d39 100644
--- a/ld/testsuite/ld-elf/elf.exp
+++ b/ld/testsuite/ld-elf/elf.exp
@@ -119,6 +119,17 @@ if { [istarget "i?86-*-*"] || [istarget "x86_64-*-*"] } {
     set ASFLAGS "$ASFLAGS -mx86-used-note=no"
 }
 
+# Build libraries required for SHF_GNU_RETAIN tests.
+if { [check_gc_sections_available] && [supports_gnu_osabi] } {
+    run_ld_link_tests [list \
+	[list "Build libretain5.a" "" "" "" \
+	    {retain5lib.s} {} "libretain5.a"] \
+	[list "Build libretain6.a" "" "" "" \
+	    {retain6lib.s} {} "libretain6.a"] \
+	]
+}
+
+
 set test_list [lsort [glob -nocomplain $srcdir/$subdir/*.d]]
 foreach t $test_list {
     # We need to strip the ".d", but can leave the dirname.
diff --git a/ld/testsuite/ld-elf/retain1.s b/ld/testsuite/ld-elf/retain1.s
new file mode 100644
index 0000000000..f7716faabe
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1.s
@@ -0,0 +1,104 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, %object
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, %object
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, %object
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, %object
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, %object
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, %object
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, %function
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"awR"
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"awR"
+	.type	retain1, %object
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"awR"
+	.type	retain2, %object
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"awR"
+	.type	sretain0, %object
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"awR"
+	.type	sretain1, %object
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aRw"
+	.type	sretain2, %object
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"Rax"
+	.global	fnretain1
+	.type	fnretain1, %function
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, %function
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"awR"
+	.type	lsretain0.2, %object
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aRw"
+	.type	lsretain1.1, %object
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aRw"
+	.type	lsretain2.0, %object
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain1a.d b/ld/testsuite/ld-elf/retain1a.d
new file mode 100644
index 0000000000..75abb9856c
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1a.d
@@ -0,0 +1,28 @@
+#name: SHF_GNU_RETAIN 1a
+#source: retain1.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . fnretain1
+#...
+[0-9a-f]+ . lsretain0.2
+#...
+[0-9a-f]+ . lsretain1.1
+#...
+[0-9a-f]+ . lsretain2.0
+#...
+[0-9a-f]+ . retain0
+#...
+[0-9a-f]+ . retain1
+#...
+[0-9a-f]+ . retain2
+#...
+[0-9a-f]+ . sretain0
+#...
+[0-9a-f]+ . sretain1
+#...
+[0-9a-f]+ . sretain2
+#pass
diff --git a/ld/testsuite/ld-elf/retain1b.d b/ld/testsuite/ld-elf/retain1b.d
new file mode 100644
index 0000000000..815c0150f5
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1b.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN 1b
+#source: retain1.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#nm: -n
+
+#failif
+#...
+[0-9a-f]+ . .*discard.*
+#...
diff --git a/ld/testsuite/ld-elf/retain2.d b/ld/testsuite/ld-elf/retain2.d
new file mode 100644
index 0000000000..11efd6ddb8
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain2.d
@@ -0,0 +1,6 @@
+#name: SHF_GNU_RETAIN 2 (remove SHF_GNU_RETAIN sections by placing 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-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
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.d b/ld/testsuite/ld-elf/retain3.d
new file mode 100644
index 0000000000..911f5b7594
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.d
@@ -0,0 +1,12 @@
+#name: SHF_GNU_RETAIN 3 (keep sections referenced by retained sections)
+#source: retain3.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . bar
+#...
+[0-9a-f]+ . foo
+#pass
diff --git a/ld/testsuite/ld-elf/retain3.s b/ld/testsuite/ld-elf/retain3.s
new file mode 100644
index 0000000000..ce315cbaa6
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.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, %function
+foo:
+	.word 0
+
+	.section	.text.bar,"axR"
+	.global	bar
+	.type	bar, %function
+bar:
+	.long foo
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain4.d b/ld/testsuite/ld-elf/retain4.d
new file mode 100644
index 0000000000..e94898d681
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain4.d
@@ -0,0 +1,10 @@
+#name: SHF_GNU_RETAIN 4 (keep orphaned sections when not discarding)
+#source: retain4.s
+#ld: -e _start --gc-sections --orphan-handling=place
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . orphaned_fn
+#pass
diff --git a/ld/testsuite/ld-elf/retain4.s b/ld/testsuite/ld-elf/retain4.s
new file mode 100644
index 0000000000..9f350cd3b2
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain4.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	orphaned_fn
+	.type	orphaned_fn, %function
+orphaned_fn:
+	.word 0
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain5.d b/ld/testsuite/ld-elf/retain5.d
new file mode 100644
index 0000000000..378799599e
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5.d
@@ -0,0 +1,12 @@
+#name: SHF_GNU_RETAIN 5 (don't pull SHF_GNU_RETAIN section out of lib)
+#source: retain5main.s
+#ld: --gc-sections -e _start --print-gc-sections -Ltmpdir -lretain5 -Map=retain5.map
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#map: retain5.map
+#DUMPPROG: nm
+
+#failif
+#...
+[0-9a-f]+ . foo
+#...
diff --git a/ld/testsuite/ld-elf/retain5.map b/ld/testsuite/ld-elf/retain5.map
new file mode 100644
index 0000000000..6b97c2a220
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5.map
@@ -0,0 +1,5 @@
+# Check that the library was actually loaded to catch any false PASS.
+
+#...
+LOAD tmpdir/libretain5.a
+#pass
diff --git a/ld/testsuite/ld-elf/retain5lib.s b/ld/testsuite/ld-elf/retain5lib.s
new file mode 100644
index 0000000000..4e83731719
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5lib.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, %function
+foo:
+	.long undefined_sym
diff --git a/ld/testsuite/ld-elf/retain5main.s b/ld/testsuite/ld-elf/retain5main.s
new file mode 100644
index 0000000000..89a7784d13
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5main.s
@@ -0,0 +1,5 @@
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain6a.d b/ld/testsuite/ld-elf/retain6a.d
new file mode 100644
index 0000000000..92872deffc
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6a.d
@@ -0,0 +1,14 @@
+#name: SHF_GNU_RETAIN 6a (pull section out of lib required by SHF_GNU_RETAIN section)
+#source: retain6main.s
+#ld: --gc-sections -e _start -u bar -Ltmpdir -lretain6
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . bar
+#...
+[0-9a-f]+ . retain_from_lib
+#...
+[0-9a-f]+ . retained_fn
+#pass
diff --git a/ld/testsuite/ld-elf/retain6b.d b/ld/testsuite/ld-elf/retain6b.d
new file mode 100644
index 0000000000..a797bf7837
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6b.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN 6b (pull section out of lib required by SHF_GNU_RETAIN section)
+#source: retain6main.s
+#ld: --gc-sections -e _start -u bar -Ltmpdir -lretain6
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#failif
+#...
+[0-9a-f]+ . .*discard.*
+#...
diff --git a/ld/testsuite/ld-elf/retain6lib.s b/ld/testsuite/ld-elf/retain6lib.s
new file mode 100644
index 0000000000..a393dbac61
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6lib.s
@@ -0,0 +1,17 @@
+	.section	.text.bar,"ax"
+	.global	bar
+	.type	bar, %function
+bar:
+	.word 0
+
+	.section	.text.retain_from_lib,"axR"
+	.global	retain_from_lib
+	.type	retain_from_lib, %function
+retain_from_lib:
+	.word 0
+
+	.section	.text.discard_from_lib,"ax"
+	.global	discard_from_lib
+	.type	discard_from_lib, %function
+discard_from_lib:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain6main.s b/ld/testsuite/ld-elf/retain6main.s
new file mode 100644
index 0000000000..a66c5b3247
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6main.s
@@ -0,0 +1,13 @@
+/* Undefined symbol reference in retained section .text.retained_fn requires
+   symbol definition to be pulled out of library.  */
+	.section	.text.retained_fn,"axR"
+	.global	retained_fn
+	.type	retained_fn, %function
+retained_fn:
+	.long bar
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain7.s b/ld/testsuite/ld-elf/retain7.s
new file mode 100644
index 0000000000..2d80947fbd
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain7.s
@@ -0,0 +1,104 @@
+	.section	.bss,"aw"
+	.global	discard0
+	.type	discard0, %object
+discard0:
+	.zero	2
+
+	.section	.bss,"aw"
+	.global	discard1
+	.type	discard1, %object
+discard1:
+	.zero	2
+
+	.section	.data,"aw"
+	.global	discard2
+	.type	discard2, %object
+discard2:
+	.word	1
+
+	.section	.bss,"aw"
+	.type	sdiscard0, %object
+sdiscard0:
+	.zero	2
+
+	.section	.bss,"aw"
+	.type	sdiscard1, %object
+sdiscard1:
+	.zero	2
+
+	.section	.data,"aw"
+	.type	sdiscard2, %object
+sdiscard2:
+	.word	1
+
+	.text
+	.global	fndiscard0
+	.type	fndiscard0, %function
+fndiscard0:
+	.word 0
+
+	.section	.bss,"awR"
+	.global	retain0
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.section	.bss,"awR"
+	.global	retain1
+	.type	retain1, %object
+retain1:
+	.zero	2
+
+	.section	.data,"awR"
+	.global	retain2
+	.type	retain2, %object
+retain2:
+	.word	1
+
+	.section	.bss,"awR"
+	.type	sretain0, %object
+sretain0:
+	.zero	2
+
+	.section	.bss,"awR"
+	.type	sretain1, %object
+sretain1:
+	.zero	2
+
+	.section	.data,"aRw"
+	.type	sretain2, %object
+sretain2:
+	.word	1
+
+	.section	.text,"Rax"
+	.global	fnretain1
+	.type	fnretain1, %function
+fnretain1:
+	.word	0
+
+	.section	.text,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, %function
+fndiscard2:
+	.word	0
+
+	.section	.bss,"awR"
+	.type	lsretain0.2, %object
+lsretain0.2:
+	.zero	2
+
+	.section	.bss,"aRw"
+	.type	lsretain1.1, %object
+lsretain1.1:
+	.zero	2
+
+	.section	.data,"aRw"
+	.type	lsretain2.0, %object
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain7a.d b/ld/testsuite/ld-elf/retain7a.d
new file mode 100644
index 0000000000..055bb74da9
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain7a.d
@@ -0,0 +1,28 @@
+#name: SHF_GNU_RETAIN 7a (flag applied to default section names)
+#source: retain7.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . fnretain1
+#...
+[0-9a-f]+ . lsretain0.2
+#...
+[0-9a-f]+ . lsretain1.1
+#...
+[0-9a-f]+ . lsretain2.0
+#...
+[0-9a-f]+ . retain0
+#...
+[0-9a-f]+ . retain1
+#...
+[0-9a-f]+ . retain2
+#...
+[0-9a-f]+ . sretain0
+#...
+[0-9a-f]+ . sretain1
+#...
+[0-9a-f]+ . sretain2
+#pass
diff --git a/ld/testsuite/ld-elf/retain7b.d b/ld/testsuite/ld-elf/retain7b.d
new file mode 100644
index 0000000000..f268047c35
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain7b.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN 7b (flag applied to default section names)
+#source: retain7.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#nm: -n
+
+#failif
+#...
+[0-9a-f]+ . .*discard.*
+#...
-- 
2.28.0

Comments

H.J. Lu via Gcc-patches Oct. 26, 2020, 7:08 p.m. | #1
On 10/6/20 12:10 PM, Jozef Lawrynowicz wrote:

> Should "used" apply SHF_GNU_RETAIN?

> ===================================

> Another talking point is whether the existing "used" attribute should

> apply the SHF_GNU_RETAIN flag to the containing section.

> 

> It seems unlikely that a user applies the "used" attribute to a

> declaration, and means for it to be saved from only compiler

> optimization, but *not* linker optimization. So perhaps it would be

> beneficial for "used" to apply SHF_GNU_RETAIN in some way.

> 

> If "used" did apply SHF_GNU_RETAIN, we would also have to

> consider the above options for how to apply SHF_GNU_RETAIN to the

> section. Since the "used" attribute has been around for a while 

> it might not be appropriate for its behavior to be changed to place the

> associated declaration in its own, unique section, as in option (2).

> 


To me, if I use attribute((used)), and the linker still garbage
collects the symbol, then the toolchain has a bug.  Is there any
use case that would suggest otherwise?

Thanks,
Pedro Alves
H.J. Lu via Gcc-patches Oct. 26, 2020, 7:12 p.m. | #2
On 10/6/20 12:10 PM, Jozef Lawrynowicz wrote:
> The changes would also only affect targets

> that support the GNU ELF OSABI, which would lead to inconsistent

> behavior between non-GNU OS's.


Well, a separate __attribute__((retain)) will necessarily only work
on GNU ELF targets, so that just shifts the "inconsistent" behavior
elsewhere.

Pedro Alves
Jozef Lawrynowicz Oct. 30, 2020, 1:51 p.m. | #3
On Mon, Oct 26, 2020 at 07:08:06PM +0000, Pedro Alves via Gcc-patches wrote:
> On 10/6/20 12:10 PM, Jozef Lawrynowicz wrote:

> 

> > Should "used" apply SHF_GNU_RETAIN?

> > ===================================

> > Another talking point is whether the existing "used" attribute should

> > apply the SHF_GNU_RETAIN flag to the containing section.

> > 

> > It seems unlikely that a user applies the "used" attribute to a

> > declaration, and means for it to be saved from only compiler

> > optimization, but *not* linker optimization. So perhaps it would be

> > beneficial for "used" to apply SHF_GNU_RETAIN in some way.

> > 

> > If "used" did apply SHF_GNU_RETAIN, we would also have to

> > consider the above options for how to apply SHF_GNU_RETAIN to the

> > section. Since the "used" attribute has been around for a while 

> > it might not be appropriate for its behavior to be changed to place the

> > associated declaration in its own, unique section, as in option (2).

> > 

> 

> To me, if I use attribute((used)), and the linker still garbage

> collects the symbol, then the toolchain has a bug.  Is there any

> use case that would suggest otherwise?


I revised the implementation so the "used" attribute will save the
symbol from garbage collection.

By implementing the TARGET_MARK_DECL_PRESERVED macro, a
".retain <symname>" directive will be emitted for decls that had the
"used" attribute applied.

GAS will set the SHF_GNU_RETAIN flag on sections containing symbols
referenced in ".retain" directives.
GAS still supports setting the "R" flag in .section directives, but GCC
won't emit these.

LD will save SHF_GNU_RETAIN sections from garbage collection.

For reference, I've attached the Binutils and GCC patches that implement
this. The results from a bootstrap and regtest for x86_64-pc-linux-gnu
and light testing for arm-eabi are looking good, but I need to add more
tests and clean the patches up before final submission.

Thanks!
Jozef

> 

> Thanks,

> Pedro Alves

>
diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 140a98594d..ffb75f7919 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -1897,14 +1897,15 @@ struct output_elf_obj_tdata
   bfd_boolean flags_init;
 };
 
-/* Indicate if the bfd contains SHF_GNU_MBIND sections or symbols that
-   have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE binding.  Used
-   to set the osabi field in the ELF header structure.  */
+/* Indicate if the bfd contains SHF_GNU_MBIND/SHF_GNU_RETAIN sections or
+   symbols that have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE
+   binding.  Used to set the osabi field in the ELF header structure.  */
 enum elf_gnu_osabi
 {
   elf_gnu_osabi_mbind = 1 << 0,
   elf_gnu_osabi_ifunc = 1 << 1,
   elf_gnu_osabi_unique = 1 << 2,
+  elf_gnu_osabi_retain = 1 << 3,
 };
 
 typedef struct elf_section_list
@@ -2034,7 +2035,7 @@ struct elf_obj_tdata
   ENUM_BITFIELD (dynamic_lib_link_class) dyn_lib_class : 4;
 
   /* Whether the bfd uses OS specific bits that require ELFOSABI_GNU.  */
-  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 3;
+  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 4;
 
   /* Whether if the bfd contains the GNU_PROPERTY_NO_COPY_ON_PROTECTED
      property.  */
diff --git a/bfd/elf.c b/bfd/elf.c
index 9d7cbd52e0..8ec21d7705 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -1066,9 +1066,12 @@ _bfd_elf_make_section_from_shdr (bfd *abfd,
       /* FIXME: We should not recognize SHF_GNU_MBIND for ELFOSABI_NONE,
 	 but binutils as of 2019-07-23 did not set the EI_OSABI header
 	 byte.  */
-    case ELFOSABI_NONE:
     case ELFOSABI_GNU:
     case ELFOSABI_FREEBSD:
+      if ((hdr->sh_flags & SHF_GNU_RETAIN) != 0)
+	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_retain;
+      /* Fall through */
+    case ELFOSABI_NONE:
       if ((hdr->sh_flags & SHF_GNU_MBIND) != 0)
 	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_mbind;
       break;
@@ -12454,8 +12457,8 @@ _bfd_elf_final_write_processing (bfd *abfd)
     i_ehdrp->e_ident[EI_OSABI] = get_elf_backend_data (abfd)->elf_osabi;
 
   /* Set the osabi field to ELFOSABI_GNU if the binary contains
-     SHF_GNU_MBIND sections or symbols of STT_GNU_IFUNC type or
-     STB_GNU_UNIQUE binding.  */
+     SHF_GNU_MBIND or SHF_GNU_RETAIN sections or symbols of STT_GNU_IFUNC type
+     or STB_GNU_UNIQUE binding.  */
   if (elf_tdata (abfd)->has_gnu_osabi != 0)
     {
       if (i_ehdrp->e_ident[EI_OSABI] == ELFOSABI_NONE)
@@ -12464,11 +12467,17 @@ _bfd_elf_final_write_processing (bfd *abfd)
 	       && i_ehdrp->e_ident[EI_OSABI] != ELFOSABI_FREEBSD)
 	{
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_mbind)
-	    _bfd_error_handler (_("GNU_MBIND section is unsupported"));
+	    _bfd_error_handler (_("GNU_MBIND section is supported only by GNU "
+				  "and FreeBSD targets"));
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_ifunc)
-	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is unsupported"));
+	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is supported "
+				  "only by GNU and FreeBSD targets"));
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_unique)
-	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is unsupported"));
+	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is supported "
+				  "only by GNU and FreeBSD targets"));
+	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_retain)
+	    _bfd_error_handler (_("GNU_RETAIN section is supported "
+				  "only by GNU and FreeBSD targets"));
 	  bfd_set_error (bfd_error_sorry);
 	  return FALSE;
 	}
diff --git a/bfd/elflink.c b/bfd/elflink.c
index e23d189b98..22af7bbb1d 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -10733,6 +10733,10 @@ elf_link_input_bfd (struct elf_final_link_info *flinfo, bfd *input_bfd)
       extsymoff = symtab_hdr->sh_info;
     }
 
+  /* Enable GNU OSABI features in the output BFD that are used in the input
+     BFD.  */
+  elf_tdata (output_bfd)->has_gnu_osabi |= elf_tdata (input_bfd)->has_gnu_osabi;
+
   /* Read the local symbols.  */
   isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
   if (isymbuf == NULL && locsymcount != 0)
@@ -14103,7 +14107,9 @@ bfd_elf_gc_sections (bfd *abfd, struct bfd_link_info *info)
 			    == SHT_FINI_ARRAY)))
 		|| (elf_section_data (o)->this_hdr.sh_type == SHT_NOTE
 		    && elf_next_in_group (o) == NULL
-		    && elf_linked_to_section (o) == NULL)))
+		    && elf_linked_to_section (o) == NULL)
+		|| ((elf_tdata (sub)->has_gnu_osabi & elf_gnu_osabi_retain)
+		    && (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 35e4e303e1..32c264d74e 100644
--- a/binutils/NEWS
+++ b/binutils/NEWS
@@ -7,6 +7,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.
+
 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 03cfc97464..dd82ff8899 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -5996,6 +5996,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)
@@ -6028,7 +6030,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_TLS:		sindex = 9; break;
 	    case SHF_EXCLUDE:		sindex = 18; break;
 	    case SHF_COMPRESSED:	sindex = 20; break;
-	    case SHF_GNU_MBIND:		sindex = 24; break;
 
 	    default:
 	      sindex = -1;
@@ -6080,10 +6081,26 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 		  if (flag == SHF_PPC_VLE)
 		    sindex = 25;
 		  break;
+		}
 
+	      switch (filedata->file_header.e_ident[EI_OSABI])
+		{
+		case ELFOSABI_GNU:
+		case ELFOSABI_FREEBSD:
+		  if (flag == SHF_GNU_RETAIN)
+		    sindex = 26;
+		  /* Fall through */
+		case ELFOSABI_NONE:
+		  if (flag == SHF_GNU_MBIND)
+		    /* We should not recognize SHF_GNU_MBIND for
+		       ELFOSABI_NONE, but binutils as of 2019-07-23 did
+		       not set the EI_OSABI header byte.  */
+		    sindex = 24;
+		  break;
 		default:
 		  break;
 		}
+	      break;
 	    }
 
 	  if (sindex != -1)
@@ -6126,7 +6143,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_TLS:		*p = 'T'; break;
 	    case SHF_EXCLUDE:		*p = 'E'; break;
 	    case SHF_COMPRESSED:	*p = 'C'; break;
-	    case SHF_GNU_MBIND:		*p = 'D'; break;
 
 	    default:
 	      if ((filedata->file_header.e_machine == EM_X86_64
@@ -6136,14 +6152,37 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 		*p = 'l';
 	      else if (filedata->file_header.e_machine == EM_ARM
 		       && flag == SHF_ARM_PURECODE)
-		  *p = 'y';
+		*p = 'y';
 	      else if (filedata->file_header.e_machine == EM_PPC
 		       && flag == SHF_PPC_VLE)
-		  *p = 'v';
+		*p = 'v';
 	      else if (flag & SHF_MASKOS)
 		{
-		  *p = 'o';
-		  sh_flags &= ~ SHF_MASKOS;
+		  switch (filedata->file_header.e_ident[EI_OSABI])
+		    {
+		    case ELFOSABI_GNU:
+		    case ELFOSABI_FREEBSD:
+		      if (flag == SHF_GNU_RETAIN)
+			{
+			  *p = 'R';
+			  break;
+			}
+		      /* Fall through */
+		    case ELFOSABI_NONE:
+		      if (flag == SHF_GNU_MBIND)
+			{
+			  /* We should not recognize SHF_GNU_MBIND for
+			     ELFOSABI_NONE, but binutils as of 2019-07-23 did
+			     not set the EI_OSABI header byte.  */
+			  *p = 'D';
+			  break;
+			}
+		      /* Fall through */
+		    default:
+		      *p = 'o';
+		      sh_flags &= ~SHF_MASKOS;
+		      break;
+		    }
 		}
 	      else if (flag & SHF_MASKPROC)
 		{
diff --git a/binutils/testsuite/binutils-all/readelf-maskos-1a.d b/binutils/testsuite/binutils-all/readelf-maskos-1a.d
new file mode 100644
index 0000000000..7b27358599
--- /dev/null
+++ b/binutils/testsuite/binutils-all/readelf-maskos-1a.d
@@ -0,0 +1,10 @@
+#name: Unknown SHF_MASKOS value in section
+#source: readelf-maskos.s
+#notarget: [supports_gnu_osabi] msp430-*-elf visium-*-elf
+#xfail: arm-*-elf
+#readelf: -S --wide
+# PR26722 for the arm-*-elf XFAIL
+
+#...
+  \[[ 0-9]+\] .data.retain_var.*WAo.*
+#pass
diff --git a/binutils/testsuite/binutils-all/readelf-maskos-1b.d b/binutils/testsuite/binutils-all/readelf-maskos-1b.d
new file mode 100644
index 0000000000..2cbb58a73b
--- /dev/null
+++ b/binutils/testsuite/binutils-all/readelf-maskos-1b.d
@@ -0,0 +1,12 @@
+#name: -t (section details) for unknown SHF_MASKOS value in section
+#source: readelf-maskos.s
+#notarget: [supports_gnu_osabi] msp430-*-elf visium-*-elf
+#xfail: arm-*-elf
+#readelf: -S -t --wide
+# PR26722 for the arm-*-elf XFAIL
+
+#...
+  \[[ 0-9]+\] .data.retain_var
+       PROGBITS +0+ +[0-9a-f]+ +[0-9a-f]+ +[0-9a-f]+ +0 +0 +(1|2|4|8)
+       \[00200003\]: WRITE, ALLOC, OS \(00200000\)
+#pass
diff --git a/binutils/testsuite/binutils-all/readelf-maskos.s b/binutils/testsuite/binutils-all/readelf-maskos.s
new file mode 100644
index 0000000000..d671119bca
--- /dev/null
+++ b/binutils/testsuite/binutils-all/readelf-maskos.s
@@ -0,0 +1,11 @@
+  .section	.data.retain_var,"0x200003"
+	.global	retain_var
+	.type	retain_var, %object
+retain_var:
+	.long	2
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/binutils/testsuite/binutils-all/readelf.exp b/binutils/testsuite/binutils-all/readelf.exp
index 1fb36ae5c4..9d1d496e5c 100644
--- a/binutils/testsuite/binutils-all/readelf.exp
+++ b/binutils/testsuite/binutils-all/readelf.exp
@@ -364,8 +364,15 @@ 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 "retain1a"
+    run_dump_test "retain1b"
+    run_dump_test "readelf-maskos-1a"
+    run_dump_test "readelf-maskos-1b"
+}
 
 # 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.s b/binutils/testsuite/binutils-all/retain1.s
new file mode 100644
index 0000000000..f7716faabe
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1.s
@@ -0,0 +1,104 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, %object
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, %object
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, %object
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, %object
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, %object
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, %object
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, %function
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"awR"
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"awR"
+	.type	retain1, %object
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"awR"
+	.type	retain2, %object
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"awR"
+	.type	sretain0, %object
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"awR"
+	.type	sretain1, %object
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aRw"
+	.type	sretain2, %object
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"Rax"
+	.global	fnretain1
+	.type	fnretain1, %function
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, %function
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"awR"
+	.type	lsretain0.2, %object
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aRw"
+	.type	lsretain1.1, %object
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aRw"
+	.type	lsretain2.0, %object
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/binutils/testsuite/binutils-all/retain1a.d b/binutils/testsuite/binutils-all/retain1a.d
new file mode 100644
index 0000000000..6397ac52ae
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1a.d
@@ -0,0 +1,18 @@
+#name: readelf SHF_GNU_RETAIN
+#source: retain1.s
+#target: [supports_gnu_osabi]
+#readelf: -S --wide
+
+#...
+  \[[ 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/retain1b.d b/binutils/testsuite/binutils-all/retain1b.d
new file mode 100644
index 0000000000..12bc388ba1
--- /dev/null
+++ b/binutils/testsuite/binutils-all/retain1b.d
@@ -0,0 +1,46 @@
+#name: -t (section details) for readelf SHF_GNU_RETAIN
+#source: retain1.s
+#target: [supports_gnu_osabi]
+#readelf: -S -t --wide
+
+#...
+  \[[ 0-9]+\] .bss.retain0
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.retain1
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .data.retain2
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.sretain0
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.sretain1
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .data.sretain2
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .text.fnretain1
+#...
+       \[0+200006\]: ALLOC, EXEC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.lsretain0
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .bss.lsretain1
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#...
+  \[[ 0-9]+\] .data.lsretain2
+#...
+       \[0+200003\]: WRITE, ALLOC, GNU_RETAIN
+#pass
diff --git a/binutils/testsuite/lib/binutils-common.exp b/binutils/testsuite/lib/binutils-common.exp
index b9a1e6e4bc..a43639bafb 100644
--- a/binutils/testsuite/lib/binutils-common.exp
+++ b/binutils/testsuite/lib/binutils-common.exp
@@ -195,13 +195,15 @@ proc match_target { target } {
 
 # True if the ELF target supports setting the ELF header OSABI field
 # to ELFOSABI_GNU or ELFOSABI_FREEBSD, a requirement for STT_GNU_IFUNC
-# symbol and SHF_GNU_MBIND section support.
+# symbol and SHF_GNU_MBIND or SHF_GNU_RETAIN section support.
 #
 # This generally depends on the target OS only, however there are a
 # number of exceptions for bare metal targets as follows.  The MSP430
 # and Visium targets set OSABI to ELFOSABI_STANDALONE.  Likewise
 # non-EABI ARM targets set OSABI to ELFOSABI_ARM
 #
+# Non-Linux HPPA defaults to ELFOSABI_HPUX.
+#
 # Note that some TI C6X targets use ELFOSABI_C6000_* but one doesn't,
 # so we don't try to sort out tic6x here.  (The effect is that linker
 # testcases will generally need to exclude tic6x or use a -m option.)
@@ -227,6 +229,7 @@ proc supports_gnu_osabi {} {
     }
     if { [istarget "arm*-*-*"]
 	 || [istarget "msp430-*-*"]
+	 || [istarget "hppa-unknown-elf"]
 	 || [istarget "visium-*-*"] } {
 	return 0
     }
diff --git a/gas/NEWS b/gas/NEWS
index 41cc668e61..5340eb8223 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -44,6 +44,11 @@
 
 * Configure with --enable-x86-used-note by default for Linux/x86.
 
+* Add support for the "R" flag in the .section directive.
+  This flag requires ELFOSABI_GNU or ELFOSABI_FREEBSD, and applies the
+  ELF SHF_GNU_RETAIN flag to the specified section.  This flag specifies
+  the section should not be garbage collected by the linker.
+
 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 f061ea61f3..11f6795229 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -79,6 +79,7 @@ static void obj_elf_tls_common (int);
 static void obj_elf_lcomm (int);
 static void obj_elf_struct (int);
 static void obj_elf_attach_to_group (int);
+static void obj_elf_retain (int);
 
 static const pseudo_typeS elf_pseudo_table[] =
 {
@@ -121,6 +122,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},
@@ -529,9 +533,9 @@ get_section_by_match (bfd *abfd ATTRIBUTE_UNUSED, asection *sec, void *inf)
   const char *group_name = elf_group_name (sec);
   const char *linked_to_symbol_name
     = sec->map_head.linked_to_symbol_name;
-  unsigned int info = elf_section_data (sec)->this_hdr.sh_info;
+  unsigned int sh_info = elf_section_data (sec)->this_hdr.sh_info;
 
-  return (info == match->info
+  return (sh_info == match->sh_info
 	  && ((bfd_section_flags (sec) & SEC_ASSEMBLER_SECTION_ID)
 	       == (match->flags & SEC_ASSEMBLER_SECTION_ID))
 	  && sec->section_id == match->section_id
@@ -740,7 +744,7 @@ obj_elf_change_section (const char *name,
 	type = bfd_elf_get_default_section_type (flags);
       elf_section_type (sec) = type;
       elf_section_flags (sec) = attr;
-      elf_section_data (sec)->this_hdr.sh_info = match_p->info;
+      elf_section_data (sec)->this_hdr.sh_info = match_p->sh_info;
 
       /* Prevent SEC_HAS_CONTENTS from being inadvertently set.  */
       if (type == SHT_NOBITS)
@@ -806,9 +810,16 @@ 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;
+	    {
+	      /* FIXME: Maybe we should consider removing a previously set
+		 processor or application specific attribute as suspicious ?  */
+	      /* Don't overwrite a previously set SHF_GNU_RETAIN flag for the
+		 section.  The entire section will be marked retained.  */
+	      if ((elf_tdata (stdoutput)->has_gnu_osabi & elf_gnu_osabi_retain)
+		  && ((elf_section_flags (old_sec) & SHF_GNU_RETAIN)))
+		attr |= SHF_GNU_RETAIN;
+	      elf_section_flags (sec) = attr;
+	    }
 
 	  if ((flags & SEC_MERGE) && old_sec->entsize != (unsigned) entsize)
 	    as_bad (_("changed section entity size for %s"), name);
@@ -861,6 +872,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;
@@ -890,8 +904,32 @@ obj_elf_parse_section_letters (char *str, size_t len,
 	      if (ISDIGIT (*str))
 		{
 		  char * end;
+		  struct elf_backend_data *bed;
+		  bfd_vma numeric_flags = strtoul (str, &end, 0);
+
+		  attr |= numeric_flags;
+
+		  bed = (struct elf_backend_data *)
+		    get_elf_backend_data (stdoutput);
+
+		  if (bed->elf_osabi == ELFOSABI_NONE
+		      || bed->elf_osabi == ELFOSABI_STANDALONE
+		      || bed->elf_osabi == ELFOSABI_GNU
+		      || bed->elf_osabi == ELFOSABI_FREEBSD)
+		    {
+		      /* Add flags in the SHF_MASKOS range to gnu_attr for
+			 OSABIs that support those flags.
+			 Also adding the flags for ELFOSABI_{NONE,STANDALONE}
+			 allows them to be validated later in obj_elf_section.
+			 We can't just always set these bits in gnu_attr for
+			 all OSABIs, since Binutils does not recognize all
+			 SHF_MASKOS bits for non-GNU OSABIs.  It's therefore
+			 possible that numeric flags are being used to set bits
+			 in the SHF_MASKOS range for those targets, and we
+			 don't want assembly to fail in those situations.  */
+		      *gnu_attr |= (numeric_flags & SHF_MASKOS);
+		    }
 
-		  attr |= strtoul (str, & end, 0);
 		  /* Update str and len, allowing for the fact that
 		     we will execute str++ and len-- below.  */
 		  end --;
@@ -1287,18 +1325,21 @@ obj_elf_section (int push)
 	      if (ISDIGIT (* input_line_pointer))
 		{
 		  char *t = input_line_pointer;
-		  match.info = strtoul (input_line_pointer,
+		  match.sh_info = strtoul (input_line_pointer,
 					&input_line_pointer, 0);
-		  if (match.info == (unsigned int) -1)
+		  if (match.sh_info == (unsigned int) -1)
 		    {
 		      as_warn (_("unsupported mbind section info: %s"), t);
-		      match.info = 0;
+		      match.sh_info = 0;
 		    }
 		}
 	      else
 		input_line_pointer = save;
 	    }
 
+	  if ((gnu_attr & SHF_GNU_RETAIN) != 0)
+	    match.sh_flags |= SHF_GNU_RETAIN;
+
 	  if (*input_line_pointer == ',')
 	    {
 	      char *save = input_line_pointer;
@@ -1387,26 +1428,39 @@ obj_elf_section (int push)
  done:
   demand_empty_rest_of_line ();
 
-  obj_elf_change_section (name, type, attr, entsize, &match, linkonce,
-			  push);
-
-  if ((gnu_attr & SHF_GNU_MBIND) != 0)
+  if ((gnu_attr & (SHF_GNU_MBIND | SHF_GNU_RETAIN)) != 0)
     {
       struct elf_backend_data *bed;
+      bfd_boolean mbind_p = (gnu_attr & SHF_GNU_MBIND) != 0;
 
-      if ((attr & SHF_ALLOC) == 0)
+      if (mbind_p && (attr & SHF_ALLOC) == 0)
 	as_bad (_("SHF_ALLOC isn't set for GNU_MBIND section: %s"), name);
 
       bed = (struct elf_backend_data *) get_elf_backend_data (stdoutput);
-      if (bed->elf_osabi == ELFOSABI_NONE)
-	bed->elf_osabi = ELFOSABI_GNU;
-      else if (bed->elf_osabi != ELFOSABI_GNU
-	       && bed->elf_osabi != ELFOSABI_FREEBSD)
-	as_bad (_("GNU_MBIND section is supported only by GNU "
-		  "and FreeBSD targets"));
-      elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_mbind;
+
+      if (bed->elf_osabi != ELFOSABI_GNU
+	  && bed->elf_osabi != ELFOSABI_FREEBSD
+	  && bed->elf_osabi != ELFOSABI_NONE)
+	{
+	  as_bad (_("%s section is supported only by GNU and FreeBSD targets"),
+		  mbind_p ? "GNU_MBIND" : "GNU_RETAIN");
+	}
+      else
+	{
+	  if (bed->elf_osabi == ELFOSABI_NONE)
+	    bed->elf_osabi = ELFOSABI_GNU;
+
+	  if (mbind_p)
+	    elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_mbind;
+	  if ((gnu_attr & SHF_GNU_RETAIN) != 0)
+	    elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_retain;
+
+	  attr |= gnu_attr;
+	}
     }
-  elf_section_flags (now_seg) |= gnu_attr;
+
+  obj_elf_change_section (name, type, attr, entsize, &match, linkonce,
+			  push);
 
   if (linked_to_section_index != -1UL)
     {
@@ -2031,6 +2085,34 @@ obj_elf_gnu_attribute (int ignored ATTRIBUTE_UNUSED)
   obj_elf_vendor_attribute (OBJ_ATTR_GNU);
 }
 
+/* TODO */
+void
+obj_elf_retain (int arg ATTRIBUTE_UNUSED)
+{
+  struct elf_backend_data *bed;
+  symbolS *sym;
+
+  bed = (struct elf_backend_data *) get_elf_backend_data (stdoutput);
+
+  if (bed->elf_osabi != ELFOSABI_GNU
+      && bed->elf_osabi != ELFOSABI_FREEBSD
+      && bed->elf_osabi != ELFOSABI_NONE)
+    {
+      as_bad (_(".retain directive is supported only by GNU and FreeBSD targets"));
+    }
+  else
+    {
+      if (bed->elf_osabi == ELFOSABI_NONE)
+	bed->elf_osabi = ELFOSABI_GNU;
+      elf_tdata (stdoutput)->has_gnu_osabi |= elf_gnu_osabi_retain;
+    }
+
+  sym = get_sym_from_input_line_and_check ();
+  symbol_get_obj (sym)->retain = 1;
+
+  demand_empty_rest_of_line ();
+}
+
 void
 elf_obj_read_begin_hook (void)
 {
@@ -2624,6 +2706,9 @@ elf_frob_symbol (symbolS *symp, int *puntp)
 	}
     }
 
+  if (symbol_get_obj (symp)->retain)
+    elf_section_flags (S_GET_SEGMENT (symp)) |= SHF_GNU_RETAIN;
+
   /* Double check weak symbols.  */
   if (S_IS_WEAK (symp))
     {
diff --git a/gas/config/obj-elf.h b/gas/config/obj-elf.h
index 4f29572eef..628f5b529d 100644
--- a/gas/config/obj-elf.h
+++ b/gas/config/obj-elf.h
@@ -84,6 +84,10 @@ struct elf_obj_sy
   /* Whether visibility of the symbol should be changed.  */
   ENUM_BITFIELD (elf_visibility) visibility : 2;
 
+  /* Set when the symbol was marked with the .retain directive, so it's
+     containing section must be marked with SHF_GNU_RETAIN.  */
+  unsigned int retain : 1;
+
   /* Use this to keep track of .size expressions that involve
      differences that we can't compute yet.  */
   expressionS *size;
@@ -106,8 +110,9 @@ struct elf_section_match
 {
   const char *   group_name;
   const char *   linked_to_symbol_name;
-  unsigned int   info;
   unsigned int   section_id;
+  unsigned int   sh_info;		/* ELF section information.  */
+  bfd_vma	 sh_flags;		/* ELF section flags.  */
   flagword       flags;
 };
 
diff --git a/gas/doc/as.texi b/gas/doc/as.texi
index 4d5294552a..e92432a8bd 100644
--- a/gas/doc/as.texi
+++ b/gas/doc/as.texi
@@ -6657,6 +6657,9 @@ section is a member of a section group
 section is used for thread-local-storage
 @item ?
 section is a member of the previously-current section's group, if any
+@item R
+retained section (apply SHF_GNU_RETAIN to prevent linker garbage
+collection, GNU ELF extension)
 @item @code{<number>}
 a numeric value indicating the bits to be set in the ELF section header's flags
 field.  Note - if one or more of the alphabetic characters described above is
diff --git a/gas/testsuite/gas/elf/elf.exp b/gas/testsuite/gas/elf/elf.exp
index 9d75154483..49d5a47959 100644
--- a/gas/testsuite/gas/elf/elf.exp
+++ b/gas/testsuite/gas/elf/elf.exp
@@ -261,8 +261,11 @@ if { [is_elf_format] } then {
     run_dump_test "section19"
     run_dump_test "section20"
     run_dump_test "section21"
+    run_dump_test "section22"
+    run_dump_test "section23a"
+    run_dump_test "section23b"
+    run_dump_test "section24"
     run_dump_test "sh-link-zero"
-
     run_dump_test "dwarf2-1" $dump_opts
     run_dump_test "dwarf2-2" $dump_opts
     run_dump_test "dwarf2-3" $dump_opts
diff --git a/gas/testsuite/gas/elf/section10.d b/gas/testsuite/gas/elf/section10.d
index 554a791f1d..6aa7b088b1 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\)
+[ 	]*\[.*fedff030\]: 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\)
+[ 	]*\[.*fedf0000\]:.* EXCLUDE, OS \(.*ed00000\), PROC \(.*[3467]0000000\), UNKNOWN \(.*f0000\)
 [ 	]*\[.*\][ 	]+.data.foo
 [ 	]*LOUSER\+0x7f000000[ 	].*
 [ 	]*\[0+003\]: WRITE, ALLOC
diff --git a/gas/testsuite/gas/elf/section10.s b/gas/testsuite/gas/elf/section10.s
index 29f1184523..d52b3458fb 100644
--- a/gas/testsuite/gas/elf/section10.s
+++ b/gas/testsuite/gas/elf/section10.s
@@ -7,7 +7,7 @@
 	.word 2
 
 	# Make sure that specifying further arguments to .sections is still supported
-	.section sec3, "0xfefff000MS", %progbits, 32
+	.section sec3, "0xfedff000MS", %progbits, 32
 	.word 3
 
 	# Make sure that extra flags can be set for well known sections as well.
@@ -19,7 +19,7 @@
 	.word 5
 
 	# Test both together, with a quoted type value.
-	.section sec5, "0xfeff0000", "0x80000009"
+	.section sec5, "0xfedf0000", "0x80000009"
 	.word 6
 
 	# Test that declaring an extended version of a known special section works.
diff --git a/gas/testsuite/gas/elf/section22.d b/gas/testsuite/gas/elf/section22.d
new file mode 100644
index 0000000000..8aa7fcfc34
--- /dev/null
+++ b/gas/testsuite/gas/elf/section22.d
@@ -0,0 +1,19 @@
+#readelf: -h -S --wide
+#name: SHF_GNU_RETAIN sections 22
+#notarget: ![supports_gnu_osabi]
+
+#...
+ +OS/ABI: +UNIX - GNU
+#...
+  \[..\] .text.discard0[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX.*
+#...
+  \[..\] .data.discard1[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+#...
+  \[..\] .bss.discard2[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  WA.*
+#...
+  \[..\] .bss.retain0[ 	]+NOBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#...
+  \[..\] .data.retain1[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#...
+  \[..\] .text.retain2[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR.*
+#pass
diff --git a/gas/testsuite/gas/elf/section22.s b/gas/testsuite/gas/elf/section22.s
new file mode 100644
index 0000000000..66ed990e57
--- /dev/null
+++ b/gas/testsuite/gas/elf/section22.s
@@ -0,0 +1,34 @@
+	.section	.text.discard0,"ax",%progbits
+	.global	discard0
+	.type	discard0, %function
+discard0:
+	.word	0
+
+	.section	.data.discard1,"aw"
+	.global	discard1
+	.type	discard1, %object
+discard1:
+	.word	1
+
+	.section	.bss.discard2,"aw"
+	.global	discard2
+	.type	discard2, %object
+discard2:
+	.zero	2
+
+	.section	.bss.retain0,"awR",%nobits
+	.global	retain0
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.section	.data.retain1,"awR",%progbits
+	.type	retain1, %object
+retain1:
+	.word	1
+
+	.section	.text.retain2,"axR",%progbits
+	.global	retain2
+	.type	retain2, %function
+retain2:
+	.word	0
diff --git a/gas/testsuite/gas/elf/section23.s b/gas/testsuite/gas/elf/section23.s
new file mode 100644
index 0000000000..d671119bca
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23.s
@@ -0,0 +1,11 @@
+  .section	.data.retain_var,"0x200003"
+	.global	retain_var
+	.type	retain_var, %object
+retain_var:
+	.long	2
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/gas/testsuite/gas/elf/section23a.d b/gas/testsuite/gas/elf/section23a.d
new file mode 100644
index 0000000000..1d850d9e8e
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23a.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN set with numeric flag value in .section
+#source: section23.s
+#target: [supports_gnu_osabi]
+#readelf: -h -S --wide
+
+#...
+ +OS/ABI: +UNIX - GNU
+#...
+  \[..\] .data.retain_var[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 WAR.*
+#pass
+
diff --git a/gas/testsuite/gas/elf/section23b.d b/gas/testsuite/gas/elf/section23b.d
new file mode 100644
index 0000000000..c85200e5ff
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23b.d
@@ -0,0 +1,6 @@
+#name: SHF_GNU_RETAIN set with numeric flag value in .section for non-GNU OSABI target
+#source: section23.s
+#error_output: section23b.err
+#target: msp430-*-elf visium-*-elf
+
+# This test only runs for targets which set ELFOSABI_STANDALONE.
diff --git a/gas/testsuite/gas/elf/section23b.err b/gas/testsuite/gas/elf/section23b.err
new file mode 100644
index 0000000000..83de60c397
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23b.err
@@ -0,0 +1,2 @@
+.*: Assembler messages:
+.*:1: Error: GNU_RETAIN section is supported only by GNU and FreeBSD targets
diff --git a/gas/testsuite/gas/elf/section24.d b/gas/testsuite/gas/elf/section24.d
new file mode 100644
index 0000000000..5ee4aee3af
--- /dev/null
+++ b/gas/testsuite/gas/elf/section24.d
@@ -0,0 +1,18 @@
+#name: Warn for SHF_GNU_RETAIN set on existing section
+#notarget: ![supports_gnu_osabi] rx-*-*
+#readelf: -h -S --wide
+#warning_output: section24.l
+# rx-*-* does not automatically create a ".text" section when starting assembly.
+
+#...
+ +OS/ABI: +UNIX - GNU
+#...
+  \[..\] .text[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX .*
+#...
+  \[..\] .text[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR .*
+#...
+  \[..\] .text.foo[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00 AXR .*
+#...
+  \[..\] .text.bar[ 	]+PROGBITS[ 	]+[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ 00  AX .*
+#pass
+
diff --git a/gas/testsuite/gas/elf/section24.l b/gas/testsuite/gas/elf/section24.l
new file mode 100644
index 0000000000..e0ea36078c
--- /dev/null
+++ b/gas/testsuite/gas/elf/section24.l
@@ -0,0 +1,4 @@
+[^:]*: Assembler messages:
+[^:]*:4: Warning: ignoring changed section attributes for .text
+[^:]*:20: Warning: ignoring changed section attributes for .text.foo
+[^:]*:30: Warning: ignoring changed section attributes for .text.bar
diff --git a/gas/testsuite/gas/elf/section24.s b/gas/testsuite/gas/elf/section24.s
new file mode 100644
index 0000000000..deaff0a323
--- /dev/null
+++ b/gas/testsuite/gas/elf/section24.s
@@ -0,0 +1,32 @@
+/* The default .text section automatically created by the assembler does not
+   have the SHF_GNU_RETAIN flag set, so the "R" flag cannot be used with that,
+   or any other, default section.  */
+	.section	.text,"axR",%progbits
+retain_bad:
+	.word 0
+
+/* A unique .text section with SHF_GNU_RETAIN applied can be created.  */
+	.section	.text,"axR",%progbits,unique,0
+retain_good:
+	.word 0
+
+/* SHF_GNU_RETAIN can be applied to a new section.  */
+	.section	.text.foo,"axR",%progbits
+foo_retain:
+	.word 0
+
+/* If the section is used again without SHF_GNU_RETAIN, a warning should be
+   emitted.  */
+	.section	.text.foo,"ax",%progbits
+foo:
+	.word 0
+
+	.section	.text.bar,"ax",%progbits
+bar:
+	.word 0
+
+/* SHF_GNU_RETAIN cannot be applied to a section which was already explicitly
+   declared without SHF_GNU_RETAIN set.  */
+	.section	.text.bar,"axR",%progbits
+bar_retain:
+	.word 0
diff --git a/include/elf/common.h b/include/elf/common.h
index b3c30e0e2f..a17cafcc70 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 81c44191d2..bb23010dad 100644
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -23,6 +23,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.
+
 Changes in 2.35:
 
 * X86 NaCl target support is removed.
diff --git a/ld/ld.texi b/ld/ld.texi
index 48e78aecdb..40c209d914 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -1805,6 +1805,9 @@ 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.
+
 @kindex --print-gc-sections
 @kindex --no-print-gc-sections
 @cindex garbage collection
@@ -5265,6 +5268,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.
+
 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 f2ff0397c7..bd06ab0d39 100644
--- a/ld/testsuite/ld-elf/elf.exp
+++ b/ld/testsuite/ld-elf/elf.exp
@@ -119,6 +119,17 @@ if { [istarget "i?86-*-*"] || [istarget "x86_64-*-*"] } {
     set ASFLAGS "$ASFLAGS -mx86-used-note=no"
 }
 
+# Build libraries required for SHF_GNU_RETAIN tests.
+if { [check_gc_sections_available] && [supports_gnu_osabi] } {
+    run_ld_link_tests [list \
+	[list "Build libretain5.a" "" "" "" \
+	    {retain5lib.s} {} "libretain5.a"] \
+	[list "Build libretain6.a" "" "" "" \
+	    {retain6lib.s} {} "libretain6.a"] \
+	]
+}
+
+
 set test_list [lsort [glob -nocomplain $srcdir/$subdir/*.d]]
 foreach t $test_list {
     # We need to strip the ".d", but can leave the dirname.
diff --git a/ld/testsuite/ld-elf/retain1.s b/ld/testsuite/ld-elf/retain1.s
new file mode 100644
index 0000000000..f7716faabe
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1.s
@@ -0,0 +1,104 @@
+	.global	discard0
+	.section	.bss.discard0,"aw"
+	.type	discard0, %object
+discard0:
+	.zero	2
+
+	.global	discard1
+	.section	.bss.discard1,"aw"
+	.type	discard1, %object
+discard1:
+	.zero	2
+
+	.global	discard2
+	.section	.data.discard2,"aw"
+	.type	discard2, %object
+discard2:
+	.word	1
+
+	.section	.bss.sdiscard0,"aw"
+	.type	sdiscard0, %object
+sdiscard0:
+	.zero	2
+
+	.section	.bss.sdiscard1,"aw"
+	.type	sdiscard1, %object
+sdiscard1:
+	.zero	2
+
+	.section	.data.sdiscard2,"aw"
+	.type	sdiscard2, %object
+sdiscard2:
+	.word	1
+
+	.section	.text.fndiscard0,"ax"
+	.global	fndiscard0
+	.type	fndiscard0, %function
+fndiscard0:
+	.word 0
+
+	.global	retain0
+	.section	.bss.retain0,"awR"
+	.type	retain0, %object
+retain0:
+	.zero	2
+
+	.global	retain1
+	.section	.bss.retain1,"awR"
+	.type	retain1, %object
+retain1:
+	.zero	2
+
+	.global	retain2
+	.section	.data.retain2,"awR"
+	.type	retain2, %object
+retain2:
+	.word	1
+
+	.section	.bss.sretain0,"awR"
+	.type	sretain0, %object
+sretain0:
+	.zero	2
+
+	.section	.bss.sretain1,"awR"
+	.type	sretain1, %object
+sretain1:
+	.zero	2
+
+	.section	.data.sretain2,"aRw"
+	.type	sretain2, %object
+sretain2:
+	.word	1
+
+	.section	.text.fnretain1,"Rax"
+	.global	fnretain1
+	.type	fnretain1, %function
+fnretain1:
+	.word	0
+
+	.section	.text.fndiscard2,"ax"
+	.global	fndiscard2
+	.type	fndiscard2, %function
+fndiscard2:
+	.word	0
+
+	.section	.bss.lsretain0,"awR"
+	.type	lsretain0.2, %object
+lsretain0.2:
+	.zero	2
+
+	.section	.bss.lsretain1,"aRw"
+	.type	lsretain1.1, %object
+lsretain1.1:
+	.zero	2
+
+	.section	.data.lsretain2,"aRw"
+	.type	lsretain2.0, %object
+lsretain2.0:
+	.word	1
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain1a.d b/ld/testsuite/ld-elf/retain1a.d
new file mode 100644
index 0000000000..75abb9856c
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1a.d
@@ -0,0 +1,28 @@
+#name: SHF_GNU_RETAIN 1a
+#source: retain1.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . fnretain1
+#...
+[0-9a-f]+ . lsretain0.2
+#...
+[0-9a-f]+ . lsretain1.1
+#...
+[0-9a-f]+ . lsretain2.0
+#...
+[0-9a-f]+ . retain0
+#...
+[0-9a-f]+ . retain1
+#...
+[0-9a-f]+ . retain2
+#...
+[0-9a-f]+ . sretain0
+#...
+[0-9a-f]+ . sretain1
+#...
+[0-9a-f]+ . sretain2
+#pass
diff --git a/ld/testsuite/ld-elf/retain1b.d b/ld/testsuite/ld-elf/retain1b.d
new file mode 100644
index 0000000000..815c0150f5
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1b.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN 1b
+#source: retain1.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#nm: -n
+
+#failif
+#...
+[0-9a-f]+ . .*discard.*
+#...
diff --git a/ld/testsuite/ld-elf/retain2.d b/ld/testsuite/ld-elf/retain2.d
new file mode 100644
index 0000000000..11efd6ddb8
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain2.d
@@ -0,0 +1,6 @@
+#name: SHF_GNU_RETAIN 2 (remove SHF_GNU_RETAIN sections by placing 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-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
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.d b/ld/testsuite/ld-elf/retain3.d
new file mode 100644
index 0000000000..911f5b7594
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.d
@@ -0,0 +1,12 @@
+#name: SHF_GNU_RETAIN 3 (keep sections referenced by retained sections)
+#source: retain3.s
+#ld: -e _start --gc-sections
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . bar
+#...
+[0-9a-f]+ . foo
+#pass
diff --git a/ld/testsuite/ld-elf/retain3.s b/ld/testsuite/ld-elf/retain3.s
new file mode 100644
index 0000000000..ce315cbaa6
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.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, %function
+foo:
+	.word 0
+
+	.section	.text.bar,"axR"
+	.global	bar
+	.type	bar, %function
+bar:
+	.long foo
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain4.d b/ld/testsuite/ld-elf/retain4.d
new file mode 100644
index 0000000000..e94898d681
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain4.d
@@ -0,0 +1,10 @@
+#name: SHF_GNU_RETAIN 4 (keep orphaned sections when not discarding)
+#source: retain4.s
+#ld: -e _start --gc-sections --orphan-handling=place
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . orphaned_fn
+#pass
diff --git a/ld/testsuite/ld-elf/retain4.s b/ld/testsuite/ld-elf/retain4.s
new file mode 100644
index 0000000000..9f350cd3b2
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain4.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	orphaned_fn
+	.type	orphaned_fn, %function
+orphaned_fn:
+	.word 0
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain5.d b/ld/testsuite/ld-elf/retain5.d
new file mode 100644
index 0000000000..378799599e
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5.d
@@ -0,0 +1,12 @@
+#name: SHF_GNU_RETAIN 5 (don't pull SHF_GNU_RETAIN section out of lib)
+#source: retain5main.s
+#ld: --gc-sections -e _start --print-gc-sections -Ltmpdir -lretain5 -Map=retain5.map
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#map: retain5.map
+#DUMPPROG: nm
+
+#failif
+#...
+[0-9a-f]+ . foo
+#...
diff --git a/ld/testsuite/ld-elf/retain5.map b/ld/testsuite/ld-elf/retain5.map
new file mode 100644
index 0000000000..6b97c2a220
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5.map
@@ -0,0 +1,5 @@
+# Check that the library was actually loaded to catch any false PASS.
+
+#...
+LOAD tmpdir/libretain5.a
+#pass
diff --git a/ld/testsuite/ld-elf/retain5lib.s b/ld/testsuite/ld-elf/retain5lib.s
new file mode 100644
index 0000000000..4e83731719
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5lib.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, %function
+foo:
+	.long undefined_sym
diff --git a/ld/testsuite/ld-elf/retain5main.s b/ld/testsuite/ld-elf/retain5main.s
new file mode 100644
index 0000000000..89a7784d13
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5main.s
@@ -0,0 +1,5 @@
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain6a.d b/ld/testsuite/ld-elf/retain6a.d
new file mode 100644
index 0000000000..92872deffc
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6a.d
@@ -0,0 +1,14 @@
+#name: SHF_GNU_RETAIN 6a (pull section out of lib required by SHF_GNU_RETAIN section)
+#source: retain6main.s
+#ld: --gc-sections -e _start -u bar -Ltmpdir -lretain6
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#...
+[0-9a-f]+ . bar
+#...
+[0-9a-f]+ . retain_from_lib
+#...
+[0-9a-f]+ . retained_fn
+#pass
diff --git a/ld/testsuite/ld-elf/retain6b.d b/ld/testsuite/ld-elf/retain6b.d
new file mode 100644
index 0000000000..a797bf7837
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6b.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN 6b (pull section out of lib required by SHF_GNU_RETAIN section)
+#source: retain6main.s
+#ld: --gc-sections -e _start -u bar -Ltmpdir -lretain6
+#skip: mep-*-* dlx-*-* d30v-*-* pj-*-* pru-*-* xgate-*-* s12z-*-*
+#notarget: ![supports_gnu_osabi] ![check_gc_sections_available]
+#DUMPPROG: nm
+
+#failif
+#...
+[0-9a-f]+ . .*discard.*
+#...
diff --git a/ld/testsuite/ld-elf/retain6lib.s b/ld/testsuite/ld-elf/retain6lib.s
new file mode 100644
index 0000000000..a393dbac61
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6lib.s
@@ -0,0 +1,17 @@
+	.section	.text.bar,"ax"
+	.global	bar
+	.type	bar, %function
+bar:
+	.word 0
+
+	.section	.text.retain_from_lib,"axR"
+	.global	retain_from_lib
+	.type	retain_from_lib, %function
+retain_from_lib:
+	.word 0
+
+	.section	.text.discard_from_lib,"ax"
+	.global	discard_from_lib
+	.type	discard_from_lib, %function
+discard_from_lib:
+	.word 0
diff --git a/ld/testsuite/ld-elf/retain6main.s b/ld/testsuite/ld-elf/retain6main.s
new file mode 100644
index 0000000000..a66c5b3247
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6main.s
@@ -0,0 +1,13 @@
+/* Undefined symbol reference in retained section .text.retained_fn requires
+   symbol definition to be pulled out of library.  */
+	.section	.text.retained_fn,"axR"
+	.global	retained_fn
+	.type	retained_fn, %function
+retained_fn:
+	.long bar
+
+	.section	.text._start,"ax"
+	.global	_start
+	.type	_start, %function
+_start:
+	.word 0
commit c683d66982f7e16e03e637c64f219d5a4d61ad8e
Author: Jozef Lawrynowicz <jozef.l@mittosystems.com>
Date:   Thu Oct 29 21:00:07 2020 +0000

    Implement TARGET_MARK_DECL_PRESERVED for ELF targets supporting SHF_GNU_RETAIN

diff --git a/gcc/config.in b/gcc/config.in
index 3657c46f349..585475e8908 100644
--- a/gcc/config.in
+++ b/gcc/config.in
@@ -1346,6 +1346,12 @@
 #endif
 
 
+/* Define if your assembler supports .retain directive. */
+#ifndef USED_FOR_TARGET
+#undef HAVE_GAS_RETAIN
+#endif
+
+
 /* Define if your assembler supports specifying the exclude section flag. */
 #ifndef USED_FOR_TARGET
 #undef HAVE_GAS_SECTION_EXCLUDE
diff --git a/gcc/config/elfos.h b/gcc/config/elfos.h
index 74a3eafda6b..da6d0de44bc 100644
--- a/gcc/config/elfos.h
+++ b/gcc/config/elfos.h
@@ -474,3 +474,9 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 
 #undef TARGET_LIBC_HAS_FUNCTION
 #define TARGET_LIBC_HAS_FUNCTION no_c99_libc_has_function
+
+#if HAVE_GAS_RETAIN
+#ifndef TARGET_ASM_MARK_DECL_PRESERVED
+#define TARGET_ASM_MARK_DECL_PRESERVED default_elf_mark_decl_preserved
+#endif
+#endif
diff --git a/gcc/configure b/gcc/configure
index abff47d30eb..6ee92333caf 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -24223,6 +24223,43 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
+# Test if the assembler supports the .retain directive for indicating the
+# section containing the specified symbol should not be garbage collected by
+# the linker.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for retain directive" >&5
+$as_echo_n "checking assembler for retain directive... " >&6; }
+if ${gcc_cv_as_retain_r+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  gcc_cv_as_retain_r=no
+  if test x$gcc_cv_as != x; then
+    $as_echo '.global foo1
+  .retain foo1' > conftest.s
+    if { ac_try='$gcc_cv_as $gcc_cv_as_flags  -o conftest.o conftest.s >&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+    then
+	gcc_cv_as_retain_r=yes
+    else
+      echo "configure: failed program was" >&5
+      cat conftest.s >&5
+    fi
+    rm -f conftest.o conftest.s
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_cv_as_retain_r" >&5
+$as_echo "$gcc_cv_as_retain_r" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_GAS_RETAIN `if test $gcc_cv_as_retain_r = yes; then echo 1; else echo 0; fi`
+_ACEOF
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section merging support" >&5
 $as_echo_n "checking assembler for section merging support... " >&6; }
 if ${gcc_cv_as_shf_merge+:} false; then :
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 26a5d8e3619..c32876c8c7e 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -3216,6 +3216,16 @@ AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_EXCLUDE,
   [`if test $gcc_cv_as_section_exclude_e = yes || test $gcc_cv_as_section_exclude_hash = yes; then echo 1; else echo 0; fi`],
 [Define if your assembler supports specifying the exclude section flag.])
 
+# Test if the assembler supports the .retain directive for indicating the
+# section containing the specified symbol should not be garbage collected by
+# the linker.
+gcc_GAS_CHECK_FEATURE([retain directive], gcc_cv_as_retain_r,,,
+ [.global foo1
+  .retain foo1])
+AC_DEFINE_UNQUOTED(HAVE_GAS_RETAIN,
+  [`if test $gcc_cv_as_retain_r = yes; then echo 1; else echo 0; fi`],
+[Define if your assembler supports .retain directive.])
+
 gcc_GAS_CHECK_FEATURE(section merging support, gcc_cv_as_shf_merge,
  [elf,2,12,0], [--fatal-warnings],
  [.section .rodata.str, "aMS", @progbits, 1])
diff --git a/gcc/output.h b/gcc/output.h
index eb253c50329..c0eba372c5d 100644
--- a/gcc/output.h
+++ b/gcc/output.h
@@ -609,6 +609,10 @@ extern void default_elf_init_array_asm_out_constructor (rtx, int);
 extern void default_elf_fini_array_asm_out_destructor (rtx, int);
 extern int maybe_assemble_visibility (tree);
 
+#if HAVE_GAS_RETAIN
+void default_elf_mark_decl_preserved (const char *);
+#endif
+
 extern int default_address_cost (rtx, machine_mode, addr_space_t, bool);
 
 /* Stack usage.  */
diff --git a/gcc/varasm.c b/gcc/varasm.c
index ea0b59cf44a..618bc605a73 100644
--- a/gcc/varasm.c
+++ b/gcc/varasm.c
@@ -8276,6 +8276,17 @@ default_elf_fini_array_asm_out_destructor (rtx symbol, int priority)
   assemble_addr_to_section (symbol, sec);
 }
 
+#if HAVE_GAS_RETAIN
+void
+default_elf_mark_decl_preserved (const char *name)
+{
+  fprintf (asm_out_file, "\t.retain ");
+  assemble_name (asm_out_file, name);
+  fputc ('\n', asm_out_file);
+}
+#endif
+
+
 /* Default TARGET_ASM_OUTPUT_IDENT hook.
 
    This is a bit of a cheat.  The real default is a no-op, but this

Patch

diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 70b00037d98..e9051832252 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -153,6 +153,7 @@  static tree handle_designated_init_attribute (tree *, tree, tree, int, bool *);
 static tree handle_patchable_function_entry_attribute (tree *, tree, tree,
 						       int, bool *);
 static tree handle_copy_attribute (tree *, tree, tree, int, bool *);
+static tree handle_retain_attribute (tree *, tree, tree, int, bool *);
 
 /* Helper to define attribute exclusions.  */
 #define ATTR_EXCL(name, function, type, variable)	\
@@ -491,6 +492,8 @@  const struct attribute_spec c_common_attribute_table[] =
 			      handle_noinit_attribute, attr_noinit_exclusions },
   { "access",		      1, 3, false, true, true, false,
 			      handle_access_attribute, NULL },
+  { "retain",		      0, 0, true, false, false, false,
+			      handle_retain_attribute, NULL },
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -2562,6 +2565,46 @@  handle_alias_ifunc_attribute (bool is_alias, tree *node, tree name, tree args,
 	       decl, is_alias ? "alias" : "ifunc");
     }
 
+  return NULL_TREE;
+}
+
+/* Handle a "retain" attribute; arguments as in
+   struct attribute_spec.handler.  */
+
+static tree
+handle_retain_attribute (tree * pnode ATTRIBUTE_UNUSED,
+			 tree   name ATTRIBUTE_UNUSED,
+			 tree   args ATTRIBUTE_UNUSED,
+			 int    flags ATTRIBUTE_UNUSED,
+			 bool *no_add_attrs ATTRIBUTE_UNUSED)
+{
+#if HAVE_GAS_SECTION_RETAIN
+  tree node = *pnode;
+
+  if (!targetm_common.have_named_sections)
+    {
+      warning (OPT_Wattributes, "%qE attribute not supported by this target",
+	       name);
+      *no_add_attrs = true;
+    }
+  else if (TREE_CODE (node) == FUNCTION_DECL
+      || (VAR_P (node) && TREE_STATIC (node)))
+    {
+      TREE_USED (node) = 1;
+      DECL_PRESERVE_P (node) = 1;
+      DECL_RETAIN_P (node) = 1;
+      if (VAR_P (node))
+	DECL_READ_P (node) = 1;
+    }
+  else
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      *no_add_attrs = true;
+    }
+#else
+  warning (OPT_Wattributes, "%qE attribute not supported by this target", name);
+  *no_add_attrs = true;
+#endif
 
   return NULL_TREE;
 }
diff --git a/gcc/cfgexpand.c b/gcc/cfgexpand.c
index 1eaa1da11b9..c3599995f9e 100644
--- a/gcc/cfgexpand.c
+++ b/gcc/cfgexpand.c
@@ -6432,8 +6432,9 @@  pass_expand::execute (function *fun)
   crtl->init_stack_alignment ();
   fun->cfg->max_jumptable_ents = 0;
 
-  /* Resovle the function section.  Some targets, like ARM EABI rely on knowledge
-     of the function section at exapnsion time to predict distance of calls.  */
+  /* Resolve the function section.  Some targets, like ARM EABI, rely on
+     knowledge of the function section at expansion time to predict distance of
+     calls.  */
   resolve_unique_section (current_function_decl, 0, flag_function_sections);
 
   /* Expand the variables recorded during gimple lowering.  */
diff --git a/gcc/config.in b/gcc/config.in
index 5835ceaf669..8d2654504fe 100644
--- a/gcc/config.in
+++ b/gcc/config.in
@@ -1339,6 +1339,12 @@ 
 #endif
 
 
+/* Define if your assembler supports specifying the retain section flag. */
+#ifndef USED_FOR_TARGET
+#undef HAVE_GAS_SECTION_RETAIN
+#endif
+
+
 /* Define 0/1 if your assembler supports marking sections with SHF_MERGE flag.
    */
 #ifndef USED_FOR_TARGET
diff --git a/gcc/configure b/gcc/configure
index 33a3e34029f..7ce5d61320d 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -24218,6 +24218,42 @@  cat >>confdefs.h <<_ACEOF
 _ACEOF
 
 
+# Test if the assembler supports the section flag 'R' for specifying a
+# section which should not be garbage collected by the linker.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section retain flag" >&5
+$as_echo_n "checking assembler for section retain flag... " >&6; }
+if ${gcc_cv_as_section_retain_r+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  gcc_cv_as_section_retain_r=no
+  if test x$gcc_cv_as != x; then
+    $as_echo '.section foo1,"aR"
+  .byte 0' > conftest.s
+    if { ac_try='$gcc_cv_as $gcc_cv_as_flags  -o conftest.o conftest.s >&5'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+    then
+	gcc_cv_as_section_retain_r=yes
+    else
+      echo "configure: failed program was" >&5
+      cat conftest.s >&5
+    fi
+    rm -f conftest.o conftest.s
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_cv_as_section_retain_r" >&5
+$as_echo "$gcc_cv_as_section_retain_r" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_GAS_SECTION_RETAIN `if test $gcc_cv_as_section_retain_r = yes; then echo 1; else echo 0; fi`
+_ACEOF
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section merging support" >&5
 $as_echo_n "checking assembler for section merging support... " >&6; }
 if ${gcc_cv_as_shf_merge+:} false; then :
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 975f6d97c4b..dbf0b0fc8f5 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -3211,6 +3211,15 @@  AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_EXCLUDE,
   [`if test $gcc_cv_as_section_exclude_e = yes || test $gcc_cv_as_section_exclude_hash = yes; then echo 1; else echo 0; fi`],
 [Define if your assembler supports specifying the exclude section flag.])
 
+# Test if the assembler supports the section flag 'R' for specifying a
+# section which should not be garbage collected by the linker.
+gcc_GAS_CHECK_FEATURE([section retain flag], gcc_cv_as_section_retain_r,,,
+ [.section foo1,"aR"
+  .byte 0])
+AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_RETAIN,
+  [`if test $gcc_cv_as_section_retain_r = yes; then echo 1; else echo 0; fi`],
+[Define if your assembler supports specifying the retain section flag.])
+
 gcc_GAS_CHECK_FEATURE(section merging support, gcc_cv_as_shf_merge,
  [elf,2,12,0], [--fatal-warnings],
  [.section .rodata.str, "aMS", @progbits, 1])
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index c9f72991bba..dd62ec2c815 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3578,6 +3578,17 @@  diagnosed.  Because a pure function cannot have any observable side
 effects it does not make sense for such a function to return @code{void}.
 Declaring such a function is diagnosed.
 
+@item retain
+@cindex @code{retain} function attribute
+The @code{retain} attribute, attached to a function, means that function must
+not be garbage collected by the linker, even if it appears unused.
+
+The function is put in a new, unique section marked with the
+@code{SHF_GNU_RETAIN} flag.  This flag is a GNU extension to the ELF format.
+
+This attribute implies, and has the same restrictions as, the @code{used}
+attribute.
+
 @item returns_nonnull
 @cindex @code{returns_nonnull} function attribute
 The @code{returns_nonnull} attribute specifies that the function
@@ -7183,6 +7194,18 @@  been fixed in GCC 4.4 but the change can lead to differences in the
 structure layout.  See the documentation of
 @option{-Wpacked-bitfield-compat} for more information.
 
+@item retain
+@cindex @code{retain} variable attribute
+The @code{retain} attribute, attached to a variable with static storage, means
+that variable must not be garbage collected by the linker, even if it appears
+unused.
+
+The variable is put in a new, unique section marked with the
+@code{SHF_GNU_RETAIN} flag.  This flag is a GNU extension to the ELF format.
+
+This attribute implies, and has the same restrictions as, the @code{used}
+attribute.
+
 @item section ("@var{section-name}")
 @cindex @code{section} variable attribute
 Normally, the compiler places the objects it generates in sections like
diff --git a/gcc/output.h b/gcc/output.h
index eb253c50329..2a723969bd9 100644
--- a/gcc/output.h
+++ b/gcc/output.h
@@ -381,7 +381,8 @@  extern void no_asm_to_stream (FILE *);
 #define SECTION_COMMON   0x800000	/* contains common data */
 #define SECTION_RELRO	 0x1000000	/* data is readonly after relocation processing */
 #define SECTION_EXCLUDE  0x2000000	/* discarded by the linker */
-#define SECTION_MACH_DEP 0x4000000	/* subsequent bits reserved for target */
+#define SECTION_RETAIN   0x4000000	/* retained by the linker, SHF_GNU_RETAIN */
+#define SECTION_MACH_DEP 0x8000000	/* subsequent bits reserved for target */
 
 /* This SECTION_STYLE is used for unnamed sections that we can switch
    to using a special assembler directive.  */
diff --git a/gcc/testsuite/c-c++-common/attr-retain-1.c b/gcc/testsuite/c-c++-common/attr-retain-1.c
new file mode 100644
index 00000000000..252c3b56e4b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-1.c
@@ -0,0 +1,83 @@ 
+/* { dg-do compile } */
+/* { dg-require-effective-target retain } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.discard0,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.discard1,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.data\\.discard2,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.rodata\\.discard3,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.sdiscard0,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.sdiscard1,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.data\\.sdiscard2,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.rodata\\.sdiscard3,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.text\\.fdiscard0,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.discard4,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.discard5,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.data\\.discard6,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.retain0,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.retain1,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.data\\.retain2,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.rodata\\.retain3,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.sretain0,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.sretain1,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.rodata\\.sretain3,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.text\\.fretain0,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.retain4,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.bss\\.retain5,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.data\\.retain6,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.rodata\\.retain7,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.tbss\\.tretain0,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.tbss\\.tretain1,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.tdata\\.tretain2,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\\.trodata\\.tretain3,\"\\w+R" } } */
+
+/* Test the retain attribute when functions and variables have explicit
+   sections, set by the section attribute.  */
+
+#define RETAIN __attribute__((retain))
+#define SECTION(X) __attribute__((section(X)))
+
+/* This group of functions and data should be garbage collected by the
+   linker.  */
+int SECTION(".bss.discard0") discard0;
+int SECTION(".bss.discard1") discard1 = 0;
+int SECTION(".data.discard2") discard2 = 1;
+const int SECTION(".rodata.discard3") discard3 = 2;
+
+static int SECTION(".bss.sdiscard0") sdiscard0;
+static int SECTION(".bss.sdiscard1") sdiscard1 = 0;
+static int SECTION(".data.sdiscard2") sdiscard2 = 1;
+static const int SECTION(".rodata.sdiscard3") sdiscard3 = 2;
+
+void SECTION(".text.fdiscard0") fdiscard0 (void) {}
+
+/* The "retain" attribute set on this group of functions and data should protect
+   them from linker garbage collection.  */
+int RETAIN SECTION(".bss.retain0") retain0;
+int RETAIN SECTION(".bss.retain1") retain1 = 0;
+int RETAIN SECTION(".data.retain2") retain2 = 1;
+const int RETAIN SECTION(".rodata.retain3") retain3 = 2;
+static int RETAIN SECTION(".bss.sretain0") sretain0;
+static int RETAIN SECTION(".bss.sretain1") sretain1 = 0;
+static int RETAIN SECTION(".data.sretain2") sretain2 = 1;
+static const int RETAIN SECTION(".rodata.sretain3") sretain3 = 2;
+
+__thread int RETAIN SECTION(".tbss.tretain0") tretain0;
+__thread int RETAIN SECTION(".tbss.tretain1") tretain1 = 0;
+__thread int RETAIN SECTION(".tdata.tretain2") tretain2 = 1;
+__thread const int RETAIN SECTION(".trodata.tretain3") tretain3 = 2;
+
+void RETAIN SECTION(".text.fretain0") fretain0 (void) {}
+
+/* .text.fdiscard2 should be garbage collected, but the static variables defined
+   inside it with the retain attribute so should not be garbage collected.  */
+void SECTION(".text.fdiscard2")
+fdiscard2 (void)
+{
+  static int SECTION(".bss.discard4") discard4;
+  static int SECTION(".bss.discard5") discard5 = 0;
+  static int SECTION(".data.discard6") discard6 = 1;
+  static const int SECTION(".rodata.discard7") discard7 = 2;
+  static int RETAIN SECTION(".bss.retain4") retain4;
+  static int RETAIN SECTION(".bss.retain5") retain5 = 0;
+  static int RETAIN SECTION(".data.retain6") retain6 = 1;
+  static const int RETAIN SECTION(".rodata.retain7") retain7 = 2;
+}
diff --git a/gcc/testsuite/c-c++-common/attr-retain-2.c b/gcc/testsuite/c-c++-common/attr-retain-2.c
new file mode 100644
index 00000000000..48ce956adb1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-2.c
@@ -0,0 +1,70 @@ 
+/* { dg-do compile } */
+/* { dg-require-effective-target retain } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain1\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain2\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain3\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain1\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain3\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*fretain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain4\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain5\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain6\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain7\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain1\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain2\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain3\[^,\]*,\"\\w+R" } } */
+
+/* Test the retain attribute when there are no explicit sections for any
+   functions or variables.  */
+
+#define RETAIN __attribute__((retain))
+
+/* This group of functions and data should be garbage collected by the
+   linker.  */
+int discard0;
+int discard1 = 0;
+int discard2 = 1;
+const int discard3 = 2;
+
+static int sdiscard0;
+static int sdiscard1 = 0;
+static int sdiscard2 = 1;
+static const int sdiscard3 = 2;
+
+void fdiscard0 (void) {}
+
+/* The "retain" attribute set on this group of functions and data should protect
+   them from linker garbage collection.  */
+int RETAIN retain0;
+int RETAIN retain1 = 0;
+int RETAIN retain2 = 1;
+const int RETAIN retain3 = 2;
+static int RETAIN sretain0;
+static int RETAIN sretain1 = 0;
+static int RETAIN sretain2 = 1;
+static const int RETAIN sretain3 = 2;
+__thread int RETAIN tretain0;
+__thread int RETAIN tretain1 = 0;
+__thread int RETAIN tretain2 = 1;
+__thread const int RETAIN tretain3 = 2;
+
+void RETAIN fretain0 (void) {}
+
+/* .text.fdiscard2 should be garbage collected, but the static variables defined
+   inside it with the retain attribute so should not be garbage collected.  */
+void
+fdiscard2 (void)
+{
+  static int discard4;
+  static int discard5 = 0;
+  static int discard6 = 1;
+  static const int discard7 = 2;
+  static int RETAIN retain4;
+  static int RETAIN retain5 = 0;
+  static int RETAIN retain6 = 1;
+  static const int RETAIN retain7 = 2;
+}
+
diff --git a/gcc/testsuite/c-c++-common/attr-retain-3.c b/gcc/testsuite/c-c++-common/attr-retain-3.c
new file mode 100644
index 00000000000..fc85495fb78
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-3.c
@@ -0,0 +1,81 @@ 
+/* { dg-do compile } */
+/* { dg-require-effective-target retain } */
+/* { dg-options "-ffunction-sections -fdata-sections" } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard0\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard1\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard2\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard3\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sdiscard0\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sdiscard1\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sdiscard3\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*fdiscard0\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard4\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard5\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard6\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*discard7\[^,\]*,\"\[^R\W\]+\"" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain1\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain2\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain3\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain1\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain3\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*fretain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain4\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain5\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain6\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain7\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain0\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain1\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain2\[^,\]*,\"\\w+R" } } */
+/* { dg-final { scan-assembler "\\.section\t\[^,\]*tretain3\[^,\]*,\"\\w+R" } } */
+
+/* Test the retain attribute when -ffunction-sections and -fdata-sections
+   options are in use.  */
+
+#define RETAIN __attribute__((retain))
+
+/* This group of functions and data should be garbage collected by the
+   linker.  */
+int discard0;
+int discard1 = 0;
+int discard2 = 1;
+const int discard3 = 2;
+
+static int sdiscard0;
+static int sdiscard1 = 0;
+static int sdiscard2 = 1;
+static const int sdiscard3 = 2;
+
+void fdiscard0 (void) {}
+
+/* The "retain" attribute set on this group of functions and data should protect
+   them from linker garbage collection.  */
+int RETAIN retain0;
+int RETAIN retain1 = 0;
+int RETAIN retain2 = 1;
+const int RETAIN retain3 = 2;
+static int RETAIN sretain0;
+static int RETAIN sretain1 = 0;
+static int RETAIN sretain2 = 1;
+static const int RETAIN sretain3 = 2;
+__thread int RETAIN tretain0;
+__thread int RETAIN tretain1 = 0;
+__thread int RETAIN tretain2 = 1;
+__thread const int RETAIN tretain3 = 2;
+void RETAIN fretain0 (void) {}
+
+/* .text.fdiscard2 should be garbage collected, but the static variables defined
+   inside it with the retain attribute so should not be garbage collected.  */
+void
+fdiscard2 (void)
+{
+  static int discard4;
+  static int discard5 = 0;
+  static int discard6 = 1;
+  static const int discard7 = 2;
+  static int RETAIN retain4;
+  static int RETAIN retain5 = 0;
+  static int RETAIN retain6 = 1;
+  static const int RETAIN retain7 = 2;
+}
diff --git a/gcc/testsuite/c-c++-common/attr-retain-4.c b/gcc/testsuite/c-c++-common/attr-retain-4.c
new file mode 100644
index 00000000000..a9f2b20183c
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-4.c
@@ -0,0 +1,10 @@ 
+/* { dg-do compile } */
+/* { dg-require-effective-target retain } */
+
+typedef int int_retain __attribute__((retain)); /* { dg-warning "'retain' attribute ignored" } */
+
+void
+foo (void)
+{
+  int __attribute__((retain)) a; /* { dg-warning "'retain' attribute ignored" } */
+}
diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp
index 8314e443c43..43f68a7b40a 100644
--- a/gcc/testsuite/lib/target-supports.exp
+++ b/gcc/testsuite/lib/target-supports.exp
@@ -377,6 +377,15 @@  proc check_effective_target_noinit { } {
     return 0
 }
 
+# The retain attribute is only supported by some targets.
+# This proc returns 1 if it's supported, 0 if it's not.
+
+proc check_effective_target_retain { } {
+    return [check_no_compiler_messages retain_available assembly {
+	int __attribute__((retain)) a = 1;
+    }]
+}
+
 ###############################
 # proc check_visibility_available { what_kind }
 ###############################
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 0e158784d0e..73f740ca763 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1690,6 +1690,7 @@  struct GTY(()) tree_decl_common {
   unsigned abstract_flag : 1;
   unsigned artificial_flag : 1;
   unsigned preserve_flag: 1;
+  unsigned retain_flag: 1;
   unsigned debug_expr_is_from : 1;
 
   unsigned lang_flag_0 : 1;
diff --git a/gcc/tree.h b/gcc/tree.h
index 5bb6e7bc000..7a27827db74 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -2641,6 +2641,13 @@  extern tree vector_element_bits_tree (const_tree);
 #define DECL_PRESERVE_P(DECL) \
   DECL_COMMON_CHECK (DECL)->decl_common.preserve_flag
 
+/* Nonzero for a decl that is decorated using attribute retain.
+   This indicates to compiler tools that this decl needs to be preserved,
+   and to the link editor that the section containing the decl should not be
+   garbage collected.  */
+#define DECL_RETAIN_P(DECL) \
+  DECL_COMMON_CHECK (DECL)->decl_common.retain_flag
+
 /* For function local variables of COMPLEX and VECTOR types,
    indicates that the variable is not aliased, and that all
    modifications to the variable have been adjusted so that
diff --git a/gcc/varasm.c b/gcc/varasm.c
index ea0b59cf44a..dc7c7176a10 100644
--- a/gcc/varasm.c
+++ b/gcc/varasm.c
@@ -464,7 +464,8 @@  resolve_unique_section (tree decl, int reloc ATTRIBUTE_UNUSED,
   if (DECL_SECTION_NAME (decl) == NULL
       && targetm_common.have_named_sections
       && (flag_function_or_data_sections
-	  || DECL_COMDAT_GROUP (decl)))
+	  || DECL_COMDAT_GROUP (decl)
+	  || DECL_RETAIN_P (decl)))
     {
       targetm.asm_out.unique_section (decl, reloc);
       if (DECL_SECTION_NAME (decl))
@@ -6619,6 +6620,9 @@  default_section_type_flags (tree decl, const char *name, int reloc)
   if (decl && VAR_P (decl) && DECL_THREAD_LOCAL_P (decl))
     flags |= SECTION_TLS | SECTION_WRITE;
 
+  if (decl && DECL_RETAIN_P (decl))
+    flags |= SECTION_RETAIN;
+
   if (strcmp (name, ".bss") == 0
       || strncmp (name, ".bss.", 5) == 0
       || strncmp (name, ".gnu.linkonce.b.", 16) == 0
@@ -6738,6 +6742,8 @@  default_elf_asm_named_section (const char *name, unsigned int flags,
       if (flags & SECTION_MACH_DEP)
 	*f++ = MACH_DEP_SECTION_ASM_FLAG;
 #endif
+      if (flags & SECTION_RETAIN)
+	*f++ = 'R';
       *f = '\0';
     }