Source file src/cmd/compile/internal/staticdata/data.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package staticdata
     6  
     7  import (
     8  	"encoding/base64"
     9  	"fmt"
    10  	"go/constant"
    11  	"io"
    12  	"os"
    13  	"slices"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  
    18  	"cmd/compile/internal/base"
    19  	"cmd/compile/internal/ir"
    20  	"cmd/compile/internal/objw"
    21  	"cmd/compile/internal/types"
    22  	"cmd/internal/hash"
    23  	"cmd/internal/obj"
    24  	"cmd/internal/objabi"
    25  	"cmd/internal/src"
    26  )
    27  
    28  // InitAddrOffset writes the static name symbol lsym to n, it does not modify n.
    29  // It's the caller responsibility to make sure lsym is from ONAME/PEXTERN node.
    30  func InitAddrOffset(n *ir.Name, noff int64, lsym *obj.LSym, off int64) {
    31  	if n.Op() != ir.ONAME {
    32  		base.Fatalf("InitAddr n op %v", n.Op())
    33  	}
    34  	if n.Sym() == nil {
    35  		base.Fatalf("InitAddr nil n sym")
    36  	}
    37  	s := n.Linksym()
    38  	s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, off)
    39  }
    40  
    41  // InitAddr is InitAddrOffset, with offset fixed to 0.
    42  func InitAddr(n *ir.Name, noff int64, lsym *obj.LSym) {
    43  	InitAddrOffset(n, noff, lsym, 0)
    44  }
    45  
    46  // InitSlice writes a static slice symbol {lsym, lencap, lencap} to n+noff, it does not modify n.
    47  // It's the caller responsibility to make sure lsym is from ONAME node.
    48  func InitSlice(n *ir.Name, noff int64, lsym *obj.LSym, lencap int64) {
    49  	s := n.Linksym()
    50  	s.WriteAddr(base.Ctxt, noff, types.PtrSize, lsym, 0)
    51  	s.WriteInt(base.Ctxt, noff+types.SliceLenOffset, types.PtrSize, lencap)
    52  	s.WriteInt(base.Ctxt, noff+types.SliceCapOffset, types.PtrSize, lencap)
    53  }
    54  
    55  func InitSliceBytes(nam *ir.Name, off int64, s string) {
    56  	if nam.Op() != ir.ONAME {
    57  		base.Fatalf("InitSliceBytes %v", nam)
    58  	}
    59  	InitSlice(nam, off, slicedata(nam.Pos(), s), int64(len(s)))
    60  }
    61  
    62  const (
    63  	stringSymPrefix  = "go:string."
    64  	stringSymPattern = ".gostring.%d.%s"
    65  )
    66  
    67  // shortHashString converts the hash to a string for use with stringSymPattern.
    68  // We cut it to 16 bytes and then base64-encode to make it even smaller.
    69  func shortHashString(hash []byte) string {
    70  	return base64.StdEncoding.EncodeToString(hash[:16])
    71  }
    72  
    73  // StringSym returns a symbol containing the string s.
    74  // The symbol contains the string data, not a string header.
    75  func StringSym(pos src.XPos, s string) (data *obj.LSym) {
    76  	var symname string
    77  	if len(s) > 100 {
    78  		// Huge strings are hashed to avoid long names in object files.
    79  		// Indulge in some paranoia by writing the length of s, too,
    80  		// as protection against length extension attacks.
    81  		// Same pattern is known to fileStringSym below.
    82  		h := hash.New32()
    83  		io.WriteString(h, s)
    84  		symname = fmt.Sprintf(stringSymPattern, len(s), shortHashString(h.Sum(nil)))
    85  	} else {
    86  		// Small strings get named directly by their contents.
    87  		symname = strconv.Quote(s)
    88  	}
    89  
    90  	symdata := base.Ctxt.Lookup(stringSymPrefix + symname)
    91  	if !symdata.OnList() {
    92  		off := dstringdata(symdata, 0, s, pos, "string")
    93  		objw.Global(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL)
    94  		symdata.Set(obj.AttrContentAddressable, true)
    95  		symdata.Align = 1
    96  	}
    97  
    98  	return symdata
    99  }
   100  
   101  // StringSymNoCommon is like StringSym, but produces a symbol that is not content-
   102  // addressable. This symbol is not supposed to appear in the final binary, it is
   103  // only used to pass string arguments to the linker like R_USENAMEDMETHOD does.
   104  func StringSymNoCommon(s string) (data *obj.LSym) {
   105  	var nameSym obj.LSym
   106  	nameSym.WriteString(base.Ctxt, 0, len(s), s)
   107  	objw.Global(&nameSym, int32(len(s)), obj.RODATA)
   108  	return &nameSym
   109  }
   110  
   111  // maxFileSize is the maximum file size permitted by the linker
   112  // (see issue #9862).
   113  const maxFileSize = int64(2e9)
   114  
   115  // fileStringSym returns a symbol for the contents and the size of file.
   116  // If readonly is true, the symbol shares storage with any literal string
   117  // or other file with the same content and is placed in a read-only section.
   118  // If readonly is false, the symbol is a read-write copy separate from any other,
   119  // for use as the backing store of a []byte.
   120  // The content hash of file is copied into hashBytes. (If hash is nil, nothing is copied.)
   121  // The returned symbol contains the data itself, not a string header.
   122  func fileStringSym(pos src.XPos, file string, readonly bool, hashBytes []byte) (*obj.LSym, int64, error) {
   123  	f, err := os.Open(file)
   124  	if err != nil {
   125  		return nil, 0, err
   126  	}
   127  	defer f.Close()
   128  	info, err := f.Stat()
   129  	if err != nil {
   130  		return nil, 0, err
   131  	}
   132  	if !info.Mode().IsRegular() {
   133  		return nil, 0, fmt.Errorf("not a regular file")
   134  	}
   135  	size := info.Size()
   136  	if size <= 1*1024 {
   137  		data, err := io.ReadAll(f)
   138  		if err != nil {
   139  			return nil, 0, err
   140  		}
   141  		if int64(len(data)) != size {
   142  			return nil, 0, fmt.Errorf("file changed between reads")
   143  		}
   144  		var sym *obj.LSym
   145  		if readonly {
   146  			sym = StringSym(pos, string(data))
   147  		} else {
   148  			sym = slicedata(pos, string(data))
   149  		}
   150  		if len(hashBytes) > 0 {
   151  			sum := hash.Sum32(data)
   152  			copy(hashBytes, sum[:])
   153  		}
   154  		return sym, size, nil
   155  	}
   156  	if size > maxFileSize {
   157  		// ggloblsym takes an int32,
   158  		// and probably the rest of the toolchain
   159  		// can't handle such big symbols either.
   160  		// See golang.org/issue/9862.
   161  		return nil, 0, fmt.Errorf("file too large (%d bytes > %d bytes)", size, maxFileSize)
   162  	}
   163  
   164  	// File is too big to read and keep in memory.
   165  	// Compute hashBytes if needed for read-only content hashing or if the caller wants it.
   166  	var sum []byte
   167  	if readonly || len(hashBytes) > 0 {
   168  		h := hash.New32()
   169  		n, err := io.Copy(h, f)
   170  		if err != nil {
   171  			return nil, 0, err
   172  		}
   173  		if n != size {
   174  			return nil, 0, fmt.Errorf("file changed between reads")
   175  		}
   176  		sum = h.Sum(nil)
   177  		copy(hashBytes, sum)
   178  	}
   179  
   180  	var symdata *obj.LSym
   181  	if readonly {
   182  		symname := fmt.Sprintf(stringSymPattern, size, shortHashString(sum))
   183  		symdata = base.Ctxt.Lookup(stringSymPrefix + symname)
   184  		if !symdata.OnList() {
   185  			info := symdata.NewFileInfo()
   186  			info.Name = file
   187  			info.Size = size
   188  			objw.Global(symdata, int32(size), obj.DUPOK|obj.RODATA|obj.LOCAL)
   189  			symdata.Align = 1
   190  			// Note: AttrContentAddressable cannot be set here,
   191  			// because the content-addressable-handling code
   192  			// does not know about file symbols.
   193  		}
   194  	} else {
   195  		// Emit a zero-length data symbol
   196  		// and then fix up length and content to use file.
   197  		symdata = slicedata(pos, "")
   198  		symdata.Size = size
   199  		symdata.Type = objabi.SNOPTRDATA
   200  		info := symdata.NewFileInfo()
   201  		info.Name = file
   202  		info.Size = size
   203  	}
   204  
   205  	return symdata, size, nil
   206  }
   207  
   208  var slicedataGen int
   209  
   210  func slicedata(pos src.XPos, s string) *obj.LSym {
   211  	slicedataGen++
   212  	symname := fmt.Sprintf(".gobytes.%d", slicedataGen)
   213  	lsym := types.LocalPkg.Lookup(symname).LinksymABI(obj.ABI0)
   214  	off := dstringdata(lsym, 0, s, pos, "slice")
   215  	objw.Global(lsym, int32(off), obj.NOPTR|obj.LOCAL)
   216  	lsym.Align = 1
   217  
   218  	return lsym
   219  }
   220  
   221  func dstringdata(s *obj.LSym, off int, t string, pos src.XPos, what string) int {
   222  	// Objects that are too large will cause the data section to overflow right away,
   223  	// causing a cryptic error message by the linker. Check for oversize objects here
   224  	// and provide a useful error message instead.
   225  	if int64(len(t)) > 2e9 {
   226  		base.ErrorfAt(pos, 0, "%v with length %v is too big", what, len(t))
   227  		return 0
   228  	}
   229  
   230  	s.WriteString(base.Ctxt, int64(off), len(t), t)
   231  	return off + len(t)
   232  }
   233  
   234  var (
   235  	funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym)
   236  	funcsyms   []*ir.Name // functions that need function value symbols
   237  )
   238  
   239  // FuncLinksym returns n·f, the function value symbol for n.
   240  func FuncLinksym(n *ir.Name) *obj.LSym {
   241  	if n.Op() != ir.ONAME || n.Class != ir.PFUNC {
   242  		base.Fatalf("expected func name: %v", n)
   243  	}
   244  	s := n.Sym()
   245  
   246  	// funcsymsmu here serves to protect not just mutations of funcsyms (below),
   247  	// but also the package lookup of the func sym name,
   248  	// since this function gets called concurrently from the backend.
   249  	// There are no other concurrent package lookups in the backend,
   250  	// except for the types package, which is protected separately.
   251  	// Reusing funcsymsmu to also cover this package lookup
   252  	// avoids a general, broader, expensive package lookup mutex.
   253  	funcsymsmu.Lock()
   254  	sf, existed := s.Pkg.LookupOK(ir.FuncSymName(s))
   255  	if !existed {
   256  		funcsyms = append(funcsyms, n)
   257  	}
   258  	funcsymsmu.Unlock()
   259  
   260  	return sf.Linksym()
   261  }
   262  
   263  func GlobalLinksym(n *ir.Name) *obj.LSym {
   264  	if n.Op() != ir.ONAME || n.Class != ir.PEXTERN {
   265  		base.Fatalf("expected global variable: %v", n)
   266  	}
   267  	return n.Linksym()
   268  }
   269  
   270  func WriteFuncSyms() {
   271  	slices.SortFunc(funcsyms, func(a, b *ir.Name) int {
   272  		return strings.Compare(a.Linksym().Name, b.Linksym().Name)
   273  	})
   274  	for _, nam := range funcsyms {
   275  		s := nam.Sym()
   276  		sf := s.Pkg.Lookup(ir.FuncSymName(s)).Linksym()
   277  
   278  		// While compiling package runtime, we might try to create
   279  		// funcsyms for functions from both types.LocalPkg and
   280  		// ir.Pkgs.Runtime.
   281  		if base.Flag.CompilingRuntime && sf.OnList() {
   282  			continue
   283  		}
   284  
   285  		// Function values must always reference ABIInternal
   286  		// entry points.
   287  		target := s.Linksym()
   288  		if target.ABI() != obj.ABIInternal {
   289  			base.Fatalf("expected ABIInternal: %v has %v", target, target.ABI())
   290  		}
   291  		objw.SymPtr(sf, 0, target, 0)
   292  		objw.Global(sf, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
   293  	}
   294  }
   295  
   296  // InitConst writes the static literal c to n.
   297  // Neither n nor c is modified.
   298  func InitConst(n *ir.Name, noff int64, c ir.Node, wid int) {
   299  	if n.Op() != ir.ONAME {
   300  		base.Fatalf("InitConst n op %v", n.Op())
   301  	}
   302  	if n.Sym() == nil {
   303  		base.Fatalf("InitConst nil n sym")
   304  	}
   305  	if c.Op() == ir.ONIL {
   306  		return
   307  	}
   308  	if c.Op() != ir.OLITERAL {
   309  		base.Fatalf("InitConst c op %v", c.Op())
   310  	}
   311  	s := n.Linksym()
   312  	switch u := c.Val(); u.Kind() {
   313  	case constant.Bool:
   314  		i := int64(obj.Bool2int(constant.BoolVal(u)))
   315  		s.WriteInt(base.Ctxt, noff, wid, i)
   316  
   317  	case constant.Int:
   318  		s.WriteInt(base.Ctxt, noff, wid, ir.IntVal(c.Type(), u))
   319  
   320  	case constant.Float:
   321  		f, _ := constant.Float64Val(u)
   322  		switch c.Type().Kind() {
   323  		case types.TFLOAT32:
   324  			s.WriteFloat32(base.Ctxt, noff, float32(f))
   325  		case types.TFLOAT64:
   326  			s.WriteFloat64(base.Ctxt, noff, f)
   327  		}
   328  
   329  	case constant.Complex:
   330  		re, _ := constant.Float64Val(constant.Real(u))
   331  		im, _ := constant.Float64Val(constant.Imag(u))
   332  		switch c.Type().Kind() {
   333  		case types.TCOMPLEX64:
   334  			s.WriteFloat32(base.Ctxt, noff, float32(re))
   335  			s.WriteFloat32(base.Ctxt, noff+4, float32(im))
   336  		case types.TCOMPLEX128:
   337  			s.WriteFloat64(base.Ctxt, noff, re)
   338  			s.WriteFloat64(base.Ctxt, noff+8, im)
   339  		}
   340  
   341  	case constant.String:
   342  		i := constant.StringVal(u)
   343  		symdata := StringSym(n.Pos(), i)
   344  		s.WriteAddr(base.Ctxt, noff, types.PtrSize, symdata, 0)
   345  		s.WriteInt(base.Ctxt, noff+int64(types.PtrSize), types.PtrSize, int64(len(i)))
   346  
   347  	default:
   348  		base.Fatalf("InitConst unhandled OLITERAL %v", c)
   349  	}
   350  }
   351  

View as plain text