[PATCH v3] LoongArch: Fix relaxation alignment with ld -r (PR 33236)

mengqinggang <[email protected]>
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
LoongArch has the same issue as RISC-V for PR 33236 [1].

Section alignment can't be adjusted for objects generated by ld -r.
If previous sections are relaxed, the subsequent section maybe misaligned.

To fix this, add an align section and an align relocation before each
section when ld -r. And change the section alignment to 4 to disable
the default section start address calculation.

ld.lld has fixed this issue in the following two patches [2] [3].

[1] https://sourceware.org/bugzilla/show_bug.cgi?id=33236
[2] https://github.com/llvm/llvm-project/pull/151639
[3] https://github.com/llvm/llvm-project/pull/198147
---
v2 -> v3:
- Delete redundant comment code.

v1 -> v2:
- Remove changes in ldlang.c.
- Stop ldlang.c:size_input_section padding by changing
  the input section alignment_power.

 bfd/elfnn-loongarch.c                         | 150 +++++++++++++++
 bfd/elfxx-loongarch.h                         |  10 +
 ld/emultempl/loongarchelf.em                  | 180 ++++++++++++++++++
 .../ld-loongarch-elf/relax-align-ld-r.d       |   9 +
 .../ld-loongarch-elf/relax-align-ld-r.s       |  12 ++
 ld/testsuite/ld-loongarch-elf/relax.exp       |   1 +
 6 files changed, 362 insertions(+)
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s

diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index b7af2041238..91b5d8d4d76 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -7054,6 +7054,156 @@ elf_loongarch64_hash_symbol (struct elf_link_hash_entry *h)
   return _bfd_elf_hash_symbol (h);
 }
 
+/* Write nops to align section.  */
+
+static bool
+loongarch_build_align_nops (asection *section, asection *align_section)
+{
+  if (section->output_section == NULL
+      || align_section->output_section == NULL)
+    {
+      _bfd_error_handler (_("Could not assign to an output section."));
+      return false;
+    }
+
+  bfd_byte *loc = align_section->contents;
+  for (bfd_size_type i = 0; i < (align_section->size / 4); i++)
+    {
+      bfd_putl32 (LARCH_NOP, loc);
+      loc += 4;
+    }
+
+  return true;
+}
+
+/* Emit an align relocation and a related undefined symbol.  */
+
+static bool
+loongarch_add_align_relocs (asection *align_sec, bfd* align_bfd)
+{
+  elf_backend_data *bed = get_elf_backend_data (align_bfd);
+
+  if (elf_section_data (align_sec)->relocs == NULL)
+    {
+      align_sec->reloc_count = 1;
+      align_sec->flags |= SEC_RELOC;
+
+      Elf_Internal_Rela *rela = (Elf_Internal_Rela *)
+	bfd_malloc (sizeof (Elf_Internal_Rela));
+      if (rela == NULL)
+	return false;
+
+      rela->r_offset = 0;
+      rela->r_addend = align_sec->size;
+      rela->r_info = ELFNN_R_INFO (0, R_LARCH_ALIGN);
+      elf_section_data (align_sec)->relocs = rela;
+
+      Elf_Internal_Shdr *rela_hdr = (Elf_Internal_Shdr *)
+	bfd_zalloc (align_bfd, sizeof (Elf_Internal_Shdr));
+      if (rela_hdr == NULL)
+       return false;
+
+      rela_hdr->sh_size = sizeof (Elf_Internal_Rela);
+      rela_hdr->sh_entsize = sizeof (Elf_Internal_Rela);
+      rela_hdr->sh_type = SHT_RELA;
+      rela_hdr->sh_addralign = (bfd_vma) 1 << bed->s->log_file_align;
+      elf_section_data (align_sec)->rela.hdr = rela_hdr;
+    }
+
+  /* Used in loongarch_elf_relax_section for align relocations.  */
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (align_bfd);
+  if (symtab_hdr->contents == NULL)
+    {
+      Elf_Internal_Sym *sym = (Elf_Internal_Sym *)
+	bfd_zmalloc (sizeof (Elf_Internal_Sym));
+      if (sym == NULL)
+	return false;
+
+      /* Match if (r_symndx < symtab_hdr->sh_info) branch in
+	 loongarch_elf_relocate_section to avoid find link failed.  */
+      symtab_hdr->sh_info = 1;
+      symtab_hdr->contents = (unsigned char *) sym;
+    }
+
+  return true;
+}
+
+/* Section name for aligns is the associated section name plus this
+   string.  */
+#define ALIGN_SUFFIX ".align"
+
+/* Determine and set the size of the align section for a final link.  */
+
+bool
+elfNN_loongarch_size_aligns (bfd *output_bfd,
+			     bfd *align_bfd,
+			     struct bfd_link_info *info,
+			     asection * (*add_align_section)
+					  (const char *, asection *),
+			     void (*layout_sections_again) (void))
+{
+  bool need_laying_out = false;
+  for (bfd *input_bfd = info->input_bfds; input_bfd != NULL;
+       input_bfd = input_bfd->link.next)
+    {
+      asection *sec;
+      asection *align_sec;
+
+      if (!is_loongarch_elf (input_bfd)
+	  || (input_bfd->flags & BFD_LINKER_CREATED) != 0)
+	continue;
+
+      for (sec = input_bfd->sections; sec != NULL; sec = sec->next)
+	{
+	  /* If this section is not a code section, do nothing.  */
+	  if ((sec->flags & SEC_CODE) == 0)
+	    continue;
+
+	  /* If this section is a link-once section that will be
+	     discarded, then don't create any stubs.  */
+	  if (sec->output_section == NULL
+	      || sec->output_section->owner != output_bfd)
+	    continue;
+
+	  if (sec->alignment_power > 2)
+	    {
+	      char *s_name;
+	      size_t namelen;
+	      bfd_size_type len;
+	      namelen = strlen (sec->name);
+	      len = namelen + sizeof (ALIGN_SUFFIX);
+	      s_name = bfd_alloc (align_bfd, len);
+	      if (s_name == NULL)
+		return false;
+	      memcpy (s_name, sec->name, namelen);
+	      memcpy (s_name + namelen, ALIGN_SUFFIX, sizeof (ALIGN_SUFFIX));
+
+	      align_sec = add_align_section (s_name, sec);
+
+	      if (align_sec == NULL)
+		return false;
+
+	      if (! loongarch_build_align_nops (sec, align_sec))
+		return false;
+
+	      if (! loongarch_add_align_relocs (align_sec, align_bfd))
+		return false;
+
+	      /* Change the section alignment to 4 to disable the default
+		 behavior of "dot += alignment_needed" in ldlang.c.  */
+	      bfd_set_section_alignment (sec, 2);
+
+	      need_laying_out = true;
+	    }
+	}
+    }
+
+  if (need_laying_out)
+    layout_sections_again ();
+
+  return true;
+}
+
 #define TARGET_LITTLE_SYM loongarch_elfNN_vec
 #define TARGET_LITTLE_NAME "elfNN-loongarch"
 #define ELF_ARCH bfd_arch_loongarch
diff --git a/bfd/elfxx-loongarch.h b/bfd/elfxx-loongarch.h
index 400b7377ccd..b295b7a9070 100644
--- a/bfd/elfxx-loongarch.h
+++ b/bfd/elfxx-loongarch.h
@@ -45,6 +45,16 @@ bfd_elf64_loongarch_set_data_segment_info (struct bfd_link_info *, int *);
 bfd_byte *
 loongarch_write_unsigned_leb128 (bfd_byte *, size_t, bfd_vma) ATTRIBUTE_HIDDEN;
 
+extern bool elf64_loongarch_size_aligns
+  (bfd *, bfd *, struct bfd_link_info *,
+   struct bfd_section * (*) (const char *, struct bfd_section *),
+   void (*) (void));
+
+extern bool elf32_loongarch_size_aligns
+  (bfd *, bfd *, struct bfd_link_info *,
+   struct bfd_section * (*) (const char *, struct bfd_section *),
+   void (*) (void));
+
 /* TRUE if this is a PLT reference to a local IFUNC.  */
 #define PLT_LOCAL_IFUNC_P(INFO, H) \
   ((H)->dynindx == -1 \
diff --git a/ld/emultempl/loongarchelf.em b/ld/emultempl/loongarchelf.em
index 713331d30fc..901186cd114 100644
--- a/ld/emultempl/loongarchelf.em
+++ b/ld/emultempl/loongarchelf.em
@@ -41,6 +41,10 @@ PARSE_AND_LIST_ARGS_CASES=${PARSE_AND_LIST_ARGS_CASES}'
 '
 
 fragment <<EOF
+
+/* Fake input file for align.  */
+static lang_input_statement_type *align_file;
+
 static void
 larch_elf_before_allocation (void)
 {
@@ -61,6 +65,134 @@ larch_elf_before_allocation (void)
   link_info.relax_pass = 3;
 }
 
+/* Traverse the linker tree to insert the align section
+   before input section.  */
+
+static bool
+hook_in_align (lang_statement_list_type add,
+	       asection *input_section,
+	       lang_statement_union_type **lp)
+{
+  bool ret;
+  lang_statement_union_type *l;
+
+  for (; (l = *lp) != NULL; lp = &l->header.next)
+    {
+      switch (l->header.type)
+	{
+	case lang_constructors_statement_enum:
+	  ret = hook_in_align (add, input_section, &constructor_list.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_output_section_statement_enum:
+	  ret = hook_in_align (add, input_section,
+			       &l->output_section_statement.children.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_wild_statement_enum:
+	  ret = hook_in_align (add, input_section,
+			       &l->wild_statement.children.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_group_statement_enum:
+	  ret = hook_in_align (add, input_section,
+			       &l->group_statement.children.head);
+	  if (ret)
+	    return ret;
+	  break;
+
+	case lang_input_section_enum:
+	  if (l->input_section.section == input_section)
+	    {
+	      /* We've found our section.  Insert the align immediately
+		 before its associated input section.  */
+	      *lp = add.head;
+	      add.head->header.next = l;
+	      return true;
+	    }
+	  break;
+
+	case lang_data_statement_enum:
+	case lang_reloc_statement_enum:
+	case lang_object_symbols_statement_enum:
+	case lang_output_statement_enum:
+	case lang_target_statement_enum:
+	case lang_input_statement_enum:
+	case lang_assignment_statement_enum:
+	case lang_padding_statement_enum:
+	case lang_address_statement_enum:
+	case lang_fill_statement_enum:
+	  break;
+
+	default:
+	  FAIL ();
+	  break;
+	}
+    }
+
+  return false;
+}
+
+/* Create a new align section, and arrange for it to be linked
+   immediately before INPUT_SECTION.  */
+
+static asection *
+elf${ELFSIZE}_loongarch_add_align_section (const char *align_sec_name,
+					   asection *input_section)
+{
+  flagword flags;
+  asection *align_sec;
+  asection *output_section;
+  lang_statement_list_type add_child;
+  lang_output_section_statement_type *os;
+
+  flags = (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_CODE
+	   | SEC_HAS_CONTENTS | SEC_RELOC | SEC_IN_MEMORY | SEC_KEEP);
+  align_sec = bfd_make_section_anyway_with_flags (align_file->the_bfd,
+						  align_sec_name, flags);
+  if (align_sec == NULL)
+    goto err_ret;
+
+  align_sec->veneer = 1;
+  bfd_set_section_alignment (align_sec, 2);
+
+  output_section = input_section->output_section;
+  os = lang_output_section_get (output_section);
+
+  lang_list_init (&add_child);
+  lang_add_section (&add_child, align_sec, NULL, NULL, os);
+
+  if (add_child.head == NULL)
+    goto err_ret;
+
+  align_sec->size = (1 << input_section->alignment_power) - 4;
+  align_sec->contents = bfd_alloc (align_file->the_bfd, align_sec->size);
+  if (align_sec->contents == NULL && align_sec->size != 0)
+    goto err_ret;
+  align_sec->alloced = 1;
+
+  if (hook_in_align (add_child, input_section, &os->children.head))
+    return align_sec;
+
+ err_ret:
+  einfo (_("%X%P: can not make align section: %E\n"));
+  return NULL;
+}
+
+static void
+gldloongarch_layout_sections_again (void)
+{
+  /* If we have changed sizes of the align sections, then we need
+     to recalculate all the section offsets.  */
+  ldelf_map_segments (true);
+}
+
 static void
 gld${EMULATION_NAME}_after_allocation (void)
 {
@@ -78,6 +210,21 @@ gld${EMULATION_NAME}_after_allocation (void)
 	}
     }
 
+  /* If generating a relocatable output file, we have to add align
+     at the start of sections.  */
+  if (align_file != NULL && bfd_link_relocatable (&link_info))
+    {
+      if (! elf${ELFSIZE}_loongarch_size_aligns (link_info.output_bfd,
+			align_file->the_bfd,
+			&link_info,
+			&elf${ELFSIZE}_loongarch_add_align_section,
+			&gldloongarch_layout_sections_again))
+	{
+	  einfo (_("%X%P: can not size align section: %E\n"));
+	  return;
+	}
+    }
+
   /* The program header size of executable file may increase.  */
   if (bfd_get_flavour (link_info.output_bfd) == bfd_target_elf_flavour
       && !bfd_link_relocatable (&link_info))
@@ -101,7 +248,40 @@ gld${EMULATION_NAME}_after_allocation (void)
   ldelf_map_segments (need_layout);
 }
 
+/* This is called before the input files are opened.  We create a new
+   fake input file to hold the align sections.  */
+
+static void
+loongarch_elf_create_output_section_statements (void)
+{
+  if (! bfd_link_relocatable (&link_info))
+    return;
+
+  align_file = lang_add_input_file ("linker aligns",
+				    lang_input_file_is_fake_enum,
+				    NULL);
+  align_file->the_bfd = bfd_create ("linker aligns",
+				    link_info.output_bfd);
+  if (align_file->the_bfd == NULL
+      || ! bfd_set_arch_mach (align_file->the_bfd,
+			      bfd_get_arch (link_info.output_bfd),
+			      bfd_get_mach (link_info.output_bfd)))
+    {
+      fatal (_("%P: can not create BFD: %E\n"));
+      return;
+    }
+
+  align_file->the_bfd->flags |= BFD_LINKER_CREATED;
+
+  Elf_Internal_Ehdr *ehdr = elf_elfheader (align_file->the_bfd);
+  elf_backend_data *bed = get_elf_backend_data (link_info.output_bfd);
+  ehdr->e_ident[EI_CLASS] = bed->s->elfclass;
+
+  ldlang_add_file (align_file);
+}
+
 EOF
 
 LDEMUL_BEFORE_ALLOCATION=larch_elf_before_allocation
 LDEMUL_AFTER_ALLOCATION=gld${EMULATION_NAME}_after_allocation
+LDEMUL_CREATE_OUTPUT_SECTION_STATEMENTS=loongarch_elf_create_output_section_statements
diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
new file mode 100644
index 00000000000..a2d19734a59
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d
@@ -0,0 +1,9 @@
+#as:
+#ld: -r
+#objdump: -Dr
+
+#...
+.*R_LARCH_ALIGN.*
+#...
+.*R_LARCH_ALIGN.*
+#...
diff --git a/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
new file mode 100644
index 00000000000..19555a7148e
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s
@@ -0,0 +1,12 @@
+# Add an align section before text section when ld -r.
+# There will be two R_LARCH_ALIGN relocations after ld -r.
+.text
+  addi.d $a0, $a0, 1
+  call36 func
+  .align 3
+func:
+  addi.d $a0, $a0, 1
+
+# Only add a align section for code section.
+.data
+  .4byte 0x1234
diff --git a/ld/testsuite/ld-loongarch-elf/relax.exp b/ld/testsuite/ld-loongarch-elf/relax.exp
index 31787a93967..bb8429ab515 100644
--- a/ld/testsuite/ld-loongarch-elf/relax.exp
+++ b/ld/testsuite/ld-loongarch-elf/relax.exp
@@ -48,6 +48,7 @@ proc run_partial_linking_align_test {} {
 
 if [istarget loongarch*-*-*] {
   run_dump_test "relax-align-1"
+  run_dump_test "relax-align-ld-r"
 }
 
 if [istarget loongarch64-*-*] {
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.