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