[PATCH v3] Support SHF_GNU_RETAIN ELF Section Flag

Jozef Lawrynowicz jozef.l@mittosystems.com
Mon Oct 19 19:47:10 GMT 2020


Ping for this with a small amendment - removed the unnecessary skip
list in the LD tests.

On Fri, Oct 09, 2020 at 11:59:47AM +0100, Jozef Lawrynowicz wrote:
> The attached patch is version 3 of the SHF_GNU_RETAIN patch that was
> previously discussed here:
> 
> Version 1:
> https://sourceware.org/pipermail/binutils/2020-September/113406.html
> Version 2:
> https://sourceware.org/pipermail/binutils/2020-October/113559.html
> 
> The following changes have been made since version 2:
> - Removed "if it is unused" phrase from SHF_GNU_RETAIN definition.
>   The definition now reads:
>   "The link editor should not garbage collect the section."
> - Aligned OSABI error messages in bfd/elf.c with those in
>   gas/config/obj-elf.c.
> - Added OSABI checks before SHF_GNU_RETAIN is used in bfd/elflink.c.
> - Added OSABI checks and new tests before SHF_GNU_RETAIN and
>   SHF_GNU_MBIND are used in binutils/readelf.c.
> - Removed support in GAS for unique sections to be created with the same
>   name, but different states for SHF_GNU_RETAIN. This exception is not
>   required for SHF_GNU_RETAIN support.
> - Added warning/error message when SHF_GNU_RETAIN state is not
>   consistent when changing sections. Added tests for this.
> - Added support for flags in the SHF_MASKOS range to be set with numeric
>   values to the flags argument of the .section directive. Added tests
>   for this.
> 
> I've successfully regtested the patch for the Binutils, GAS and LD
> testsuites for the following targets:
> 
> aarch64-elf arc-elf arm-eabi arm-elf avr-elf bfin-elf cr16-elf cris-elf
> crx-elf csky-elf d10v-elf d30v-elf dlx-elf epiphany-elf fr30-elf frv-elf
> ft32-elf h8300-elf hppa-elf i386-elf ip2k-elf iq2000-elf lm32-elf
> m32c-elf m32r-elf m68hc11-elf m68hc12-elf m68k-elf mcore-elf mep-elf
> metag-elf microblaze-elf mips-elf moxie-elf msp430-elf mt-elf nios2-elf
> or1k-elf pj-elf ppc-elf pru-elf riscv-elf rl78-elf rx-elf s12z-elf
> score-elf sh-elf sparc-elf spu-elf tic6x-elf tilegx-elf tilepro-elf
> v850-elf visium-elf wasm32-elf xgate-elf xstormy16-elf xtensa-elf
> z80-elf.
> 
> Ok to apply?
> 
> Thanks,
> Jozef

> From 9709145da0644b6657a892ad8e513ace81359c7d Mon Sep 17 00:00:00 2001
> From: Jozef Lawrynowicz <jozef.l@mittosystems.com>
> Date: Fri, 9 Oct 2020 11:52:55 +0100
> Subject: [PATCH] Support SHF_GNU_RETAIN ELF section flag
> 
> The SHF_GNU_RETAIN section flag is an extension to the GNU ELF OSABI.
> It is defined as follows:
> 
> =======================================================================
> Section Attribute Flags
> +-------------------------------------+
> | Name           | Value              |
> +-------------------------------------+
> | SHF_GNU_RETAIN | 0x200000 (1 << 21) |
> +-------------------------------------+
> 
> SHF_GNU_RETAIN
>   The link editor should not garbage collect the section.
> 
> =======================================================================
> 
> Note that there is not a direct mapping of SHF_GNU_RETAIN to the BFD
> section flag SEC_KEEP. This would prevent the user being able to
> explicitly remove an SHF_GNU_RETAIN section by placing it in /DISCARD/.
> 
> bfd/ChangeLog:
> 
> 2020-10-09  Jozef Lawrynowicz  <jozef.l@mittosystems.com>
> 	H.J. Lu  <hongjiu.lu@intel.com>
> 
> 	* elf-bfd.h (enum elf_gnu_osabi): Add elf_gnu_osabi_retain.
> 	(struct elf_obj_tdata): Increase has_gnu_osabi to 4 bits.
> 	* elf.c (_bfd_elf_make_section_from_shdr): Set elf_gnu_osabi_retain
> 	for SHF_GNU_RETAIN.
> 	(_bfd_elf_final_write_processing): Report if SHF_GNU_RETAIN is
> 	not supported by the OSABI.
> 	Adjust error messages.
> 	* elflink.c (bfd_elf_gc_sections): gc_mark the section if
> 	SHF_GNU_RETAIN is set.
> 
> binutils/ChangeLog:
> 
> 2020-10-09  Jozef Lawrynowicz  <jozef.l@mittosystems.com>
> 
> 	* NEWS: Announce SHF_GNU_RETAIN.
> 	* readelf.c (get_elf_section_flags): Handle SHF_GNU_RETAIN.
> 	Recognize SHF_GNU_RETAIN and SHF_GNU_MBIND only for supported OSABIs.
> 	* testsuite/binutils-all/readelf.exp: Run new tests.
> 	Don't run run_dump_test when there isn't an assembler available.
> 	* testsuite/lib/binutils-common.exp (supports_gnu_osabi): Adjust
> 	comment.
> 	* testsuite/binutils-all/readelf-maskos-1a.d: New test.
> 	* testsuite/binutils-all/readelf-maskos-1b.d: New test.
> 	* testsuite/binutils-all/readelf-maskos.s: New test.
> 	* testsuite/binutils-all/retain1.s: New test.
> 	* testsuite/binutils-all/retain1a.d: New test.
> 	* testsuite/binutils-all/retain1b.d: New test.
> 
> gas/ChangeLog:
> 
> 2020-10-09  Jozef Lawrynowicz  <jozef.l@mittosystems.com>
> 	H.J. Lu  <hongjiu.lu@intel.com>
> 
> 	* NEWS: Announce SHF_GNU_RETAIN.
> 	* config/obj-elf.c (get_section_by_match): Update struct member name.
> 	(obj_elf_change_section): Warn/error for state change of SHF_GNU_RETAIN.
> 	(obj_elf_parse_section_letters): Handle 'R' flag.
> 	Handle numeric flag values within the SHF_MASKOS range.
> 	(obj_elf_section): Validate SHF_GNU_RETAIN usage.
> 	* config/obj-elf.h (struct elf_section_match): Adjust "info" member
> 	name to "sh_info".  Add "sh_flags" member.
> 	* doc/as.texi (Section): Document 'R' flag.
> 	* testsuite/gas/elf/elf.exp: Run new tests.
> 	* testsuite/gas/elf/section10.d: Don't test SHF_GNU_RETAIN bit of
> 	section flag.
> 	* testsuite/gas/elf/section10.s: Don't set SHF_GNU_RETAIN bit of
> 	section flag.
> 	* testsuite/gas/elf/section22.d: New test.
> 	* testsuite/gas/elf/section22.s: New test.
> 	* testsuite/gas/elf/section23.s: New test.
> 	* testsuite/gas/elf/section23a.d: New test.
> 	* testsuite/gas/elf/section23b.d: New test.
> 	* testsuite/gas/elf/section23b.err: New test.
> 	* testsuite/gas/elf/section24.d: New test.
> 	* testsuite/gas/elf/section24.l: New test.
> 	* testsuite/gas/elf/section24.s: New test.
> 
> include/ChangeLog:
> 
> 2020-10-09  Jozef Lawrynowicz  <jozef.l@mittosystems.com>
> 
> 	* elf/common.h (SHF_GNU_RETAIN): Define.
> 
> ld/ChangeLog:
> 
> 2020-10-09  Jozef Lawrynowicz  <jozef.l@mittosystems.com>
> 
> 	* NEWS: Announce SHF_GNU_RETAIN.
> 	* ld.texi (garbage collection): Document SHF_GNU_RETAIN.
> 	(Output Section Discarding): Likewise.
> 	* testsuite/ld-elf/elf.exp: Run new tests.
> 	* testsuite/ld-elf/retain1.s: New test.
> 	* testsuite/ld-elf/retain1a.d: New test.
> 	* testsuite/ld-elf/retain1b.d: New test.
> 	* testsuite/ld-elf/retain2.d: New test.
> 	* testsuite/ld-elf/retain2.ld: New test.
> 	* testsuite/ld-elf/retain2.map: New test.
> 	* testsuite/ld-elf/retain3.d: New test.
> 	* testsuite/ld-elf/retain3.s: New test.
> 	* testsuite/ld-elf/retain4.d: New test.
> 	* testsuite/ld-elf/retain4.s: New test.
> 	* testsuite/ld-elf/retain5.d: New test.
> 	* testsuite/ld-elf/retain5.map: New test.
> 	* testsuite/ld-elf/retain5lib.s: New test.
> 	* testsuite/ld-elf/retain5main.s: New test.
> 	* testsuite/ld-elf/retain6a.d: New test.
> 	* testsuite/ld-elf/retain6b.d: New test.
> 	* testsuite/ld-elf/retain6lib.s: New test.
> 	* testsuite/ld-elf/retain6main.s: New test.
> ---
>  bfd/elf-bfd.h                                 |   9 +-
>  bfd/elf.c                                     |  17 ++-
>  bfd/elflink.c                                 |   4 +-
>  binutils/NEWS                                 |   4 +
>  binutils/readelf.c                            |  51 ++++++++-
>  .../binutils-all/readelf-maskos-1a.d          |  10 ++
>  .../binutils-all/readelf-maskos-1b.d          |  12 ++
>  .../testsuite/binutils-all/readelf-maskos.s   |  11 ++
>  binutils/testsuite/binutils-all/readelf.exp   |   9 +-
>  binutils/testsuite/binutils-all/retain1.s     | 104 ++++++++++++++++++
>  binutils/testsuite/binutils-all/retain1a.d    |  18 +++
>  binutils/testsuite/binutils-all/retain1b.d    |  46 ++++++++
>  binutils/testsuite/lib/binutils-common.exp    |   5 +-
>  gas/NEWS                                      |   5 +
>  gas/config/obj-elf.c                          |  88 +++++++++++----
>  gas/config/obj-elf.h                          |   3 +-
>  gas/doc/as.texi                               |   3 +
>  gas/testsuite/gas/elf/elf.exp                 |   5 +-
>  gas/testsuite/gas/elf/section10.d             |   4 +-
>  gas/testsuite/gas/elf/section10.s             |   4 +-
>  gas/testsuite/gas/elf/section22.d             |  19 ++++
>  gas/testsuite/gas/elf/section22.s             |  34 ++++++
>  gas/testsuite/gas/elf/section23.s             |  11 ++
>  gas/testsuite/gas/elf/section23a.d            |  11 ++
>  gas/testsuite/gas/elf/section23b.d            |   6 +
>  gas/testsuite/gas/elf/section23b.err          |   2 +
>  gas/testsuite/gas/elf/section24.d             |  18 +++
>  gas/testsuite/gas/elf/section24.l             |   4 +
>  gas/testsuite/gas/elf/section24.s             |  32 ++++++
>  include/elf/common.h                          |   1 +
>  ld/NEWS                                       |   4 +
>  ld/ld.texi                                    |   7 ++
>  ld/testsuite/ld-elf/elf.exp                   |  11 ++
>  ld/testsuite/ld-elf/retain1.s                 | 104 ++++++++++++++++++
>  ld/testsuite/ld-elf/retain1a.d                |  28 +++++
>  ld/testsuite/ld-elf/retain1b.d                |  11 ++
>  ld/testsuite/ld-elf/retain2.d                 |   6 +
>  ld/testsuite/ld-elf/retain2.ld                |   7 ++
>  ld/testsuite/ld-elf/retain2.map               |  32 ++++++
>  ld/testsuite/ld-elf/retain3.d                 |  12 ++
>  ld/testsuite/ld-elf/retain3.s                 |  19 ++++
>  ld/testsuite/ld-elf/retain4.d                 |  10 ++
>  ld/testsuite/ld-elf/retain4.s                 |  13 +++
>  ld/testsuite/ld-elf/retain5.d                 |  12 ++
>  ld/testsuite/ld-elf/retain5.map               |   5 +
>  ld/testsuite/ld-elf/retain5lib.s              |   6 +
>  ld/testsuite/ld-elf/retain5main.s             |   5 +
>  ld/testsuite/ld-elf/retain6a.d                |  14 +++
>  ld/testsuite/ld-elf/retain6b.d                |  11 ++
>  ld/testsuite/ld-elf/retain6lib.s              |  17 +++
>  ld/testsuite/ld-elf/retain6main.s             |  13 +++
>  51 files changed, 853 insertions(+), 44 deletions(-)
>  create mode 100644 binutils/testsuite/binutils-all/readelf-maskos-1a.d
>  create mode 100644 binutils/testsuite/binutils-all/readelf-maskos-1b.d
>  create mode 100644 binutils/testsuite/binutils-all/readelf-maskos.s
>  create mode 100644 binutils/testsuite/binutils-all/retain1.s
>  create mode 100644 binutils/testsuite/binutils-all/retain1a.d
>  create mode 100644 binutils/testsuite/binutils-all/retain1b.d
>  create mode 100644 gas/testsuite/gas/elf/section22.d
>  create mode 100644 gas/testsuite/gas/elf/section22.s
>  create mode 100644 gas/testsuite/gas/elf/section23.s
>  create mode 100644 gas/testsuite/gas/elf/section23a.d
>  create mode 100644 gas/testsuite/gas/elf/section23b.d
>  create mode 100644 gas/testsuite/gas/elf/section23b.err
>  create mode 100644 gas/testsuite/gas/elf/section24.d
>  create mode 100644 gas/testsuite/gas/elf/section24.l
>  create mode 100644 gas/testsuite/gas/elf/section24.s
>  create mode 100644 ld/testsuite/ld-elf/retain1.s
>  create mode 100644 ld/testsuite/ld-elf/retain1a.d
>  create mode 100644 ld/testsuite/ld-elf/retain1b.d
>  create mode 100644 ld/testsuite/ld-elf/retain2.d
>  create mode 100644 ld/testsuite/ld-elf/retain2.ld
>  create mode 100644 ld/testsuite/ld-elf/retain2.map
>  create mode 100644 ld/testsuite/ld-elf/retain3.d
>  create mode 100644 ld/testsuite/ld-elf/retain3.s
>  create mode 100644 ld/testsuite/ld-elf/retain4.d
>  create mode 100644 ld/testsuite/ld-elf/retain4.s
>  create mode 100644 ld/testsuite/ld-elf/retain5.d
>  create mode 100644 ld/testsuite/ld-elf/retain5.map
>  create mode 100644 ld/testsuite/ld-elf/retain5lib.s
>  create mode 100644 ld/testsuite/ld-elf/retain5main.s
>  create mode 100644 ld/testsuite/ld-elf/retain6a.d
>  create mode 100644 ld/testsuite/ld-elf/retain6b.d
>  create mode 100644 ld/testsuite/ld-elf/retain6lib.s
>  create mode 100644 ld/testsuite/ld-elf/retain6main.s
> 
> diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
> index 140a98594d..ffb75f7919 100644
> --- a/bfd/elf-bfd.h
> +++ b/bfd/elf-bfd.h
> @@ -1897,14 +1897,15 @@ struct output_elf_obj_tdata
>    bfd_boolean flags_init;
>  };
>  
> -/* Indicate if the bfd contains SHF_GNU_MBIND sections or symbols that
> -   have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE binding.  Used
> -   to set the osabi field in the ELF header structure.  */
> +/* Indicate if the bfd contains SHF_GNU_MBIND/SHF_GNU_RETAIN sections or
> +   symbols that have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE
> +   binding.  Used to set the osabi field in the ELF header structure.  */
>  enum elf_gnu_osabi
>  {
>    elf_gnu_osabi_mbind = 1 << 0,
>    elf_gnu_osabi_ifunc = 1 << 1,
>    elf_gnu_osabi_unique = 1 << 2,
> +  elf_gnu_osabi_retain = 1 << 3,
>  };
>  
>  typedef struct elf_section_list
> @@ -2034,7 +2035,7 @@ struct elf_obj_tdata
>    ENUM_BITFIELD (dynamic_lib_link_class) dyn_lib_class : 4;
>  
>    /* Whether the bfd uses OS specific bits that require ELFOSABI_GNU.  */
> -  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 3;
> +  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 4;
>  
>    /* Whether if the bfd contains the GNU_PROPERTY_NO_COPY_ON_PROTECTED
>       property.  */
> diff --git a/bfd/elf.c b/bfd/elf.c
> index 9d7cbd52e0..dc097e825a 100644
> --- a/bfd/elf.c
> +++ b/bfd/elf.c
> @@ -1066,9 +1066,12 @@ _bfd_elf_make_section_from_shdr (bfd *abfd,
>        /* FIXME: We should not recognize SHF_GNU_MBIND for ELFOSABI_NONE,
>  	 but binutils as of 2019-07-23 did not set the EI_OSABI header
>  	 byte.  */
> -    case ELFOSABI_NONE:
>      case ELFOSABI_GNU:
>      case ELFOSABI_FREEBSD:
> +      if ((hdr->sh_flags & SHF_GNU_RETAIN) != 0)
> +	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_retain;
> +      /* Fall through */
> +    case ELFOSABI_NONE:
>        if ((hdr->sh_flags & SHF_GNU_MBIND) != 0)
>  	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_mbind;
>        break;
> @@ -12464,11 +12467,17 @@ _bfd_elf_final_write_processing (bfd *abfd)
>  	       && i_ehdrp->e_ident[EI_OSABI] != ELFOSABI_FREEBSD)
>  	{
>  	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_mbind)
> -	    _bfd_error_handler (_("GNU_MBIND section is unsupported"));
> +	    _bfd_error_handler (_("GNU_MBIND section is supported only by GNU "
> +				  "and FreeBSD targets"));
>  	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_ifunc)
> -	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is unsupported"));
> +	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is supported "
> +				  "only by GNU and FreeBSD targets"));
>  	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_unique)
> -	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is unsupported"));
> +	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is supported "
> +				  "only by GNU and FreeBSD targets"));
> +	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_retain)
> +	    _bfd_error_handler (_("GNU_RETAIN section is supported "
> +				  "only by GNU and FreeBSD targets"));
>  	  bfd_set_error (bfd_error_sorry);
>  	  return FALSE;
>  	}
> diff --git a/bfd/elflink.c b/bfd/elflink.c
> index e23d189b98..742254055c 100644
> --- a/bfd/elflink.c
> +++ b/bfd/elflink.c
> @@ -14103,7 +14103,9 @@ bfd_elf_gc_sections (bfd *abfd, struct bfd_link_info *info)
>  			    == SHT_FINI_ARRAY)))
>  		|| (elf_section_data (o)->this_hdr.sh_type == SHT_NOTE
>  		    && elf_next_in_group (o) == NULL
> -		    && elf_linked_to_section (o) == NULL)))
> +		    && elf_linked_to_section (o) == NULL)
> +		|| ((elf_tdata (sub)->has_gnu_osabi & elf_gnu_osabi_retain)
> +		    && (elf_section_flags (o) & SHF_GNU_RETAIN))))
>  	  {
>  	    if (!_bfd_elf_gc_mark (info, o, gc_mark_hook))
>  	      return FALSE;
> diff --git a/binutils/NEWS b/binutils/NEWS
> index c0dc73d7d8..ceea7731c5 100644
> --- a/binutils/NEWS
> +++ b/binutils/NEWS
> @@ -4,6 +4,10 @@
>    symbol names.  In addition the --demangle=<style>, --no-demangle,
>    --recurse-limit and --no-recurse-limit options are also now availale.
>  
> +* Add support for the SHF_GNU_RETAIN ELF section flag.
> +  This flag specifies that the section should not be garbage collected by the
> +  linker.
> +
>  Changes in 2.35:
>  
>  * Changed readelf's display of symbol names when wide mode is not enabled.
> diff --git a/binutils/readelf.c b/binutils/readelf.c
> index 9ba4e29a65..e6ec99a2cc 100644
> --- a/binutils/readelf.c
> +++ b/binutils/readelf.c
> @@ -5977,6 +5977,8 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
>        /* 24 */ { STRING_COMMA_LEN ("GNU_MBIND") },
>        /* VLE specific.  */
>        /* 25 */ { STRING_COMMA_LEN ("VLE") },
> +      /* GNU specific.  */
> +      /* 26 */ { STRING_COMMA_LEN ("GNU_RETAIN") },
>      };
>  
>    if (do_section_details)
> @@ -6009,7 +6011,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
>  	    case SHF_TLS:		sindex = 9; break;
>  	    case SHF_EXCLUDE:		sindex = 18; break;
>  	    case SHF_COMPRESSED:	sindex = 20; break;
> -	    case SHF_GNU_MBIND:		sindex = 24; break;
>  
>  	    default:
>  	      sindex = -1;
> @@ -6061,10 +6062,26 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
>  		  if (flag == SHF_PPC_VLE)
>  		    sindex = 25;
>  		  break;
> +		}
>  
> +	      switch (filedata->file_header.e_ident[EI_OSABI])
> +		{
> +		case ELFOSABI_GNU:
> +		case ELFOSABI_FREEBSD:
> +		  if (flag == SHF_GNU_RETAIN)
> +		    sindex = 26;
> +		  /* Fall through */
> +		case ELFOSABI_NONE:
> +		  if (flag == SHF_GNU_MBIND)
> +		    /* We should not recognize SHF_GNU_MBIND for
> +		       ELFOSABI_NONE, but binutils as of 2019-07-23 did
> +		       not set the EI_OSABI header byte.  */
> +		    sindex = 24;
> +		  break;
>  		default:
>  		  break;
>  		}
> +	      break;
>  	    }
>  
>  	  if (sindex != -1)
> @@ -6107,7 +6124,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
>  	    case SHF_TLS:		*p = 'T'; break;
>  	    case SHF_EXCLUDE:		*p = 'E'; break;
>  	    case SHF_COMPRESSED:	*p = 'C'; break;
> -	    case SHF_GNU_MBIND:		*p = 'D'; break;
>  
>  	    default:
>  	      if ((filedata->file_header.e_machine == EM_X86_64
> @@ -6117,14 +6133,37 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
>  		*p = 'l';
>  	      else if (filedata->file_header.e_machine == EM_ARM
>  		       && flag == SHF_ARM_PURECODE)
> -		  *p = 'y';
> +		*p = 'y';
>  	      else if (filedata->file_header.e_machine == EM_PPC
>  		       && flag == SHF_PPC_VLE)
> -		  *p = 'v';
> +		*p = 'v';
>  	      else if (flag & SHF_MASKOS)
>  		{
> -		  *p = 'o';
> -		  sh_flags &= ~ SHF_MASKOS;
> +		  switch (filedata->file_header.e_ident[EI_OSABI])
> +		    {
> +		    case ELFOSABI_GNU:
> +		    case ELFOSABI_FREEBSD:
> +		      if (flag == SHF_GNU_RETAIN)
> +			{
> +			  *p = 'R';
> +			  break;
> +			}
> +		      /* Fall through */
> +		    case ELFOSABI_NONE:
> +		      if (flag == SHF_GNU_MBIND)
> +			{
> +			  /* We should not recognize SHF_GNU_MBIND for
> +			     ELFOSABI_NONE, but binutils as of 2019-07-23 did
> +			     not set the EI_OSABI header byte.  */
> +			  *p = 'D';
> +			  break;
> +			}
> +		      /* Fall through */
> +		    default:
> +		      *p = 'o';
> +		      sh_flags &= ~SHF_MASKOS;
> +		      break;
> +		    }
>  		}
>  	      else if (flag & SHF_MASKPROC)
>  		{
> diff --git a/binutils/testsuite/binutils-all/readelf-maskos-1a.d b/binutils/testsuite/binutils-all/readelf-maskos-1a.d
> new file mode 100644
> index 0000000000..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 1107725ea6..33005b5904 100644
> --- a/gas/NEWS
> +++ b/gas/NEWS
> @@ -30,6 +30,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..6586478975 100644
> --- a/gas/config/obj-elf.c
> +++ b/gas/config/obj-elf.c
> @@ -529,9 +529,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 +740,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)
> @@ -798,7 +798,10 @@ obj_elf_change_section (const char *name,
>  	       & (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_CODE
>  		  | SEC_EXCLUDE | SEC_SORT_ENTRIES | SEC_MERGE | SEC_STRINGS
>  		  | SEC_LINK_ONCE | SEC_LINK_DUPLICATES_DISCARD
> -		  | SEC_THREAD_LOCAL)))
> +		  | SEC_THREAD_LOCAL))
> +	      || ((elf_tdata (stdoutput)->has_gnu_osabi & elf_gnu_osabi_retain)
> +		  && ((elf_section_flags (old_sec) ^ match_p->sh_flags)
> +		      & SHF_GNU_RETAIN)))
>  	    {
>  	      if (ssect != NULL)
>  		as_warn (_("ignoring changed section attributes for %s"), name);
> @@ -861,6 +864,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 +896,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 +1317,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 +1420,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)
>      {
> diff --git a/gas/config/obj-elf.h b/gas/config/obj-elf.h
> index 4f29572eef..0a91ed462f 100644
> --- a/gas/config/obj-elf.h
> +++ b/gas/config/obj-elf.h
> @@ -106,8 +106,9 @@ struct elf_section_match
>  {
>    const char *   group_name;
>    const char *   linked_to_symbol_name;
> -  unsigned int   info;
>    unsigned int   section_id;
> +  unsigned int   sh_info;		/* ELF section information.  */
> +  bfd_vma	 sh_flags;		/* ELF section flags.  */
>    flagword       flags;
>  };
>  
> diff --git a/gas/doc/as.texi b/gas/doc/as.texi
> index 4d5294552a..e92432a8bd 100644
> --- a/gas/doc/as.texi
> +++ b/gas/doc/as.texi
> @@ -6657,6 +6657,9 @@ section is a member of a section group
>  section is used for thread-local-storage
>  @item ?
>  section is a member of the previously-current section's group, if any
> +@item R
> +retained section (apply SHF_GNU_RETAIN to prevent linker garbage
> +collection, GNU ELF extension)
>  @item @code{<number>}
>  a numeric value indicating the bits to be set in the ELF section header's flags
>  field.  Note - if one or more of the alphabetic characters described above is
> diff --git a/gas/testsuite/gas/elf/elf.exp b/gas/testsuite/gas/elf/elf.exp
> index 9d75154483..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 571e21af29..c01e562c78 100644
> --- a/include/elf/common.h
> +++ b/include/elf/common.h
> @@ -554,6 +554,7 @@
>  /* #define SHF_MASKOS	0x0F000000    *//* OS-specific semantics */
>  #define SHF_MASKOS	0x0FF00000	/* New value, Oct 4, 1999 Draft */
>  #define SHF_GNU_BUILD_NOTE    (1 << 20)	/* Section contains GNU BUILD ATTRIBUTE notes.  */
> +#define SHF_GNU_RETAIN	      (1 << 21)	/* Section should not be garbage collected by the linker.  */
>  #define SHF_MASKPROC	0xF0000000	/* Processor-specific semantics */
>  
>  /* This used to be implemented as a processor specific section flag.
> diff --git a/ld/NEWS b/ld/NEWS
> index e4ae43b257..c103bcebc4 100644
> --- a/ld/NEWS
> +++ b/ld/NEWS
> @@ -15,6 +15,10 @@
>    unless you are working on a project that has its own analogue
>    of symbol tables that are not reflected in the ELF symtabs.
>  
> +* Add support for the SHF_GNU_RETAIN ELF section flag.
> +  This flag specifies that the section should not be garbage collected by the
> +  linker.
> +
>  Changes in 2.35:
>  
>  * X86 NaCl target support is removed.
> diff --git a/ld/ld.texi b/ld/ld.texi
> index ee592df6c2..849006f0fb 100644
> --- a/ld/ld.texi
> +++ b/ld/ld.texi
> @@ -1787,6 +1787,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
> @@ -5232,6 +5235,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
> -- 
> 2.28.0
> 

-------------- next part --------------
>From 3a89108b2a9e466be7fe82a0f19cd6978d96e3ec Mon Sep 17 00:00:00 2001
From: Jozef Lawrynowicz <jozef.l@mittosystems.com>
Date: Mon, 19 Oct 2020 20:43:17 +0100
Subject: [PATCH] Support SHF_GNU_RETAIN ELF section flag

The SHF_GNU_RETAIN section flag is an extension to the GNU ELF OSABI.
It is defined as follows:

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

SHF_GNU_RETAIN
  The link editor should not garbage collect the section.

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

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

bfd/ChangeLog:

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

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

binutils/ChangeLog:

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

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

gas/ChangeLog:

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

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

include/ChangeLog:

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

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

ld/ChangeLog:

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

	* NEWS: Announce SHF_GNU_RETAIN.
	* ld.texi (garbage collection): Document SHF_GNU_RETAIN.
	(Output Section Discarding): Likewise.
	* testsuite/ld-elf/elf.exp: Run new tests.
	* testsuite/ld-elf/retain1.s: New test.
	* testsuite/ld-elf/retain1a.d: New test.
	* testsuite/ld-elf/retain1b.d: New test.
	* testsuite/ld-elf/retain2.d: New test.
	* testsuite/ld-elf/retain2.ld: New test.
	* testsuite/ld-elf/retain2.map: New test.
	* testsuite/ld-elf/retain3.d: New test.
	* testsuite/ld-elf/retain3.s: New test.
	* testsuite/ld-elf/retain4.d: New test.
	* testsuite/ld-elf/retain4.s: New test.
	* testsuite/ld-elf/retain5.d: New test.
	* testsuite/ld-elf/retain5.map: New test.
	* testsuite/ld-elf/retain5lib.s: New test.
	* testsuite/ld-elf/retain5main.s: New test.
	* testsuite/ld-elf/retain6a.d: New test.
	* testsuite/ld-elf/retain6b.d: New test.
	* testsuite/ld-elf/retain6lib.s: New test.
	* testsuite/ld-elf/retain6main.s: New test.
---
 bfd/elf-bfd.h                                 |   9 +-
 bfd/elf.c                                     |  17 ++-
 bfd/elflink.c                                 |   4 +-
 binutils/NEWS                                 |   4 +
 binutils/readelf.c                            |  51 ++++++++-
 .../binutils-all/readelf-maskos-1a.d          |  10 ++
 .../binutils-all/readelf-maskos-1b.d          |  12 ++
 .../testsuite/binutils-all/readelf-maskos.s   |  11 ++
 binutils/testsuite/binutils-all/readelf.exp   |   9 +-
 binutils/testsuite/binutils-all/retain1.s     | 104 ++++++++++++++++++
 binutils/testsuite/binutils-all/retain1a.d    |  18 +++
 binutils/testsuite/binutils-all/retain1b.d    |  46 ++++++++
 binutils/testsuite/lib/binutils-common.exp    |   5 +-
 gas/NEWS                                      |   5 +
 gas/config/obj-elf.c                          |  88 +++++++++++----
 gas/config/obj-elf.h                          |   3 +-
 gas/doc/as.texi                               |   3 +
 gas/testsuite/gas/elf/elf.exp                 |   5 +-
 gas/testsuite/gas/elf/section10.d             |   4 +-
 gas/testsuite/gas/elf/section10.s             |   4 +-
 gas/testsuite/gas/elf/section22.d             |  19 ++++
 gas/testsuite/gas/elf/section22.s             |  34 ++++++
 gas/testsuite/gas/elf/section23.s             |  11 ++
 gas/testsuite/gas/elf/section23a.d            |  11 ++
 gas/testsuite/gas/elf/section23b.d            |   6 +
 gas/testsuite/gas/elf/section23b.err          |   2 +
 gas/testsuite/gas/elf/section24.d             |  18 +++
 gas/testsuite/gas/elf/section24.l             |   4 +
 gas/testsuite/gas/elf/section24.s             |  32 ++++++
 include/elf/common.h                          |   1 +
 ld/NEWS                                       |   4 +
 ld/ld.texi                                    |   7 ++
 ld/testsuite/ld-elf/elf.exp                   |  11 ++
 ld/testsuite/ld-elf/retain1.s                 | 104 ++++++++++++++++++
 ld/testsuite/ld-elf/retain1a.d                |  27 +++++
 ld/testsuite/ld-elf/retain1b.d                |  10 ++
 ld/testsuite/ld-elf/retain2.d                 |   5 +
 ld/testsuite/ld-elf/retain2.ld                |   7 ++
 ld/testsuite/ld-elf/retain2.map               |  32 ++++++
 ld/testsuite/ld-elf/retain3.d                 |  11 ++
 ld/testsuite/ld-elf/retain3.s                 |  19 ++++
 ld/testsuite/ld-elf/retain4.d                 |   9 ++
 ld/testsuite/ld-elf/retain4.s                 |  13 +++
 ld/testsuite/ld-elf/retain5.d                 |  11 ++
 ld/testsuite/ld-elf/retain5.map               |   5 +
 ld/testsuite/ld-elf/retain5lib.s              |   6 +
 ld/testsuite/ld-elf/retain5main.s             |   5 +
 ld/testsuite/ld-elf/retain6a.d                |  13 +++
 ld/testsuite/ld-elf/retain6b.d                |  10 ++
 ld/testsuite/ld-elf/retain6lib.s              |  17 +++
 ld/testsuite/ld-elf/retain6main.s             |  13 +++
 51 files changed, 845 insertions(+), 44 deletions(-)
 create mode 100644 binutils/testsuite/binutils-all/readelf-maskos-1a.d
 create mode 100644 binutils/testsuite/binutils-all/readelf-maskos-1b.d
 create mode 100644 binutils/testsuite/binutils-all/readelf-maskos.s
 create mode 100644 binutils/testsuite/binutils-all/retain1.s
 create mode 100644 binutils/testsuite/binutils-all/retain1a.d
 create mode 100644 binutils/testsuite/binutils-all/retain1b.d
 create mode 100644 gas/testsuite/gas/elf/section22.d
 create mode 100644 gas/testsuite/gas/elf/section22.s
 create mode 100644 gas/testsuite/gas/elf/section23.s
 create mode 100644 gas/testsuite/gas/elf/section23a.d
 create mode 100644 gas/testsuite/gas/elf/section23b.d
 create mode 100644 gas/testsuite/gas/elf/section23b.err
 create mode 100644 gas/testsuite/gas/elf/section24.d
 create mode 100644 gas/testsuite/gas/elf/section24.l
 create mode 100644 gas/testsuite/gas/elf/section24.s
 create mode 100644 ld/testsuite/ld-elf/retain1.s
 create mode 100644 ld/testsuite/ld-elf/retain1a.d
 create mode 100644 ld/testsuite/ld-elf/retain1b.d
 create mode 100644 ld/testsuite/ld-elf/retain2.d
 create mode 100644 ld/testsuite/ld-elf/retain2.ld
 create mode 100644 ld/testsuite/ld-elf/retain2.map
 create mode 100644 ld/testsuite/ld-elf/retain3.d
 create mode 100644 ld/testsuite/ld-elf/retain3.s
 create mode 100644 ld/testsuite/ld-elf/retain4.d
 create mode 100644 ld/testsuite/ld-elf/retain4.s
 create mode 100644 ld/testsuite/ld-elf/retain5.d
 create mode 100644 ld/testsuite/ld-elf/retain5.map
 create mode 100644 ld/testsuite/ld-elf/retain5lib.s
 create mode 100644 ld/testsuite/ld-elf/retain5main.s
 create mode 100644 ld/testsuite/ld-elf/retain6a.d
 create mode 100644 ld/testsuite/ld-elf/retain6b.d
 create mode 100644 ld/testsuite/ld-elf/retain6lib.s
 create mode 100644 ld/testsuite/ld-elf/retain6main.s

diff --git a/bfd/elf-bfd.h b/bfd/elf-bfd.h
index 140a98594d..ffb75f7919 100644
--- a/bfd/elf-bfd.h
+++ b/bfd/elf-bfd.h
@@ -1897,14 +1897,15 @@ struct output_elf_obj_tdata
   bfd_boolean flags_init;
 };
 
-/* Indicate if the bfd contains SHF_GNU_MBIND sections or symbols that
-   have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE binding.  Used
-   to set the osabi field in the ELF header structure.  */
+/* Indicate if the bfd contains SHF_GNU_MBIND/SHF_GNU_RETAIN sections or
+   symbols that have the STT_GNU_IFUNC symbol type or STB_GNU_UNIQUE
+   binding.  Used to set the osabi field in the ELF header structure.  */
 enum elf_gnu_osabi
 {
   elf_gnu_osabi_mbind = 1 << 0,
   elf_gnu_osabi_ifunc = 1 << 1,
   elf_gnu_osabi_unique = 1 << 2,
+  elf_gnu_osabi_retain = 1 << 3,
 };
 
 typedef struct elf_section_list
@@ -2034,7 +2035,7 @@ struct elf_obj_tdata
   ENUM_BITFIELD (dynamic_lib_link_class) dyn_lib_class : 4;
 
   /* Whether the bfd uses OS specific bits that require ELFOSABI_GNU.  */
-  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 3;
+  ENUM_BITFIELD (elf_gnu_osabi) has_gnu_osabi : 4;
 
   /* Whether if the bfd contains the GNU_PROPERTY_NO_COPY_ON_PROTECTED
      property.  */
diff --git a/bfd/elf.c b/bfd/elf.c
index 9d7cbd52e0..dc097e825a 100644
--- a/bfd/elf.c
+++ b/bfd/elf.c
@@ -1066,9 +1066,12 @@ _bfd_elf_make_section_from_shdr (bfd *abfd,
       /* FIXME: We should not recognize SHF_GNU_MBIND for ELFOSABI_NONE,
 	 but binutils as of 2019-07-23 did not set the EI_OSABI header
 	 byte.  */
-    case ELFOSABI_NONE:
     case ELFOSABI_GNU:
     case ELFOSABI_FREEBSD:
+      if ((hdr->sh_flags & SHF_GNU_RETAIN) != 0)
+	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_retain;
+      /* Fall through */
+    case ELFOSABI_NONE:
       if ((hdr->sh_flags & SHF_GNU_MBIND) != 0)
 	elf_tdata (abfd)->has_gnu_osabi |= elf_gnu_osabi_mbind;
       break;
@@ -12464,11 +12467,17 @@ _bfd_elf_final_write_processing (bfd *abfd)
 	       && i_ehdrp->e_ident[EI_OSABI] != ELFOSABI_FREEBSD)
 	{
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_mbind)
-	    _bfd_error_handler (_("GNU_MBIND section is unsupported"));
+	    _bfd_error_handler (_("GNU_MBIND section is supported only by GNU "
+				  "and FreeBSD targets"));
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_ifunc)
-	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is unsupported"));
+	    _bfd_error_handler (_("symbol type STT_GNU_IFUNC is supported "
+				  "only by GNU and FreeBSD targets"));
 	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_unique)
-	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is unsupported"));
+	    _bfd_error_handler (_("symbol binding STB_GNU_UNIQUE is supported "
+				  "only by GNU and FreeBSD targets"));
+	  if (elf_tdata (abfd)->has_gnu_osabi & elf_gnu_osabi_retain)
+	    _bfd_error_handler (_("GNU_RETAIN section is supported "
+				  "only by GNU and FreeBSD targets"));
 	  bfd_set_error (bfd_error_sorry);
 	  return FALSE;
 	}
diff --git a/bfd/elflink.c b/bfd/elflink.c
index e23d189b98..742254055c 100644
--- a/bfd/elflink.c
+++ b/bfd/elflink.c
@@ -14103,7 +14103,9 @@ bfd_elf_gc_sections (bfd *abfd, struct bfd_link_info *info)
 			    == SHT_FINI_ARRAY)))
 		|| (elf_section_data (o)->this_hdr.sh_type == SHT_NOTE
 		    && elf_next_in_group (o) == NULL
-		    && elf_linked_to_section (o) == NULL)))
+		    && elf_linked_to_section (o) == NULL)
+		|| ((elf_tdata (sub)->has_gnu_osabi & elf_gnu_osabi_retain)
+		    && (elf_section_flags (o) & SHF_GNU_RETAIN))))
 	  {
 	    if (!_bfd_elf_gc_mark (info, o, gc_mark_hook))
 	      return FALSE;
diff --git a/binutils/NEWS b/binutils/NEWS
index c0dc73d7d8..ceea7731c5 100644
--- a/binutils/NEWS
+++ b/binutils/NEWS
@@ -4,6 +4,10 @@
   symbol names.  In addition the --demangle=<style>, --no-demangle,
   --recurse-limit and --no-recurse-limit options are also now availale.
 
+* Add support for the SHF_GNU_RETAIN ELF section flag.
+  This flag specifies that the section should not be garbage collected by the
+  linker.
+
 Changes in 2.35:
 
 * Changed readelf's display of symbol names when wide mode is not enabled.
diff --git a/binutils/readelf.c b/binutils/readelf.c
index 9ba4e29a65..e6ec99a2cc 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -5977,6 +5977,8 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
       /* 24 */ { STRING_COMMA_LEN ("GNU_MBIND") },
       /* VLE specific.  */
       /* 25 */ { STRING_COMMA_LEN ("VLE") },
+      /* GNU specific.  */
+      /* 26 */ { STRING_COMMA_LEN ("GNU_RETAIN") },
     };
 
   if (do_section_details)
@@ -6009,7 +6011,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_TLS:		sindex = 9; break;
 	    case SHF_EXCLUDE:		sindex = 18; break;
 	    case SHF_COMPRESSED:	sindex = 20; break;
-	    case SHF_GNU_MBIND:		sindex = 24; break;
 
 	    default:
 	      sindex = -1;
@@ -6061,10 +6062,26 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 		  if (flag == SHF_PPC_VLE)
 		    sindex = 25;
 		  break;
+		}
 
+	      switch (filedata->file_header.e_ident[EI_OSABI])
+		{
+		case ELFOSABI_GNU:
+		case ELFOSABI_FREEBSD:
+		  if (flag == SHF_GNU_RETAIN)
+		    sindex = 26;
+		  /* Fall through */
+		case ELFOSABI_NONE:
+		  if (flag == SHF_GNU_MBIND)
+		    /* We should not recognize SHF_GNU_MBIND for
+		       ELFOSABI_NONE, but binutils as of 2019-07-23 did
+		       not set the EI_OSABI header byte.  */
+		    sindex = 24;
+		  break;
 		default:
 		  break;
 		}
+	      break;
 	    }
 
 	  if (sindex != -1)
@@ -6107,7 +6124,6 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 	    case SHF_TLS:		*p = 'T'; break;
 	    case SHF_EXCLUDE:		*p = 'E'; break;
 	    case SHF_COMPRESSED:	*p = 'C'; break;
-	    case SHF_GNU_MBIND:		*p = 'D'; break;
 
 	    default:
 	      if ((filedata->file_header.e_machine == EM_X86_64
@@ -6117,14 +6133,37 @@ get_elf_section_flags (Filedata * filedata, bfd_vma sh_flags)
 		*p = 'l';
 	      else if (filedata->file_header.e_machine == EM_ARM
 		       && flag == SHF_ARM_PURECODE)
-		  *p = 'y';
+		*p = 'y';
 	      else if (filedata->file_header.e_machine == EM_PPC
 		       && flag == SHF_PPC_VLE)
-		  *p = 'v';
+		*p = 'v';
 	      else if (flag & SHF_MASKOS)
 		{
-		  *p = 'o';
-		  sh_flags &= ~ SHF_MASKOS;
+		  switch (filedata->file_header.e_ident[EI_OSABI])
+		    {
+		    case ELFOSABI_GNU:
+		    case ELFOSABI_FREEBSD:
+		      if (flag == SHF_GNU_RETAIN)
+			{
+			  *p = 'R';
+			  break;
+			}
+		      /* Fall through */
+		    case ELFOSABI_NONE:
+		      if (flag == SHF_GNU_MBIND)
+			{
+			  /* We should not recognize SHF_GNU_MBIND for
+			     ELFOSABI_NONE, but binutils as of 2019-07-23 did
+			     not set the EI_OSABI header byte.  */
+			  *p = 'D';
+			  break;
+			}
+		      /* Fall through */
+		    default:
+		      *p = 'o';
+		      sh_flags &= ~SHF_MASKOS;
+		      break;
+		    }
 		}
 	      else if (flag & SHF_MASKPROC)
 		{
diff --git a/binutils/testsuite/binutils-all/readelf-maskos-1a.d b/binutils/testsuite/binutils-all/readelf-maskos-1a.d
new file mode 100644
index 0000000000..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 1107725ea6..33005b5904 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -30,6 +30,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..6586478975 100644
--- a/gas/config/obj-elf.c
+++ b/gas/config/obj-elf.c
@@ -529,9 +529,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 +740,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)
@@ -798,7 +798,10 @@ obj_elf_change_section (const char *name,
 	       & (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_CODE
 		  | SEC_EXCLUDE | SEC_SORT_ENTRIES | SEC_MERGE | SEC_STRINGS
 		  | SEC_LINK_ONCE | SEC_LINK_DUPLICATES_DISCARD
-		  | SEC_THREAD_LOCAL)))
+		  | SEC_THREAD_LOCAL))
+	      || ((elf_tdata (stdoutput)->has_gnu_osabi & elf_gnu_osabi_retain)
+		  && ((elf_section_flags (old_sec) ^ match_p->sh_flags)
+		      & SHF_GNU_RETAIN)))
 	    {
 	      if (ssect != NULL)
 		as_warn (_("ignoring changed section attributes for %s"), name);
@@ -861,6 +864,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 +896,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 +1317,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 +1420,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)
     {
diff --git a/gas/config/obj-elf.h b/gas/config/obj-elf.h
index 4f29572eef..0a91ed462f 100644
--- a/gas/config/obj-elf.h
+++ b/gas/config/obj-elf.h
@@ -106,8 +106,9 @@ struct elf_section_match
 {
   const char *   group_name;
   const char *   linked_to_symbol_name;
-  unsigned int   info;
   unsigned int   section_id;
+  unsigned int   sh_info;		/* ELF section information.  */
+  bfd_vma	 sh_flags;		/* ELF section flags.  */
   flagword       flags;
 };
 
diff --git a/gas/doc/as.texi b/gas/doc/as.texi
index 4d5294552a..e92432a8bd 100644
--- a/gas/doc/as.texi
+++ b/gas/doc/as.texi
@@ -6657,6 +6657,9 @@ section is a member of a section group
 section is used for thread-local-storage
 @item ?
 section is a member of the previously-current section's group, if any
+@item R
+retained section (apply SHF_GNU_RETAIN to prevent linker garbage
+collection, GNU ELF extension)
 @item @code{<number>}
 a numeric value indicating the bits to be set in the ELF section header's flags
 field.  Note - if one or more of the alphabetic characters described above is
diff --git a/gas/testsuite/gas/elf/elf.exp b/gas/testsuite/gas/elf/elf.exp
index 9d75154483..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 571e21af29..c01e562c78 100644
--- a/include/elf/common.h
+++ b/include/elf/common.h
@@ -554,6 +554,7 @@
 /* #define SHF_MASKOS	0x0F000000    *//* OS-specific semantics */
 #define SHF_MASKOS	0x0FF00000	/* New value, Oct 4, 1999 Draft */
 #define SHF_GNU_BUILD_NOTE    (1 << 20)	/* Section contains GNU BUILD ATTRIBUTE notes.  */
+#define SHF_GNU_RETAIN	      (1 << 21)	/* Section should not be garbage collected by the linker.  */
 #define SHF_MASKPROC	0xF0000000	/* Processor-specific semantics */
 
 /* This used to be implemented as a processor specific section flag.
diff --git a/ld/NEWS b/ld/NEWS
index e4ae43b257..c103bcebc4 100644
--- a/ld/NEWS
+++ b/ld/NEWS
@@ -15,6 +15,10 @@
   unless you are working on a project that has its own analogue
   of symbol tables that are not reflected in the ELF symtabs.
 
+* Add support for the SHF_GNU_RETAIN ELF section flag.
+  This flag specifies that the section should not be garbage collected by the
+  linker.
+
 Changes in 2.35:
 
 * X86 NaCl target support is removed.
diff --git a/ld/ld.texi b/ld/ld.texi
index ee592df6c2..849006f0fb 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -1787,6 +1787,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
@@ -5232,6 +5235,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
-- 
2.28.0



More information about the Binutils mailing list