For ld -r, when an input STT_SECTION symbol is redirected to the output
section symbol, elf_link_input_bfd() folds sec->output_offset into the
RELA addend. LoongArch relaxation does not update these relocation
addends, leaving the relocated target incorrect.
a.s:
.text
call .text
b.s:
.text
call .text
Before the fix, with:
as a.s -o a.o
as b.s -o b.o
ld -r a.o b.o -o ab.o
readelf -rW ab.o reported:
Relocation section .rela.text:
0000000000000000 R_LARCH_CALL36 .text + 0
0000000000000008 R_LARCH_CALL36 .text + 8
During relaxation, shrinking the first call36 sequence from 8 bytes to 4
bytes moves the second call PC from 0x8 to 0x4. Its addend must be
remapped from 0x8 to 0x4 as well. Otherwise the relocation still
targets offset 0x8, adding an extra 0x4 displacement to the second call.
The fix adds loongarch_relax_adjust_reloc_addends() to adjust relocation
addends according to the instructions deleted during relaxation. sec_flg1
marks sections that contain a local STT_SECTION relocation with a nonzero
addend. Only flagged sections call this helper, and it changes addends
only for STT_SECTION symbols.
bfd/
* elfnn-loongarch.c (loongarch_sec_has_nonzero_addend_reloc): Define.
(loongarch_elf_check_relocs): Mark sections containing local STT_SECTION
relocations with nonzero addends.
(loongarch_calc_relaxed_addr): Update comments.
(loongarch_relax_adjust_reloc_addends): New function.
(loongarch_relax_perform_deletes): Return bool and adjust addends
of relaxed local STT_SECTION relocations.
(loongarch_elf_relax_section): Propagate failures from
loongarch_relax_perform_deletes.
ld/
* testsuite/ld-loongarch-elf/relax.exp: Run new tests.
* testsuite/ld-loongarch-elf/relax-stt-section-a.s: New file.
* testsuite/ld-loongarch-elf/relax-stt-section-b.s: New file.
* testsuite/ld-loongarch-elf/relax-stt-section-ld-r.d: New file.
* testsuite/ld-loongarch-elf/relax-stt-section-ld.d: New file.
* testsuite/ld-loongarch-elf/relax-stt-section.ld: New file.
---
bfd/elfnn-loongarch.c | 98 +++++++++++++++++--
.../ld-loongarch-elf/relax-stt-section-a.s | 9 ++
.../ld-loongarch-elf/relax-stt-section-b.s | 9 ++
.../ld-loongarch-elf/relax-stt-section-ld-r.d | 19 ++++
.../ld-loongarch-elf/relax-stt-section-ld.d | 20 ++++
.../ld-loongarch-elf/relax-stt-section.ld | 8 ++
ld/testsuite/ld-loongarch-elf/relax.exp | 2 +
7 files changed, 156 insertions(+), 9 deletions(-)
create mode 100644 ld/testsuite/ld-loongarch-elf/relax-stt-section-a.s
create mode 100644 ld/testsuite/ld-loongarch-elf/relax-stt-section-b.s
create mode 100644 ld/testsuite/ld-loongarch-elf/relax-stt-section-ld-r.d
create mode 100644 ld/testsuite/ld-loongarch-elf/relax-stt-section-ld.d
create mode 100644 ld/testsuite/ld-loongarch-elf/relax-stt-section.ld
@@ -177,6 +177,10 @@ loongarch_elf_new_section_hook (bfd *abfd, asection *sec)
#define loongarch_elf_hash_table(p) \
((struct loongarch_elf_link_hash_table *) ((p)->hash)) \
+/* True if a local STT_SECTION relocation against SEC has a nonzero
+ addend. */
+#define loongarch_sec_has_nonzero_addend_reloc(sec) ((sec)->sec_flg1)
+
/* During linker relaxation, indicates whether the section has already
undergone alignment processing and no more byte deletion is permitted. */
#define loongarch_sec_closed_for_deletion(sec) ((sec)->sec_flg0)
@@ -1186,6 +1190,21 @@ loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
is_abs_symbol = bfd_is_abs_symbol (&h->root);
}
+ /* Mark the target section so relaxation can skip sections without
+ such relocations. */
+ if (r_symndx < symtab_hdr->sh_info
+ && rel->r_addend != 0
+ && r_type != R_LARCH_ALIGN
+ && ELF_ST_TYPE (isym->st_info) == STT_SECTION
+ && isym->st_shndx < elf_numsections (abfd)
+ && elf_elfsections (abfd)[isym->st_shndx] != NULL)
+ {
+ asection *sym_sec
+ = elf_elfsections (abfd)[isym->st_shndx]->bfd_section;
+ if (sym_sec != NULL)
+ loongarch_sec_has_nonzero_addend_reloc (sym_sec) = true;
+ }
+
/* It is referenced by a non-shared object. */
if (h != NULL)
h->ref_regular = 1;
@@ -5056,9 +5075,9 @@ loongarch_calc_relaxed_addr (struct bfd_link_info *info, bfd_vma offset)
struct pending_delete_op *op;
splay_tree_node node;
+ /* A NULL pdops means the byte-deletion stage has passed, so offset
+ is unchanged . */
if (!pdops)
- /* Currently this means we are past the stages where byte deletion could
- possibly happen. */
return offset;
/* Find the op that starts just before the given address. */
@@ -5070,7 +5089,7 @@ loongarch_calc_relaxed_addr (struct bfd_link_info *info, bfd_vma offset)
op = (struct pending_delete_op *)node->value;
/* If offset is inside this op's range, it is actually one of the deleted
- bytes, so the adjusted node->key should be returned in this case. */
+ bytes, return the previous node->value in this case. */
bfd_vma op_end_off = (bfd_vma)node->key + op->size;
if (offset < op_end_off)
{
@@ -5219,7 +5238,62 @@ loongarch_relax_resize_symbol (bfd_size_type *size, bfd_vma orig_value,
}
}
-static void
+/* Adjust relocations that refer to SEC through a local section symbol.
+ The section symbol value is zero, so the offset into SEC is stored in
+ the addend. Such relocations may be in any section of ABFD. */
+static bool
+loongarch_relax_adjust_reloc_addends (bfd *abfd, asection *sec,
+ struct bfd_link_info *link_info)
+{
+ asection *relsec;
+ Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+ Elf_Internal_Sym *local_syms = (Elf_Internal_Sym *) symtab_hdr->contents;
+ unsigned int sec_shndx = _bfd_elf_section_from_bfd_section (abfd, sec);
+
+ for (relsec = abfd->sections; relsec != NULL; relsec = relsec->next)
+ {
+ Elf_Internal_Rela *relocs;
+ unsigned int i;
+
+ if (relsec->reloc_count == 0)
+ continue;
+
+ relocs = elf_section_data (relsec)->relocs;
+ if (relocs == NULL)
+ {
+ /* Keep addend updates for later relaxation passes. */
+ relocs = _bfd_elf_link_read_relocs (abfd, relsec, NULL, NULL,
+ true);
+ if (relocs == NULL)
+ return false;
+ }
+
+ for (i = 0; i < relsec->reloc_count; i++)
+ {
+ Elf_Internal_Rela *rel = relocs + i;
+ if (rel->r_addend == 0)
+ continue;
+
+ unsigned int r_symndx = ELFNN_R_SYM (rel->r_info);
+ unsigned int r_type = ELFNN_R_TYPE (rel->r_info);
+
+ /* R_LARCH_ALIGN stores alignment information in r_addend. */
+ if (r_symndx >= symtab_hdr->sh_info
+ || r_type == R_LARCH_ALIGN)
+ continue;
+
+ Elf_Internal_Sym *sym = local_syms + r_symndx;
+ if (ELF_ST_TYPE (sym->st_info) == STT_SECTION
+ && sym->st_shndx == sec_shndx)
+ rel->r_addend = loongarch_calc_relaxed_addr (link_info,
+ rel->r_addend);
+ }
+ }
+
+ return true;
+}
+
+static bool
loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
struct bfd_link_info *link_info)
{
@@ -5245,7 +5319,7 @@ loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
if (node1 == NULL)
/* No pending delete ops, nothing to do. */
- return;
+ return true;
/* Actually delete the bytes. For each delete op the pointer arithmetics
look like this:
@@ -5293,9 +5367,11 @@ loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
sec->size -= op1->cumulative_offset;
}
- /* Adjust the location of all of the relocs. Note that we need not
- adjust the addends, since all PC-relative references must be against
- symbols, which we will adjust below. */
+ if (loongarch_sec_has_nonzero_addend_reloc (sec))
+ if (! loongarch_relax_adjust_reloc_addends (abfd, sec, link_info))
+ return false;
+
+ /* Adjust the location of all of the relocs. */
for (i = 0; i < sec->reloc_count; i++)
if (data->relocs[i].r_offset < toaddr)
data->relocs[i].r_offset = loongarch_calc_relaxed_addr (
@@ -5374,6 +5450,8 @@ loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
loongarch_relax_resize_symbol (&sym_hash->size, orig_value, pdops);
}
}
+
+ return true;
}
/* Start perform TLS type transition.
@@ -6382,9 +6460,11 @@ loongarch_elf_relax_section (bfd *abfd, asection *sec,
if (pdops)
{
- loongarch_relax_perform_deletes (abfd, sec, info);
+ bool ok = loongarch_relax_perform_deletes (abfd, sec, info);
htab->pending_delete_ops = NULL;
splay_tree_delete (pdops);
+ if (!ok)
+ return false;
}
return true;
new file mode 100644
@@ -0,0 +1,9 @@
+ .text
+ call36 .text
+ call36 .data
+a:
+ call36 a
+ .align 3
+
+.data
+ .4byte 0x123
new file mode 100644
@@ -0,0 +1,9 @@
+ .text
+ call36 .text
+ call36 .data
+b:
+ call36 b
+ .align 3
+
+.data
+ .4byte 0x123
new file mode 100644
@@ -0,0 +1,19 @@
+#name: LoongArch relax STT_SECTION addend
+#source: relax-stt-section-a.s
+#source: relax-stt-section-b.s
+#ld: -r
+#readelf: -rW
+
+#...
+0+4[ \t]+.*R_LARCH_CALL36.*\.text \+ 4
+#...
+0+c[ \t]+.*R_LARCH_CALL36.*\.data \+ 0
+#...
+0+14[ \t]+.*R_LARCH_CALL36.*a \+ 0
+#...
+0+24[ \t]+.*R_LARCH_CALL36.*\.text \+ 24
+#...
+0+2c[ \t]+.*R_LARCH_CALL36.*\.data \+ 4
+#...
+0+34[ \t]+.*R_LARCH_CALL36.*b \+ 0
+#pass
new file mode 100644
@@ -0,0 +1,20 @@
+#name: LoongArch relax STT_SECTION addend executable
+#source: relax-stt-section-a.s
+#source: relax-stt-section-b.s
+#ld: -r
+#ld_after_inputfiles: && cp tmpdir/dump tmpdir/relax-stt-section-ld-r.o && $LD -o tmpdir/dump tmpdir/relax-stt-section-ld-r.o -e 0 -T $srcdir/$subdir/relax-stt-section.ld
+#objdump: -d -M no-aliases
+
+#...
+.*[ \t]+bl[ \t]+.*# 12000000 <a-0x8>
+#...
+.*[ \t]+bl[ \t]+.*# 12010000 <b\+0xffe8>
+#...
+.*[ \t]+bl[ \t]+.*# 12000008 <a>
+#...
+.*[ \t]+bl[ \t]+.*# 12000010 <a\+0x8>
+#...
+.*[ \t]+bl[ \t]+.*# 12010004 <b\+0xffec>
+#...
+.*[ \t]+bl[ \t]+.*# 12000018 <b>
+#pass
new file mode 100644
@@ -0,0 +1,8 @@
+SECTIONS
+{
+ . = 0x12000000;
+ .text : { *(.text) }
+
+ . = 0x12010000;
+ .data : { *(.data) }
+}
@@ -50,6 +50,8 @@ if [istarget loongarch*-*-*] {
run_dump_test "relax-align-1"
run_dump_test "relax-align-ld-r"
run_dump_test "relax-align-ld-r-norelax"
+ run_dump_test "relax-stt-section-ld-r"
+ run_dump_test "relax-stt-section-ld"
}
if [istarget loongarch64-*-*] {