1// Copyright 2022 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 pkginit
6
7import (
8	"strings"
9
10	"cmd/compile/internal/base"
11	"cmd/compile/internal/ir"
12	"cmd/compile/internal/typecheck"
13	"cmd/compile/internal/types"
14	"cmd/internal/src"
15)
16
17// instrumentGlobals declares a global array of _asan_global structures and initializes it.
18func instrumentGlobals(fn *ir.Func) *ir.Name {
19	asanGlobalStruct, asanLocationStruct, defStringstruct := createtypes()
20	lname := typecheck.Lookup
21	tconv := typecheck.ConvNop
22	// Make a global array of asanGlobalStruct type.
23	// var asanglobals []asanGlobalStruct
24	arraytype := types.NewArray(asanGlobalStruct, int64(len(InstrumentGlobalsMap)))
25	symG := lname(".asanglobals")
26	globals := ir.NewNameAt(base.Pos, symG, arraytype)
27	globals.Class = ir.PEXTERN
28	symG.Def = globals
29	typecheck.Target.Externs = append(typecheck.Target.Externs, globals)
30	// Make a global array of asanLocationStruct type.
31	// var asanL []asanLocationStruct
32	arraytype = types.NewArray(asanLocationStruct, int64(len(InstrumentGlobalsMap)))
33	symL := lname(".asanL")
34	asanlocation := ir.NewNameAt(base.Pos, symL, arraytype)
35	asanlocation.Class = ir.PEXTERN
36	symL.Def = asanlocation
37	typecheck.Target.Externs = append(typecheck.Target.Externs, asanlocation)
38	// Make three global string variables to pass the global name and module name
39	// and the name of the source file that defines it.
40	// var asanName string
41	// var asanModulename string
42	// var asanFilename string
43	symL = lname(".asanName")
44	asanName := ir.NewNameAt(base.Pos, symL, types.Types[types.TSTRING])
45	asanName.Class = ir.PEXTERN
46	symL.Def = asanName
47	typecheck.Target.Externs = append(typecheck.Target.Externs, asanName)
48
49	symL = lname(".asanModulename")
50	asanModulename := ir.NewNameAt(base.Pos, symL, types.Types[types.TSTRING])
51	asanModulename.Class = ir.PEXTERN
52	symL.Def = asanModulename
53	typecheck.Target.Externs = append(typecheck.Target.Externs, asanModulename)
54
55	symL = lname(".asanFilename")
56	asanFilename := ir.NewNameAt(base.Pos, symL, types.Types[types.TSTRING])
57	asanFilename.Class = ir.PEXTERN
58	symL.Def = asanFilename
59	typecheck.Target.Externs = append(typecheck.Target.Externs, asanFilename)
60
61	var init ir.Nodes
62	var c ir.Node
63	// globals[i].odrIndicator = 0 is the default, no need to set it explicitly here.
64	for i, n := range InstrumentGlobalsSlice {
65		setField := func(f string, val ir.Node, i int) {
66			r := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT,
67				ir.NewIndexExpr(base.Pos, globals, ir.NewInt(base.Pos, int64(i))), lname(f)), val)
68			init.Append(typecheck.Stmt(r))
69		}
70		// globals[i].beg = uintptr(unsafe.Pointer(&n))
71		c = tconv(typecheck.NodAddr(n), types.Types[types.TUNSAFEPTR])
72		c = tconv(c, types.Types[types.TUINTPTR])
73		setField("beg", c, i)
74		// Assign globals[i].size.
75		g := n.(*ir.Name)
76		size := g.Type().Size()
77		c = typecheck.DefaultLit(ir.NewInt(base.Pos, size), types.Types[types.TUINTPTR])
78		setField("size", c, i)
79		// Assign globals[i].sizeWithRedzone.
80		rzSize := GetRedzoneSizeForGlobal(size)
81		sizeWithRz := rzSize + size
82		c = typecheck.DefaultLit(ir.NewInt(base.Pos, sizeWithRz), types.Types[types.TUINTPTR])
83		setField("sizeWithRedzone", c, i)
84		// The C string type is terminated by a null character "\0", Go should use three-digit
85		// octal "\000" or two-digit hexadecimal "\x00" to create null terminated string.
86		// asanName = symbol's linkname + "\000"
87		// globals[i].name = (*defString)(unsafe.Pointer(&asanName)).data
88		name := g.Linksym().Name
89		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanName, ir.NewString(base.Pos, name+"\000"))))
90		c = tconv(typecheck.NodAddr(asanName), types.Types[types.TUNSAFEPTR])
91		c = tconv(c, types.NewPtr(defStringstruct))
92		c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
93		setField("name", c, i)
94
95		// Set the name of package being compiled as a unique identifier of a module.
96		// asanModulename = pkgName + "\000"
97		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanModulename, ir.NewString(base.Pos, types.LocalPkg.Name+"\000"))))
98		c = tconv(typecheck.NodAddr(asanModulename), types.Types[types.TUNSAFEPTR])
99		c = tconv(c, types.NewPtr(defStringstruct))
100		c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
101		setField("moduleName", c, i)
102		// Assign asanL[i].filename, asanL[i].line, asanL[i].column
103		// and assign globals[i].location = uintptr(unsafe.Pointer(&asanL[i]))
104		asanLi := ir.NewIndexExpr(base.Pos, asanlocation, ir.NewInt(base.Pos, int64(i)))
105		filename := ir.NewString(base.Pos, base.Ctxt.PosTable.Pos(n.Pos()).Filename()+"\000")
106		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanFilename, filename)))
107		c = tconv(typecheck.NodAddr(asanFilename), types.Types[types.TUNSAFEPTR])
108		c = tconv(c, types.NewPtr(defStringstruct))
109		c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
110		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("filename")), c)))
111		line := ir.NewInt(base.Pos, int64(n.Pos().Line()))
112		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("line")), line)))
113		col := ir.NewInt(base.Pos, int64(n.Pos().Col()))
114		init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("column")), col)))
115		c = tconv(typecheck.NodAddr(asanLi), types.Types[types.TUNSAFEPTR])
116		c = tconv(c, types.Types[types.TUINTPTR])
117		setField("sourceLocation", c, i)
118	}
119	fn.Body.Append(init...)
120	return globals
121}
122
123// createtypes creates the asanGlobal, asanLocation and defString struct type.
124// Go compiler does not refer to the C types, we represent the struct field
125// by a uintptr, then use type conversion to make copies of the data.
126// E.g., (*defString)(asanGlobal.name).data to C string.
127//
128// Keep in sync with src/runtime/asan/asan.go.
129// type asanGlobal struct {
130//	beg               uintptr
131//	size              uintptr
132//	size_with_redzone uintptr
133//	name              uintptr
134//	moduleName        uintptr
135//	hasDynamicInit    uintptr
136//	sourceLocation    uintptr
137//	odrIndicator      uintptr
138// }
139//
140// type asanLocation struct {
141//	filename uintptr
142//	line     int32
143//	column   int32
144// }
145//
146// defString is synthesized struct type meant to capture the underlying
147// implementations of string.
148// type defString struct {
149//	data uintptr
150//	len  uintptr
151// }
152
153func createtypes() (*types.Type, *types.Type, *types.Type) {
154	up := types.Types[types.TUINTPTR]
155	i32 := types.Types[types.TINT32]
156	fname := typecheck.Lookup
157	nxp := src.NoXPos
158	nfield := types.NewField
159	asanGlobal := types.NewStruct([]*types.Field{
160		nfield(nxp, fname("beg"), up),
161		nfield(nxp, fname("size"), up),
162		nfield(nxp, fname("sizeWithRedzone"), up),
163		nfield(nxp, fname("name"), up),
164		nfield(nxp, fname("moduleName"), up),
165		nfield(nxp, fname("hasDynamicInit"), up),
166		nfield(nxp, fname("sourceLocation"), up),
167		nfield(nxp, fname("odrIndicator"), up),
168	})
169	types.CalcSize(asanGlobal)
170
171	asanLocation := types.NewStruct([]*types.Field{
172		nfield(nxp, fname("filename"), up),
173		nfield(nxp, fname("line"), i32),
174		nfield(nxp, fname("column"), i32),
175	})
176	types.CalcSize(asanLocation)
177
178	defString := types.NewStruct([]*types.Field{
179		types.NewField(nxp, fname("data"), up),
180		types.NewField(nxp, fname("len"), up),
181	})
182	types.CalcSize(defString)
183
184	return asanGlobal, asanLocation, defString
185}
186
187// Calculate redzone for globals.
188func GetRedzoneSizeForGlobal(size int64) int64 {
189	maxRZ := int64(1 << 18)
190	minRZ := int64(32)
191	redZone := (size / minRZ / 4) * minRZ
192	switch {
193	case redZone > maxRZ:
194		redZone = maxRZ
195	case redZone < minRZ:
196		redZone = minRZ
197	}
198	// Round up to multiple of minRZ.
199	if size%minRZ != 0 {
200		redZone += minRZ - (size % minRZ)
201	}
202	return redZone
203}
204
205// InstrumentGlobalsMap contains only package-local (and unlinknamed from somewhere else)
206// globals.
207// And the key is the object name. For example, in package p, a global foo would be in this
208// map as "foo".
209// Consider range over maps is nondeterministic, make a slice to hold all the values in the
210// InstrumentGlobalsMap and iterate over the InstrumentGlobalsSlice.
211var InstrumentGlobalsMap = make(map[string]ir.Node)
212var InstrumentGlobalsSlice = make([]ir.Node, 0, 0)
213
214func canInstrumentGlobal(g ir.Node) bool {
215	if g.Op() != ir.ONAME {
216		return false
217	}
218	n := g.(*ir.Name)
219	if n.Class == ir.PFUNC {
220		return false
221	}
222	if n.Sym().Pkg != types.LocalPkg {
223		return false
224	}
225	// Do not instrument any _cgo_ related global variables, because they are declared in C code.
226	if strings.Contains(n.Sym().Name, "cgo") {
227		return false
228	}
229
230	// Do not instrument globals that are linknamed, because their home package will do the work.
231	if n.Sym().Linkname != "" {
232		return false
233	}
234
235	return true
236}
237