[v2,4/5] LoongArch: Add DT_RELR support

Message ID 20240626100453.98946-6-xry111@xry111.site
State New
Headers
Series [v2,1/5] LoongArch: Reject R_LARCH_32 from becoming a runtime reloc in ELFCLASS64 |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

Xi Ruoyao June 26, 2024, 10:04 a.m. UTC
  The logic is same as a71d87680110 ("aarch64: Add DT_RELR support").

As LoongArch does not have -z dynamic-undefined-weak, we don't need to
consider UNDEFWEAK_NO_DYNAMIC_RELOC.

The linker relaxation adds another layer of complexity.  When we delete
bytes in a section during relaxation, we need to fix up the offset in
the to-be-packed relative relocations against this section.

Signed-off-by: Xi Ruoyao <xry111@xry111.site>
---
 bfd/elfnn-loongarch.c                      | 482 ++++++++++++++++++++-
 binutils/testsuite/lib/binutils-common.exp |   1 +
 ld/emulparams/elf64loongarch.sh            |   1 +
 3 files changed, 481 insertions(+), 3 deletions(-)
  

Comments

mengqinggang June 27, 2024, 7:21 a.m. UTC | #1
在 2024/6/26 下午6:04, Xi Ruoyao 写道:
> The logic is same as a71d87680110 ("aarch64: Add DT_RELR support").
>
> As LoongArch does not have -z dynamic-undefined-weak, we don't need to
> consider UNDEFWEAK_NO_DYNAMIC_RELOC.
>
> The linker relaxation adds another layer of complexity.  When we delete
> bytes in a section during relaxation, we need to fix up the offset in
> the to-be-packed relative relocations against this section.
>
> Signed-off-by: Xi Ruoyao <xry111@xry111.site>
> ---
>   bfd/elfnn-loongarch.c                      | 482 ++++++++++++++++++++-
>   binutils/testsuite/lib/binutils-common.exp |   1 +
>   ld/emulparams/elf64loongarch.sh            |   1 +
>   3 files changed, 481 insertions(+), 3 deletions(-)
>
> diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
> index 1a84b88320b..f501589bc70 100644
> --- a/bfd/elfnn-loongarch.c
> +++ b/bfd/elfnn-loongarch.c
> @@ -84,6 +84,12 @@ struct _bfd_loongarch_elf_obj_tdata
>      && elf_tdata (bfd) != NULL						\
>      && elf_object_id (bfd) == LARCH_ELF_DATA)
>   
> +struct relr_entry
> +{
> +  asection *sec;
> +  bfd_vma off;
> +};
> +
>   struct loongarch_elf_link_hash_table
>   {
>     struct elf_link_hash_table elf;
> @@ -104,8 +110,50 @@ struct loongarch_elf_link_hash_table
>     /* The data segment phase, don't relax the section
>        when it is exp_seg_relro_adjust.  */
>     int *data_segment_phase;
> +
> +  /* Array of relative relocs to be emitted in DT_RELR format.  */
> +  bfd_size_type relr_alloc;
> +  bfd_size_type relr_count;
> +  struct relr_entry *relr;
> +
> +  /* Sorted output addresses of above relative relocs.  */
> +  bfd_vma *relr_sorted;
> +
> +  /* Layout recomputation count.  */
> +  bfd_size_type relr_layout_iter;
>   };
>   
> +struct loongarch_elf_section_data
> +{
> +  struct bfd_elf_section_data elf;
> +
> +  /* &htab->relr[i] where i is the smallest number s.t.
> +     htab->relr[i].sec == &elf.  NULL if there exists no such i.  */


Why  htab->relr[i].sec == &elf?


> +  struct relr_entry *relr;
> +};
> +
> +/* We need an additional field in elf_section_data to handle complex
> +   interactions between DT_RELR and relaxation.  */
> +static bool
> +loongarch_elf_new_section_hook (bfd *abfd, asection *sec)
> +{
> +  if (!sec->used_by_bfd)
> +    {
> +      struct loongarch_elf_section_data *sdata;
> +      size_t amt = sizeof (*sdata);
> +
> +      sdata = bfd_zalloc (abfd, amt);
> +      if (!sdata)
> +	return false;
> +      sec->used_by_bfd = sdata;
> +    }
> +
> +  return _bfd_elf_new_section_hook (abfd, sec);
> +}
> +
> +#define loongarch_elf_section_data(x) \
> +  ((struct loongarch_elf_section_data *) elf_section_data (x))
> +
>   /* Get the LoongArch ELF linker hash table from a link_info structure.  */
>   #define loongarch_elf_hash_table(p)					\
>     (elf_hash_table_id (elf_hash_table (p)) == LARCH_ELF_DATA		\
> @@ -927,6 +975,20 @@ loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>         if (rel + 1 != relocs + sec->reloc_count
>   	  && ELFNN_R_TYPE (rel[1].r_info) == R_LARCH_RELAX)
>   	r_type = loongarch_tls_transition (abfd, info, h, r_symndx, r_type);
> +
> +      /* I don't want to spend time supporting DT_RELR with old object
> +	 files doing stack-based relocs.  */
> +      if (info->enable_dt_relr
> +	  && r_type >= R_LARCH_SOP_PUSH_PCREL
> +	  && r_type <= R_LARCH_SOP_POP_32_U)
> +	{
> +	  /* xgettext:c-format */
> +	  _bfd_error_handler (_("%pB: stack based reloc type (%u) is not "
> +				"supported with -z pack-relative-relocs"),
> +			      abfd, r_type);
> +	  return false;
> +	}
> +
>         switch (r_type)
>   	{
>   	case R_LARCH_GOT_PC_HI20:
> @@ -1118,6 +1180,20 @@ loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>   	    return false;
>   	  break;
>   
> +	case R_LARCH_ALIGN:
> +	  /* Check against irrational R_LARCH_ALIGN relocs which may cause
> +	     removing an odd number of bytes and disrupt DT_RELR.  */
> +	  if (rel->r_offset % 4 != 0)
> +	    {
> +	      /* xgettext:c-format */
> +	      _bfd_error_handler (
> +		_("%pB: R_LARCH_ALIGN with offset %" PRId64 " not aligned "
> +		  "to instruction boundary"),
> +		abfd, (uint64_t) rel->r_offset);
> +	      return false;
> +	    }
> +	  break;
> +
>   	default:
>   	  break;
>   	}
> @@ -1832,6 +1908,342 @@ maybe_set_textrel (struct elf_link_hash_entry *h, void *info_p)
>     return true;
>   }
>   
> +static bool
> +record_relr (struct loongarch_elf_link_hash_table *htab, asection *sec,
> +	     bfd_vma off, asection *sreloc)
> +{
> +  struct relr_entry **sec_relr = &loongarch_elf_section_data (sec)->relr;
> +
> +  /* Undo the relocation section size accounting.  */
> +  BFD_ASSERT (sreloc->size >= sizeof (ElfNN_External_Rela));
> +  sreloc->size -= sizeof (ElfNN_External_Rela);
> +
> +  BFD_ASSERT (off % 2 == 0 && sec->alignment_power > 0);
> +  if (htab->relr_count >= htab->relr_alloc)
> +    {
> +      if (htab->relr_alloc == 0)
> +	htab->relr_alloc = 4096;
> +      else
> +	htab->relr_alloc *= 2;
> +
> +      htab->relr = bfd_realloc (htab->relr,
> +				htab->relr_alloc * sizeof (*htab->relr));
> +      if (!htab->relr)
> +	return false;
> +    }
> +  htab->relr[htab->relr_count].sec = sec;
> +  htab->relr[htab->relr_count].off = off;
> +  if (*sec_relr == NULL)
> +    *sec_relr = &htab->relr[htab->relr_count];
> +  htab->relr_count++;
> +  return true;
> +}
> +
> +static bool
> +record_relr_local_got_relocs (bfd *input_bfd, struct bfd_link_info *info)
> +{
> +  bfd_vma *local_got_offsets = elf_local_got_offsets (input_bfd);
> +  char *local_tls_type = _bfd_loongarch_elf_local_got_tls_type (input_bfd);
> +  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (input_bfd);
> +  struct loongarch_elf_link_hash_table *htab =
> +    loongarch_elf_hash_table (info);
> +
> +  if (!local_got_offsets || !local_tls_type || !bfd_link_pic (info))
> +    return true;
> +
> +  for (unsigned i = 0; i < symtab_hdr->sh_info; i++)
> +    {
> +      bfd_vma off = local_got_offsets[i];
> +
> +      /* FIXME: If the local symbol is in SHN_ABS then emitting
> +	 a relative relocation is not correct, but it seems to be wrong
> +	 in loongarch_elf_relocate_section too.  */
> +      if (local_tls_type[i] == GOT_NORMAL
> +	  && !record_relr (htab, htab->elf.sgot, off, htab->elf.srelgot))
> +	return false;
> +    }
> +
> +  return true;
> +}
> +
> +static bool
> +record_relr_dyn_got_relocs (struct elf_link_hash_entry *h, void *inf)
> +{
> +  struct bfd_link_info *info = (struct bfd_link_info *) inf;
> +  struct loongarch_elf_link_hash_table *htab =
> +    loongarch_elf_hash_table (info);
> +
> +  if (h->root.type == bfd_link_hash_indirect)
> +    return true;
> +  if (h->type == STT_GNU_IFUNC && h->def_regular)
> +    return true;
> +  if (h->got.refcount <= 0)
> +    return true;
> +  if (loongarch_elf_hash_entry (h)->tls_type
> +      & (GOT_TLS_GD | GOT_TLS_IE | GOT_TLS_GDESC))
> +    return true;
> +  if (!bfd_link_pic (info))
> +    return true;
> +
> +  /* On LoongArch a GOT entry for undefined weak symbol is never relocated
> +     with R_LARCH_RELATIVE: we don't have -z dynamic-undefined-weak, thus
> +     the GOT entry is either const 0 (if the symbol is LARCH_REF_LOCAL) or
> +     relocated with R_LARCH_NN (otherwise).  */
> +  if (h->root.type == bfd_link_hash_undefweak)
> +    return true;
> +
> +  if (!LARCH_REF_LOCAL (info, h))
> +    return true;
> +  if (bfd_is_abs_symbol (&h->root))
> +    return true;
> +
> +  if (!record_relr (htab, htab->elf.sgot, h->got.offset,
> +		    htab->elf.srelgot))
> +    return false;
> +
> +  return true;
> +}
> +
> +static bool
> +record_relr_non_got_relocs (bfd *input_bfd, struct bfd_link_info *info,
> +			    asection *sec)
> +{
> +  asection *sreloc;
> +  struct loongarch_elf_link_hash_table *htab;
> +  Elf_Internal_Rela *relocs, *rel, *rel_end;
> +  Elf_Internal_Shdr *symtab_hdr;
> +  struct elf_link_hash_entry **sym_hashes;
> +
> +  if (!bfd_link_pic (info))
> +    return true;
> +  if (sec->reloc_count == 0)
> +    return true;
> +  if ((sec->flags & (SEC_RELOC | SEC_ALLOC | SEC_DEBUGGING))
> +       != (SEC_RELOC | SEC_ALLOC))
> +    return true;
> +  if (sec->alignment_power == 0)
> +    return true;
> +  if (discarded_section (sec))
> +    return true;
> +
> +  sreloc = elf_section_data (sec)->sreloc;
> +  if (sreloc == NULL)
> +    return true;
> +
> +  htab = loongarch_elf_hash_table (info);
> +  symtab_hdr = &elf_symtab_hdr (input_bfd);
> +  sym_hashes = elf_sym_hashes (input_bfd);
> +  relocs = _bfd_elf_link_info_read_relocs (input_bfd, info, sec, NULL,
> +					   NULL, info->keep_memory);
> +  BFD_ASSERT (relocs != NULL);
> +  rel_end = relocs + sec->reloc_count;
> +  for (rel = relocs; rel < rel_end; rel++)
> +    {
> +      unsigned r_symndx = ELFNN_R_SYM (rel->r_info);
> +      struct elf_link_hash_entry *h = NULL;
> +      asection *def_sec = NULL;
> +
> +      if (ELFNN_R_TYPE (rel->r_info) != R_LARCH_NN
> +	  || rel->r_offset % 2 != 0)
> +	continue;
> +
> +      /* The logical below must match loongarch_elf_relocate_section.  */
> +      if (r_symndx < symtab_hdr->sh_info)
> +	{
> +	  /* A local symbol.  */
> +	  Elf_Internal_Sym *isym;
> +	  isym = bfd_sym_from_r_symndx (&htab->elf.sym_cache, input_bfd,
> +					r_symndx);
> +	  BFD_ASSERT(isym != NULL);
> +
> +	  /* Local STT_GNU_IFUNC symbol uses R_LARCH_IRELATIVE for
> +	     R_LARCH_NN, not R_LARCH_RELATIVE.  */
> +	  if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
> +	    continue;
> +	  def_sec = bfd_section_from_elf_index (input_bfd, isym->st_shndx);
> +	}
> +      else
> +	{
> +	  h = sym_hashes[r_symndx - symtab_hdr->sh_info];
> +	  while (h->root.type == bfd_link_hash_indirect
> +		 || h->root.type == bfd_link_hash_warning)
> +	    h = (struct elf_link_hash_entry *) h->root.u.i.link;
> +
> +	  /* Filter out symbols that cannot have a relative reloc.  */
> +	  if (h->dyn_relocs == NULL)
> +	    continue;
> +	  if (bfd_is_abs_symbol (&h->root))
> +	    continue;
> +	  if (h->type == STT_GNU_IFUNC)
> +	    continue;
> +
> +	  if (h->root.type == bfd_link_hash_defined
> +	      || h->root.type == bfd_link_hash_defweak)
> +	    def_sec = h->root.u.def.section;
> +
> +	  /* On LoongArch an R_LARCH_NN against undefined weak symbol
> +	     is never converted to R_LARCH_RELATIVE: we don't have
> +	     -z dynamic-undefined-weak, thus the reloc is either removed
> +	     (if the symbol is LARCH_REF_LOCAL) or kept (otherwise).  */
> +	  if (h->root.type == bfd_link_hash_undefweak)
> +	    continue;
> +
> +	  if (!LARCH_REF_LOCAL (info, h))
> +	    continue;
> +	}
> +
> +      if (!def_sec || discarded_section (def_sec))
> +	continue;
> +
> +      if (!record_relr (htab, sec, rel->r_offset, sreloc))
> +	return false;
> +    }
> +
> +  return true;
> +}
> +
> +static int
> +cmp_relr_addr (const void *p, const void *q)
> +{
> +  const bfd_vma *a = p, *b = q;
> +  return (*a > *b) - (*a < *b);
> +}
> +
> +static bool
> +sort_relr (struct bfd_link_info *info,
> +	   struct loongarch_elf_link_hash_table *htab)
> +{
> +  if (htab->relr_count == 0)
> +    return true;
> +
> +  bfd_vma *addr = htab->relr_sorted;
> +  if (!addr)
> +    {
> +      addr = bfd_malloc (htab->relr_count * sizeof (*addr));
> +      if (!addr)
> +	return false;
> +      htab->relr_sorted = addr;
> +    }
> +
> +  for (bfd_size_type i = 0; i < htab->relr_count; i++)
> +    {
> +      bfd_vma off = _bfd_elf_section_offset (info->output_bfd, info,
> +					     htab->relr[i].sec,
> +					     htab->relr[i].off);
> +      addr[i] = htab->relr[i].sec->output_section->vma
> +		+ htab->relr[i].sec->output_offset + off;
> +    }
> +  qsort(addr, htab->relr_count, sizeof (*addr), cmp_relr_addr);
> +  return true;
> +}
> +
> +static bool
> +loongarch_elf_size_relative_relocs (struct bfd_link_info *info,
> +				    bool *need_layout)
> +{
> +  struct loongarch_elf_link_hash_table *htab =
> +    loongarch_elf_hash_table (info);
> +  asection *srelrdyn = htab->elf.srelrdyn;
> +
> +  *need_layout = false;
> +
> +  if (!sort_relr (info, htab))
> +    return false;
> +  bfd_vma *addr = htab->relr_sorted;
> +
> +  BFD_ASSERT (srelrdyn != NULL);
> +  bfd_size_type oldsize = srelrdyn->size;
> +  srelrdyn->size = 0;
> +  for (bfd_size_type i = 0; i < htab->relr_count; )
> +    {
> +      bfd_vma base = addr[i];
> +      i++;
> +      srelrdyn->size += NN / 8;
> +      base += NN / 8;
> +      while (1)
> +	{
> +	  bfd_size_type start_i = i;
> +	  while (i < htab->relr_count
> +		 && addr[i] - base < (NN - 1) * (NN / 8)
> +		 && (addr[i] - base) % (NN / 8) == 0)
> +	    i++;
> +	  if (i == start_i)
> +	    break;
> +	  srelrdyn->size += NN / 8;
> +	  base += (NN - 1) * (NN / 8);
> +	}
> +    }
> +  if (srelrdyn->size != oldsize)
> +    {
> +      *need_layout = true;
> +      /* Stop after a few iterations in case the layout does not converge,
> +	 but we can only stop when the size would shrink (and pad the
> +	 spare space with 1.  */
> +      if (htab->relr_layout_iter++ > 5 && srelrdyn->size < oldsize)
> +	{
> +	  srelrdyn->size = oldsize;
> +	  *need_layout = false;
> +	}
> +    }
> +  return true;
> +}
> +
> +static bool
> +loongarch_elf_finish_relative_relocs (struct bfd_link_info *info)
> +{
> +  struct loongarch_elf_link_hash_table *htab =
> +    loongarch_elf_hash_table (info);
> +  asection *srelrdyn = htab->elf.srelrdyn;
> +  bfd *dynobj = htab->elf.dynobj;
> +
> +  if (!srelrdyn || srelrdyn->size == 0)
> +    return true;
> +
> +  srelrdyn->contents = bfd_alloc (dynobj, srelrdyn->size);
> +  if (!srelrdyn->contents)
> +    return false;
> +
> +  bfd_vma *addr = htab->relr_sorted;
> +  bfd_byte *loc = srelrdyn->contents;
> +  for (bfd_size_type i = 0; i < htab->relr_count; )
> +    {
> +      bfd_vma base = addr[i];
> +      i++;
> +      bfd_put_NN (dynobj, base, loc);
> +      loc += NN / 8;
> +      base += NN / 8;
> +      while (1)
> +	{
> +	  uintNN_t bits = 0;
> +	  while (i < htab->relr_count)
> +	    {
> +	      bfd_vma delta = addr[i] - base;
> +	      if (delta >= (NN - 1) * (NN / 8) || delta % (NN / 8) != 0)
> +		break;
> +	      bits |= (uintNN_t) 1 << (delta / (NN / 8));
> +	      i++;
> +	    }
> +	  if (bits == 0)
> +	    break;
> +	  bfd_put_NN (dynobj, (bits << 1) | 1, loc);
> +	  loc += NN / 8;
> +	  base += (NN - 1) * (NN / 8);
> +	}
> +    }
> +
> +  free (addr);
> +  htab->relr_sorted = NULL;
> +
> +  /* Pad any excess with 1's, a do-nothing encoding.  */
> +  while (loc < srelrdyn->contents + srelrdyn->size)
> +    {
> +      bfd_put_NN (dynobj, 1, loc);
> +      loc += NN / 8;
> +    }
> +
> +  return true;
> +}
> +
>   static bool
>   loongarch_elf_late_size_sections (bfd *output_bfd,
>   				  struct bfd_link_info *info)
> @@ -2012,6 +2424,24 @@ loongarch_elf_late_size_sections (bfd *output_bfd,
>         && (htab->elf.splt == NULL || htab->elf.splt->size == 0))
>       htab->elf.sgotplt->size = 0;
>   
> +  if (info->enable_dt_relr && !bfd_link_relocatable (info))
> +    {
> +      elf_link_hash_traverse (&htab->elf, record_relr_dyn_got_relocs, info);
> +
> +      for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link.next)
> +	{
> +	  if (!is_loongarch_elf (ibfd))
> +	    continue;
> +
> +	  for (s = ibfd->sections; s != NULL; s = s->next)
> +	    if (!record_relr_non_got_relocs (ibfd, info, s))
> +	      return false;
> +
> +	  if (!record_relr_local_got_relocs (ibfd, info))
> +	    return false;
> +	}
> +    }
> +
>     /* The check_relocs and adjust_dynamic_symbol entry points have
>        determined the sizes of the various dynamic sections.  Allocate
>        memory for them.  */
> @@ -2036,6 +2466,14 @@ loongarch_elf_late_size_sections (bfd *output_bfd,
>   	      s->reloc_count = 0;
>   	    }
>   	}
> +      else if (s == htab->elf.srelrdyn && htab->relr_count == 0)
> +	{
> +	  /* Remove .relr.dyn based on relr_count, not size, since
> +	     it is not sized yet.  */
> +	    s->flags |= SEC_EXCLUDE;
> +	  /* Allocate contents later.  */
> +	    continue;
> +	}
>         else
>   	{
>   	  /* It's not one of our sections.  */
> @@ -2968,6 +3406,17 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>   			info, input_bfd, input_section, rel, howto,
>   			bfd_reloc_notsupported, is_undefweak, name, msg);
>   		    }
> +		  else if (info->enable_dt_relr
> +			   && (ELFNN_R_TYPE (outrel.r_info)
> +			       == R_LARCH_RELATIVE)
> +			   && input_section->alignment_power != 0
> +			   && rel->r_offset % 2 == 0)
> +		    /* Don't emit a relative relocation that is packed,
> +		       only apply the addend (as if we are applying the
> +		       original R_LARCH_NN reloc in a PDE).  */
> +		    r = perform_relocation (rel, input_section, howto,
> +					    relocation, input_bfd,
> +					    contents);
>   		  else
>   		    loongarch_elf_append_rela (output_bfd, sreloc,
>   					       &outrel);
> @@ -3695,7 +4144,7 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>   		  got_off = local_got_offsets[r_symndx] & (~(bfd_vma)1);
>   		  if ((local_got_offsets[r_symndx] & 1) == 0)
>   		    {
> -		      if (bfd_link_pic (info))
> +		      if (bfd_link_pic (info) && !info->enable_dt_relr)
>   			{
>   			  Elf_Internal_Rela rela;
>   			  rela.r_offset = sec_addr (got) + got_off;
> @@ -4107,6 +4556,14 @@ loongarch_relax_delete_bytes (bfd *abfd,
>     unsigned int sec_shndx = _bfd_elf_section_from_bfd_section (abfd, sec);
>     struct bfd_elf_section_data *data = elf_section_data (sec);
>     bfd_byte *contents = data->this_hdr.contents;
> +  struct relr_entry *relr =
> +    ((struct loongarch_elf_section_data *)elf_section_data (sec))->relr;
> +  struct loongarch_elf_link_hash_table *htab =
> +    loongarch_elf_hash_table (link_info);
> +  struct relr_entry *relr_end = NULL;
> +
> +  if (htab->relr_count)
> +    relr_end = htab->relr + htab->relr_count;
>   
>     /* Actually delete the bytes.  */
>     sec->size -= count;
> @@ -4119,6 +4576,11 @@ loongarch_relax_delete_bytes (bfd *abfd,
>       if (data->relocs[i].r_offset > addr && data->relocs[i].r_offset < toaddr)
>         data->relocs[i].r_offset -= count;
>   
> +  /* Likewise for relative relocs to be packed into .relr.  */
> +  for (; relr && relr < relr_end && relr->sec == sec; relr++)
> +    if (relr->off > addr && relr->off < toaddr)
> +      relr->off -= count;
> +
>     /* Adjust the local symbols defined in this section.  */
>     for (i = 0; i < symtab_hdr->sh_info; i++)
>       {
> @@ -5207,9 +5669,18 @@ loongarch_elf_finish_dynamic_symbol (bfd *output_bfd,
>         else if (bfd_link_pic (info) && LARCH_REF_LOCAL (info, h))
>   	{
>   	  asection *sec = h->root.u.def.section;
> +	  bfd_vma linkaddr = h->root.u.def.value + sec->output_section->vma
> +			     + sec->output_offset;
> +
> +	  /* Don't emit relative relocs if they are packed, but we need
> +	     to write the addend (link-time addr) into the GOT then.  */
> +	  if (info->enable_dt_relr)
> +	    {
> +	      bfd_put_NN (output_bfd, linkaddr, sgot->contents + off);
> +	      goto skip_got_reloc;
> +	    }
>   	  rela.r_info = ELFNN_R_INFO (0, R_LARCH_RELATIVE);
> -	  rela.r_addend = (h->root.u.def.value + sec->output_section->vma
> -			   + sec->output_offset);
> +	  rela.r_addend = linkaddr;
>   	}
>         else
>   	{
> @@ -5220,6 +5691,7 @@ loongarch_elf_finish_dynamic_symbol (bfd *output_bfd,
>   
>         loongarch_elf_append_rela (output_bfd, srela, &rela);
>       }
> +skip_got_reloc:
>   
>     /* Mark some specially defined symbols as absolute.  */
>     if (h == htab->elf.hdynamic || h == htab->elf.hgot || h == htab->elf.hplt)
> @@ -5673,6 +6145,10 @@ elf_loongarch64_hash_symbol (struct elf_link_hash_entry *h)
>   #define elf_backend_grok_psinfo loongarch_elf_grok_psinfo
>   #define elf_backend_hash_symbol elf_loongarch64_hash_symbol
>   #define bfd_elfNN_bfd_relax_section loongarch_elf_relax_section
> +#define elf_backend_size_relative_relocs loongarch_elf_size_relative_relocs
> +#define elf_backend_finish_relative_relocs \
> +  loongarch_elf_finish_relative_relocs
> +#define bfd_elfNN_new_section_hook loongarch_elf_new_section_hook
>   
>   #define elf_backend_dtrel_excludes_plt 1
>   
> diff --git a/binutils/testsuite/lib/binutils-common.exp b/binutils/testsuite/lib/binutils-common.exp
> index f0136577b6c..ce7413a77e5 100644
> --- a/binutils/testsuite/lib/binutils-common.exp
> +++ b/binutils/testsuite/lib/binutils-common.exp
> @@ -474,6 +474,7 @@ proc supports_dt_relr {} {
>   	  || [istarget i?86-*-*]
>   	  || [istarget powerpc64*-*-*]
>   	  || [istarget aarch64*-*-*])
> +	  || [istarget loongarch64*-*-*]
>   	 && ([istarget *-*-linux*]
>   	     || [istarget *-*-gnu*]) } {
>   	return 1
> diff --git a/ld/emulparams/elf64loongarch.sh b/ld/emulparams/elf64loongarch.sh
> index d7b2229e213..8c805da987e 100644
> --- a/ld/emulparams/elf64loongarch.sh
> +++ b/ld/emulparams/elf64loongarch.sh
> @@ -1,4 +1,5 @@
>   source_sh ${srcdir}/emulparams/elf64loongarch-defs.sh
> +source_sh ${srcdir}/emulparams/dt-relr.sh
>   OUTPUT_FORMAT="elf64-loongarch"
>   
>   case "$target" in
  
mengqinggang June 27, 2024, 7:27 a.m. UTC | #2
> @@ -2036,6 +2466,14 @@ loongarch_elf_late_size_sections (bfd *output_bfd,
>   	      s->reloc_count = 0;
>   	    }
>   	}
> +      else if (s == htab->elf.srelrdyn && htab->relr_count == 0)
> +	{
> +	  /* Remove .relr.dyn based on relr_count, not size, since
> +	     it is not sized yet.  */
> +	    s->flags |= SEC_EXCLUDE;

Two extra spaces are indented.


> +	  /* Allocate contents later.  */
> +	    continue;

Two extra spaces are indented.


> +	}
  
Xi Ruoyao June 27, 2024, 10:13 a.m. UTC | #3
On Thu, 2024-06-27 at 15:21 +0800, mengqinggang wrote:

/* snip */

> > +struct loongarch_elf_section_data
> > +{
> > +  struct bfd_elf_section_data elf;
> > +
> > +  /* &htab->relr[i] where i is the smallest number s.t.
> > +     htab->relr[i].sec == &elf.  NULL if there exists no such i. 
> > */
> 
> 
> Why  htab->relr[i].sec == &elf?

Oops this is wrong.  Should be

elf_section_data(htab->relr[i].sec) == &elf

will fix in V3.

/* snip */

> > @@ -4107,6 +4556,14 @@ loongarch_relax_delete_bytes (bfd *abfd,
> >     unsigned int sec_shndx = _bfd_elf_section_from_bfd_section (abfd, sec);
> >     struct bfd_elf_section_data *data = elf_section_data (sec);
> >     bfd_byte *contents = data->this_hdr.contents;
> > +  struct relr_entry *relr =
> > +    ((struct loongarch_elf_section_data *)elf_section_data (sec))->relr;

And here should (re)use loongarch_elf_section_data(sec)->relr.  Will fix
in V3 as well.
  
Xi Ruoyao June 27, 2024, 10:16 a.m. UTC | #4
On Thu, 2024-06-27 at 15:27 +0800, mengqinggang wrote:
> > +      else if (s == htab->elf.srelrdyn && htab->relr_count == 0)
> > +	{
> > +	  /* Remove .relr.dyn based on relr_count, not size, since
> > +	     it is not sized yet.  */
> > +	    s->flags |= SEC_EXCLUDE;
> 
> Two extra spaces are indented.
> 
> 
> > +	  /* Allocate contents later.  */
> > +	    continue;
> 
> Two extra spaces are indented.

Will remove the extra spaces in V3.
  

Patch

diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index 1a84b88320b..f501589bc70 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -84,6 +84,12 @@  struct _bfd_loongarch_elf_obj_tdata
    && elf_tdata (bfd) != NULL						\
    && elf_object_id (bfd) == LARCH_ELF_DATA)
 
+struct relr_entry
+{
+  asection *sec;
+  bfd_vma off;
+};
+
 struct loongarch_elf_link_hash_table
 {
   struct elf_link_hash_table elf;
@@ -104,8 +110,50 @@  struct loongarch_elf_link_hash_table
   /* The data segment phase, don't relax the section
      when it is exp_seg_relro_adjust.  */
   int *data_segment_phase;
+
+  /* Array of relative relocs to be emitted in DT_RELR format.  */
+  bfd_size_type relr_alloc;
+  bfd_size_type relr_count;
+  struct relr_entry *relr;
+
+  /* Sorted output addresses of above relative relocs.  */
+  bfd_vma *relr_sorted;
+
+  /* Layout recomputation count.  */
+  bfd_size_type relr_layout_iter;
 };
 
+struct loongarch_elf_section_data
+{
+  struct bfd_elf_section_data elf;
+
+  /* &htab->relr[i] where i is the smallest number s.t.
+     htab->relr[i].sec == &elf.  NULL if there exists no such i.  */
+  struct relr_entry *relr;
+};
+
+/* We need an additional field in elf_section_data to handle complex
+   interactions between DT_RELR and relaxation.  */
+static bool
+loongarch_elf_new_section_hook (bfd *abfd, asection *sec)
+{
+  if (!sec->used_by_bfd)
+    {
+      struct loongarch_elf_section_data *sdata;
+      size_t amt = sizeof (*sdata);
+
+      sdata = bfd_zalloc (abfd, amt);
+      if (!sdata)
+	return false;
+      sec->used_by_bfd = sdata;
+    }
+
+  return _bfd_elf_new_section_hook (abfd, sec);
+}
+
+#define loongarch_elf_section_data(x) \
+  ((struct loongarch_elf_section_data *) elf_section_data (x))
+
 /* Get the LoongArch ELF linker hash table from a link_info structure.  */
 #define loongarch_elf_hash_table(p)					\
   (elf_hash_table_id (elf_hash_table (p)) == LARCH_ELF_DATA		\
@@ -927,6 +975,20 @@  loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
       if (rel + 1 != relocs + sec->reloc_count
 	  && ELFNN_R_TYPE (rel[1].r_info) == R_LARCH_RELAX)
 	r_type = loongarch_tls_transition (abfd, info, h, r_symndx, r_type);
+
+      /* I don't want to spend time supporting DT_RELR with old object
+	 files doing stack-based relocs.  */
+      if (info->enable_dt_relr
+	  && r_type >= R_LARCH_SOP_PUSH_PCREL
+	  && r_type <= R_LARCH_SOP_POP_32_U)
+	{
+	  /* xgettext:c-format */
+	  _bfd_error_handler (_("%pB: stack based reloc type (%u) is not "
+				"supported with -z pack-relative-relocs"),
+			      abfd, r_type);
+	  return false;
+	}
+
       switch (r_type)
 	{
 	case R_LARCH_GOT_PC_HI20:
@@ -1118,6 +1180,20 @@  loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	    return false;
 	  break;
 
+	case R_LARCH_ALIGN:
+	  /* Check against irrational R_LARCH_ALIGN relocs which may cause
+	     removing an odd number of bytes and disrupt DT_RELR.  */
+	  if (rel->r_offset % 4 != 0)
+	    {
+	      /* xgettext:c-format */
+	      _bfd_error_handler (
+		_("%pB: R_LARCH_ALIGN with offset %" PRId64 " not aligned "
+		  "to instruction boundary"),
+		abfd, (uint64_t) rel->r_offset);
+	      return false;
+	    }
+	  break;
+
 	default:
 	  break;
 	}
@@ -1832,6 +1908,342 @@  maybe_set_textrel (struct elf_link_hash_entry *h, void *info_p)
   return true;
 }
 
+static bool
+record_relr (struct loongarch_elf_link_hash_table *htab, asection *sec,
+	     bfd_vma off, asection *sreloc)
+{
+  struct relr_entry **sec_relr = &loongarch_elf_section_data (sec)->relr;
+
+  /* Undo the relocation section size accounting.  */
+  BFD_ASSERT (sreloc->size >= sizeof (ElfNN_External_Rela));
+  sreloc->size -= sizeof (ElfNN_External_Rela);
+
+  BFD_ASSERT (off % 2 == 0 && sec->alignment_power > 0);
+  if (htab->relr_count >= htab->relr_alloc)
+    {
+      if (htab->relr_alloc == 0)
+	htab->relr_alloc = 4096;
+      else
+	htab->relr_alloc *= 2;
+
+      htab->relr = bfd_realloc (htab->relr,
+				htab->relr_alloc * sizeof (*htab->relr));
+      if (!htab->relr)
+	return false;
+    }
+  htab->relr[htab->relr_count].sec = sec;
+  htab->relr[htab->relr_count].off = off;
+  if (*sec_relr == NULL)
+    *sec_relr = &htab->relr[htab->relr_count];
+  htab->relr_count++;
+  return true;
+}
+
+static bool
+record_relr_local_got_relocs (bfd *input_bfd, struct bfd_link_info *info)
+{
+  bfd_vma *local_got_offsets = elf_local_got_offsets (input_bfd);
+  char *local_tls_type = _bfd_loongarch_elf_local_got_tls_type (input_bfd);
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (input_bfd);
+  struct loongarch_elf_link_hash_table *htab =
+    loongarch_elf_hash_table (info);
+
+  if (!local_got_offsets || !local_tls_type || !bfd_link_pic (info))
+    return true;
+
+  for (unsigned i = 0; i < symtab_hdr->sh_info; i++)
+    {
+      bfd_vma off = local_got_offsets[i];
+
+      /* FIXME: If the local symbol is in SHN_ABS then emitting
+	 a relative relocation is not correct, but it seems to be wrong
+	 in loongarch_elf_relocate_section too.  */
+      if (local_tls_type[i] == GOT_NORMAL
+	  && !record_relr (htab, htab->elf.sgot, off, htab->elf.srelgot))
+	return false;
+    }
+
+  return true;
+}
+
+static bool
+record_relr_dyn_got_relocs (struct elf_link_hash_entry *h, void *inf)
+{
+  struct bfd_link_info *info = (struct bfd_link_info *) inf;
+  struct loongarch_elf_link_hash_table *htab =
+    loongarch_elf_hash_table (info);
+
+  if (h->root.type == bfd_link_hash_indirect)
+    return true;
+  if (h->type == STT_GNU_IFUNC && h->def_regular)
+    return true;
+  if (h->got.refcount <= 0)
+    return true;
+  if (loongarch_elf_hash_entry (h)->tls_type
+      & (GOT_TLS_GD | GOT_TLS_IE | GOT_TLS_GDESC))
+    return true;
+  if (!bfd_link_pic (info))
+    return true;
+
+  /* On LoongArch a GOT entry for undefined weak symbol is never relocated
+     with R_LARCH_RELATIVE: we don't have -z dynamic-undefined-weak, thus
+     the GOT entry is either const 0 (if the symbol is LARCH_REF_LOCAL) or
+     relocated with R_LARCH_NN (otherwise).  */
+  if (h->root.type == bfd_link_hash_undefweak)
+    return true;
+
+  if (!LARCH_REF_LOCAL (info, h))
+    return true;
+  if (bfd_is_abs_symbol (&h->root))
+    return true;
+
+  if (!record_relr (htab, htab->elf.sgot, h->got.offset,
+		    htab->elf.srelgot))
+    return false;
+
+  return true;
+}
+
+static bool
+record_relr_non_got_relocs (bfd *input_bfd, struct bfd_link_info *info,
+			    asection *sec)
+{
+  asection *sreloc;
+  struct loongarch_elf_link_hash_table *htab;
+  Elf_Internal_Rela *relocs, *rel, *rel_end;
+  Elf_Internal_Shdr *symtab_hdr;
+  struct elf_link_hash_entry **sym_hashes;
+
+  if (!bfd_link_pic (info))
+    return true;
+  if (sec->reloc_count == 0)
+    return true;
+  if ((sec->flags & (SEC_RELOC | SEC_ALLOC | SEC_DEBUGGING))
+       != (SEC_RELOC | SEC_ALLOC))
+    return true;
+  if (sec->alignment_power == 0)
+    return true;
+  if (discarded_section (sec))
+    return true;
+
+  sreloc = elf_section_data (sec)->sreloc;
+  if (sreloc == NULL)
+    return true;
+
+  htab = loongarch_elf_hash_table (info);
+  symtab_hdr = &elf_symtab_hdr (input_bfd);
+  sym_hashes = elf_sym_hashes (input_bfd);
+  relocs = _bfd_elf_link_info_read_relocs (input_bfd, info, sec, NULL,
+					   NULL, info->keep_memory);
+  BFD_ASSERT (relocs != NULL);
+  rel_end = relocs + sec->reloc_count;
+  for (rel = relocs; rel < rel_end; rel++)
+    {
+      unsigned r_symndx = ELFNN_R_SYM (rel->r_info);
+      struct elf_link_hash_entry *h = NULL;
+      asection *def_sec = NULL;
+
+      if (ELFNN_R_TYPE (rel->r_info) != R_LARCH_NN
+	  || rel->r_offset % 2 != 0)
+	continue;
+
+      /* The logical below must match loongarch_elf_relocate_section.  */
+      if (r_symndx < symtab_hdr->sh_info)
+	{
+	  /* A local symbol.  */
+	  Elf_Internal_Sym *isym;
+	  isym = bfd_sym_from_r_symndx (&htab->elf.sym_cache, input_bfd,
+					r_symndx);
+	  BFD_ASSERT(isym != NULL);
+
+	  /* Local STT_GNU_IFUNC symbol uses R_LARCH_IRELATIVE for
+	     R_LARCH_NN, not R_LARCH_RELATIVE.  */
+	  if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+	    continue;
+	  def_sec = bfd_section_from_elf_index (input_bfd, isym->st_shndx);
+	}
+      else
+	{
+	  h = sym_hashes[r_symndx - symtab_hdr->sh_info];
+	  while (h->root.type == bfd_link_hash_indirect
+		 || h->root.type == bfd_link_hash_warning)
+	    h = (struct elf_link_hash_entry *) h->root.u.i.link;
+
+	  /* Filter out symbols that cannot have a relative reloc.  */
+	  if (h->dyn_relocs == NULL)
+	    continue;
+	  if (bfd_is_abs_symbol (&h->root))
+	    continue;
+	  if (h->type == STT_GNU_IFUNC)
+	    continue;
+
+	  if (h->root.type == bfd_link_hash_defined
+	      || h->root.type == bfd_link_hash_defweak)
+	    def_sec = h->root.u.def.section;
+
+	  /* On LoongArch an R_LARCH_NN against undefined weak symbol
+	     is never converted to R_LARCH_RELATIVE: we don't have
+	     -z dynamic-undefined-weak, thus the reloc is either removed
+	     (if the symbol is LARCH_REF_LOCAL) or kept (otherwise).  */
+	  if (h->root.type == bfd_link_hash_undefweak)
+	    continue;
+
+	  if (!LARCH_REF_LOCAL (info, h))
+	    continue;
+	}
+
+      if (!def_sec || discarded_section (def_sec))
+	continue;
+
+      if (!record_relr (htab, sec, rel->r_offset, sreloc))
+	return false;
+    }
+
+  return true;
+}
+
+static int
+cmp_relr_addr (const void *p, const void *q)
+{
+  const bfd_vma *a = p, *b = q;
+  return (*a > *b) - (*a < *b);
+}
+
+static bool
+sort_relr (struct bfd_link_info *info,
+	   struct loongarch_elf_link_hash_table *htab)
+{
+  if (htab->relr_count == 0)
+    return true;
+
+  bfd_vma *addr = htab->relr_sorted;
+  if (!addr)
+    {
+      addr = bfd_malloc (htab->relr_count * sizeof (*addr));
+      if (!addr)
+	return false;
+      htab->relr_sorted = addr;
+    }
+
+  for (bfd_size_type i = 0; i < htab->relr_count; i++)
+    {
+      bfd_vma off = _bfd_elf_section_offset (info->output_bfd, info,
+					     htab->relr[i].sec,
+					     htab->relr[i].off);
+      addr[i] = htab->relr[i].sec->output_section->vma
+		+ htab->relr[i].sec->output_offset + off;
+    }
+  qsort(addr, htab->relr_count, sizeof (*addr), cmp_relr_addr);
+  return true;
+}
+
+static bool
+loongarch_elf_size_relative_relocs (struct bfd_link_info *info,
+				    bool *need_layout)
+{
+  struct loongarch_elf_link_hash_table *htab =
+    loongarch_elf_hash_table (info);
+  asection *srelrdyn = htab->elf.srelrdyn;
+
+  *need_layout = false;
+
+  if (!sort_relr (info, htab))
+    return false;
+  bfd_vma *addr = htab->relr_sorted;
+
+  BFD_ASSERT (srelrdyn != NULL);
+  bfd_size_type oldsize = srelrdyn->size;
+  srelrdyn->size = 0;
+  for (bfd_size_type i = 0; i < htab->relr_count; )
+    {
+      bfd_vma base = addr[i];
+      i++;
+      srelrdyn->size += NN / 8;
+      base += NN / 8;
+      while (1)
+	{
+	  bfd_size_type start_i = i;
+	  while (i < htab->relr_count
+		 && addr[i] - base < (NN - 1) * (NN / 8)
+		 && (addr[i] - base) % (NN / 8) == 0)
+	    i++;
+	  if (i == start_i)
+	    break;
+	  srelrdyn->size += NN / 8;
+	  base += (NN - 1) * (NN / 8);
+	}
+    }
+  if (srelrdyn->size != oldsize)
+    {
+      *need_layout = true;
+      /* Stop after a few iterations in case the layout does not converge,
+	 but we can only stop when the size would shrink (and pad the
+	 spare space with 1.  */
+      if (htab->relr_layout_iter++ > 5 && srelrdyn->size < oldsize)
+	{
+	  srelrdyn->size = oldsize;
+	  *need_layout = false;
+	}
+    }
+  return true;
+}
+
+static bool
+loongarch_elf_finish_relative_relocs (struct bfd_link_info *info)
+{
+  struct loongarch_elf_link_hash_table *htab =
+    loongarch_elf_hash_table (info);
+  asection *srelrdyn = htab->elf.srelrdyn;
+  bfd *dynobj = htab->elf.dynobj;
+
+  if (!srelrdyn || srelrdyn->size == 0)
+    return true;
+
+  srelrdyn->contents = bfd_alloc (dynobj, srelrdyn->size);
+  if (!srelrdyn->contents)
+    return false;
+
+  bfd_vma *addr = htab->relr_sorted;
+  bfd_byte *loc = srelrdyn->contents;
+  for (bfd_size_type i = 0; i < htab->relr_count; )
+    {
+      bfd_vma base = addr[i];
+      i++;
+      bfd_put_NN (dynobj, base, loc);
+      loc += NN / 8;
+      base += NN / 8;
+      while (1)
+	{
+	  uintNN_t bits = 0;
+	  while (i < htab->relr_count)
+	    {
+	      bfd_vma delta = addr[i] - base;
+	      if (delta >= (NN - 1) * (NN / 8) || delta % (NN / 8) != 0)
+		break;
+	      bits |= (uintNN_t) 1 << (delta / (NN / 8));
+	      i++;
+	    }
+	  if (bits == 0)
+	    break;
+	  bfd_put_NN (dynobj, (bits << 1) | 1, loc);
+	  loc += NN / 8;
+	  base += (NN - 1) * (NN / 8);
+	}
+    }
+
+  free (addr);
+  htab->relr_sorted = NULL;
+
+  /* Pad any excess with 1's, a do-nothing encoding.  */
+  while (loc < srelrdyn->contents + srelrdyn->size)
+    {
+      bfd_put_NN (dynobj, 1, loc);
+      loc += NN / 8;
+    }
+
+  return true;
+}
+
 static bool
 loongarch_elf_late_size_sections (bfd *output_bfd,
 				  struct bfd_link_info *info)
@@ -2012,6 +2424,24 @@  loongarch_elf_late_size_sections (bfd *output_bfd,
       && (htab->elf.splt == NULL || htab->elf.splt->size == 0))
     htab->elf.sgotplt->size = 0;
 
+  if (info->enable_dt_relr && !bfd_link_relocatable (info))
+    {
+      elf_link_hash_traverse (&htab->elf, record_relr_dyn_got_relocs, info);
+
+      for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link.next)
+	{
+	  if (!is_loongarch_elf (ibfd))
+	    continue;
+
+	  for (s = ibfd->sections; s != NULL; s = s->next)
+	    if (!record_relr_non_got_relocs (ibfd, info, s))
+	      return false;
+
+	  if (!record_relr_local_got_relocs (ibfd, info))
+	    return false;
+	}
+    }
+
   /* The check_relocs and adjust_dynamic_symbol entry points have
      determined the sizes of the various dynamic sections.  Allocate
      memory for them.  */
@@ -2036,6 +2466,14 @@  loongarch_elf_late_size_sections (bfd *output_bfd,
 	      s->reloc_count = 0;
 	    }
 	}
+      else if (s == htab->elf.srelrdyn && htab->relr_count == 0)
+	{
+	  /* Remove .relr.dyn based on relr_count, not size, since
+	     it is not sized yet.  */
+	    s->flags |= SEC_EXCLUDE;
+	  /* Allocate contents later.  */
+	    continue;
+	}
       else
 	{
 	  /* It's not one of our sections.  */
@@ -2968,6 +3406,17 @@  loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 			info, input_bfd, input_section, rel, howto,
 			bfd_reloc_notsupported, is_undefweak, name, msg);
 		    }
+		  else if (info->enable_dt_relr
+			   && (ELFNN_R_TYPE (outrel.r_info)
+			       == R_LARCH_RELATIVE)
+			   && input_section->alignment_power != 0
+			   && rel->r_offset % 2 == 0)
+		    /* Don't emit a relative relocation that is packed,
+		       only apply the addend (as if we are applying the
+		       original R_LARCH_NN reloc in a PDE).  */
+		    r = perform_relocation (rel, input_section, howto,
+					    relocation, input_bfd,
+					    contents);
 		  else
 		    loongarch_elf_append_rela (output_bfd, sreloc,
 					       &outrel);
@@ -3695,7 +4144,7 @@  loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 		  got_off = local_got_offsets[r_symndx] & (~(bfd_vma)1);
 		  if ((local_got_offsets[r_symndx] & 1) == 0)
 		    {
-		      if (bfd_link_pic (info))
+		      if (bfd_link_pic (info) && !info->enable_dt_relr)
 			{
 			  Elf_Internal_Rela rela;
 			  rela.r_offset = sec_addr (got) + got_off;
@@ -4107,6 +4556,14 @@  loongarch_relax_delete_bytes (bfd *abfd,
   unsigned int sec_shndx = _bfd_elf_section_from_bfd_section (abfd, sec);
   struct bfd_elf_section_data *data = elf_section_data (sec);
   bfd_byte *contents = data->this_hdr.contents;
+  struct relr_entry *relr =
+    ((struct loongarch_elf_section_data *)elf_section_data (sec))->relr;
+  struct loongarch_elf_link_hash_table *htab =
+    loongarch_elf_hash_table (link_info);
+  struct relr_entry *relr_end = NULL;
+
+  if (htab->relr_count)
+    relr_end = htab->relr + htab->relr_count;
 
   /* Actually delete the bytes.  */
   sec->size -= count;
@@ -4119,6 +4576,11 @@  loongarch_relax_delete_bytes (bfd *abfd,
     if (data->relocs[i].r_offset > addr && data->relocs[i].r_offset < toaddr)
       data->relocs[i].r_offset -= count;
 
+  /* Likewise for relative relocs to be packed into .relr.  */
+  for (; relr && relr < relr_end && relr->sec == sec; relr++)
+    if (relr->off > addr && relr->off < toaddr)
+      relr->off -= count;
+
   /* Adjust the local symbols defined in this section.  */
   for (i = 0; i < symtab_hdr->sh_info; i++)
     {
@@ -5207,9 +5669,18 @@  loongarch_elf_finish_dynamic_symbol (bfd *output_bfd,
       else if (bfd_link_pic (info) && LARCH_REF_LOCAL (info, h))
 	{
 	  asection *sec = h->root.u.def.section;
+	  bfd_vma linkaddr = h->root.u.def.value + sec->output_section->vma
+			     + sec->output_offset;
+
+	  /* Don't emit relative relocs if they are packed, but we need
+	     to write the addend (link-time addr) into the GOT then.  */
+	  if (info->enable_dt_relr)
+	    {
+	      bfd_put_NN (output_bfd, linkaddr, sgot->contents + off);
+	      goto skip_got_reloc;
+	    }
 	  rela.r_info = ELFNN_R_INFO (0, R_LARCH_RELATIVE);
-	  rela.r_addend = (h->root.u.def.value + sec->output_section->vma
-			   + sec->output_offset);
+	  rela.r_addend = linkaddr;
 	}
       else
 	{
@@ -5220,6 +5691,7 @@  loongarch_elf_finish_dynamic_symbol (bfd *output_bfd,
 
       loongarch_elf_append_rela (output_bfd, srela, &rela);
     }
+skip_got_reloc:
 
   /* Mark some specially defined symbols as absolute.  */
   if (h == htab->elf.hdynamic || h == htab->elf.hgot || h == htab->elf.hplt)
@@ -5673,6 +6145,10 @@  elf_loongarch64_hash_symbol (struct elf_link_hash_entry *h)
 #define elf_backend_grok_psinfo loongarch_elf_grok_psinfo
 #define elf_backend_hash_symbol elf_loongarch64_hash_symbol
 #define bfd_elfNN_bfd_relax_section loongarch_elf_relax_section
+#define elf_backend_size_relative_relocs loongarch_elf_size_relative_relocs
+#define elf_backend_finish_relative_relocs \
+  loongarch_elf_finish_relative_relocs
+#define bfd_elfNN_new_section_hook loongarch_elf_new_section_hook
 
 #define elf_backend_dtrel_excludes_plt 1
 
diff --git a/binutils/testsuite/lib/binutils-common.exp b/binutils/testsuite/lib/binutils-common.exp
index f0136577b6c..ce7413a77e5 100644
--- a/binutils/testsuite/lib/binutils-common.exp
+++ b/binutils/testsuite/lib/binutils-common.exp
@@ -474,6 +474,7 @@  proc supports_dt_relr {} {
 	  || [istarget i?86-*-*]
 	  || [istarget powerpc64*-*-*]
 	  || [istarget aarch64*-*-*])
+	  || [istarget loongarch64*-*-*]
 	 && ([istarget *-*-linux*]
 	     || [istarget *-*-gnu*]) } {
 	return 1
diff --git a/ld/emulparams/elf64loongarch.sh b/ld/emulparams/elf64loongarch.sh
index d7b2229e213..8c805da987e 100644
--- a/ld/emulparams/elf64loongarch.sh
+++ b/ld/emulparams/elf64loongarch.sh
@@ -1,4 +1,5 @@ 
 source_sh ${srcdir}/emulparams/elf64loongarch-defs.sh
+source_sh ${srcdir}/emulparams/dt-relr.sh
 OUTPUT_FORMAT="elf64-loongarch"
 
 case "$target" in