go/src/cmd/internal/obj/objfile.go

919 lines
23 KiB
Go
Raw Normal View History

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Writing Go object files.
package obj
import (
"bytes"
"cmd/internal/bio"
"cmd/internal/goobj"
"cmd/internal/notsha256"
"cmd/internal/objabi"
"cmd/internal/sys"
"encoding/binary"
"fmt"
"internal/abi"
cmd/compile: speed up compiling with -S Compiling with -S was not implemented with performance in mind. It allocates profligately. Compiling with -S is ~58% slower, allocates ~47% more memory, and does ~183% more allocations. compilecmp now uses -S to do finer-grained comparisons between compiler versions, so I now care about its performance. This change picks some of the lowest hanging fruit, mostly by modifying printing routines to print directly to a writer, rather than constructing a string first. I have confirmed that compiling std+cmd with "-gcflags=all=-S -p=1" and CGO_ENABLED=0 yields identical results before/after this change. (-p=1 makes package compilation order deterministic. CGO_ENABLED=0 prevents cgo temp workdirs from showing up in filenames.) Using the -S flag, the compiler performance impact is: name old time/op new time/op delta Template 344ms ± 2% 301ms ± 2% -12.45% (p=0.000 n=22+24) Unicode 136ms ± 3% 121ms ± 3% -11.40% (p=0.000 n=24+25) GoTypes 1.24s ± 5% 1.09s ± 3% -12.58% (p=0.000 n=25+25) Compiler 5.66s ± 4% 5.06s ± 2% -10.56% (p=0.000 n=25+20) SSA 19.9s ± 3% 17.2s ± 4% -13.64% (p=0.000 n=25+25) Flate 212ms ± 2% 188ms ± 2% -11.33% (p=0.000 n=25+24) GoParser 278ms ± 3% 242ms ± 1% -12.84% (p=0.000 n=23+24) Reflect 743ms ± 3% 657ms ± 5% -11.56% (p=0.000 n=24+25) Tar 295ms ± 2% 263ms ± 2% -10.78% (p=0.000 n=25+25) XML 409ms ± 2% 360ms ± 3% -12.03% (p=0.000 n=24+25) [Geo mean] 714ms 629ms -11.92% name old user-time/op new user-time/op delta Template 430ms ± 5% 388ms ± 3% -9.76% (p=0.000 n=21+24) Unicode 202ms ±12% 171ms ± 5% -15.21% (p=0.000 n=25+23) GoTypes 1.58s ± 3% 1.42s ± 3% -9.58% (p=0.000 n=24+24) Compiler 7.42s ± 3% 6.68s ± 8% -9.93% (p=0.000 n=25+25) SSA 26.9s ± 3% 22.9s ± 3% -14.85% (p=0.000 n=25+25) Flate 260ms ± 6% 234ms ± 3% -9.69% (p=0.000 n=23+25) GoParser 354ms ± 1% 296ms ± 3% -16.46% (p=0.000 n=23+25) Reflect 953ms ± 2% 865ms ± 4% -9.14% (p=0.000 n=24+24) Tar 380ms ± 2% 348ms ± 2% -8.28% (p=0.000 n=25+22) XML 530ms ± 3% 451ms ± 3% -15.01% (p=0.000 n=24+23) [Geo mean] 929ms 819ms -11.84% name old alloc/op new alloc/op delta Template 54.1MB ± 0% 44.3MB ± 0% -18.24% (p=0.000 n=24+24) Unicode 33.5MB ± 0% 30.6MB ± 0% -8.57% (p=0.000 n=25+25) GoTypes 189MB ± 0% 152MB ± 0% -19.55% (p=0.000 n=25+23) Compiler 875MB ± 0% 703MB ± 0% -19.70% (p=0.000 n=25+25) SSA 3.19GB ± 0% 2.51GB ± 0% -21.50% (p=0.000 n=25+25) Flate 32.9MB ± 0% 27.3MB ± 0% -17.04% (p=0.000 n=25+25) GoParser 43.9MB ± 0% 35.1MB ± 0% -20.19% (p=0.000 n=25+25) Reflect 117MB ± 0% 96MB ± 0% -18.22% (p=0.000 n=24+23) Tar 48.6MB ± 0% 40.6MB ± 0% -16.39% (p=0.000 n=25+24) XML 65.7MB ± 0% 53.9MB ± 0% -17.93% (p=0.000 n=25+23) [Geo mean] 118MB 97MB -17.80% name old allocs/op new allocs/op delta Template 1.07M ± 0% 0.60M ± 0% -43.90% (p=0.000 n=25+24) Unicode 539k ± 0% 398k ± 0% -26.20% (p=0.000 n=23+25) GoTypes 3.97M ± 0% 2.19M ± 0% -44.90% (p=0.000 n=25+24) Compiler 17.6M ± 0% 9.5M ± 0% -46.39% (p=0.000 n=22+23) SSA 66.1M ± 0% 34.1M ± 0% -48.41% (p=0.000 n=25+22) Flate 629k ± 0% 365k ± 0% -41.95% (p=0.000 n=25+25) GoParser 929k ± 0% 500k ± 0% -46.11% (p=0.000 n=25+25) Reflect 2.49M ± 0% 1.47M ± 0% -41.00% (p=0.000 n=24+25) Tar 919k ± 0% 534k ± 0% -41.94% (p=0.000 n=25+24) XML 1.28M ± 0% 0.71M ± 0% -44.72% (p=0.000 n=25+24) [Geo mean] 2.32M 1.33M -42.82% This change also speeds up cmd/objdump a modest amount, ~4%. Change-Id: I7c7aa2b365688bc44b3ef6e1d03bcf934699cabc Reviewed-on: https://go-review.googlesource.com/c/go/+/216857 Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2020-01-26 09:59:25 -08:00
"io"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
const UnlinkablePkg = "<unlinkable>" // invalid package path, used when compiled without -p flag
// Entry point of writing new object file.
func WriteObjFile(ctxt *Link, b *bio.Writer) {
debugAsmEmit(ctxt)
genFuncInfoSyms(ctxt)
w := writer{
Writer: goobj.NewWriter(b),
ctxt: ctxt,
pkgpath: objabi.PathToPrefix(ctxt.Pkgpath),
}
start := b.Offset()
w.init()
// Header
// We just reserve the space. We'll fill in the offsets later.
flags := uint32(0)
if ctxt.Flag_shared {
flags |= goobj.ObjFlagShared
}
if w.pkgpath == UnlinkablePkg {
flags |= goobj.ObjFlagUnlinkable
}
if w.pkgpath == "" {
log.Fatal("empty package path")
}
if ctxt.IsAsm {
flags |= goobj.ObjFlagFromAssembly
}
h := goobj.Header{
Magic: goobj.Magic,
Fingerprint: ctxt.Fingerprint,
Flags: flags,
}
h.Write(w.Writer)
// String table
w.StringTable()
// Autolib
h.Offsets[goobj.BlkAutolib] = w.Offset()
for i := range ctxt.Imports {
ctxt.Imports[i].Write(w.Writer)
}
// Package references
h.Offsets[goobj.BlkPkgIdx] = w.Offset()
for _, pkg := range w.pkglist {
w.StringRef(pkg)
}
// File table (for DWARF and pcln generation).
h.Offsets[goobj.BlkFile] = w.Offset()
for _, f := range ctxt.PosTable.FileTable() {
w.StringRef(filepath.ToSlash(f))
}
// Symbol definitions
h.Offsets[goobj.BlkSymdef] = w.Offset()
for _, s := range ctxt.defs {
w.Sym(s)
}
// Short hashed symbol definitions
h.Offsets[goobj.BlkHashed64def] = w.Offset()
for _, s := range ctxt.hashed64defs {
w.Sym(s)
}
// Hashed symbol definitions
h.Offsets[goobj.BlkHasheddef] = w.Offset()
for _, s := range ctxt.hasheddefs {
w.Sym(s)
}
// Non-pkg symbol definitions
h.Offsets[goobj.BlkNonpkgdef] = w.Offset()
for _, s := range ctxt.nonpkgdefs {
w.Sym(s)
}
// Non-pkg symbol references
h.Offsets[goobj.BlkNonpkgref] = w.Offset()
for _, s := range ctxt.nonpkgrefs {
w.Sym(s)
}
// Referenced package symbol flags
h.Offsets[goobj.BlkRefFlags] = w.Offset()
w.refFlags()
// Hashes
h.Offsets[goobj.BlkHash64] = w.Offset()
for _, s := range ctxt.hashed64defs {
w.Hash64(s)
}
h.Offsets[goobj.BlkHash] = w.Offset()
for _, s := range ctxt.hasheddefs {
w.Hash(s)
}
// TODO: hashedrefs unused/unsupported for now
// Reloc indexes
h.Offsets[goobj.BlkRelocIdx] = w.Offset()
nreloc := uint32(0)
lists := [][]*LSym{ctxt.defs, ctxt.hashed64defs, ctxt.hasheddefs, ctxt.nonpkgdefs}
for _, list := range lists {
for _, s := range list {
w.Uint32(nreloc)
nreloc += uint32(len(s.R))
}
}
w.Uint32(nreloc)
// Symbol Info indexes
h.Offsets[goobj.BlkAuxIdx] = w.Offset()
naux := uint32(0)
for _, list := range lists {
for _, s := range list {
w.Uint32(naux)
naux += uint32(nAuxSym(s))
}
}
w.Uint32(naux)
// Data indexes
h.Offsets[goobj.BlkDataIdx] = w.Offset()
dataOff := int64(0)
for _, list := range lists {
for _, s := range list {
w.Uint32(uint32(dataOff))
dataOff += int64(len(s.P))
if file := s.File(); file != nil {
dataOff += int64(file.Size)
}
}
}
if int64(uint32(dataOff)) != dataOff {
log.Fatalf("data too large")
}
w.Uint32(uint32(dataOff))
// Relocs
h.Offsets[goobj.BlkReloc] = w.Offset()
for _, list := range lists {
for _, s := range list {
sort.Sort(relocByOff(s.R)) // some platforms (e.g. PE) requires relocations in address order
for i := range s.R {
w.Reloc(&s.R[i])
}
}
}
// Aux symbol info
h.Offsets[goobj.BlkAux] = w.Offset()
for _, list := range lists {
for _, s := range list {
w.Aux(s)
}
}
// Data
h.Offsets[goobj.BlkData] = w.Offset()
for _, list := range lists {
for _, s := range list {
w.Bytes(s.P)
if file := s.File(); file != nil {
w.writeFile(ctxt, file)
}
[dev.debug] cmd/compile: better DWARF with optimizations on Debuggers use DWARF information to find local variables on the stack and in registers. Prior to this CL, the DWARF information for functions claimed that all variables were on the stack at all times. That's incorrect when optimizations are enabled, and results in debuggers showing data that is out of date or complete gibberish. After this CL, the compiler is capable of representing variable locations more accurately, and attempts to do so. Due to limitations of the SSA backend, it's not possible to be completely correct. There are a number of problems in the current design. One of the easier to understand is that variable names currently must be attached to an SSA value, but not all assignments in the source code actually result in machine code. For example: type myint int var a int b := myint(int) and b := (*uint64)(unsafe.Pointer(a)) don't generate machine code because the underlying representation is the same, so the correct value of b will not be set when the user would expect. Generating the more precise debug information is behind a flag, dwarflocationlists. Because of the issues described above, setting the flag may not make the debugging experience much better, and may actually make it worse in cases where the variable actually is on the stack and the more complicated analysis doesn't realize it. A number of changes are included: - Add a new pseudo-instruction, RegKill, which indicates that the value in the register has been clobbered. - Adjust regalloc to emit RegKills in the right places. Significantly, this means that phis are mixed with StoreReg and RegKills after regalloc. - Track variable decomposition in ssa.LocalSlots. - After the SSA backend is done, analyze the result and build location lists for each LocalSlot. - After assembly is done, update the location lists with the assembled PC offsets, recompose variables, and build DWARF location lists. Emit the list as a new linker symbol, one per function. - In the linker, aggregate the location lists into a .debug_loc section. TODO: - currently disabled for non-X86/AMD64 because there are no data tables. go build -toolexec 'toolstash -cmp' -a std succeeds. With -dwarflocationlists false: before: f02812195637909ff675782c0b46836a8ff01976 after: 06f61e8112a42ac34fb80e0c818b3cdb84a5e7ec benchstat -geomean /tmp/220352263 /tmp/621364410 completed 15 of 15, estimated time remaining 0s (eta 3:52PM) name old time/op new time/op delta Template 199ms ± 3% 198ms ± 2% ~ (p=0.400 n=15+14) Unicode 96.6ms ± 5% 96.4ms ± 5% ~ (p=0.838 n=15+15) GoTypes 653ms ± 2% 647ms ± 2% ~ (p=0.102 n=15+14) Flate 133ms ± 6% 129ms ± 3% -2.62% (p=0.041 n=15+15) GoParser 164ms ± 5% 159ms ± 3% -3.05% (p=0.000 n=15+15) Reflect 428ms ± 4% 422ms ± 3% ~ (p=0.156 n=15+13) Tar 123ms ±10% 124ms ± 8% ~ (p=0.461 n=15+15) XML 228ms ± 3% 224ms ± 3% -1.57% (p=0.045 n=15+15) [Geo mean] 206ms 377ms +82.86% name old user-time/op new user-time/op delta Template 292ms ±10% 301ms ±12% ~ (p=0.189 n=15+15) Unicode 166ms ±37% 158ms ±14% ~ (p=0.418 n=15+14) GoTypes 962ms ± 6% 963ms ± 7% ~ (p=0.976 n=15+15) Flate 207ms ±19% 200ms ±14% ~ (p=0.345 n=14+15) GoParser 246ms ±22% 240ms ±15% ~ (p=0.587 n=15+15) Reflect 611ms ±13% 587ms ±14% ~ (p=0.085 n=15+13) Tar 211ms ±12% 217ms ±14% ~ (p=0.355 n=14+15) XML 335ms ±15% 320ms ±18% ~ (p=0.169 n=15+15) [Geo mean] 317ms 583ms +83.72% name old alloc/op new alloc/op delta Template 40.2MB ± 0% 40.2MB ± 0% -0.15% (p=0.000 n=14+15) Unicode 29.2MB ± 0% 29.3MB ± 0% ~ (p=0.624 n=15+15) GoTypes 114MB ± 0% 114MB ± 0% -0.15% (p=0.000 n=15+14) Flate 25.7MB ± 0% 25.6MB ± 0% -0.18% (p=0.000 n=13+15) GoParser 32.2MB ± 0% 32.2MB ± 0% -0.14% (p=0.003 n=15+15) Reflect 77.8MB ± 0% 77.9MB ± 0% ~ (p=0.061 n=15+15) Tar 27.1MB ± 0% 27.0MB ± 0% -0.11% (p=0.029 n=15+15) XML 42.7MB ± 0% 42.5MB ± 0% -0.29% (p=0.000 n=15+15) [Geo mean] 42.1MB 75.0MB +78.05% name old allocs/op new allocs/op delta Template 402k ± 1% 398k ± 0% -0.91% (p=0.000 n=15+15) Unicode 344k ± 1% 344k ± 0% ~ (p=0.715 n=15+14) GoTypes 1.18M ± 0% 1.17M ± 0% -0.91% (p=0.000 n=15+14) Flate 243k ± 0% 240k ± 1% -1.05% (p=0.000 n=13+15) GoParser 327k ± 1% 324k ± 1% -0.96% (p=0.000 n=15+15) Reflect 984k ± 1% 982k ± 0% ~ (p=0.050 n=15+15) Tar 261k ± 1% 259k ± 1% -0.77% (p=0.000 n=15+15) XML 411k ± 0% 404k ± 1% -1.55% (p=0.000 n=15+15) [Geo mean] 439k 755k +72.01% name old text-bytes new text-bytes delta HelloSize 694kB ± 0% 694kB ± 0% -0.00% (p=0.000 n=15+15) name old data-bytes new data-bytes delta HelloSize 5.55kB ± 0% 5.55kB ± 0% ~ (all equal) name old bss-bytes new bss-bytes delta HelloSize 133kB ± 0% 133kB ± 0% ~ (all equal) name old exe-bytes new exe-bytes delta HelloSize 1.04MB ± 0% 1.04MB ± 0% ~ (all equal) Change-Id: I991fc553ef175db46bb23b2128317bbd48de70d8 Reviewed-on: https://go-review.googlesource.com/41770 Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
2017-07-21 18:30:19 -04:00
}
}
// Blocks used only by tools (objdump, nm).
// Referenced symbol names from other packages
h.Offsets[goobj.BlkRefName] = w.Offset()
w.refNames()
h.Offsets[goobj.BlkEnd] = w.Offset()
// Fix up block offsets in the header
end := start + int64(w.Offset())
b.MustSeek(start, 0)
h.Write(w.Writer)
b.MustSeek(end, 0)
}
type writer struct {
*goobj.Writer
filebuf []byte
ctxt *Link
pkgpath string // the package import path (escaped), "" if unknown
pkglist []string // list of packages referenced, indexed by ctxt.pkgIdx
// scratch space for writing (the Write methods escape
// as they are interface calls)
tmpSym goobj.Sym
tmpReloc goobj.Reloc
tmpAux goobj.Aux
tmpHash64 goobj.Hash64Type
tmpHash goobj.HashType
tmpRefFlags goobj.RefFlags
tmpRefName goobj.RefName
}
// prepare package index list
func (w *writer) init() {
w.pkglist = make([]string, len(w.ctxt.pkgIdx)+1)
w.pkglist[0] = "" // dummy invalid package for index 0
for pkg, i := range w.ctxt.pkgIdx {
w.pkglist[i] = pkg
}
}
func (w *writer) writeFile(ctxt *Link, file *FileInfo) {
f, err := os.Open(file.Name)
if err != nil {
ctxt.Diag("%v", err)
return
}
defer f.Close()
if w.filebuf == nil {
w.filebuf = make([]byte, 1024)
}
buf := w.filebuf
written := int64(0)
for {
n, err := f.Read(buf)
w.Bytes(buf[:n])
written += int64(n)
if err == io.EOF {
break
}
if err != nil {
ctxt.Diag("%v", err)
return
}
}
if written != file.Size {
ctxt.Diag("copy %s: unexpected length %d != %d", file.Name, written, file.Size)
}
}
func (w *writer) StringTable() {
w.AddString("")
for _, p := range w.ctxt.Imports {
w.AddString(p.Pkg)
}
for _, pkg := range w.pkglist {
w.AddString(pkg)
}
w.ctxt.traverseSyms(traverseAll, func(s *LSym) {
// Don't put names of builtins into the string table (to save
// space).
if s.PkgIdx == goobj.PkgIdxBuiltin {
return
}
// TODO: this includes references of indexed symbols from other packages,
// for which the linker doesn't need the name. Consider moving them to
// a separate block (for tools only).
if w.ctxt.Flag_noRefName && s.PkgIdx < goobj.PkgIdxSpecial {
// Don't include them if Flag_noRefName
return
}
if w.pkgpath != "" {
s.Name = strings.Replace(s.Name, "\"\".", w.pkgpath+".", -1)
}
w.AddString(s.Name)
})
// All filenames are in the postable.
for _, f := range w.ctxt.PosTable.FileTable() {
w.AddString(filepath.ToSlash(f))
}
}
// cutoff is the maximum data section size permitted by the linker
// (see issue #9862).
const cutoff = int64(2e9) // 2 GB (or so; looks better in errors than 2^31)
func (w *writer) Sym(s *LSym) {
abi := uint16(s.ABI())
if s.Static() {
abi = goobj.SymABIstatic
}
flag := uint8(0)
if s.DuplicateOK() {
flag |= goobj.SymFlagDupok
}
if s.Local() {
flag |= goobj.SymFlagLocal
}
if s.MakeTypelink() {
flag |= goobj.SymFlagTypelink
}
if s.Leaf() {
flag |= goobj.SymFlagLeaf
}
if s.NoSplit() {
flag |= goobj.SymFlagNoSplit
}
if s.ReflectMethod() {
flag |= goobj.SymFlagReflectMethod
}
if strings.HasPrefix(s.Name, "type:") && s.Name[5] != '.' && s.Type == objabi.SRODATA {
flag |= goobj.SymFlagGoType
}
flag2 := uint8(0)
if s.UsedInIface() {
flag2 |= goobj.SymFlagUsedInIface
}
if strings.HasPrefix(s.Name, "go:itab.") && s.Type == objabi.SRODATA {
flag2 |= goobj.SymFlagItab
}
if strings.HasPrefix(s.Name, w.ctxt.Pkgpath) && strings.HasPrefix(s.Name[len(w.ctxt.Pkgpath):], ".") && strings.HasPrefix(s.Name[len(w.ctxt.Pkgpath)+1:], objabi.GlobalDictPrefix) {
flag2 |= goobj.SymFlagDict
}
if s.IsPkgInit() {
flag2 |= goobj.SymFlagPkgInit
}
name := s.Name
if strings.HasPrefix(name, "gofile..") {
name = filepath.ToSlash(name)
}
var align uint32
if fn := s.Func(); fn != nil {
align = uint32(fn.Align)
}
if s.ContentAddressable() && s.Size != 0 {
// We generally assume data symbols are naturally aligned
// (e.g. integer constants), except for strings and a few
// compiler-emitted funcdata. If we dedup a string symbol and
// a non-string symbol with the same content, we should keep
// the largest alignment.
// TODO: maybe the compiler could set the alignment for all
// data symbols more carefully.
switch {
case strings.HasPrefix(s.Name, "go:string."),
strings.HasPrefix(name, "type:.namedata."),
strings.HasPrefix(name, "type:.importpath."),
strings.HasSuffix(name, ".opendefer"),
strings.HasSuffix(name, ".arginfo0"),
cmd/compile, runtime: track argument stack slot liveness Currently, for stack traces (e.g. at panic or when runtime.Stack is called), we print argument values from the stack. With register ABI, we may never store the argument to stack therefore the argument value on stack may be meaningless. This causes confusion. This CL makes the compiler keep trace of which argument stack slots are meaningful. If it is meaningful, it will be printed in stack traces as before. If it may not be meaningful, it will be printed as the stack value with a question mark ("?"). In general, the value could be meaningful on some code paths but not others depending on the execution, and the compiler couldn't know statically, so we still print the stack value, instead of not printing it at all. Also note that if the argument variable is updated in the function body the printed value may be stale (like before register ABI) but still considered meaningful. Arguments passed on stack are always meaningful therefore always printed without a question mark. Results are never printed, as before. (Due to a bug in the compiler we sometimes don't spill args into their dedicated spill slots (as we should), causing it having fewer meaningful values than it should be.) This increases binary sizes a bit: old new hello 1129760 1142080 +1.09% cmd/go 13932320 14088016 +1.12% cmd/link 6267696 6329168 +0.98% Fixes #45728. Change-Id: I308a0402e5c5ab94ca0953f8bd85a56acd28f58c Reviewed-on: https://go-review.googlesource.com/c/go/+/352057 Trust: Cherry Mui <cherryyz@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com>
2021-09-24 16:46:05 -04:00
strings.HasSuffix(name, ".arginfo1"),
strings.HasSuffix(name, ".argliveinfo"):
// These are just bytes, or varints.
align = 1
case strings.HasPrefix(name, "gclocals·"):
// It has 32-bit fields.
align = 4
default:
switch {
case w.ctxt.Arch.PtrSize == 8 && s.Size%8 == 0:
align = 8
case s.Size%4 == 0:
align = 4
case s.Size%2 == 0:
align = 2
default:
align = 1
}
}
}
if s.Size > cutoff {
w.ctxt.Diag("%s: symbol too large (%d bytes > %d bytes)", s.Name, s.Size, cutoff)
}
o := &w.tmpSym
o.SetName(name, w.Writer)
o.SetABI(abi)
o.SetType(uint8(s.Type))
o.SetFlag(flag)
o.SetFlag2(flag2)
o.SetSiz(uint32(s.Size))
o.SetAlign(align)
o.Write(w.Writer)
}
func (w *writer) Hash64(s *LSym) {
if !s.ContentAddressable() || len(s.R) != 0 {
panic("Hash of non-content-addressable symbol")
}
w.tmpHash64 = contentHash64(s)
w.Bytes(w.tmpHash64[:])
}
func (w *writer) Hash(s *LSym) {
if !s.ContentAddressable() {
panic("Hash of non-content-addressable symbol")
}
w.tmpHash = w.contentHash(s)
w.Bytes(w.tmpHash[:])
}
// contentHashSection returns a mnemonic for s's section.
// The goal is to prevent content-addressability from moving symbols between sections.
// contentHashSection only distinguishes between sets of sections for which this matters.
// Allowing flexibility increases the effectiveness of content-addressibility.
// But in some cases, such as doing addressing based on a base symbol,
// we need to ensure that a symbol is always in a particular section.
// Some of these conditions are duplicated in cmd/link/internal/ld.(*Link).symtab.
// TODO: instead of duplicating them, have the compiler decide where symbols go.
func contentHashSection(s *LSym) byte {
name := s.Name
if s.IsPcdata() {
return 'P'
}
if strings.HasPrefix(name, "gcargs.") ||
strings.HasPrefix(name, "gclocals.") ||
strings.HasPrefix(name, "gclocals·") ||
strings.HasSuffix(name, ".opendefer") ||
strings.HasSuffix(name, ".arginfo0") ||
strings.HasSuffix(name, ".arginfo1") ||
cmd/compile, runtime: track argument stack slot liveness Currently, for stack traces (e.g. at panic or when runtime.Stack is called), we print argument values from the stack. With register ABI, we may never store the argument to stack therefore the argument value on stack may be meaningless. This causes confusion. This CL makes the compiler keep trace of which argument stack slots are meaningful. If it is meaningful, it will be printed in stack traces as before. If it may not be meaningful, it will be printed as the stack value with a question mark ("?"). In general, the value could be meaningful on some code paths but not others depending on the execution, and the compiler couldn't know statically, so we still print the stack value, instead of not printing it at all. Also note that if the argument variable is updated in the function body the printed value may be stale (like before register ABI) but still considered meaningful. Arguments passed on stack are always meaningful therefore always printed without a question mark. Results are never printed, as before. (Due to a bug in the compiler we sometimes don't spill args into their dedicated spill slots (as we should), causing it having fewer meaningful values than it should be.) This increases binary sizes a bit: old new hello 1129760 1142080 +1.09% cmd/go 13932320 14088016 +1.12% cmd/link 6267696 6329168 +0.98% Fixes #45728. Change-Id: I308a0402e5c5ab94ca0953f8bd85a56acd28f58c Reviewed-on: https://go-review.googlesource.com/c/go/+/352057 Trust: Cherry Mui <cherryyz@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com>
2021-09-24 16:46:05 -04:00
strings.HasSuffix(name, ".argliveinfo") ||
strings.HasSuffix(name, ".wrapinfo") ||
strings.HasSuffix(name, ".args_stackmap") ||
strings.HasSuffix(name, ".stkobj") {
return 'F' // go:func.* or go:funcrel.*
}
if strings.HasPrefix(name, "type:") {
return 'T'
}
return 0
}
func contentHash64(s *LSym) goobj.Hash64Type {
if contentHashSection(s) != 0 {
panic("short hash of non-default-section sym " + s.Name)
}
var b goobj.Hash64Type
copy(b[:], s.P)
return b
}
// Compute the content hash for a content-addressable symbol.
// We build a content hash based on its content and relocations.
// Depending on the category of the referenced symbol, we choose
// different hash algorithms such that the hash is globally
// consistent.
// - For referenced content-addressable symbol, its content hash
// is globally consistent.
// - For package symbol and builtin symbol, its local index is
// globally consistent.
// - For non-package symbol, its fully-expanded name is globally
// consistent. For now, we require we know the current package
// path so we can always expand symbol names. (Otherwise,
// symbols with relocations are not considered hashable.)
//
// For now, we assume there is no circular dependencies among
// hashed symbols.
func (w *writer) contentHash(s *LSym) goobj.HashType {
h := notsha256.New()
var tmp [14]byte
// Include the size of the symbol in the hash.
// This preserves the length of symbols, preventing the following two symbols
// from hashing the same:
//
// [2]int{1,2} ≠ [10]int{1,2,0,0,0...}
//
// In this case, if the smaller symbol is alive, the larger is not kept unless
// needed.
binary.LittleEndian.PutUint64(tmp[:8], uint64(s.Size))
// Some symbols require being in separate sections.
tmp[8] = contentHashSection(s)
h.Write(tmp[:9])
// The compiler trims trailing zeros _sometimes_. We just do
// it always.
h.Write(bytes.TrimRight(s.P, "\x00"))
for i := range s.R {
r := &s.R[i]
binary.LittleEndian.PutUint32(tmp[:4], uint32(r.Off))
tmp[4] = r.Siz
tmp[5] = uint8(r.Type)
binary.LittleEndian.PutUint64(tmp[6:14], uint64(r.Add))
h.Write(tmp[:])
rs := r.Sym
if rs == nil {
fmt.Printf("symbol: %s\n", s)
fmt.Printf("relocation: %#v\n", r)
panic("nil symbol target in relocation")
}
switch rs.PkgIdx {
case goobj.PkgIdxHashed64:
h.Write([]byte{0})
t := contentHash64(rs)
h.Write(t[:])
case goobj.PkgIdxHashed:
h.Write([]byte{1})
t := w.contentHash(rs)
h.Write(t[:])
case goobj.PkgIdxNone:
h.Write([]byte{2})
io.WriteString(h, rs.Name) // name is already expanded at this point
case goobj.PkgIdxBuiltin:
h.Write([]byte{3})
binary.LittleEndian.PutUint32(tmp[:4], uint32(rs.SymIdx))
h.Write(tmp[:4])
case goobj.PkgIdxSelf:
io.WriteString(h, w.pkgpath)
binary.LittleEndian.PutUint32(tmp[:4], uint32(rs.SymIdx))
h.Write(tmp[:4])
default:
io.WriteString(h, rs.Pkg)
binary.LittleEndian.PutUint32(tmp[:4], uint32(rs.SymIdx))
h.Write(tmp[:4])
}
}
var b goobj.HashType
copy(b[:], h.Sum(nil))
return b
}
func makeSymRef(s *LSym) goobj.SymRef {
if s == nil {
return goobj.SymRef{}
}
if s.PkgIdx == 0 || !s.Indexed() {
fmt.Printf("unindexed symbol reference: %v\n", s)
panic("unindexed symbol reference")
}
return goobj.SymRef{PkgIdx: uint32(s.PkgIdx), SymIdx: uint32(s.SymIdx)}
}
func (w *writer) Reloc(r *Reloc) {
o := &w.tmpReloc
o.SetOff(r.Off)
o.SetSiz(r.Siz)
o.SetType(uint16(r.Type))
o.SetAdd(r.Add)
o.SetSym(makeSymRef(r.Sym))
o.Write(w.Writer)
}
func (w *writer) aux1(typ uint8, rs *LSym) {
o := &w.tmpAux
o.SetType(typ)
o.SetSym(makeSymRef(rs))
o.Write(w.Writer)
}
func (w *writer) Aux(s *LSym) {
if s.Gotype != nil {
w.aux1(goobj.AuxGotype, s.Gotype)
}
if fn := s.Func(); fn != nil {
w.aux1(goobj.AuxFuncInfo, fn.FuncInfoSym)
for _, d := range fn.Pcln.Funcdata {
w.aux1(goobj.AuxFuncdata, d)
}
if fn.dwarfInfoSym != nil && fn.dwarfInfoSym.Size != 0 {
w.aux1(goobj.AuxDwarfInfo, fn.dwarfInfoSym)
}
if fn.dwarfLocSym != nil && fn.dwarfLocSym.Size != 0 {
w.aux1(goobj.AuxDwarfLoc, fn.dwarfLocSym)
}
if fn.dwarfRangesSym != nil && fn.dwarfRangesSym.Size != 0 {
w.aux1(goobj.AuxDwarfRanges, fn.dwarfRangesSym)
}
if fn.dwarfDebugLinesSym != nil && fn.dwarfDebugLinesSym.Size != 0 {
w.aux1(goobj.AuxDwarfLines, fn.dwarfDebugLinesSym)
}
if fn.Pcln.Pcsp != nil && fn.Pcln.Pcsp.Size != 0 {
w.aux1(goobj.AuxPcsp, fn.Pcln.Pcsp)
}
if fn.Pcln.Pcfile != nil && fn.Pcln.Pcfile.Size != 0 {
w.aux1(goobj.AuxPcfile, fn.Pcln.Pcfile)
}
if fn.Pcln.Pcline != nil && fn.Pcln.Pcline.Size != 0 {
w.aux1(goobj.AuxPcline, fn.Pcln.Pcline)
}
if fn.Pcln.Pcinline != nil && fn.Pcln.Pcinline.Size != 0 {
w.aux1(goobj.AuxPcinline, fn.Pcln.Pcinline)
}
if fn.sehUnwindInfoSym != nil && fn.sehUnwindInfoSym.Size != 0 {
w.aux1(goobj.AuxSehUnwindInfo, fn.sehUnwindInfoSym)
}
for _, pcSym := range fn.Pcln.Pcdata {
w.aux1(goobj.AuxPcdata, pcSym)
}
if fn.WasmImportSym != nil {
if fn.WasmImportSym.Size == 0 {
panic("wasmimport aux sym must have non-zero size")
}
w.aux1(goobj.AuxWasmImport, fn.WasmImportSym)
}
}
}
// Emits flags of referenced indexed symbols.
func (w *writer) refFlags() {
seen := make(map[*LSym]bool)
w.ctxt.traverseSyms(traverseRefs, func(rs *LSym) { // only traverse refs, not auxs, as tools don't need auxs
switch rs.PkgIdx {
case goobj.PkgIdxNone, goobj.PkgIdxHashed64, goobj.PkgIdxHashed, goobj.PkgIdxBuiltin, goobj.PkgIdxSelf: // not an external indexed reference
return
case goobj.PkgIdxInvalid:
panic("unindexed symbol reference")
}
if seen[rs] {
return
}
seen[rs] = true
symref := makeSymRef(rs)
flag2 := uint8(0)
if rs.UsedInIface() {
flag2 |= goobj.SymFlagUsedInIface
}
if flag2 == 0 {
return // no need to write zero flags
}
o := &w.tmpRefFlags
o.SetSym(symref)
o.SetFlag2(flag2)
o.Write(w.Writer)
})
}
// Emits names of referenced indexed symbols, used by tools (objdump, nm)
// only.
func (w *writer) refNames() {
if w.ctxt.Flag_noRefName {
return
}
seen := make(map[*LSym]bool)
w.ctxt.traverseSyms(traverseRefs, func(rs *LSym) { // only traverse refs, not auxs, as tools don't need auxs
switch rs.PkgIdx {
case goobj.PkgIdxNone, goobj.PkgIdxHashed64, goobj.PkgIdxHashed, goobj.PkgIdxBuiltin, goobj.PkgIdxSelf: // not an external indexed reference
return
case goobj.PkgIdxInvalid:
panic("unindexed symbol reference")
}
if seen[rs] {
return
}
seen[rs] = true
symref := makeSymRef(rs)
o := &w.tmpRefName
o.SetSym(symref)
o.SetName(rs.Name, w.Writer)
o.Write(w.Writer)
})
// TODO: output in sorted order?
// Currently tools (cmd/internal/goobj package) doesn't use mmap,
// and it just read it into a map in memory upfront. If it uses
// mmap, if the output is sorted, it probably could avoid reading
// into memory and just do lookups in the mmap'd object file.
}
// return the number of aux symbols s have.
func nAuxSym(s *LSym) int {
n := 0
if s.Gotype != nil {
n++
}
if fn := s.Func(); fn != nil {
// FuncInfo is an aux symbol, each Funcdata is an aux symbol
n += 1 + len(fn.Pcln.Funcdata)
if fn.dwarfInfoSym != nil && fn.dwarfInfoSym.Size != 0 {
n++
}
if fn.dwarfLocSym != nil && fn.dwarfLocSym.Size != 0 {
n++
}
if fn.dwarfRangesSym != nil && fn.dwarfRangesSym.Size != 0 {
n++
}
if fn.dwarfDebugLinesSym != nil && fn.dwarfDebugLinesSym.Size != 0 {
n++
}
if fn.Pcln.Pcsp != nil && fn.Pcln.Pcsp.Size != 0 {
n++
}
if fn.Pcln.Pcfile != nil && fn.Pcln.Pcfile.Size != 0 {
n++
}
if fn.Pcln.Pcline != nil && fn.Pcln.Pcline.Size != 0 {
n++
}
if fn.Pcln.Pcinline != nil && fn.Pcln.Pcinline.Size != 0 {
n++
}
if fn.sehUnwindInfoSym != nil && fn.sehUnwindInfoSym.Size != 0 {
n++
}
n += len(fn.Pcln.Pcdata)
if fn.WasmImport != nil {
if fn.WasmImportSym == nil || fn.WasmImportSym.Size == 0 {
panic("wasmimport aux sym must exist and have non-zero size")
}
n++
}
}
return n
}
// generate symbols for FuncInfo.
func genFuncInfoSyms(ctxt *Link) {
infosyms := make([]*LSym, 0, len(ctxt.Text))
var b bytes.Buffer
symidx := int32(len(ctxt.defs))
for _, s := range ctxt.Text {
fn := s.Func()
if fn == nil {
continue
}
o := goobj.FuncInfo{
cmd/compile,cmd/link,runtime: add start line numbers to func metadata This adds the function "start line number" to runtime._func and runtime.inlinedCall objects. The "start line number" is the line number of the func keyword or TEXT directive for assembly. Subtracting the start line number from PC line number provides the relative line offset of a PC from the the start of the function. This helps with source stability by allowing code above the function to move without invalidating samples within the function. Encoding start line rather than relative lines directly is convenient because the pprof format already contains a start line field. This CL uses a straightforward encoding of explictly including a start line field in every _func and inlinedCall. It is possible that we could compress this further in the future. e.g., functions with a prologue usually have <line of PC 0> == <start line>. In runtime.test, 95% of functions have <line of PC 0> == <start line>. According to bent, this is geomean +0.83% binary size vs master and -0.31% binary size vs 1.19. Note that //line directives can change the file and line numbers arbitrarily. The encoded start line is as adjusted by //line directives. Since this can change in the middle of a function, `line - start line` offset calculations may not be meaningful if //line directives are in use. For #55022. Change-Id: Iaabbc6dd4f85ffdda294266ef982ae838cc692f6 Reviewed-on: https://go-review.googlesource.com/c/go/+/429638 Run-TryBot: Michael Pratt <mpratt@google.com> Auto-Submit: Michael Pratt <mpratt@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com>
2022-09-07 13:23:19 -04:00
Args: uint32(fn.Args),
Locals: uint32(fn.Locals),
FuncID: fn.FuncID,
FuncFlag: fn.FuncFlag,
StartLine: fn.StartLine,
}
pc := &fn.Pcln
i := 0
o.File = make([]goobj.CUFileIndex, len(pc.UsedFiles))
for f := range pc.UsedFiles {
o.File[i] = f
i++
}
sort.Slice(o.File, func(i, j int) bool { return o.File[i] < o.File[j] })
o.InlTree = make([]goobj.InlTreeNode, len(pc.InlTree.nodes))
for i, inl := range pc.InlTree.nodes {
f, l := ctxt.getFileIndexAndLine(inl.Pos)
o.InlTree[i] = goobj.InlTreeNode{
Parent: int32(inl.Parent),
File: goobj.CUFileIndex(f),
Line: l,
Func: makeSymRef(inl.Func),
ParentPC: inl.ParentPC,
}
}
o.Write(&b)
p := b.Bytes()
isym := &LSym{
Type: objabi.SDATA, // for now, I don't think it matters
PkgIdx: goobj.PkgIdxSelf,
SymIdx: symidx,
P: append([]byte(nil), p...),
Size: int64(len(p)),
}
isym.Set(AttrIndexed, true)
symidx++
infosyms = append(infosyms, isym)
fn.FuncInfoSym = isym
b.Reset()
auxsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym, fn.sehUnwindInfoSym}
for _, s := range auxsyms {
if s == nil || s.Size == 0 {
continue
}
s.PkgIdx = goobj.PkgIdxSelf
s.SymIdx = symidx
s.Set(AttrIndexed, true)
symidx++
infosyms = append(infosyms, s)
}
}
ctxt.defs = append(ctxt.defs, infosyms...)
}
func writeAuxSymDebug(ctxt *Link, par *LSym, aux *LSym) {
// Most aux symbols (ex: funcdata) are not interesting--
// pick out just the DWARF ones for now.
if aux.Type != objabi.SDWARFLOC &&
aux.Type != objabi.SDWARFFCN &&
aux.Type != objabi.SDWARFABSFCN &&
aux.Type != objabi.SDWARFLINES &&
aux.Type != objabi.SDWARFRANGE {
return
}
ctxt.writeSymDebugNamed(aux, "aux for "+par.Name)
}
func debugAsmEmit(ctxt *Link) {
if ctxt.Debugasm > 0 {
ctxt.traverseSyms(traverseDefs, ctxt.writeSymDebug)
if ctxt.Debugasm > 1 {
fn := func(par *LSym, aux *LSym) {
writeAuxSymDebug(ctxt, par, aux)
}
ctxt.traverseAuxSyms(traverseAux, fn)
}
}
}
func (ctxt *Link) writeSymDebug(s *LSym) {
ctxt.writeSymDebugNamed(s, s.Name)
}
func (ctxt *Link) writeSymDebugNamed(s *LSym, name string) {
ver := ""
if ctxt.Debugasm > 1 {
ver = fmt.Sprintf("<%d>", s.ABI())
}
fmt.Fprintf(ctxt.Bso, "%s%s ", name, ver)
if s.Type != 0 {
fmt.Fprintf(ctxt.Bso, "%v ", s.Type)
}
if s.Static() {
fmt.Fprint(ctxt.Bso, "static ")
}
if s.DuplicateOK() {
fmt.Fprintf(ctxt.Bso, "dupok ")
}
if s.CFunc() {
fmt.Fprintf(ctxt.Bso, "cfunc ")
}
if s.NoSplit() {
fmt.Fprintf(ctxt.Bso, "nosplit ")
}
if s.Func() != nil && s.Func().FuncFlag&abi.FuncFlagTopFrame != 0 {
fmt.Fprintf(ctxt.Bso, "topframe ")
}
if s.Func() != nil && s.Func().FuncFlag&abi.FuncFlagAsm != 0 {
fmt.Fprintf(ctxt.Bso, "asm ")
}
fmt.Fprintf(ctxt.Bso, "size=%d", s.Size)
if s.Type == objabi.STEXT {
fn := s.Func()
fmt.Fprintf(ctxt.Bso, " args=%#x locals=%#x funcid=%#x align=%#x", uint64(fn.Args), uint64(fn.Locals), uint64(fn.FuncID), uint64(fn.Align))
if s.Leaf() {
fmt.Fprintf(ctxt.Bso, " leaf")
}
}
fmt.Fprintf(ctxt.Bso, "\n")
if s.Type == objabi.STEXT {
for p := s.Func().Text; p != nil; p = p.Link {
fmt.Fprintf(ctxt.Bso, "\t%#04x ", uint(int(p.Pc)))
if ctxt.Debugasm > 1 {
io.WriteString(ctxt.Bso, p.String())
} else {
p.InnermostString(ctxt.Bso)
}
fmt.Fprintln(ctxt.Bso)
}
}
for i := 0; i < len(s.P); i += 16 {
fmt.Fprintf(ctxt.Bso, "\t%#04x", uint(i))
j := i
for ; j < i+16 && j < len(s.P); j++ {
fmt.Fprintf(ctxt.Bso, " %02x", s.P[j])
}
for ; j < i+16; j++ {
fmt.Fprintf(ctxt.Bso, " ")
}
fmt.Fprintf(ctxt.Bso, " ")
for j = i; j < i+16 && j < len(s.P); j++ {
c := int(s.P[j])
b := byte('.')
if ' ' <= c && c <= 0x7e {
b = byte(c)
}
ctxt.Bso.WriteByte(b)
}
fmt.Fprintf(ctxt.Bso, "\n")
}
sort.Sort(relocByOff(s.R)) // generate stable output
for _, r := range s.R {
name := ""
ver := ""
if r.Sym != nil {
name = r.Sym.Name
if ctxt.Debugasm > 1 {
ver = fmt.Sprintf("<%d>", r.Sym.ABI())
}
} else if r.Type == objabi.R_TLS_LE {
name = "TLS"
}
if ctxt.Arch.InFamily(sys.ARM, sys.PPC64) {
fmt.Fprintf(ctxt.Bso, "\trel %d+%d t=%d %s%s+%x\n", int(r.Off), r.Siz, r.Type, name, ver, uint64(r.Add))
} else {
fmt.Fprintf(ctxt.Bso, "\trel %d+%d t=%d %s%s+%d\n", int(r.Off), r.Siz, r.Type, name, ver, r.Add)
}
}
}
// relocByOff sorts relocations by their offsets.
type relocByOff []Reloc
func (x relocByOff) Len() int { return len(x) }
func (x relocByOff) Less(i, j int) bool { return x[i].Off < x[j].Off }
func (x relocByOff) Swap(i, j int) { x[i], x[j] = x[j], x[i] }