mirror of
https://github.com/golang/go
synced 2024-09-23 17:10:13 -06:00
cmd/compile: enable Asan check for global variables
With this patch, -asan option can detect the error memory access to global variables. So this patch makes a few changes: 1. Add the asanregisterglobals runtime support function, which calls asan runtime function _asan_register_globals to register global variables. 2. Create a new initialization function for the package being compiled. This function initializes an array of instrumented global variables and pass it to function runtime.asanregisterglobals. An instrumented global variable has trailing redzone. 3. Writes the new size of instrumented global variables that have trailing redzones into object file. 4. Notice that the current implementation is only compatible with the ASan library from version v7 to v9. Therefore, using the -asan option requires that the gcc version is not less than 7 and the clang version is less than 4, otherwise a segmentation fault will occur. So this patch adds a check on whether the compiler being used is a supported version in cmd/go. (This is a redo of CL 401775 with a fix for a build break due to an intervening commit that removed the internal/execabs package.) Updates #44853. Change-Id: I719d4ef2b22cb2d5516e1494cd453c3efb47d6c7 Reviewed-on: https://go-review.googlesource.com/c/go/+/403851 Auto-Submit: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
parent
c570f0eda2
commit
1b0f9fbb67
@ -22,6 +22,14 @@ func TestASAN(t *testing.T) {
|
||||
if !aSanSupported(goos, goarch) {
|
||||
t.Skipf("skipping on %s/%s; -asan option is not supported.", goos, goarch)
|
||||
}
|
||||
// The current implementation is only compatible with the ASan library from version
|
||||
// v7 to v9 (See the description in src/runtime/asan/asan.go). Therefore, using the
|
||||
// -asan option must use a compatible version of ASan library, which requires that
|
||||
// the gcc version is not less than 7 and the clang version is not less than 9,
|
||||
// otherwise a segmentation fault will occur.
|
||||
if !compilerRequiredAsanVersion() {
|
||||
t.Skipf("skipping: too old version of compiler")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
requireOvercommit(t)
|
||||
|
@ -184,17 +184,16 @@ func compilerVersion() (version, error) {
|
||||
var match [][]byte
|
||||
if bytes.HasPrefix(out, []byte("gcc")) {
|
||||
compiler.name = "gcc"
|
||||
|
||||
cmd, err := cc("-dumpversion")
|
||||
cmd, err := cc("-v")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := cmd.Output()
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
// gcc, but does not support gcc's "-dumpversion" flag?!
|
||||
// gcc, but does not support gcc's "-v" flag?!
|
||||
return err
|
||||
}
|
||||
gccRE := regexp.MustCompile(`(\d+)\.(\d+)`)
|
||||
gccRE := regexp.MustCompile(`gcc version (\d+)\.(\d+)`)
|
||||
match = gccRE.FindSubmatch(out)
|
||||
} else {
|
||||
clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
|
||||
@ -235,6 +234,22 @@ func compilerSupportsLocation() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// compilerRequiredAsanVersion reports whether the compiler is the version required by Asan.
|
||||
func compilerRequiredAsanVersion() bool {
|
||||
compiler, err := compilerVersion()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
switch compiler.name {
|
||||
case "gcc":
|
||||
return compiler.major >= 7
|
||||
case "clang":
|
||||
return compiler.major >= 9
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type compilerCheck struct {
|
||||
once sync.Once
|
||||
err error
|
||||
|
@ -70,6 +70,7 @@ var NoInstrumentPkgs = []string{
|
||||
"runtime/msan",
|
||||
"runtime/asan",
|
||||
"internal/cpu",
|
||||
"buildcfg",
|
||||
}
|
||||
|
||||
// Don't insert racefuncenter/racefuncexit into the following packages.
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/noder"
|
||||
"cmd/compile/internal/objw"
|
||||
"cmd/compile/internal/pkginit"
|
||||
"cmd/compile/internal/reflectdata"
|
||||
"cmd/compile/internal/staticdata"
|
||||
"cmd/compile/internal/typecheck"
|
||||
@ -110,7 +111,6 @@ func dumpCompilerObj(bout *bio.Writer) {
|
||||
func dumpdata() {
|
||||
numExterns := len(typecheck.Target.Externs)
|
||||
numDecls := len(typecheck.Target.Decls)
|
||||
|
||||
dumpglobls(typecheck.Target.Externs)
|
||||
reflectdata.CollectPTabs()
|
||||
numExports := len(typecheck.Target.Exports)
|
||||
@ -287,7 +287,20 @@ func ggloblnod(nam *ir.Name) {
|
||||
if nam.Type() != nil && !nam.Type().HasPointers() {
|
||||
flags |= obj.NOPTR
|
||||
}
|
||||
base.Ctxt.Globl(s, nam.Type().Size(), flags)
|
||||
size := nam.Type().Size()
|
||||
linkname := nam.Sym().Linkname
|
||||
name := nam.Sym().Name
|
||||
|
||||
// We've skipped linkname'd globals's instrument, so we can skip them here as well.
|
||||
if base.Flag.ASan && linkname == "" && pkginit.InstrumentGlobalsMap[name] != nil {
|
||||
// Write the new size of instrumented global variables that have
|
||||
// trailing redzones into object file.
|
||||
rzSize := pkginit.GetRedzoneSizeForGlobal(size)
|
||||
sizeWithRZ := rzSize + size
|
||||
base.Ctxt.Globl(s, sizeWithRZ, flags)
|
||||
} else {
|
||||
base.Ctxt.Globl(s, size, flags)
|
||||
}
|
||||
if nam.LibfuzzerExtraCounter() {
|
||||
s.Type = objabi.SLIBFUZZER_EXTRA_COUNTER
|
||||
}
|
||||
|
@ -442,7 +442,7 @@ func parseGoEmbed(args string) ([]string, error) {
|
||||
// the name, normally "pkg.init", is altered to "pkg.init.0".
|
||||
var renameinitgen int
|
||||
|
||||
func renameinit() *types.Sym {
|
||||
func Renameinit() *types.Sym {
|
||||
s := typecheck.LookupNum("init.", renameinitgen)
|
||||
renameinitgen++
|
||||
return s
|
||||
|
@ -104,7 +104,7 @@ func (g *irgen) obj(obj types2.Object) *ir.Name {
|
||||
var typ *types.Type
|
||||
if recv := sig.Recv(); recv == nil {
|
||||
if obj.Name() == "init" {
|
||||
sym = renameinit()
|
||||
sym = Renameinit()
|
||||
} else {
|
||||
sym = g.sym(obj)
|
||||
}
|
||||
|
@ -645,7 +645,7 @@ func (pr *pkgReader) objIdx(idx int, implicits, explicits []*types.Type) ir.Node
|
||||
|
||||
case pkgbits.ObjFunc:
|
||||
if sym.Name == "init" {
|
||||
sym = renameinit()
|
||||
sym = Renameinit()
|
||||
}
|
||||
name := do(ir.ONAME, true)
|
||||
setType(name, r.signature(sym.Pkg, nil))
|
||||
|
@ -7,6 +7,7 @@ package pkginit
|
||||
import (
|
||||
"cmd/compile/internal/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/noder"
|
||||
"cmd/compile/internal/objw"
|
||||
"cmd/compile/internal/staticinit"
|
||||
"cmd/compile/internal/typecheck"
|
||||
@ -83,6 +84,58 @@ func Task() *ir.Name {
|
||||
}
|
||||
deps = append(deps, n.(*ir.Name).Linksym())
|
||||
}
|
||||
if base.Flag.ASan {
|
||||
// Make an initialization function to call runtime.asanregisterglobals to register an
|
||||
// array of instrumented global variables when -asan is enabled. An instrumented global
|
||||
// variable is described by a structure.
|
||||
// See the _asan_global structure declared in src/runtime/asan/asan.go.
|
||||
//
|
||||
// func init {
|
||||
// var globals []_asan_global {...}
|
||||
// asanregisterglobals(&globals[0], len(globals))
|
||||
// }
|
||||
for _, n := range typecheck.Target.Externs {
|
||||
if canInstrumentGlobal(n) {
|
||||
name := n.Sym().Name
|
||||
InstrumentGlobalsMap[name] = n
|
||||
InstrumentGlobalsSlice = append(InstrumentGlobalsSlice, n)
|
||||
}
|
||||
}
|
||||
ni := len(InstrumentGlobalsMap)
|
||||
if ni != 0 {
|
||||
// Make an init._ function.
|
||||
base.Pos = base.AutogeneratedPos
|
||||
typecheck.DeclContext = ir.PEXTERN
|
||||
name := noder.Renameinit()
|
||||
fnInit := typecheck.DeclFunc(name, ir.NewFuncType(base.Pos, nil, nil, nil))
|
||||
|
||||
// Get an array of intrumented global variables.
|
||||
globals := instrumentGlobals(fnInit)
|
||||
|
||||
// Call runtime.asanregisterglobals function to poison redzones.
|
||||
// runtime.asanregisterglobals(unsafe.Pointer(&globals[0]), ni)
|
||||
asanf := typecheck.NewName(ir.Pkgs.Runtime.Lookup("asanregisterglobals"))
|
||||
ir.MarkFunc(asanf)
|
||||
asanf.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{
|
||||
types.NewField(base.Pos, nil, types.Types[types.TUNSAFEPTR]),
|
||||
types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]),
|
||||
}, nil))
|
||||
asancall := ir.NewCallExpr(base.Pos, ir.OCALL, asanf, nil)
|
||||
asancall.Args.Append(typecheck.ConvNop(typecheck.NodAddr(
|
||||
ir.NewIndexExpr(base.Pos, globals, ir.NewInt(0))), types.Types[types.TUNSAFEPTR]))
|
||||
asancall.Args.Append(typecheck.ConvNop(ir.NewInt(int64(ni)), types.Types[types.TUINTPTR]))
|
||||
|
||||
fnInit.Body.Append(asancall)
|
||||
typecheck.FinishFuncBody()
|
||||
typecheck.Func(fnInit)
|
||||
ir.CurFunc = fnInit
|
||||
typecheck.Stmts(fnInit.Body)
|
||||
ir.CurFunc = nil
|
||||
|
||||
typecheck.Target.Decls = append(typecheck.Target.Decls, fnInit)
|
||||
typecheck.Target.Inits = append(typecheck.Target.Inits, fnInit)
|
||||
}
|
||||
}
|
||||
|
||||
// Record user init functions.
|
||||
for _, fn := range typecheck.Target.Inits {
|
||||
|
241
src/cmd/compile/internal/pkginit/initAsanGlobals.go
Normal file
241
src/cmd/compile/internal/pkginit/initAsanGlobals.go
Normal file
@ -0,0 +1,241 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
package pkginit
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"cmd/compile/internal/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/typecheck"
|
||||
"cmd/compile/internal/types"
|
||||
"cmd/internal/src"
|
||||
)
|
||||
|
||||
// instrumentGlobals declares a global array of _asan_global structures and initializes it.
|
||||
func instrumentGlobals(fn *ir.Func) *ir.Name {
|
||||
asanGlobalStruct, asanLocationStruct, defStringstruct := createtypes()
|
||||
lname := typecheck.Lookup
|
||||
tconv := typecheck.ConvNop
|
||||
// Make a global array of asanGlobalStruct type.
|
||||
// var asanglobals []asanGlobalStruct
|
||||
arraytype := types.NewArray(asanGlobalStruct, int64(len(InstrumentGlobalsMap)))
|
||||
symG := lname(".asanglobals")
|
||||
globals := typecheck.NewName(symG)
|
||||
globals.SetType(arraytype)
|
||||
globals.Class = ir.PEXTERN
|
||||
symG.Def = globals
|
||||
typecheck.Target.Externs = append(typecheck.Target.Externs, globals)
|
||||
// Make a global array of asanLocationStruct type.
|
||||
// var asanL []asanLocationStruct
|
||||
arraytype = types.NewArray(asanLocationStruct, int64(len(InstrumentGlobalsMap)))
|
||||
symL := lname(".asanL")
|
||||
asanlocation := typecheck.NewName(symL)
|
||||
asanlocation.SetType(arraytype)
|
||||
asanlocation.Class = ir.PEXTERN
|
||||
symL.Def = asanlocation
|
||||
typecheck.Target.Externs = append(typecheck.Target.Externs, asanlocation)
|
||||
// Make three global string variables to pass the global name and module name
|
||||
// and the name of the source file that defines it.
|
||||
// var asanName string
|
||||
// var asanModulename string
|
||||
// var asanFilename string
|
||||
symL = lname(".asanName")
|
||||
asanName := typecheck.NewName(symL)
|
||||
asanName.SetType(types.Types[types.TSTRING])
|
||||
asanName.Class = ir.PEXTERN
|
||||
symL.Def = asanName
|
||||
typecheck.Target.Externs = append(typecheck.Target.Externs, asanName)
|
||||
|
||||
symL = lname(".asanModulename")
|
||||
asanModulename := typecheck.NewName(symL)
|
||||
asanModulename.SetType(types.Types[types.TSTRING])
|
||||
asanModulename.Class = ir.PEXTERN
|
||||
symL.Def = asanModulename
|
||||
typecheck.Target.Externs = append(typecheck.Target.Externs, asanModulename)
|
||||
|
||||
symL = lname(".asanFilename")
|
||||
asanFilename := typecheck.NewName(symL)
|
||||
asanFilename.SetType(types.Types[types.TSTRING])
|
||||
asanFilename.Class = ir.PEXTERN
|
||||
symL.Def = asanFilename
|
||||
typecheck.Target.Externs = append(typecheck.Target.Externs, asanFilename)
|
||||
|
||||
var init ir.Nodes
|
||||
var c ir.Node
|
||||
// globals[i].odrIndicator = 0 is the default, no need to set it explicitly here.
|
||||
for i, n := range InstrumentGlobalsSlice {
|
||||
setField := func(f string, val ir.Node, i int) {
|
||||
r := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT,
|
||||
ir.NewIndexExpr(base.Pos, globals, ir.NewInt(int64(i))), lname(f)), val)
|
||||
init.Append(typecheck.Stmt(r))
|
||||
}
|
||||
// globals[i].beg = uintptr(unsafe.Pointer(&n))
|
||||
c = tconv(typecheck.NodAddr(n), types.Types[types.TUNSAFEPTR])
|
||||
c = tconv(c, types.Types[types.TUINTPTR])
|
||||
setField("beg", c, i)
|
||||
// Assign globals[i].size.
|
||||
g := n.(*ir.Name)
|
||||
size := g.Type().Size()
|
||||
c = tconv(ir.NewInt(size), types.Types[types.TUINTPTR])
|
||||
setField("size", c, i)
|
||||
// Assign globals[i].sizeWithRedzone.
|
||||
rzSize := GetRedzoneSizeForGlobal(size)
|
||||
sizeWithRz := rzSize + size
|
||||
c = tconv(ir.NewInt(sizeWithRz), types.Types[types.TUINTPTR])
|
||||
setField("sizeWithRedzone", c, i)
|
||||
// The C string type is terminated by a null charactor "\0", Go should use three-digit
|
||||
// octal "\000" or two-digit hexadecimal "\x00" to create null terminated string.
|
||||
// asanName = symbol's linkname + "\000"
|
||||
// globals[i].name = (*defString)(unsafe.Pointer(&asanName)).data
|
||||
name := g.Linksym().Name
|
||||
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanName, ir.NewString(name+"\000"))))
|
||||
c = tconv(typecheck.NodAddr(asanName), types.Types[types.TUNSAFEPTR])
|
||||
c = tconv(c, types.NewPtr(defStringstruct))
|
||||
c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
|
||||
setField("name", c, i)
|
||||
|
||||
// Set the name of package being compiled as a unique identifier of a module.
|
||||
// asanModulename = pkgName + "\000"
|
||||
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanModulename, ir.NewString(types.LocalPkg.Name+"\000"))))
|
||||
c = tconv(typecheck.NodAddr(asanModulename), types.Types[types.TUNSAFEPTR])
|
||||
c = tconv(c, types.NewPtr(defStringstruct))
|
||||
c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
|
||||
setField("moduleName", c, i)
|
||||
// Assign asanL[i].filename, asanL[i].line, asanL[i].column
|
||||
// and assign globals[i].location = uintptr(unsafe.Pointer(&asanL[i]))
|
||||
asanLi := ir.NewIndexExpr(base.Pos, asanlocation, ir.NewInt(int64(i)))
|
||||
filename := ir.NewString(base.Ctxt.PosTable.Pos(n.Pos()).Filename() + "\000")
|
||||
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, asanFilename, filename)))
|
||||
c = tconv(typecheck.NodAddr(asanFilename), types.Types[types.TUNSAFEPTR])
|
||||
c = tconv(c, types.NewPtr(defStringstruct))
|
||||
c = ir.NewSelectorExpr(base.Pos, ir.ODOT, c, lname("data"))
|
||||
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("filename")), c)))
|
||||
line := ir.NewInt(int64(n.Pos().Line()))
|
||||
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("line")), line)))
|
||||
col := ir.NewInt(int64(n.Pos().Col()))
|
||||
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, asanLi, lname("column")), col)))
|
||||
c = tconv(typecheck.NodAddr(asanLi), types.Types[types.TUNSAFEPTR])
|
||||
c = tconv(c, types.Types[types.TUINTPTR])
|
||||
setField("sourceLocation", c, i)
|
||||
}
|
||||
fn.Body.Append(init...)
|
||||
return globals
|
||||
}
|
||||
|
||||
// createtypes creates the asanGlobal, asanLocation and defString struct type.
|
||||
// Go compiler does not refer to the C types, we represent the struct field
|
||||
// by a uintptr, then use type conversion to make copies of the data.
|
||||
// E.g., (*defString)(asanGlobal.name).data to C string.
|
||||
//
|
||||
// Keep in sync with src/runtime/asan/asan.go.
|
||||
// type asanGlobal struct {
|
||||
// beg uintptr
|
||||
// size uintptr
|
||||
// size_with_redzone uintptr
|
||||
// name uintptr
|
||||
// moduleName uintptr
|
||||
// hasDynamicInit uintptr
|
||||
// sourceLocation uintptr
|
||||
// odrIndicator uintptr
|
||||
// }
|
||||
//
|
||||
// type asanLocation struct {
|
||||
// filename uintptr
|
||||
// line int32
|
||||
// column int32
|
||||
// }
|
||||
//
|
||||
// defString is synthesized struct type meant to capture the underlying
|
||||
// implementations of string.
|
||||
// type defString struct {
|
||||
// data uintptr
|
||||
// len uintptr
|
||||
// }
|
||||
|
||||
func createtypes() (*types.Type, *types.Type, *types.Type) {
|
||||
up := types.Types[types.TUINTPTR]
|
||||
i32 := types.Types[types.TINT32]
|
||||
fname := typecheck.Lookup
|
||||
nxp := src.NoXPos
|
||||
nfield := types.NewField
|
||||
asanGlobal := types.NewStruct(types.NoPkg, []*types.Field{
|
||||
nfield(nxp, fname("beg"), up),
|
||||
nfield(nxp, fname("size"), up),
|
||||
nfield(nxp, fname("sizeWithRedzone"), up),
|
||||
nfield(nxp, fname("name"), up),
|
||||
nfield(nxp, fname("moduleName"), up),
|
||||
nfield(nxp, fname("hasDynamicInit"), up),
|
||||
nfield(nxp, fname("sourceLocation"), up),
|
||||
nfield(nxp, fname("odrIndicator"), up),
|
||||
})
|
||||
types.CalcSize(asanGlobal)
|
||||
|
||||
asanLocation := types.NewStruct(types.NoPkg, []*types.Field{
|
||||
nfield(nxp, fname("filename"), up),
|
||||
nfield(nxp, fname("line"), i32),
|
||||
nfield(nxp, fname("column"), i32),
|
||||
})
|
||||
types.CalcSize(asanLocation)
|
||||
|
||||
defString := types.NewStruct(types.NoPkg, []*types.Field{
|
||||
types.NewField(nxp, fname("data"), up),
|
||||
types.NewField(nxp, fname("len"), up),
|
||||
})
|
||||
types.CalcSize(defString)
|
||||
|
||||
return asanGlobal, asanLocation, defString
|
||||
}
|
||||
|
||||
// Calculate redzone for globals.
|
||||
func GetRedzoneSizeForGlobal(size int64) int64 {
|
||||
maxRZ := int64(1 << 18)
|
||||
minRZ := int64(32)
|
||||
redZone := (size / minRZ / 4) * minRZ
|
||||
switch {
|
||||
case redZone > maxRZ:
|
||||
redZone = maxRZ
|
||||
case redZone < minRZ:
|
||||
redZone = minRZ
|
||||
}
|
||||
// Round up to multiple of minRZ.
|
||||
if size%minRZ != 0 {
|
||||
redZone += minRZ - (size % minRZ)
|
||||
}
|
||||
return redZone
|
||||
}
|
||||
|
||||
// InstrumentGlobalsMap contains only package-local (and unlinknamed from somewhere else)
|
||||
// globals.
|
||||
// And the key is the object name. For example, in package p, a global foo would be in this
|
||||
// map as "foo".
|
||||
// Consider range over maps is nondeterministic, make a slice to hold all the values in the
|
||||
// InstrumentGlobalsMap and iterate over the InstrumentGlobalsSlice.
|
||||
var InstrumentGlobalsMap = make(map[string]ir.Node)
|
||||
var InstrumentGlobalsSlice = make([]ir.Node, 0, 0)
|
||||
|
||||
func canInstrumentGlobal(g ir.Node) bool {
|
||||
if g.Op() != ir.ONAME {
|
||||
return false
|
||||
}
|
||||
n := g.(*ir.Name)
|
||||
if n.Class == ir.PFUNC {
|
||||
return false
|
||||
}
|
||||
if n.Sym().Pkg != types.LocalPkg {
|
||||
return false
|
||||
}
|
||||
// Do not instrument any _cgo_ related global variables, because they are declared in C code.
|
||||
if strings.Contains(n.Sym().Name, "cgo") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Do not instrument globals that are linknamed, because their home package will do the work.
|
||||
if n.Sym().Linkname != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -123,6 +123,8 @@
|
||||
// -asan
|
||||
// enable interoperation with address sanitizer.
|
||||
// Supported only on linux/arm64, linux/amd64.
|
||||
// Supported only on linux/amd64 or linux/arm64 and only with GCC 7 and higher
|
||||
// or Clang/LLVM 9 and higher.
|
||||
// -v
|
||||
// print the names of packages as they are compiled.
|
||||
// -work
|
||||
|
@ -79,6 +79,8 @@ and test commands:
|
||||
-asan
|
||||
enable interoperation with address sanitizer.
|
||||
Supported only on linux/arm64, linux/amd64.
|
||||
Supported only on linux/amd64 or linux/arm64 and only with GCC 7 and higher
|
||||
or Clang/LLVM 9 and higher.
|
||||
-v
|
||||
print the names of packages as they are compiled.
|
||||
-work
|
||||
|
@ -7,6 +7,7 @@
|
||||
package work
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/fsys"
|
||||
@ -15,8 +16,12 @@ import (
|
||||
"cmd/internal/sys"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func BuildInit() {
|
||||
@ -107,6 +112,19 @@ func instrumentInit() {
|
||||
base.SetExitStatus(2)
|
||||
base.Exit()
|
||||
}
|
||||
// The current implementation is only compatible with the ASan library from version
|
||||
// v7 to v9 (See the description in src/runtime/asan/asan.go). Therefore, using the
|
||||
// -asan option must use a compatible version of ASan library, which requires that
|
||||
// the gcc version is not less than 7 and the clang version is not less than 9,
|
||||
// otherwise a segmentation fault will occur.
|
||||
if cfg.BuildASan {
|
||||
if err := compilerRequiredAsanVersion(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
base.SetExitStatus(2)
|
||||
base.Exit()
|
||||
}
|
||||
}
|
||||
|
||||
mode := "race"
|
||||
if cfg.BuildMSan {
|
||||
mode = "msan"
|
||||
@ -310,3 +328,85 @@ func buildModeInit() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type version struct {
|
||||
name string
|
||||
major, minor int
|
||||
}
|
||||
|
||||
var compiler struct {
|
||||
sync.Once
|
||||
version
|
||||
err error
|
||||
}
|
||||
|
||||
// compilerVersion detects the version of $(go env CC).
|
||||
// It returns a non-nil error if the compiler matches a known version schema but
|
||||
// the version could not be parsed, or if $(go env CC) could not be determined.
|
||||
func compilerVersion() (version, error) {
|
||||
compiler.Once.Do(func() {
|
||||
compiler.err = func() error {
|
||||
compiler.name = "unknown"
|
||||
cc := os.Getenv("CC")
|
||||
out, err := exec.Command(cc, "--version").Output()
|
||||
if err != nil {
|
||||
// Compiler does not support "--version" flag: not Clang or GCC.
|
||||
return err
|
||||
}
|
||||
|
||||
var match [][]byte
|
||||
if bytes.HasPrefix(out, []byte("gcc")) {
|
||||
compiler.name = "gcc"
|
||||
out, err := exec.Command(cc, "-v").CombinedOutput()
|
||||
if err != nil {
|
||||
// gcc, but does not support gcc's "-v" flag?!
|
||||
return err
|
||||
}
|
||||
gccRE := regexp.MustCompile(`gcc version (\d+)\.(\d+)`)
|
||||
match = gccRE.FindSubmatch(out)
|
||||
} else {
|
||||
clangRE := regexp.MustCompile(`clang version (\d+)\.(\d+)`)
|
||||
if match = clangRE.FindSubmatch(out); len(match) > 0 {
|
||||
compiler.name = "clang"
|
||||
}
|
||||
}
|
||||
|
||||
if len(match) < 3 {
|
||||
return nil // "unknown"
|
||||
}
|
||||
if compiler.major, err = strconv.Atoi(string(match[1])); err != nil {
|
||||
return err
|
||||
}
|
||||
if compiler.minor, err = strconv.Atoi(string(match[2])); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
})
|
||||
return compiler.version, compiler.err
|
||||
}
|
||||
|
||||
// compilerRequiredAsanVersion is a copy of the function defined in
|
||||
// misc/cgo/testsanitizers/cc_test.go
|
||||
// compilerRequiredAsanVersion reports whether the compiler is the version
|
||||
// required by Asan.
|
||||
func compilerRequiredAsanVersion() error {
|
||||
compiler, err := compilerVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("-asan: the version of $(go env CC) could not be parsed")
|
||||
}
|
||||
|
||||
switch compiler.name {
|
||||
case "gcc":
|
||||
if compiler.major < 7 {
|
||||
return fmt.Errorf("-asan is not supported with C compiler %d.%d\n", compiler.major, compiler.minor)
|
||||
}
|
||||
case "clang":
|
||||
if compiler.major < 9 {
|
||||
return fmt.Errorf("-asan is not supported with C compiler %d.%d\n", compiler.major, compiler.minor)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("-asan: C compiler is not gcc or clang")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ env CGO_ENABLED=0
|
||||
[msan] ! stderr '-race'
|
||||
|
||||
[asan] ! go install -asan triv.go
|
||||
[asan] stderr '-asan requires cgo'
|
||||
[asan] stderr '(-asan: the version of $(go env CC) could not be parsed)|(-asan: C compiler is not gcc or clang)|(-asan is not supported with C compiler (\d+)\.(\d+))|(-asan requires cgo)'
|
||||
[asan] ! stderr '-msan'
|
||||
|
||||
-- triv.go --
|
@ -55,9 +55,13 @@ func asanunpoison(addr unsafe.Pointer, sz uintptr)
|
||||
//go:noescape
|
||||
func asanpoison(addr unsafe.Pointer, sz uintptr)
|
||||
|
||||
//go:noescape
|
||||
func asanregisterglobals(addr unsafe.Pointer, n uintptr)
|
||||
|
||||
// These are called from asan_GOARCH.s
|
||||
//
|
||||
//go:cgo_import_static __asan_read_go
|
||||
//go:cgo_import_static __asan_write_go
|
||||
//go:cgo_import_static __asan_unpoison_go
|
||||
//go:cgo_import_static __asan_poison_go
|
||||
//go:cgo_import_static __asan_register_globals_go
|
||||
|
@ -34,5 +34,43 @@ void __asan_poison_go(void *addr, uintptr_t sz) {
|
||||
__asan_poison_memory_region(addr, sz);
|
||||
}
|
||||
|
||||
// Keep in sync with the defination in compiler-rt
|
||||
// https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/asan/asan_interface_internal.h#L41
|
||||
// This structure is used to describe the source location of
|
||||
// a place where global was defined.
|
||||
struct _asan_global_source_location {
|
||||
const char *filename;
|
||||
int line_no;
|
||||
int column_no;
|
||||
};
|
||||
|
||||
// Keep in sync with the defination in compiler-rt
|
||||
// https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/asan/asan_interface_internal.h#L48
|
||||
// So far, the current implementation is only compatible with the ASan library from version v7 to v9.
|
||||
// https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/asan/asan_init_version.h
|
||||
// This structure describes an instrumented global variable.
|
||||
//
|
||||
// TODO: If a later version of the ASan library changes __asan_global or __asan_global_source_location
|
||||
// structure, we need to make the same changes.
|
||||
struct _asan_global {
|
||||
uintptr_t beg;
|
||||
uintptr_t size;
|
||||
uintptr_t size_with_redzone;
|
||||
const char *name;
|
||||
const char *module_name;
|
||||
uintptr_t has_dynamic_init;
|
||||
struct _asan_global_source_location *location;
|
||||
uintptr_t odr_indicator;
|
||||
};
|
||||
|
||||
|
||||
extern void __asan_register_globals(void*, long int);
|
||||
|
||||
// Register global variables.
|
||||
// The 'globals' is an array of structures describing 'n' globals.
|
||||
void __asan_register_globals_go(void *addr, uintptr_t n) {
|
||||
struct _asan_global *globals = (struct _asan_global *)(addr);
|
||||
__asan_register_globals(globals, n);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
@ -16,7 +16,8 @@ const asanenabled = false
|
||||
|
||||
// Because asanenabled is false, none of these functions should be called.
|
||||
|
||||
func asanread(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanwrite(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanunpoison(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanpoison(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanread(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanwrite(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanunpoison(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanpoison(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
func asanregisterglobals(addr unsafe.Pointer, sz uintptr) { throw("asan") }
|
||||
|
@ -61,6 +61,14 @@ TEXT runtime·asanpoison(SB), NOSPLIT, $0-16
|
||||
MOVQ $__asan_poison_go(SB), AX
|
||||
JMP asancall<>(SB)
|
||||
|
||||
// func runtime·asanregisterglobals(addr unsafe.Pointer, n uintptr)
|
||||
TEXT runtime·asanregisterglobals(SB), NOSPLIT, $0-16
|
||||
MOVD addr+0(FP), RARG0
|
||||
MOVD size+8(FP), RARG1
|
||||
// void __asan_register_globals_go(void *addr, uintptr_t n);
|
||||
MOVD $__asan_register_globals_go(SB), AX
|
||||
JMP asancall<>(SB)
|
||||
|
||||
// Switches SP to g0 stack and calls (AX). Arguments already set.
|
||||
TEXT asancall<>(SB), NOSPLIT, $0-0
|
||||
get_tls(R12)
|
||||
|
@ -50,6 +50,14 @@ TEXT runtime·asanpoison(SB), NOSPLIT, $0-16
|
||||
MOVD $__asan_poison_go(SB), FARG
|
||||
JMP asancall<>(SB)
|
||||
|
||||
// func runtime·asanregisterglobals(addr unsafe.Pointer, n uintptr)
|
||||
TEXT runtime·asanregisterglobals(SB), NOSPLIT, $0-16
|
||||
MOVD addr+0(FP), RARG0
|
||||
MOVD size+8(FP), RARG1
|
||||
// void __asan_register_globals_go(void *addr, uintptr_t n);
|
||||
MOVD $__asan_register_globals_go(SB), FARG
|
||||
JMP asancall<>(SB)
|
||||
|
||||
// Switches SP to g0 stack and calls (FARG). Arguments already set.
|
||||
TEXT asancall<>(SB), NOSPLIT, $0-0
|
||||
MOVD RSP, R19 // callee-saved
|
||||
|
Loading…
Reference in New Issue
Block a user