LoongArch: Fix relaxation alignment with ld -r (PR 33236)

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
This commit is contained in:
mengqinggang 2026-05-22 09:38:49 +08:00
parent 7858cadd33
commit 8bf4b69718
6 changed files with 355 additions and 0 deletions

View 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

View 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 \

View 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

View file

@ -0,0 +1,9 @@
#as:
#ld: -r
#objdump: -Dr
#...
.*R_LARCH_ALIGN.*
#...
.*R_LARCH_ALIGN.*
#...

View file

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

View 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-*-*] {