]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
LoongArch: Fix relaxation alignment with ld -r (PR 33236)
authormengqinggang <mengqinggang@loongson.cn>
Fri, 22 May 2026 01:38:49 +0000 (09:38 +0800)
committermengqinggang <mengqinggang@loongson.cn>
Sun, 5 Jul 2026 18:26:54 +0000 (02:26 +0800)
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

bfd/elfnn-loongarch.c
bfd/elfxx-loongarch.h
ld/emultempl/loongarchelf.em
ld/testsuite/ld-loongarch-elf/relax-align-ld-r.d [new file with mode: 0644]
ld/testsuite/ld-loongarch-elf/relax-align-ld-r.s [new file with mode: 0644]
ld/testsuite/ld-loongarch-elf/relax.exp

index b7af20412383d1861bafc542c4528d30202a003d..f4d8d32ff2f4a093ba8a18cbe8e94fae321b2374 100644 (file)
@@ -7054,6 +7054,149 @@ 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 *align_section)
+{
+  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 (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
index 400b7377ccd6d2e779f39c1d2ec291db3b95b899..b295b7a90703c704ec0b864d679c14b957952453 100644 (file)
@@ -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 \
index 713331d30fc02b46994116a720a279951e862515..901186cd114be3b59c7ccb7f2e0552e3a62945b5 100644 (file)
@@ -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 (file)
index 0000000..a2d1973
--- /dev/null
@@ -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 (file)
index 0000000..19555a7
--- /dev/null
@@ -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
index 31787a939677a298e653eb43d2ccb404872a0d49..bb8429ab515023a100ace7d74b8fba348d577ae7 100644 (file)
@@ -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-*-*] {