[PATCH] alpha: reallocate .got contents when relaxation grows a subsection

Matt Turner mattst88@gmail.com
Fri Aug 14 20:25:38 GMT 2026


elf64_alpha_early_size_sections sizes and merges the .got subsections and
then allocates each one's contents buffer.  elf64_alpha_relax_section runs
afterwards and calls elf64_alpha_size_got_sections again with may_merge
true.  That re-merge zeroes and recomputes every subsection size, and it
can make a subsection larger than it was when its buffer was allocated.
The comment there asserts that "relaxation only shrinks the table", which
is true of the table as a whole but not of an individual subsection: a
merge moves entries into one subsection and empties another.

Relaxation is not limited to --relax links.  ld/emultempl/alphaelf.em
enables it for -O as well, so an ordinary distribution build using
-Wl,-O1 takes this path.

When a subsection grows, elf64_alpha_relocate_section writes GOT slots past
the end of the allocated buffer, corrupting whatever objalloc placed after
it.  Linking a large C++ shared library with -Bsymbolic or
-Bsymbolic-functions aborts ld with a glibc "free(): invalid next size", or
produces a library whose .rela.dyn is corrupt and which faults at run time
with a GOT slot holding an unrelated pointer.  In one instance a .got
allocated at 57664 bytes grew to 64840, still under the 64K cap so nothing
diagnosed it, and 1796 slot writes fell outside the buffer.

Record the size actually allocated, and grow the buffer from
elf64_alpha_relax_section when the recomputed size exceeds it.  Nothing has
been written into the got that early, so the new buffer needs no copy; a
subsection that was empty when early_size_sections ran has no buffer at all
and is handled by the same path.

Route the got slot writes through a new alpha_got_slot, which asserts that
the slot lies inside the allocated buffer.  The assertion does not prevent
the write, but it turns a silent overrun into a reported one for whatever
resizes the got next.

The test needs the two subsections to be unmergeable when they are first
sized and mergeable once relaxation has dropped some entries, so it pairs
an object holding 8000 entries against preemptible symbols with one whose
250 entries include 150 that relaxation removes.  Without the fix it
reproduces both symptoms: the assertion fires and glibc aborts the link.

	* elf64-alpha.c (struct alpha_elf_obj_tdata): Add got_alloced.
	(alpha_got_slot): New function.
	(elf64_alpha_realloc_got_contents): New function.
	(elf64_alpha_early_size_sections): Record the allocated size.
	(elf64_alpha_relax_section): Reallocate .got contents that have
	outgrown their buffer.  Correct stale comment.
	(elf64_alpha_relocate_section): Use alpha_got_slot.
	(elf64_alpha_finish_dynamic_symbol): Likewise.

ld/
	* testsuite/ld-alpha/got-realloc-a.s: New test.
	* testsuite/ld-alpha/got-realloc-b.s: New test.
	* testsuite/ld-alpha/got-realloc.rd: New test.
	* testsuite/ld-alpha/alpha.exp: Run it.
---
 bfd/elf64-alpha.c                     | 77 +++++++++++++++++++++++----
 ld/testsuite/ld-alpha/alpha.exp       |  5 ++
 ld/testsuite/ld-alpha/got-realloc-a.s | 22 ++++++++
 ld/testsuite/ld-alpha/got-realloc-b.s | 46 ++++++++++++++++
 ld/testsuite/ld-alpha/got-realloc.rd  |  3 ++
 5 files changed, 144 insertions(+), 9 deletions(-)
 create mode 100644 ld/testsuite/ld-alpha/got-realloc-a.s
 create mode 100644 ld/testsuite/ld-alpha/got-realloc-b.s
 create mode 100644 ld/testsuite/ld-alpha/got-realloc.rd

diff --git a/bfd/elf64-alpha.c b/bfd/elf64-alpha.c
index 10175882d88..775d3bcad8f 100644
--- a/bfd/elf64-alpha.c
+++ b/bfd/elf64-alpha.c
@@ -330,6 +330,11 @@ struct alpha_elf_obj_tdata
   /* For every got, this is the section.  */
   asection *got;
 
+  /* For every got, the number of bytes actually allocated for ->got's
+     contents.  Relaxation re-runs the got sizing, which can grow a
+     subsection after its contents have been allocated.  */
+  bfd_size_type got_alloced;
+
   /* For every got, this is it's total number of words.  */
   int total_got_size;
 
@@ -1122,6 +1127,22 @@ elf64_alpha_info_to_howto (bfd *abfd, arelent *cache_ptr,
 #define alpha_got_entry_size(r_type) \
   (r_type == R_ALPHA_TLSGD || r_type == R_ALPHA_TLSLDM ? 16 : 8)
 
+/* Return the address of the word at OFF bytes into GOTENT's got slot.
+   The got is sized more than once: relaxation re-runs the sizing and can
+   grow a subsection after elf64_alpha_early_size_sections has allocated its
+   contents.  Check that the slot really lies inside the buffer we have,
+   rather than silently scribbling past the end of it.  */
+
+static bfd_byte *
+alpha_got_slot (struct alpha_elf_got_entry *gotent, unsigned int off)
+{
+  struct alpha_elf_obj_tdata *td = alpha_elf_tdata (gotent->gotobj);
+
+  BFD_ASSERT (gotent->got_offset >= 0);
+  BFD_ASSERT ((bfd_vma) gotent->got_offset + off + 8 <= td->got_alloced);
+  return td->got->contents + gotent->got_offset + off;
+}
+
 /* This is PT_TLS segment p_vaddr.  */
 #define alpha_get_dtprel_base(info) \
   (elf_hash_table (info)->tls_sec->vma)
@@ -2483,6 +2504,37 @@ elf64_alpha_size_got_sections (struct bfd_link_info *info,
   return true;
 }
 
+/* Relaxation re-runs the got sizing, and re-merging there can make a
+   subsection larger than it was when elf64_alpha_early_size_sections
+   allocated its contents.  Grow the buffers to match, rather than writing
+   off the end of them.  Nothing has been written into the got yet, so
+   there is nothing to preserve; a subsection that was empty when
+   early_size_sections ran has no buffer at all.  */
+
+static bool
+elf64_alpha_realloc_got_contents (struct bfd_link_info *info)
+{
+  bfd *i;
+
+  for (i = alpha_elf_hash_table (info)->got_list;
+       i != NULL;
+       i = alpha_elf_tdata (i)->got_link_next)
+    {
+      asection *s = alpha_elf_tdata (i)->got;
+
+      if (s->size > alpha_elf_tdata (i)->got_alloced)
+	{
+	  s->contents = (bfd_byte *) bfd_zalloc (i, s->size);
+	  if (s->contents == NULL)
+	    return false;
+	  s->alloced = 1;
+	  alpha_elf_tdata (i)->got_alloced = s->size;
+	}
+    }
+
+  return true;
+}
+
 static bool
 elf64_alpha_size_plt_section_1 (struct alpha_elf_link_hash_entry *h,
 				void * data)
@@ -2586,6 +2638,7 @@ elf64_alpha_early_size_sections (struct bfd_link_info *info)
 	  if (s->contents == NULL)
 	    return false;
 	  s->alloced = 1;
+	  alpha_elf_tdata (i)->got_alloced = s->size;
 	}
     }
 
@@ -3726,12 +3779,18 @@ elf64_alpha_relax_section (bfd *abfd, asection *sec,
       htab->relax_trip = link_info->relax_trip;
 
       /* This should never fail after the initial round, since the only error
-	 is GOT overflow, and relaxation only shrinks the table.  However, we
-	 may only merge got sections during the first pass.  If we merge
-	 sections after we've created GPREL relocs, the GP for the merged
-	 section backs up which may put the relocs out of range.  */
+	 is GOT overflow, and relaxation only shrinks the table overall.
+	 However, we may only merge got sections during the first pass.  If
+	 we merge sections after we've created GPREL relocs, the GP for the
+	 merged section backs up which may put the relocs out of range.  */
       if (!elf64_alpha_size_got_sections (link_info, relax_pass == 0))
 	abort ();
+
+      /* A re-merge can still grow an individual subsection, even though
+	 the table as a whole shrinks, so the contents allocated by
+	 elf64_alpha_early_size_sections may no longer be big enough.  */
+      if (!elf64_alpha_realloc_got_contents (link_info))
+	return false;
       if (elf_hash_table (link_info)->dynamic_sections_created)
 	{
 	  elf64_alpha_size_plt_section (link_info);
@@ -4330,7 +4389,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	      gotent->reloc_done = 1;
 
 	      bfd_put_64 (info->output_bfd, value,
-			  sgot->contents + gotent->got_offset);
+			  alpha_got_slot (gotent, 0));
 
 	      /* If the symbol has been forced local, output a
 		 RELATIVE reloc, otherwise it will be handled in
@@ -4581,7 +4640,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	      /* Note that the module index for the main program is 1.  */
 	      bfd_put_64 (info->output_bfd,
 			  !bfd_link_pic (info) && !dynamic_symbol_p,
-			  sgot->contents + gotent->got_offset);
+			  alpha_got_slot (gotent, 0));
 
 	      /* If the symbol has been forced local, output a
 		 DTPMOD64 reloc, otherwise it will be handled in
@@ -4600,7 +4659,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		  value -= dtp_base;
 		}
 	      bfd_put_64 (info->output_bfd, value,
-			  sgot->contents + gotent->got_offset + 8);
+			  alpha_got_slot (gotent, 8));
 	    }
 
 	  value = (sgot->output_section->vma
@@ -4693,7 +4752,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		    }
 		}
 	      bfd_put_64 (info->output_bfd, value,
-			  sgot->contents + gotent->got_offset);
+			  alpha_got_slot (gotent, 0));
 	    }
 
 	  value = (sgot->output_section->vma
@@ -4838,7 +4897,7 @@ elf64_alpha_finish_dynamic_symbol (struct bfd_link_info *info,
 
 	    /* Fill in the entry in the .got.  */
 	    bfd_put_64 (info->output_bfd, plt_addr,
-			sgot->contents + gotent->got_offset);
+			alpha_got_slot (gotent, 0));
 	  }
     }
   else if (alpha_elf_dynamic_symbol_p (h, info))
diff --git a/ld/testsuite/ld-alpha/alpha.exp b/ld/testsuite/ld-alpha/alpha.exp
index f6b16918927..af5d94e24a8 100644
--- a/ld/testsuite/ld-alpha/alpha.exp
+++ b/ld/testsuite/ld-alpha/alpha.exp
@@ -61,6 +61,11 @@ set alphatests {
      {emptygot.s}
      {{nm "-n" emptygot.nm}}
      "emptygot"}
+    {"got realloc after relax"
+     "-shared -relax -melf64alpha" ""
+     "" {got-realloc-a.s got-realloc-b.s}
+     {{readelf -SW got-realloc.rd}}
+     "got-realloc.so"}
 }
 
 # Not implemented yet
diff --git a/ld/testsuite/ld-alpha/got-realloc-a.s b/ld/testsuite/ld-alpha/got-realloc-a.s
new file mode 100644
index 00000000000..a4ec25ed84c
--- /dev/null
+++ b/ld/testsuite/ld-alpha/got-realloc-a.s
@@ -0,0 +1,22 @@
+/* 8000 got entries against preemptible symbols, so relaxation cannot
+   remove any of them.  That is 64000 bytes, just under MAX_GOT_SIZE, so
+   this object keeps its own got subsection and gets 64000 bytes of
+   contents allocated for it by elf64_alpha_early_size_sections.  */
+
+	.text
+	.globl	afunc
+	.ent	afunc
+afunc:
+	ldgp	$29,0($27)
+	.prologue 1
+	.irpc	w,01234567
+	.irpc	x,0123456789
+	.irpc	y,0123456789
+	.irpc	z,0123456789
+	ldq	$1,g\w\x\y\z($29)	!literal
+	.endr
+	.endr
+	.endr
+	.endr
+	ret	$31,($26),1
+	.end	afunc
diff --git a/ld/testsuite/ld-alpha/got-realloc-b.s b/ld/testsuite/ld-alpha/got-realloc-b.s
new file mode 100644
index 00000000000..8945ca58cbc
--- /dev/null
+++ b/ld/testsuite/ld-alpha/got-realloc-b.s
@@ -0,0 +1,46 @@
+/* 250 got entries, of which relaxation removes the 150 that are only used
+   by a call to a local function.  Adding this object's 250 entries to the
+   8000 in got-realloc-a.s exceeds MAX_GOT_SIZE, so the two subsections
+   cannot be merged when they are first sized.  Once relaxation has dropped
+   the 150 they fit, and re-merging grows the first subsection from the
+   64000 bytes allocated for it to 64800.  */
+
+	.macro	mkcall name, seq
+	ldq	$27,\name($29)		!literal!\seq
+	jsr	$26,($27),\name		!lituse_jsr!\seq
+	.endm
+
+	.text
+
+	.irpc	x,012
+	.irpc	y,0123456789
+	.irpc	z,01234
+	.ent	lf\x\y\z
+lf\x\y\z:
+	ldgp	$29,0($27)
+	.prologue 1
+	ret	$31,($26),1
+	.end	lf\x\y\z
+	.endr
+	.endr
+	.endr
+
+	.globl	bfunc
+	.ent	bfunc
+bfunc:
+	ldgp	$29,0($27)
+	.prologue 1
+	.irpc	x,012
+	.irpc	y,0123456789
+	.irpc	z,01234
+	mkcall	lf\x\y\z, 1\x\y\z
+	.endr
+	.endr
+	.endr
+	.irpc	x,0123456789
+	.irpc	y,0123456789
+	ldq	$1,h\x\y($29)		!literal
+	.endr
+	.endr
+	ret	$31,($26),1
+	.end	bfunc
diff --git a/ld/testsuite/ld-alpha/got-realloc.rd b/ld/testsuite/ld-alpha/got-realloc.rd
new file mode 100644
index 00000000000..75f75262eb1
--- /dev/null
+++ b/ld/testsuite/ld-alpha/got-realloc.rd
@@ -0,0 +1,3 @@
+#...
+ +\[ *[0-9]+\] \.got +PROGBITS +[0-9a-f]+ +[0-9a-f]+ 00fd20 .*
+#pass
-- 
2.54.0



More information about the Binutils mailing list