On Fri, Nov 06, 2020 at 04:39:33PM -0800, H.J. Lu via Gcc-patches wrote:
> On Fri, Nov 6, 2020 at 4:17 PM Jeff Law <l...@redhat.com> wrote:
> >
> >
> > On 11/6/20 5:13 PM, H.J. Lu wrote:
> > > On Fri, Nov 6, 2020 at 4:01 PM Jeff Law <l...@redhat.com> wrote:
> > >>
> > >> On 11/6/20 4:45 PM, H.J. Lu wrote:
> > >>> On Fri, Nov 6, 2020 at 3:37 PM Jeff Law <l...@redhat.com> wrote:
> > >>>> On 11/6/20 4:29 PM, H.J. Lu wrote:
> > >>>>> On Fri, Nov 6, 2020 at 3:22 PM Jeff Law <l...@redhat.com> wrote:
> > >>>>>> On 11/5/20 7:34 AM, H.J. Lu via Gcc-patches wrote:
> > >>>>>>> On Thu, Nov 5, 2020 at 3:37 AM Jozef Lawrynowicz
> > >>>>>>> <joze...@mittosystems.com> wrote:
> > >>>>>>>> On Thu, Nov 05, 2020 at 06:21:21AM -0500, Hans-Peter Nilsson wrote:
> > >>>>>>>>> On Wed, 4 Nov 2020, H.J. Lu wrote:
> > >>>>>>>>>> .retain is ill-defined.   For example,
> > >>>>>>>>>>
> > >>>>>>>>>> [hjl@gnu-cfl-2 gcc]$ cat /tmp/x.c
> > >>>>>>>>>> static int xyzzy __attribute__((__used__));
> > >>>>>>>>>> [hjl@gnu-cfl-2 gcc]$ ./xgcc -B./ -S /tmp/x.c -fcommon
> > >>>>>>>>>> [hjl@gnu-cfl-2 gcc]$ cat x.s
> > >>>>>>>>>> .file "x.c"
> > >>>>>>>>>> .text
> > >>>>>>>>>> .retain xyzzy  <<<<<<<<< What does it do?
> > >>>>>>>>>> .local xyzzy
> > >>>>>>>>>> .comm xyzzy,4,4
> > >>>>>>>>>> .ident "GCC: (GNU) 11.0.0 20201103 (experimental)"
> > >>>>>>>>>> .section .note.GNU-stack,"",@progbits
> > >>>>>>>>>> [hjl@gnu-cfl-2 gcc]$
> > >>>>>>>>> To answer that question: it's up to the assembler, but for ELF
> > >>>>>>>>> and SHF_GNU_RETAIN, it seems obvious it'd tell the assembler to
> > >>>>>>>>> set SHF_GNU_RETAIN for the section where the symbol ends up.
> > >>>>>>>>> We both know this isn't rocket science with binutils.
> > >>>>>>>> Indeed, and my patch handles it trivially:
> > >>>>>>>> https://sourceware.org/pipermail/binutils/2020-November/113993.html
> > >>>>>>>>
> > >>>>>>>>   +void
> > >>>>>>>>   +obj_elf_retain (int arg ATTRIBUTE_UNUSED)
> > >>>>>>>>   .... snip ....
> > >>>>>>>>   +  sym = get_sym_from_input_line_and_check ();
> > >>>>>>>>   +  symbol_get_obj (sym)->retain = 1;
> > >>>>>>>>
> > >>>>>>>>   @@ -2624,6 +2704,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))
> > >>>>>>>>        {
> > >>>>>>>>
> > >>>>>>>> We could check that the symbol named in the .retain directive has
> > >>>>>>>> already been defined, however this isn't compatible with GCC
> > >>>>>>>> mark_decl_preserved handling, since mark_decl_preserved is called
> > >>>>>>>> emitted before the local symbols are defined in the assembly output
> > >>>>>>>> file.
> > >>>>>>>>
> > >>>>>>>> GAS should at least validate that the symbol named in the .retain
> > >>>>>>>> directive does end up as a symbol though.
> > >>>>>>>>
> > >>>>>>> Don't add .retain.
> > >>>>>> Why?  I don't see why you find it so objectionable.
> > >>>>>>
> > >>>>> An ELF symbol directive should operate on symbol table:
> > >>>>>
> > >>>>> http://www.sco.com/developers/gabi/latest/ch4.symtab.html
> > >>>>>
> > >>>>> not the section flags where the symbol is defined.
> > >>>> I agree in general, but I think this is one of those cases where it's
> > >>>> not so clear.  And what you're talking about is an implementation 
> > >>>> detail.
> > >>> There is no need for such a hack.  The proper thing to do in ELF is
> > >>> to place such a symbol in a section with SHF_GNU_RETAIN flag.   This
> > >>> also avoids the question what to do with SHN_COMMON.
> > >> I'm not sure that's a good idea either.  Moving symbols into a section
> > >> other than they'd normally live doesn't seem all that wise.
> > > In ELF, a symbol must be defined in a section.  If we want to keep a 
> > > symbol,
> > > we should place it in an SHF_GNU_RETAIN section.
> >
> > Again, that's an implementation detail and it's not clear to me that one
> > approach is inherently better than the other.
> >
> >
> > >
> > >> Let's face it, there's not a great solution here.  If we mark its
> > >> existing section, then everything in that section gets kept.  If we put
> > > FWIW, this is what .retain direct does and is one reason why I object
> > > it.
> >
> > We could make .retain work with either approach.    I don't see .retain
> > as a problem at all.
> >
> >
> >
> > >
> > >> the object into a different section than it would normally live, then
> > >> that opens a whole new can of worms.
> > > We should place it in a section which it normally lives in and mark the
> > > section with SHF_GNU_RETAIN.
> >
> > And why not do that with .retain?  We define its semantics as precisely
> 
> But the .retain directive implementation being discussed here is different.
> One problem with the .retain directive is we can have
> 
> .section .data
> foo:
> ...
> bar:
> 
> .retain bar
> ...
> xxx:
> ...
> 
> What should assembler do with ".retain bar"?
> 
> > what you've written above.  The referenced symbol goes into its usual
> > section and its section is marked with SHF_GNU_RETAIN.  That seems much
> > cleaner than having to track all this in the compiler so that it can
> > twiddle the section flags.
> 
> When GCC emits a symbol definition, it places the symbol in a section
> with proper
> attributes which GCC tracks for each symbol.  It  can be extended to track
> SHF_GNU_RETAIN.

The attached patch is rough around the edges but shows my approach for
marking unnamed sections as retained, by converting them to named
sections.

I figure we don't have to wrap every usage of SECTION_RETAIN in
HAVE_GAS_SECTION_RETAIN as long as any set of SECTION_RETAIN in the
flags field is wrapped in the macro.

I think a flag to turn off the behavior (in the same way the behavior is
disabled if !defined(HAVE_GAS_SECTION_RETAIN)) would be beneficial,
I haven't added that yet.

Decls that would go in comm_section, tls_comm_section and sometimes
lcomm_section do not get retained as we can't apply the retain section
flag to these symbols. Given these go in one big common block, and
contain uninitialized data, I don't think there is a valid use case for
which these types of symbols need to be retained, but are not referenced
by the program. So I've avoided converting them to .bss or anything like
that.

Some targets alias lcomm_section for bss_section, so we can retain
sections for that case.

So far bootstrap and light testing on x86_64-pc-linux-gnu and
arm-none-eabi has shown no problems.

Thanks,
Jozef

> 
> -- 
> H.J.
commit a24999de8a6c40f87f80bfbbe8383caf9f5a3507
Author: Jozef Lawrynowicz <joze...@mittosystems.com>
Date:   Sun Nov 8 21:36:59 2020 +0000

    used is retained

diff --git a/gcc/config.in b/gcc/config.in
index b7c3107bfe3..d5bb48138d3 100644
--- a/gcc/config.in
+++ b/gcc/config.in
@@ -1352,6 +1352,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/config/arm/unknown-elf.h b/gcc/config/arm/unknown-elf.h
index 9ad2947505f..032539bc047 100644
--- a/gcc/config/arm/unknown-elf.h
+++ b/gcc/config/arm/unknown-elf.h
@@ -88,6 +88,10 @@
     }                                                                  \
   while (0)
 
+/* A local-common decl that is not in a named section will be placed in
+   bss_section.  */
+#define LCOMM_SECTION_NAME ".bss"
+
 /* The libgcc udivmod functions may throw exceptions.  If newlib is
    configured to support long longs in I/O, then printf will depend on
    udivmoddi4, which will depend on the exception unwind routines,
diff --git a/gcc/configure b/gcc/configure
index 9d2fd0dc30b..425725b358c 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -24235,6 +24235,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 73034bb902b..ae3fa794c8d 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -3221,6 +3221,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/defaults.h b/gcc/defaults.h
index f1a38626624..26bd89bcc68 100644
--- a/gcc/defaults.h
+++ b/gcc/defaults.h
@@ -357,6 +357,33 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  
If not, see
 #endif
 #endif
 
+/* These section names define the name of the section used in
+   *_SECTION_ASM_OP macros, used when converting an unnamed or noswitch
+   section to a named section.  */
+#ifndef TEXT_SECTION_NAME
+#define TEXT_SECTION_NAME ".text"
+#endif
+
+#ifndef DATA_SECTION_NAME
+#define DATA_SECTION_NAME ".data"
+#endif
+
+#ifndef READONLY_DATA_SECTION_NAME
+#define READONLY_DATA_SECTION_NAME ".rodata"
+#endif
+
+#ifndef BSS_SECTION_NAME
+#define BSS_SECTION_NAME ".bss"
+#endif
+
+#ifndef SBSS_SECTION_NAME
+#define SBSS_SECTION_NAME ".sbss"
+#endif
+
+#ifndef SDATA_SECTION_NAME
+#define SDATA_SECTION_NAME ".sdata"
+#endif
+
 /* On many systems, different EH table encodings are used under
    difference circumstances.  Some will require runtime relocations;
    some will not.  For those that do not require runtime relocations,
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 833320ba7bf..37291321c3a 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -7599,6 +7599,47 @@ defined, GCC will assume such a section does not exist.  
Do not define
 both this macro and @code{FINI_SECTION_ASM_OP}.
 @end defmac
 
+@defmac TEXT_SECTION_NAME
+A C string constant for the name of the section that TEXT_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.text}.
+@end defmac
+
+@defmac DATA_SECTION_NAME
+A C string constant for the name of the section that DATA_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.data}.
+@end defmac
+
+@defmac READONLY_DATA_SECTION_NAME
+A C string constant for the name of the section that
+READONLY_DATA_SECTION_ASM_OP will switch to.  If you don't define this,
+the default is @code{.rodata}.
+@end defmac
+
+@defmac BSS_SECTION_NAME
+A C string constant for the name of the section that BSS_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.bss}.
+@end defmac
+
+@defmac SDATA_SECTION_NAME
+A C string constant for the name of the section that SDATA_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.sdata}.
+@end defmac
+
+@defmac SBSS_SECTION_NAME
+A C string constant for the name of the section that SBSS_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.sbss}.
+@end defmac
+
+@defmac LCOMM_SECTION_NAME
+A C string constant for the name of the section that a local-common
+symbol would be placed in, as determined by the @code{ASM_OUTPUT*_LOCAL}
+macros.  If these macros do not place local-common symbols in sections,
+leave this undefined.  This affects the section local-common symbols
+marked with the @code{used} attribute are placed in.
+
+This macro is undefined by default.
+@end defmac
+
 @defmac MACH_DEP_SECTION_ASM_FLAG
 If defined, a C expression whose value is a character constant
 containing the flag used to mark a machine-dependent section.  This
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 58109be3693..64dd72c7dd8 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -4941,6 +4941,47 @@ defined, GCC will assume such a section does not exist.  
Do not define
 both this macro and @code{FINI_SECTION_ASM_OP}.
 @end defmac
 
+@defmac TEXT_SECTION_NAME
+A C string constant for the name of the section that TEXT_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.text}.
+@end defmac
+
+@defmac DATA_SECTION_NAME
+A C string constant for the name of the section that DATA_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.data}.
+@end defmac
+
+@defmac READONLY_DATA_SECTION_NAME
+A C string constant for the name of the section that
+READONLY_DATA_SECTION_ASM_OP will switch to.  If you don't define this,
+the default is @code{.rodata}.
+@end defmac
+
+@defmac BSS_SECTION_NAME
+A C string constant for the name of the section that BSS_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.bss}.
+@end defmac
+
+@defmac SDATA_SECTION_NAME
+A C string constant for the name of the section that SDATA_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.sdata}.
+@end defmac
+
+@defmac SBSS_SECTION_NAME
+A C string constant for the name of the section that SBSS_SECTION_ASM_OP
+will switch to.  If you don't define this, the default is @code{.sbss}.
+@end defmac
+
+@defmac LCOMM_SECTION_NAME
+A C string constant for the name of the section that a local-common
+symbol would be placed in, as determined by the @code{ASM_OUTPUT*_LOCAL}
+macros.  If these macros do not place local-common symbols in sections,
+leave this undefined.  This affects the section local-common symbols
+marked with the @code{used} attribute are placed in.
+
+This macro is undefined by default.
+@end defmac
+
 @defmac MACH_DEP_SECTION_ASM_FLAG
 If defined, a C expression whose value is a character constant
 containing the flag used to mark a machine-dependent section.  This
diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
index bc32a17efcd..674be5ca532 100644
--- a/gcc/dwarf2out.c
+++ b/gcc/dwarf2out.c
@@ -4095,11 +4095,6 @@ new_addr_loc_descr (rtx addr, enum dtprel_bool dtprel)
 #define DEBUG_LTO_LINE_STR_SECTION  ".gnu.debuglto_.debug_line_str"
 #endif
 
-/* Standard ELF section names for compiled code and data.  */
-#ifndef TEXT_SECTION_NAME
-#define TEXT_SECTION_NAME      ".text"
-#endif
-
 /* Section flags for .debug_str section.  */
 #define DEBUG_STR_SECTION_FLAGS                                 \
   (HAVE_GAS_SHF_MERGE && flag_merge_debug_strings               \
diff --git a/gcc/output.h b/gcc/output.h
index 2f2f1697fd8..08cf0aca5f8 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-used-3.c 
b/gcc/testsuite/c-c++-common/attr-used-3.c
new file mode 100644
index 00000000000..4f2cee6a36b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-used-3.c
@@ -0,0 +1,32 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target section_retain } */
+/* { dg-final { scan-assembler ".text,\"axR\"" } } */
+/* { dg-final { scan-assembler ".bss,\"awR\"" } } */
+/* { dg-final { scan-assembler ".data,\"awR\"" } } */
+/* { dg-final { scan-assembler ".rodata,\"aR\"" } } */
+
+void __attribute__((used)) used_fn (void) { }
+void unused_fn (void) { }
+void __attribute__((hot,used)) used_hot_fn (void) { }
+void __attribute__((hot)) unused_hot_fn (void) { }
+void __attribute__((cold,used)) used_cold_fn (void) { }
+void __attribute__((cold)) unused_cold_fn (void) { }
+int __attribute__((used)) used_bss = 0;
+int __attribute__((used)) used_data = 1;
+const int __attribute__((used)) used_rodata = 2;
+int __attribute__((used)) used_comm;
+static int __attribute__((used)) used_lcomm;
+
+int unused_bss = 0;
+int unused_data = 1;
+const int unused_rodata = 2;
+int unused_comm;
+static int unused_lcomm;
+
+/* Test switching back to the retained sections.  */
+void __attribute__((used)) used_fn2 (void) { }
+int __attribute__((used)) used_bss2 = 0;
+int __attribute__((used)) used_data2 = 1;
+const int __attribute__((used)) used_rodata2 = 2;
+int __attribute__((used)) used_comm2;
+static int __attribute__((used)) used_lcomm2;
diff --git a/gcc/testsuite/c-c++-common/attr-used-4.c 
b/gcc/testsuite/c-c++-common/attr-used-4.c
new file mode 100644
index 00000000000..e37c8d8e6cf
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-used-4.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target section_retain } */
+/* { dg-final { scan-assembler ".text.used_fn,\"axR\"" } } */
+/* { dg-final { scan-assembler ".text.used_fn2,\"axR\"" } } */
+/* { dg-final { scan-assembler ".bss.used_bss,\"awR\"" } } */
+/* { dg-final { scan-assembler ".bss.used_bss2,\"awR\"" } } */
+/* { dg-final { scan-assembler ".data.used_data,\"awR\"" } } */
+/* { dg-final { scan-assembler ".data.used_data2,\"awR\"" } } */
+/* { dg-final { scan-assembler ".rodata.used_rodata,\"aR\"" } } */
+/* { dg-final { scan-assembler ".rodata.used_rodata2,\"aR\"" } } */
+/* { dg-final { scan-assembler ".bss.used_lcomm,\"awR\"" { target arm-*-* } } 
} */
+/* { dg-final { scan-assembler ".bss.used_lcomm2,\"awR\"" { target arm-*-* } } 
} */
+/* { dg-options "-ffunction-sections -fdata-sections" } */
+
+#include "attr-used-3.c"
diff --git a/gcc/testsuite/lib/target-supports.exp 
b/gcc/testsuite/lib/target-supports.exp
index 60ebbb39f9d..7d3753948ca 100644
--- a/gcc/testsuite/lib/target-supports.exp
+++ b/gcc/testsuite/lib/target-supports.exp
@@ -380,6 +380,15 @@ proc check_effective_target_noinit { } {
     return 0
 }
 
+# The "R" flag to the assembler .section directive is only supported by some 
targets.
+# This proc returns 1 if it's supported, 0 if it's not.
+
+proc check_effective_target_section_retain { } {
+    return [check_no_compiler_messages section_retain_available object {
+       __asm__(".section .text.foo,\"axR\",%progbits");
+    }]
+}
+
 ###############################
 # proc check_visibility_available { what_kind }
 ###############################
diff --git a/gcc/varasm.c b/gcc/varasm.c
index 435c7b348a5..dd01dc713ba 100644
--- a/gcc/varasm.c
+++ b/gcc/varasm.c
@@ -314,6 +314,14 @@ get_section (const char *name, unsigned int flags, tree 
decl,
           sect->common.flags |= SECTION_NOTYPE;
           flags |= SECTION_NOTYPE;
         }
+      /* It is fine if one of the sections has SECTION_RETAIN and the other
+        doesn't.  The requirement to retain may only present itself after a
+        section has been declared.  */
+      if ((sect->common.flags ^ flags) & SECTION_RETAIN)
+       {
+         sect->common.flags |= SECTION_RETAIN;
+         flags |= SECTION_RETAIN;
+       }
       if ((sect->common.flags & ~SECTION_DECLARED) != flags
          && ((sect->common.flags | flags) & SECTION_OVERRIDE) == 0)
        {
@@ -624,6 +632,61 @@ default_function_section (tree decl, enum node_frequency 
freq,
     }
 }
 
+/* Generate a named section that has the same name as the noswitch or unnamed
+   section SECT.  */
+static section *
+convert_to_named_section (section *sect)
+{
+  unsigned int flags;
+  const char *name = NULL;
+
+  if (SECTION_STYLE (sect) == SECTION_NAMED)
+    return sect;
+
+  /* Pretend the flags came from a decl and unset SECTION_DECLARED and
+     SECTION_STYLE_MASK.  */
+  flags = sect->common.flags & ~(SECTION_DECLARED | SECTION_STYLE_MASK);
+
+  if (sect == text_section)
+    name = TEXT_SECTION_NAME;
+  else if (sect == data_section)
+    name = DATA_SECTION_NAME;
+  else if (sect == readonly_data_section)
+    name = READONLY_DATA_SECTION_NAME;
+  else if (sect == bss_section
+          || sect == bss_noswitch_section)
+    name = BSS_SECTION_NAME;
+  else if (sect == sbss_section)
+    name = SBSS_SECTION_NAME;
+  else if (sect == sdata_section)
+    name = SDATA_SECTION_NAME;
+  else if (sect == comm_section
+          || sect == tls_comm_section)
+    {
+      /* These aren't real sections, so they can't be marked with the retain
+        flag.  */
+      return sect;
+    }
+  else if (sect == lcomm_section)
+    {
+      /* Sometimes lcomm_section corresponds to a real ELF section, other times
+        only an assembler directive for the symbol name is output.  For the
+        former case, we can mark the section with the retain flag.  */
+#ifdef LCOMM_SECTION_NAME
+      name = LCOMM_SECTION_NAME;
+      /* The section is not actually common if LCOMM_SECTION_NAME is
+        defined.  */
+      flags &= ~(SECTION_COMMON);
+#else
+      return sect;
+#endif
+    }
+  else
+    gcc_unreachable ();
+
+  return get_section (name, flags, NULL_TREE);
+}
+
 /* Return the section for function DECL.
 
    If DECL is NULL_TREE, return the text section.  We can be passed
@@ -660,21 +723,24 @@ function_section_1 (tree decl, bool force_cold)
       if (targetm.asm_out.function_section)
        section = targetm.asm_out.function_section (decl, freq,
                                                    startup, exit);
-      if (section)
-       return section;
-      return get_named_section (decl, NULL, 0);
+      if (!section)
+       section = get_named_section (decl, NULL, 0);
     }
   else
-    return targetm.asm_out.select_section
-           (decl, freq == NODE_FREQUENCY_UNLIKELY_EXECUTED,
-            symtab_node::get (decl)->definition_alignment ());
+    section = targetm.asm_out.select_section
+      (decl, freq == NODE_FREQUENCY_UNLIKELY_EXECUTED,
+       symtab_node::get (decl)->definition_alignment ());
 #else
   if (targetm.asm_out.function_section)
     section = targetm.asm_out.function_section (decl, freq, startup, exit);
-  if (section)
-    return section;
-  return hot_function_section (decl);
+  if (!section)
+    section = hot_function_section (decl);
+#endif
+#if HAVE_GAS_SECTION_RETAIN
+  if (DECL_PRESERVE_P (decl))
+    section->common.flags |= SECTION_RETAIN;
 #endif
+  return section;
 }
 
 /* Return the section for function DECL.
@@ -2267,11 +2333,19 @@ assemble_variable (tree decl, int top_level 
ATTRIBUTE_UNUSED,
   if (TREE_PUBLIC (decl))
     maybe_assemble_visibility (decl);
 
+  sect = get_variable_section (decl, false);
+
   if (DECL_PRESERVE_P (decl))
-    targetm.asm_out.mark_decl_preserved (name);
+    {
+      targetm.asm_out.mark_decl_preserved (name);
+#if HAVE_GAS_SECTION_RETAIN
+      if (SECTION_STYLE (sect) != SECTION_NAMED)
+       sect = convert_to_named_section (sect);
+      sect->common.flags |= SECTION_RETAIN;
+#endif
+    }
 
   /* First make the assembler name(s) global if appropriate.  */
-  sect = get_variable_section (decl, false);
   if (TREE_PUBLIC (decl)
       && (sect->common.flags & SECTION_COMMON) == 0)
     globalize_decl (decl);
@@ -6624,6 +6698,11 @@ 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 HAVE_GAS_SECTION_RETAIN
+  if (decl && DECL_PRESERVE_P (decl))
+    flags |= SECTION_RETAIN;
+#endif
+
   if (strcmp (name, ".bss") == 0
       || strncmp (name, ".bss.", 5) == 0
       || strncmp (name, ".gnu.linkonce.b.", 16) == 0
@@ -6743,6 +6822,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';
     }
 
@@ -7635,12 +7716,23 @@ output_section_asm_op (const void *directive)
   fprintf (asm_out_file, "%s\n", (const char *) directive);
 }
 
-/* Emit assembly code to switch to section NEW_SECTION.  Do nothing if
-   the current section is NEW_SECTION.  */
+/* Emit assembly code to switch to section MAYBE_NEW_SECTION.
+   Convert the section to a named section if required.
+   Do nothing if the current section is NEW_SECTION.  */
 
 void
-switch_to_section (section *new_section)
+switch_to_section (section *maybe_new_section)
 {
+  section *new_section;
+
+  if (maybe_new_section->common.flags & SECTION_RETAIN
+      && SECTION_STYLE (maybe_new_section) != SECTION_NAMED)
+    /* SECTION_RETAIN sections must be converted to named so the "R" flag can
+       be emitted in the section directive.  */
+    new_section = convert_to_named_section (maybe_new_section);
+  else
+    new_section = maybe_new_section;
+
   if (in_section == new_section)
     return;
 
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..346424cb55 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -10733,6 +10733,14 @@ 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.  */
+  if (bed->elf_osabi == ELFOSABI_NONE
+      || bed->elf_osabi == ELFOSABI_GNU
+      || bed->elf_osabi == ELFOSABI_FREEBSD)
+    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 +14111,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..372bb0e578 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,28 @@ get_elf_section_flags (Filedata * filedata, bfd_vma 
sh_flags)
                  if (flag == SHF_PPC_VLE)
                    sindex = 25;
                  break;
+               default:
+                 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 +6145,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 +6154,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..41b1d9dde3 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 SHF_GNU_RETAIN flag, which can be applied to
+  sections using the "R" flag in the .section directive.
+  SHF_GNU_RETAIN specifies that the section should not be garbage
+  collected by the linker.  It requires the GNU or FreeBSD ELF OSABIs.
+
 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..8d3c36955e 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -861,6 +861,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 +893,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 --;
@@ -1387,26 +1414,37 @@ 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)
     {
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..2e413e1cec
--- /dev/null
+++ b/gas/testsuite/gas/elf/section23a.d
@@ -0,0 +1,10 @@
+#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..29adb5d2c9
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1a.d
@@ -0,0 +1,27 @@
+#name: SHF_GNU_RETAIN 1a
+#source: retain1.s
+#ld: -e _start --gc-sections
+#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..b1cafc9d1c
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain1b.d
@@ -0,0 +1,10 @@
+#name: SHF_GNU_RETAIN 1b
+#source: retain1.s
+#ld: -e _start --gc-sections
+#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..1a63f51aab
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain2.d
@@ -0,0 +1,5 @@
+#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
+#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..3c81a88e51
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain3.d
@@ -0,0 +1,11 @@
+#name: SHF_GNU_RETAIN 3 (keep sections referenced by retained sections)
+#source: retain3.s
+#ld: -e _start --gc-sections
+#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..b423fb9584
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain4.d
@@ -0,0 +1,9 @@
+#name: SHF_GNU_RETAIN 4 (keep orphaned sections when not discarding)
+#source: retain4.s
+#ld: -e _start --gc-sections --orphan-handling=place
+#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..86e85f8da5
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain5.d
@@ -0,0 +1,11 @@
+#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
+#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..aa93117ae7
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6a.d
@@ -0,0 +1,13 @@
+#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
+#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..f29ba71dd9
--- /dev/null
+++ b/ld/testsuite/ld-elf/retain6b.d
@@ -0,0 +1,10 @@
+#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
+#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

Reply via email to