[binutils-gdb] alpha: allocate .got contents after relaxation has sized them
Alan Modra via Binutils-cvs <[email protected]>
| Newsgroups | gmane.comp.gnu.binutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=9335533438ea55497e0bc88e4c0cf3e240337caa commit 9335533438ea55497e0bc88e4c0cf3e240337caa Author: Matt Turner <[email protected]> Date: Wed Aug 26 16:27:01 2026 -0400 alpha: allocate .got contents after relaxation has sized them 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. Rather than resizing the buffers whenever relaxation re-runs the sizing, which would throw away the previous allocation on every relaxation trip, allocate them once from elf64_alpha_final_link, by which point the sizes have settled. Nothing reads or writes the got before then. The .got subsection of the dynobj was allocated a second time by elf64_alpha_late_size_sections, along with the real dynamic sections; that allocation has the same problem and is dropped too. The other sections that loop walks are only shrunk by relaxation, so the space allocated for them there stays big enough. Route the got slot writes through a new alpha_got_slot, which asserts that the subsection has had its contents allocated and that the slot lies inside it. The assertions do not prevent the write, but they turn a future sizing mistake of this kind into a reported one rather than a silent overrun. 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 (alpha_got_slot): New function. (elf64_alpha_early_size_sections): Don't allocate the .got subsection contents here. (elf64_alpha_late_size_sections): Don't allocate .got here either. (elf64_alpha_relax_section): Correct stale comment. (elf64_alpha_final_link): Allocate the .got subsection contents. (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. Diff: --- bfd/elf64-alpha.c | 94 +++++++++++++++++++++-------------- ld/testsuite/ld-alpha/alpha.exp | 5 ++ ld/testsuite/ld-alpha/got-realloc-a.s | 21 ++++++++ ld/testsuite/ld-alpha/got-realloc-b.s | 46 +++++++++++++++++ ld/testsuite/ld-alpha/got-realloc.rd | 3 ++ 5 files changed, 133 insertions(+), 36 deletions(-) diff --git a/bfd/elf64-alpha.c b/bfd/elf64-alpha.c index c43d427989d..8b8a97a4b05 100644 --- a/bfd/elf64-alpha.c +++ b/bfd/elf64-alpha.c @@ -1122,6 +1122,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 repeatedly and its subsections re-merged as relaxation + proceeds, so check that the slot really lies inside the subsection + rather than writing 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 (td->got->contents != NULL); + BFD_ASSERT (gotent->got_offset >= 0); + BFD_ASSERT ((bfd_vma) gotent->got_offset + off + 8 <= td->got->size); + 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) @@ -2561,34 +2577,15 @@ elf64_alpha_size_plt_section (struct bfd_link_info *info) static bool elf64_alpha_early_size_sections (struct bfd_link_info *info) { - bfd *i; - struct alpha_elf_link_hash_table * htab; - if (bfd_link_relocatable (info)) return true; - htab = alpha_elf_hash_table (info); - if (htab == NULL) - return false; - - if (!elf64_alpha_size_got_sections (info, true)) - return false; - - /* Allocate space for all of the .got subsections. */ - i = htab->got_list; - for ( ; i ; i = alpha_elf_tdata(i)->got_link_next) - { - asection *s = alpha_elf_tdata(i)->got; - if (s->size > 0) - { - s->contents = (bfd_byte *) bfd_zalloc (i, s->size); - if (s->contents == NULL) - return false; - s->alloced = 1; - } - } - - return true; + /* Size the .got subsections, but do not allocate their contents here. + This sizing is not final: relaxation re-runs it, and the re-merge + there can grow a subsection even though the table as a whole only + shrinks. elf64_alpha_final_link allocates the contents once the + sizes have settled. */ + return elf64_alpha_size_got_sections (info, true); } /* The number of dynamic relocations required by a static relocation. */ @@ -2841,6 +2838,12 @@ elf64_alpha_late_size_sections (struct bfd_link_info *info) of the dynobj section names depend upon the input files. */ name = bfd_section_name (s); + /* The .got subsection of DYNOBJ is sized along with all the other + got subsections, and relaxation can still change that size. Leave + it for elf64_alpha_final_link to allocate. */ + if (strcmp (name, ".got") == 0) + continue; + if (startswith (name, ".rela")) { if (s->size != 0) @@ -3709,10 +3712,10 @@ 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 (); if (elf_hash_table (link_info)->dynamic_sections_created) @@ -4313,7 +4316,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 @@ -4564,7 +4567,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 @@ -4583,7 +4586,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 @@ -4676,7 +4679,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 @@ -4821,7 +4824,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)) @@ -5236,6 +5239,25 @@ elf64_alpha_final_link (bfd *abfd, struct bfd_link_info *info) } } + /* Allocate the contents of the .got subsections. This is left until + now because the sizes are not final until relaxation has finished + with them: a re-merge there can grow a subsection even though the + got as a whole only shrinks. */ + for (bfd *i = htab->got_list; + i != NULL; + i = alpha_elf_tdata (i)->got_link_next) + { + asection *sgot = alpha_elf_tdata (i)->got; + + if (sgot->size > 0) + { + sgot->contents = (bfd_byte *) bfd_zalloc (i, sgot->size); + if (sgot->contents == NULL) + return false; + sgot->alloced = 1; + } + } + /* Invoke the regular ELF backend linker to do all the work. */ if (! _bfd_elf_final_link (abfd, info)) return false; @@ -5243,8 +5265,8 @@ elf64_alpha_final_link (bfd *abfd, struct bfd_link_info *info) /* Now write out the computed sections. */ /* The .got subsections... */ - bfd *i, *dynobj = elf_hash_table(info)->dynobj; - for (i = htab->got_list; + bfd *dynobj = elf_hash_table(info)->dynobj; + for (bfd *i = htab->got_list; i != NULL; i = alpha_elf_tdata(i)->got_link_next) { 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..607604130b5 --- /dev/null +++ b/ld/testsuite/ld-alpha/got-realloc-a.s @@ -0,0 +1,21 @@ +/* 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. */ + + .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..4ce760ace1f --- /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 64000 + bytes 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