]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: build shstrtab from ELF sections
authorIan Lance Taylor <iant@golang.org>
Fri, 7 Nov 2025 20:25:03 +0000 (12:25 -0800)
committerIan Lance Taylor <iant@golang.org>
Thu, 27 Nov 2025 04:03:00 +0000 (20:03 -0800)
Since before Go 1 the Go linker has handled ELF by first building the
ELF section string table, and then pointing ELF section headers to it.
This duplicates code as sections are effectively created twice,
once with the name and then again with the full section header.
The code duplication also means that it's easy to create unnecessary
section names; for example, every internally linked Go program
currently contains the string ".go.fuzzcntrs" although most do not
have a section by that name.

This CL changes the linker to simply build the section string table
after all the sections are known.

Change-Id: I27ba15b2af3dc1b8d7436b6c409f818aa8e6bfb4
Reviewed-on: https://go-review.googlesource.com/c/go/+/718840
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/cmd/link/internal/ld/dwarf.go
src/cmd/link/internal/ld/elf.go

index eeac497850f5ce1be0984bef8d15217c672512ff..ff0fa5c377db7c47facba5edb2e98b8dcbbde1bb 100644 (file)
@@ -2394,28 +2394,6 @@ func (d *dwctxt) collectUnitLocs(u *sym.CompilationUnit) []loader.Sym {
        return syms
 }
 
-// Add DWARF section names to the section header string table, by calling add
-// on each name. ELF only.
-func dwarfaddshstrings(ctxt *Link, add func(string)) {
-       if *FlagW { // disable dwarf
-               return
-       }
-
-       secs := []string{"abbrev", "frame", "info", "loc", "line", "gdb_scripts"}
-       if buildcfg.Experiment.Dwarf5 {
-               secs = append(secs, "addr", "rnglists", "loclists")
-       } else {
-               secs = append(secs, "ranges", "loc")
-       }
-
-       for _, sec := range secs {
-               add(".debug_" + sec)
-               if ctxt.IsExternal() {
-                       add(elfRelType + ".debug_" + sec)
-               }
-       }
-}
-
 func dwarfaddelfsectionsyms(ctxt *Link) {
        if *FlagW { // disable dwarf
                return
index 6bda9f0ef55bce6ec1352a5c6d026508cbbfaca3..c9480222c30e8df2e0948fe17769ce0defad3373 100644 (file)
@@ -71,10 +71,15 @@ import (
 // ElfEhdr is the ELF file header.
 type ElfEhdr elf.Header64
 
-// ElfShdr is an ELF section entry, plus the section index.
+// ElfShdr is an ELF section table entry.
 type ElfShdr struct {
        elf.Section64
 
+       // nameString is the section name as a string.
+       // This is not to be confused with Name,
+       // inherited from elf.Section64, which is an offset.
+       nameString string
+
        // The section index, set by elfSortShdrs.
        // Don't read this directly, use elfShdrShnum.
        shnum elf.SectionIndex
@@ -109,7 +114,7 @@ const (
        ELF32RELSIZE  = 8
 )
 
-var elfstrdat, elfshstrdat []byte
+var elfstrdat []byte
 
 // ELFRESERVE is the total amount of space to reserve at the
 // start of the file for Header, PHeaders, SHeaders, and interp.
@@ -158,15 +163,6 @@ type ELFArch struct {
        DynamicReadOnly bool
 }
 
-type Elfstring struct {
-       s   string
-       off int
-}
-
-var elfstr [100]Elfstring
-
-var nelfstr int
-
 var buildinfo []byte
 
 // Elfinit initializes the global ehdr variable that holds the ELF header.
@@ -413,15 +409,46 @@ func elfSortShdrs(ctxt *Link) {
        shdrSorted = true
 }
 
-func elfsetstring(ctxt *Link, s loader.Sym, str string, off int) {
-       if nelfstr >= len(elfstr) {
-               ctxt.Errorf(s, "too many elf strings")
-               errorexit()
+// elfWriteShstrtab writes out the ELF section string table.
+// It also sets the Name field of the section headers.
+// It returns the length of the string table.
+func elfWriteShstrtab(ctxt *Link) uint32 {
+       // Map from section name to shstrtab offset.
+       m := make(map[string]uint32, len(shdr))
+
+       m[""] = 0
+       ctxt.Out.WriteByte(0)
+       off := uint32(1)
+
+       writeString := func(s string) {
+               m[s] = off
+               ctxt.Out.WriteString(s)
+               ctxt.Out.WriteByte(0)
+               off += uint32(len(s)) + 1
+       }
+
+       // As a minor optimization, do the relocation sections first,
+       // as they may let us reuse the suffix.
+       // That is, the offset for ".text" can point into ".rel.text".
+       // We don't do a full suffix search as the relocation sections
+       // are likely to be the only match.
+       for _, sh := range shdr {
+               if suffix, ok := strings.CutPrefix(sh.nameString, elfRelType); ok {
+                       m[suffix] = off + uint32(len(elfRelType))
+                       writeString(sh.nameString)
+               }
+       }
+
+       for _, sh := range shdr {
+               if shOff, ok := m[sh.nameString]; ok {
+                       sh.Name = shOff
+               } else {
+                       sh.Name = off
+                       writeString(sh.nameString)
+               }
        }
 
-       elfstr[nelfstr].s = str
-       elfstr[nelfstr].off = off
-       nelfstr++
+       return off
 }
 
 func elfwritephdrs(out *OutBuf) uint32 {
@@ -450,15 +477,16 @@ func newElfPhdr() *ElfPhdr {
        return e
 }
 
-func newElfShdr(name int64) *ElfShdr {
+func newElfShdr(name string) *ElfShdr {
        if shdrSorted {
                Errorf("internal error: creating a section header after they were sorted")
                errorexit()
        }
 
-       e := new(ElfShdr)
-       e.Name = uint32(name)
-       e.shnum = -1 // make invalid for now, set by elfSortShdrs
+       e := &ElfShdr{
+               nameString: name,
+               shnum:      -1, // make invalid for now, set by elfSortShdrs
+       }
        shdr = append(shdr, e)
        return e
 }
@@ -1165,36 +1193,20 @@ func elfphrelro(seg *sym.Segment) {
        ph.Align = uint64(*FlagRound)
 }
 
+// elfshname finds or creates a section given its name.
 func elfshname(name string) *ElfShdr {
-       for i := 0; i < nelfstr; i++ {
-               if name != elfstr[i].s {
-                       continue
-               }
-               off := elfstr[i].off
-               for _, sh := range shdr {
-                       if sh.Name == uint32(off) {
-                               return sh
-                       }
+       for _, sh := range shdr {
+               if sh.nameString == name {
+                       return sh
                }
-               return newElfShdr(int64(off))
        }
-       Exitf("cannot find elf name %s", name)
-       return nil
+       return newElfShdr(name)
 }
 
-// Create an ElfShdr for the section with name.
-// Create a duplicate if one already exists with that name.
+// elfshnamedup creates a new section with a given name.
+// If there is an existing section with this name, it creates a duplicate.
 func elfshnamedup(name string) *ElfShdr {
-       for i := 0; i < nelfstr; i++ {
-               if name == elfstr[i].s {
-                       off := elfstr[i].off
-                       return newElfShdr(int64(off))
-               }
-       }
-
-       Errorf("cannot find elf name %s", name)
-       errorexit()
-       return nil
+       return newElfShdr(name)
 }
 
 func elfshalloc(sect *sym.Section) *ElfShdr {
@@ -1446,140 +1458,11 @@ func addgonote(ctxt *Link, sectionName string, tag uint32, desc []byte) {
 func (ctxt *Link) doelf() {
        ldr := ctxt.loader
 
-       // Predefine strings we need for section headers.
-
-       addshstr := func(s string) int {
-               off := len(elfshstrdat)
-               elfshstrdat = append(elfshstrdat, s...)
-               elfshstrdat = append(elfshstrdat, 0)
-               return off
-       }
-
-       shstrtabAddstring := func(s string) {
-               off := addshstr(s)
-               elfsetstring(ctxt, 0, s, off)
-       }
-
-       shstrtabAddstring("")
-       shstrtabAddstring(".text")
-       shstrtabAddstring(".noptrdata")
-       shstrtabAddstring(".data")
-       shstrtabAddstring(".bss")
-       shstrtabAddstring(".noptrbss")
-       shstrtabAddstring(".go.fuzzcntrs")
-       shstrtabAddstring(".go.buildinfo")
-       shstrtabAddstring(".go.fipsinfo")
-       if ctxt.IsMIPS() {
-               shstrtabAddstring(".MIPS.abiflags")
-               shstrtabAddstring(".gnu.attributes")
-       }
-
-       // generate .tbss section for dynamic internal linker or external
-       // linking, so that various binutils could correctly calculate
-       // PT_TLS size. See https://golang.org/issue/5200.
-       if !*FlagD || ctxt.IsExternal() {
-               shstrtabAddstring(".tbss")
-       }
-       if ctxt.IsNetbsd() {
-               shstrtabAddstring(".note.netbsd.ident")
-               if *flagRace {
-                       shstrtabAddstring(".note.netbsd.pax")
-               }
-       }
-       if ctxt.IsOpenbsd() {
-               shstrtabAddstring(".note.openbsd.ident")
-       }
-       if ctxt.IsFreebsd() {
-               shstrtabAddstring(".note.tag")
-       }
-       if len(buildinfo) > 0 {
-               shstrtabAddstring(".note.gnu.build-id")
-       }
-       if *flagBuildid != "" {
-               shstrtabAddstring(".note.go.buildid")
-       }
-       shstrtabAddstring(".elfdata")
-       shstrtabAddstring(".rodata")
-       shstrtabAddstring(".gopclntab")
-       // See the comment about data.rel.ro.FOO section names in data.go.
-       relro_prefix := ""
-       if ctxt.UseRelro() {
-               shstrtabAddstring(".data.rel.ro")
-               relro_prefix = ".data.rel.ro"
-       }
-       shstrtabAddstring(relro_prefix + ".typelink")
-       shstrtabAddstring(relro_prefix + ".itablink")
-
        if ctxt.IsExternal() {
                *FlagD = true
-
-               shstrtabAddstring(elfRelType + ".text")
-               shstrtabAddstring(elfRelType + ".rodata")
-               shstrtabAddstring(elfRelType + relro_prefix + ".typelink")
-               shstrtabAddstring(elfRelType + relro_prefix + ".itablink")
-               shstrtabAddstring(elfRelType + ".noptrdata")
-               shstrtabAddstring(elfRelType + ".data")
-               if ctxt.UseRelro() {
-                       shstrtabAddstring(elfRelType + ".data.rel.ro")
-               }
-               shstrtabAddstring(elfRelType + ".go.buildinfo")
-               shstrtabAddstring(elfRelType + ".go.fipsinfo")
-               if ctxt.IsMIPS() {
-                       shstrtabAddstring(elfRelType + ".MIPS.abiflags")
-                       shstrtabAddstring(elfRelType + ".gnu.attributes")
-               }
-
-               // add a .note.GNU-stack section to mark the stack as non-executable
-               shstrtabAddstring(".note.GNU-stack")
-
-               if ctxt.IsShared() {
-                       shstrtabAddstring(".note.go.abihash")
-                       shstrtabAddstring(".note.go.pkg-list")
-                       shstrtabAddstring(".note.go.deps")
-               }
-       }
-
-       hasinitarr := ctxt.linkShared
-
-       // Shared library initializer.
-       switch ctxt.BuildMode {
-       case BuildModeCArchive, BuildModeCShared, BuildModeShared, BuildModePlugin:
-               hasinitarr = true
-       }
-
-       if hasinitarr {
-               shstrtabAddstring(".init_array")
-               shstrtabAddstring(elfRelType + ".init_array")
-       }
-
-       if !*FlagS {
-               shstrtabAddstring(".symtab")
-               shstrtabAddstring(".strtab")
        }
-       if !*FlagW {
-               dwarfaddshstrings(ctxt, shstrtabAddstring)
-       }
-
-       shstrtabAddstring(".shstrtab")
 
        if !*FlagD { // -d suppresses dynamic loader format
-               shstrtabAddstring(".interp")
-               shstrtabAddstring(".hash")
-               shstrtabAddstring(".got")
-               if ctxt.IsPPC64() {
-                       shstrtabAddstring(".glink")
-               }
-               shstrtabAddstring(".got.plt")
-               shstrtabAddstring(".dynamic")
-               shstrtabAddstring(".dynsym")
-               shstrtabAddstring(".dynstr")
-               shstrtabAddstring(elfRelType)
-               shstrtabAddstring(elfRelType + ".plt")
-
-               shstrtabAddstring(".plt")
-               shstrtabAddstring(".gnu.version")
-               shstrtabAddstring(".gnu.version_r")
-
                // dynamic symbol table - first entry all zeros
                dynsym := ldr.CreateSymForUpdate(".dynsym", 0)
 
@@ -2291,13 +2174,14 @@ elfobj:
        sh := elfshname(".shstrtab")
        eh.Shstrndx = uint16(elfShdrShnum(sh))
 
+       var shstrtabLen uint32
        ctxt.Out.SeekSet(symo)
        if *FlagS {
-               ctxt.Out.Write(elfshstrdat)
+               shstrtabLen = elfWriteShstrtab(ctxt)
        } else {
                asmElfSym(ctxt)
                ctxt.Out.Write(elfstrdat)
-               ctxt.Out.Write(elfshstrdat)
+               shstrtabLen = elfWriteShstrtab(ctxt)
                if ctxt.IsExternal() {
                        elfEmitReloc(ctxt)
                }
@@ -2328,7 +2212,7 @@ elfobj:
        sh = elfshname(".shstrtab")
        sh.Type = uint32(elf.SHT_STRTAB)
        sh.Off = shstroff
-       sh.Size = uint64(len(elfshstrdat))
+       sh.Size = uint64(shstrtabLen)
        sh.Addralign = 1
 
        // Main header