cmd/link: sort allocated ELF section headers by address

For an executable, emit the allocated section headers in address order,
so that section headers are easier for humans to read.

Change-Id: Ib5efb4734101e4a1f6b09d0e045ed643c79c7c0a
Reviewed-on: https://go-review.googlesource.com/c/go/+/718620
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Bypass: David Chase <drchase@google.com>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Ian Lance Taylor 2025-11-06 19:52:54 -08:00
parent 4879151d1d
commit 0ff323143d
4 changed files with 222 additions and 48 deletions

View file

@ -678,3 +678,61 @@ func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expected
t.Errorf("expected error message to contain %q, got:\n%s", expectedError, out)
}
}
func TestELFHeadersSorted(t *testing.T) {
testenv.MustHaveGoBuild(t)
// We can only test this for internal linking mode.
// For external linking the external linker will
// decide how to sort the sections.
testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
t.Parallel()
tmpdir := t.TempDir()
src := filepath.Join(tmpdir, "x.go")
if err := os.WriteFile(src, []byte(goSourceWithData), 0o444); err != nil {
t.Fatal(err)
}
exe := filepath.Join(tmpdir, "x.exe")
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-ldflags=-linkmode=internal", "-o", exe, src)
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("build failed: %v, output:\n%s", err, out)
}
ef, err := elf.Open(exe)
if err != nil {
t.Fatal(err)
}
defer ef.Close()
// After the first zero section header,
// we should see allocated sections,
// then unallocated sections.
// The allocated sections should be sorted by address.
i := 1
lastAddr := uint64(0)
for i < len(ef.Sections) {
sec := ef.Sections[i]
if sec.Flags&elf.SHF_ALLOC == 0 {
break
}
if sec.Addr < lastAddr {
t.Errorf("section %d %q address %#x less than previous address %#x", i, sec.Name, sec.Addr, lastAddr)
}
lastAddr = sec.Addr
i++
}
firstUnalc := i
for i < len(ef.Sections) {
sec := ef.Sections[i]
if sec.Flags&elf.SHF_ALLOC != 0 {
t.Errorf("allocated section %d %q follows first unallocated section %d %q", i, sec.Name, firstUnalc, ef.Sections[firstUnalc].Name)
}
i++
}
}

View file

@ -2428,7 +2428,7 @@ func dwarfaddelfsectionsyms(ctxt *Link) {
for _, si := range dwarfp {
s := si.secSym()
sect := ldr.SymSect(si.secSym())
putelfsectionsym(ctxt, ctxt.Out, s, sect.Elfsect.(*ElfShdr).shnum)
putelfsectionsym(ctxt, ctxt.Out, s, elfShdrShnum(sect.Elfsect.(*ElfShdr)))
}
}

View file

@ -10,6 +10,7 @@ import (
"cmd/internal/sys"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
"cmp"
"debug/elf"
"encoding/binary"
"encoding/hex"
@ -73,7 +74,22 @@ type ElfEhdr elf.Header64
// ElfShdr is an ELF section entry, plus the section index.
type ElfShdr struct {
elf.Section64
// The section index, set by elfSortShdrs.
// Don't read this directly, use elfShdrShnum.
shnum elf.SectionIndex
// Because we don't compute the final section number
// until late in the link, when the link and info fields
// hold section indexes, we store pointers, and fetch
// the final section index when we write them out.
link *ElfShdr
info *ElfShdr
// We compute the section offsets of reloc sections
// after we create the ELF section header.
// This field lets us fetch the section offset and size.
relocSect *sym.Section
}
// ElfPhdr is the ELF program, or segment, header.
@ -109,9 +125,10 @@ var (
// target platform uses.
elfRelType string
ehdr ElfEhdr
phdr = make([]*ElfPhdr, 0, 8)
shdr = make([]*ElfShdr, 0, 64)
ehdr ElfEhdr
phdr = make([]*ElfPhdr, 0, 8)
shdr = make([]*ElfShdr, 0, 64)
shdrSorted bool
interp string
)
@ -263,15 +280,72 @@ func elf32phdr(out *OutBuf, e *ElfPhdr) {
out.Write32(uint32(e.Align))
}
// elfShdrShnum returns the section index of an ElfShdr.
func elfShdrShnum(e *ElfShdr) elf.SectionIndex {
if e.shnum == -1 {
Errorf("internal error: retrieved section index before it is set")
errorexit()
}
return e.shnum
}
// elfShdrOff returns the section offset for an ElfShdr.
func elfShdrOff(e *ElfShdr) uint64 {
if e.relocSect != nil {
if e.Off != 0 {
Errorf("internal error: ElfShdr relocSect == %p Off == %d", e.relocSect, e.Off)
errorexit()
}
return e.relocSect.Reloff
}
return e.Off
}
// elfShdrSize returns the section size for an ElfShdr.
func elfShdrSize(e *ElfShdr) uint64 {
if e.relocSect != nil {
if e.Size != 0 {
Errorf("internal error: ElfShdr relocSect == %p Size == %d", e.relocSect, e.Size)
errorexit()
}
return e.relocSect.Rellen
}
return e.Size
}
// elfShdrLink returns the link value for an ElfShdr.
func elfShdrLink(e *ElfShdr) uint32 {
if e.link != nil {
if e.Link != 0 {
Errorf("internal error: ElfShdr link == %p Link == %d", e.link, e.Link)
errorexit()
}
return uint32(elfShdrShnum(e.link))
}
return e.Link
}
// elfShdrInfo returns the info value for an ElfShdr.
func elfShdrInfo(e *ElfShdr) uint32 {
if e.info != nil {
if e.Info != 0 {
Errorf("internal error: ElfShdr info == %p Info == %d", e.info, e.Info)
errorexit()
}
return uint32(elfShdrShnum(e.info))
}
return e.Info
}
func elf64shdr(out *OutBuf, e *ElfShdr) {
out.Write32(e.Name)
out.Write32(e.Type)
out.Write64(e.Flags)
out.Write64(e.Addr)
out.Write64(e.Off)
out.Write64(e.Size)
out.Write32(e.Link)
out.Write32(e.Info)
out.Write64(elfShdrOff(e))
out.Write64(elfShdrSize(e))
out.Write32(elfShdrLink(e))
out.Write32(elfShdrInfo(e))
out.Write64(e.Addralign)
out.Write64(e.Entsize)
}
@ -281,10 +355,10 @@ func elf32shdr(out *OutBuf, e *ElfShdr) {
out.Write32(e.Type)
out.Write32(uint32(e.Flags))
out.Write32(uint32(e.Addr))
out.Write32(uint32(e.Off))
out.Write32(uint32(e.Size))
out.Write32(e.Link)
out.Write32(e.Info)
out.Write32(uint32(elfShdrOff(e)))
out.Write32(uint32(elfShdrSize(e)))
out.Write32(elfShdrLink(e))
out.Write32(elfShdrInfo(e))
out.Write32(uint32(e.Addralign))
out.Write32(uint32(e.Entsize))
}
@ -303,6 +377,42 @@ func elfwriteshdrs(out *OutBuf) uint32 {
return uint32(ehdr.Shnum) * ELF32SHDRSIZE
}
// elfSortShdrs sorts the section headers so that allocated sections
// are first, in address order. This isn't required for correctness,
// but it makes the ELF file easier for humans to read.
// We only do this for an executable, not an object file.
func elfSortShdrs(ctxt *Link) {
if ctxt.LinkMode != LinkExternal {
// Use [1:] to leave the empty section header zero in place.
slices.SortStableFunc(shdr[1:], func(a, b *ElfShdr) int {
isAllocated := func(h *ElfShdr) bool {
return elf.SectionFlag(h.Flags)&elf.SHF_ALLOC != 0
}
if isAllocated(a) {
if isAllocated(b) {
if r := cmp.Compare(a.Addr, b.Addr); r != 0 {
return r
}
// With same address, sort smallest
// section first.
return cmp.Compare(a.Size, b.Size)
}
// Allocated before unallocated.
return -1
}
if isAllocated(b) {
// Allocated before unallocated.
return 1
}
return 0
})
}
for i, h := range shdr {
h.shnum = elf.SectionIndex(i)
}
shdrSorted = true
}
func elfsetstring(ctxt *Link, s loader.Sym, str string, off int) {
if nelfstr >= len(elfstr) {
ctxt.Errorf(s, "too many elf strings")
@ -341,9 +451,14 @@ func newElfPhdr() *ElfPhdr {
}
func newElfShdr(name int64) *ElfShdr {
if shdrSorted {
Errorf("internal error: creating a section header after they were sorted")
errorexit()
}
e := new(ElfShdr)
e.Name = uint32(name)
e.shnum = elf.SectionIndex(ehdr.Shnum)
e.shnum = -1 // make invalid for now, set by elfSortShdrs
shdr = append(shdr, e)
ehdr.Shnum++
return e
@ -1190,7 +1305,7 @@ func elfshreloc(arch *sys.Arch, sect *sym.Section) *ElfShdr {
// its own .rela.text.
if sect.Name == ".text" {
if sh.Info != 0 && sh.Info != uint32(sect.Elfsect.(*ElfShdr).shnum) {
if sh.info != nil && sh.info != sect.Elfsect.(*ElfShdr) {
sh = elfshnamedup(elfRelType + sect.Name)
}
}
@ -1200,10 +1315,9 @@ func elfshreloc(arch *sys.Arch, sect *sym.Section) *ElfShdr {
if typ == elf.SHT_RELA {
sh.Entsize += uint64(arch.RegSize)
}
sh.Link = uint32(elfshname(".symtab").shnum)
sh.Info = uint32(sect.Elfsect.(*ElfShdr).shnum)
sh.Off = sect.Reloff
sh.Size = sect.Rellen
sh.link = elfshname(".symtab")
sh.info = sect.Elfsect.(*ElfShdr)
sh.relocSect = sect
sh.Addralign = uint64(arch.RegSize)
return sh
}
@ -1710,19 +1824,6 @@ func asmbElf(ctxt *Link) {
var symo int64
symo = int64(Segdwarf.Fileoff + Segdwarf.Filelen)
symo = Rnd(symo, int64(ctxt.Arch.PtrSize))
ctxt.Out.SeekSet(symo)
if *FlagS {
ctxt.Out.Write(elfshstrdat)
} else {
ctxt.Out.SeekSet(symo)
asmElfSym(ctxt)
ctxt.Out.Write(elfstrdat)
ctxt.Out.Write(elfshstrdat)
if ctxt.IsExternal() {
elfEmitReloc(ctxt)
}
}
ctxt.Out.SeekSet(0)
ldr := ctxt.loader
eh := getElfEhdr()
@ -1947,9 +2048,9 @@ func asmbElf(ctxt *Link) {
sh.Entsize = ELF32SYMSIZE
}
sh.Addralign = uint64(ctxt.Arch.RegSize)
sh.Link = uint32(elfshname(".dynstr").shnum)
sh.link = elfshname(".dynstr")
// sh.info is the index of first non-local symbol (number of local symbols)
// sh.Info is the index of first non-local symbol (number of local symbols)
s := ldr.Lookup(".dynsym", 0)
i := uint32(0)
for sub := s; sub != 0; sub = ldr.SubSym(sub) {
@ -1972,7 +2073,7 @@ func asmbElf(ctxt *Link) {
sh.Type = uint32(elf.SHT_GNU_VERSYM)
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Addralign = 2
sh.Link = uint32(elfshname(".dynsym").shnum)
sh.link = elfshname(".dynsym")
sh.Entsize = 2
shsym(sh, ldr, ldr.Lookup(".gnu.version", 0))
@ -1981,7 +2082,7 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Addralign = uint64(ctxt.Arch.RegSize)
sh.Info = uint32(elfverneed)
sh.Link = uint32(elfshname(".dynstr").shnum)
sh.link = elfshname(".dynstr")
shsym(sh, ldr, ldr.Lookup(".gnu.version_r", 0))
}
@ -1991,8 +2092,8 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Entsize = ELF64RELASIZE
sh.Addralign = uint64(ctxt.Arch.RegSize)
sh.Link = uint32(elfshname(".dynsym").shnum)
sh.Info = uint32(elfshname(".plt").shnum)
sh.link = elfshname(".dynsym")
sh.info = elfshname(".plt")
shsym(sh, ldr, ldr.Lookup(".rela.plt", 0))
sh = elfshname(".rela")
@ -2000,7 +2101,7 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Entsize = ELF64RELASIZE
sh.Addralign = 8
sh.Link = uint32(elfshname(".dynsym").shnum)
sh.link = elfshname(".dynsym")
shsym(sh, ldr, ldr.Lookup(".rela", 0))
} else {
sh := elfshname(".rel.plt")
@ -2008,7 +2109,7 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Entsize = ELF32RELSIZE
sh.Addralign = 4
sh.Link = uint32(elfshname(".dynsym").shnum)
sh.link = elfshname(".dynsym")
shsym(sh, ldr, ldr.Lookup(".rel.plt", 0))
sh = elfshname(".rel")
@ -2016,7 +2117,7 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Entsize = ELF32RELSIZE
sh.Addralign = 4
sh.Link = uint32(elfshname(".dynsym").shnum)
sh.link = elfshname(".dynsym")
shsym(sh, ldr, ldr.Lookup(".rel", 0))
}
@ -2071,7 +2172,7 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Entsize = 4
sh.Addralign = uint64(ctxt.Arch.RegSize)
sh.Link = uint32(elfshname(".dynsym").shnum)
sh.link = elfshname(".dynsym")
shsym(sh, ldr, ldr.Lookup(".hash", 0))
// sh and elf.PT_DYNAMIC for .dynamic section
@ -2081,7 +2182,7 @@ func asmbElf(ctxt *Link) {
sh.Flags = uint64(elf.SHF_ALLOC + elf.SHF_WRITE)
sh.Entsize = 2 * uint64(ctxt.Arch.RegSize)
sh.Addralign = uint64(ctxt.Arch.RegSize)
sh.Link = uint32(elfshname(".dynstr").shnum)
sh.link = elfshname(".dynstr")
shsym(sh, ldr, ldr.Lookup(".dynamic", 0))
ph := newElfPhdr()
ph.Type = elf.PT_DYNAMIC
@ -2120,11 +2221,8 @@ func asmbElf(ctxt *Link) {
}
elfobj:
sh := elfshname(".shstrtab")
eh.Shstrndx = uint16(sh.shnum)
if ctxt.IsMIPS() {
sh = elfshname(".MIPS.abiflags")
sh := elfshname(".MIPS.abiflags")
sh.Type = uint32(elf.SHT_MIPS_ABIFLAGS)
sh.Flags = uint64(elf.SHF_ALLOC)
sh.Addralign = 8
@ -2190,6 +2288,24 @@ elfobj:
sh.Flags = 0
}
elfSortShdrs(ctxt)
sh := elfshname(".shstrtab")
eh.Shstrndx = uint16(elfShdrShnum(sh))
ctxt.Out.SeekSet(symo)
if *FlagS {
ctxt.Out.Write(elfshstrdat)
} else {
asmElfSym(ctxt)
ctxt.Out.Write(elfstrdat)
ctxt.Out.Write(elfshstrdat)
if ctxt.IsExternal() {
elfEmitReloc(ctxt)
}
}
ctxt.Out.SeekSet(0)
var shstroff uint64
if !*FlagS {
sh := elfshname(".symtab")
@ -2198,7 +2314,7 @@ elfobj:
sh.Size = uint64(symSize)
sh.Addralign = uint64(ctxt.Arch.RegSize)
sh.Entsize = 8 + 2*uint64(ctxt.Arch.RegSize)
sh.Link = uint32(elfshname(".strtab").shnum)
sh.link = elfshname(".strtab")
sh.Info = uint32(elfglobalsymndx)
sh = elfshname(".strtab")

View file

@ -101,7 +101,7 @@ func putelfsym(ctxt *Link, x loader.Sym, typ elf.SymType, curbind elf.SymBind) {
ldr.Errorf(x, "missing ELF section in putelfsym")
return
}
elfshnum = xosect.Elfsect.(*ElfShdr).shnum
elfshnum = elfShdrShnum(xosect.Elfsect.(*ElfShdr))
}
sname := ldr.SymExtname(x)