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 <joze...@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

Reply via email to