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

860 lines
21 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/objabi"
"cmd/internal/sys"
"crypto/sha1"
"encoding/binary"
"fmt"
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"
)
// 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 == "" {
flags |= goobj.ObjFlagNeedNameExpansion
}
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 {
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
}
}
// Pcdata
h.Offsets[goobj.BlkPcdata] = w.Offset()
for _, s := range ctxt.Text { // iteration order must match genFuncInfoSyms
// Because of the phase order, it's possible that we try to write an invalid
// object file, and the Pcln variables haven't been filled in. As such, we
// need to check that Pcsp exists, and assume the other pcln variables exist
// as well. Tests like test/fixedbugs/issue22200.go demonstrate this issue.
if fn := s.Func(); fn != nil && fn.Pcln.Pcsp != nil {
pc := &fn.Pcln
w.Bytes(pc.Pcsp.P)
w.Bytes(pc.Pcfile.P)
w.Bytes(pc.Pcline.P)
w.Bytes(pc.Pcinline.P)
for i := range pc.Pcdata {
w.Bytes(pc.Pcdata[i].P)
}
}
}
// 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
}
// 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) {
// 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.pkgpath != "" {
s.Name = strings.Replace(s.Name, "\"\".", w.pkgpath+".", -1)
}
// Don't put names of builtins into the string table (to save
// space).
if s.PkgIdx == goobj.PkgIdxBuiltin {
return
}
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
}
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() {
// We generally assume data symbols are natually aligned,
// except for strings. 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.
if s.Size != 0 && !strings.HasPrefix(s.Name, "go.string.") {
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
}
// don't bother setting align to 1.
}
}
if s.Size > cutoff {
w.ctxt.Diag("%s: symbol too large (%d bytes > %d bytes)", s.Name, s.Size, cutoff)
}
var o goobj.Sym
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")
}
b := contentHash64(s)
w.Bytes(b[:])
}
func (w *writer) Hash(s *LSym) {
if !s.ContentAddressable() {
panic("Hash of non-content-addressable symbol")
}
b := w.contentHash(s)
w.Bytes(b[:])
}
func contentHash64(s *LSym) goobj.Hash64Type {
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 := sha1.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))
h.Write(tmp[:8])
// Don't dedup type symbols with others, as they are in a different
// section.
if strings.HasPrefix(s.Name, "type.") {
h.Write([]byte{'T'})
} else {
h.Write([]byte{0})
}
// 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
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) {
var o goobj.Reloc
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) {
var o goobj.Aux
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)
}
for _, pcSym := range fn.Pcln.Pcdata {
w.aux1(goobj.AuxPcdata, pcSym)
}
}
}
// 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
}
var o goobj.RefFlags
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() {
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)
var o goobj.RefName
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++
}
n += len(fn.Pcln.Pcdata)
}
return n
}
// generate symbols for FuncInfo.
func genFuncInfoSyms(ctxt *Link) {
infosyms := make([]*LSym, 0, len(ctxt.Text))
hashedsyms := make([]*LSym, 0, 4*len(ctxt.Text))
preparePcSym := func(s *LSym) *LSym {
if s == nil {
return s
}
s.PkgIdx = goobj.PkgIdxHashed
s.SymIdx = int32(len(hashedsyms) + len(ctxt.hasheddefs))
s.Set(AttrIndexed, true)
hashedsyms = append(hashedsyms, s)
return s
}
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/asm, cmd/link, runtime: introduce FuncInfo flag bits The runtime traceback code has its own definition of which functions mark the top frame of a stack, separate from the TOPFRAME bits that exist in the assembly and are passed along in DWARF information. It's error-prone and redundant to have two different sources of truth. This CL provides the actual TOPFRAME bits to the runtime, so that the runtime can use those bits instead of reinventing its own category. This CL also adds a new bit, SPWRITE, which marks functions that write directly to SP (anything but adding and subtracting constants). Such functions must stop a traceback, because the traceback has no way to rederive the SP on entry. Again, the runtime has its own definition which is mostly correct, but also missing some functions. During ordinary goroutine context switches, such functions do not appear on the stack, so the incompleteness in the runtime usually doesn't matter. But profiling signals can arrive at any moment, and the runtime may crash during traceback if it attempts to unwind an SP-writing frame and gets out-of-sync with the actual stack. The runtime contains code to try to detect likely candidates but again it is incomplete. Deriving the SPWRITE bit automatically from the actual assembly code provides the complete truth, and passing it to the runtime lets the runtime use it. This CL is part of a stack adding windows/arm64 support (#36439), intended to land in the Go 1.17 cycle. This CL is, however, not windows/arm64-specific. It is cleanup meant to make the port (and future ports) easier. Change-Id: I227f53b23ac5b3dabfcc5e8ee3f00df4e113cf58 Reviewed-on: https://go-review.googlesource.com/c/go/+/288800 Trust: Russ Cox <rsc@golang.org> Trust: Jason A. Donenfeld <Jason@zx2c4.com> Reviewed-by: Cherry Zhang <cherryyz@google.com> Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-28 15:21:33 -05:00
Args: uint32(fn.Args),
Locals: uint32(fn.Locals),
FuncID: fn.FuncID,
FuncFlag: fn.FuncFlag,
}
pc := &fn.Pcln
o.Pcsp = makeSymRef(preparePcSym(pc.Pcsp))
o.Pcfile = makeSymRef(preparePcSym(pc.Pcfile))
o.Pcline = makeSymRef(preparePcSym(pc.Pcline))
o.Pcinline = makeSymRef(preparePcSym(pc.Pcinline))
o.Pcdata = make([]goobj.SymRef, len(pc.Pcdata))
for i, pcSym := range pc.Pcdata {
o.Pcdata[i] = makeSymRef(preparePcSym(pcSym))
}
o.Funcdataoff = make([]uint32, len(pc.Funcdataoff))
for i, x := range pc.Funcdataoff {
o.Funcdataoff[i] = uint32(x)
}
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 := getFileIndexAndLine(ctxt, 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)
isym := &LSym{
Type: objabi.SDATA, // for now, I don't think it matters
PkgIdx: goobj.PkgIdxSelf,
SymIdx: symidx,
P: append([]byte(nil), b.Bytes()...),
}
isym.Set(AttrIndexed, true)
symidx++
infosyms = append(infosyms, isym)
fn.FuncInfoSym = isym
b.Reset()
dwsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym}
for _, s := range dwsyms {
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...)
ctxt.hasheddefs = append(ctxt.hasheddefs, hashedsyms...)
}
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 ")
}
cmd/asm, cmd/link, runtime: introduce FuncInfo flag bits The runtime traceback code has its own definition of which functions mark the top frame of a stack, separate from the TOPFRAME bits that exist in the assembly and are passed along in DWARF information. It's error-prone and redundant to have two different sources of truth. This CL provides the actual TOPFRAME bits to the runtime, so that the runtime can use those bits instead of reinventing its own category. This CL also adds a new bit, SPWRITE, which marks functions that write directly to SP (anything but adding and subtracting constants). Such functions must stop a traceback, because the traceback has no way to rederive the SP on entry. Again, the runtime has its own definition which is mostly correct, but also missing some functions. During ordinary goroutine context switches, such functions do not appear on the stack, so the incompleteness in the runtime usually doesn't matter. But profiling signals can arrive at any moment, and the runtime may crash during traceback if it attempts to unwind an SP-writing frame and gets out-of-sync with the actual stack. The runtime contains code to try to detect likely candidates but again it is incomplete. Deriving the SPWRITE bit automatically from the actual assembly code provides the complete truth, and passing it to the runtime lets the runtime use it. This CL is part of a stack adding windows/arm64 support (#36439), intended to land in the Go 1.17 cycle. This CL is, however, not windows/arm64-specific. It is cleanup meant to make the port (and future ports) easier. Change-Id: I227f53b23ac5b3dabfcc5e8ee3f00df4e113cf58 Reviewed-on: https://go-review.googlesource.com/c/go/+/288800 Trust: Russ Cox <rsc@golang.org> Trust: Jason A. Donenfeld <Jason@zx2c4.com> Reviewed-by: Cherry Zhang <cherryyz@google.com> Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-28 15:21:33 -05:00
if s.Func() != nil && s.Func().FuncFlag&objabi.FuncFlag_TOPFRAME != 0 {
fmt.Fprintf(ctxt.Bso, "topframe ")
}
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", uint64(fn.Args), uint64(fn.Locals), uint64(fn.FuncID))
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] }