]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/compile: enable Asan check for global variables
authorfanzha02 <fannie.zhang@arm.com>
Thu, 29 Apr 2021 09:02:53 +0000 (17:02 +0800)
committerIan Lance Taylor <iant@google.com>
Fri, 22 Apr 2022 04:42:23 +0000 (04:42 +0000)
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.

Change-Id: I664e74dcabf5dc7ed46802859174606454e8f1d3
Reviewed-on: https://go-review.googlesource.com/c/go/+/321715
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Fannie Zhang <Fannie.Zhang@arm.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
17 files changed:
misc/cgo/testsanitizers/asan_test.go
misc/cgo/testsanitizers/cc_test.go
src/cmd/compile/internal/base/base.go
src/cmd/compile/internal/gc/obj.go
src/cmd/compile/internal/noder/noder.go
src/cmd/compile/internal/noder/object.go
src/cmd/compile/internal/noder/reader.go
src/cmd/compile/internal/pkginit/init.go
src/cmd/compile/internal/pkginit/initAsanGlobals.go [new file with mode: 0644]
src/cmd/go/alldocs.go
src/cmd/go/internal/work/build.go
src/cmd/go/internal/work/init.go
src/runtime/asan.go
src/runtime/asan/asan.go
src/runtime/asan0.go
src/runtime/asan_amd64.s
src/runtime/asan_arm64.s

index ff578ac63e1cdd26d95a52e87e5a747e078475a4..23392c00f6d1dafbfe647e40ba03d055658b913e 100644 (file)
@@ -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 4,
+       // otherwise a segmentation fault will occur.
+       if !compilerRequiredAsanVersion() {
+               t.Skipf("skipping: too old version of compiler")
+       }
 
        t.Parallel()
        requireOvercommit(t)
index 05b77932b4cbab8cd7fc36fc34a27e60a3af26da..ee3c3bf28ccb706f5cf95d53c08e067d1eb14938 100644 (file)
@@ -235,6 +235,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 true
+       default:
+               return false
+       }
+}
+
 type compilerCheck struct {
        once sync.Once
        err  error
index 39ce8e66f7313ebb94b7975075b5593eadbc6750..5e1493e275a34fda7252a1a75b063037b9483e28 100644 (file)
@@ -70,6 +70,7 @@ var NoInstrumentPkgs = []string{
        "runtime/msan",
        "runtime/asan",
        "internal/cpu",
+       "buildcfg",
 }
 
 // Don't insert racefuncenter/racefuncexit into the following packages.
index fe8b6e9d4589478dc15c229a337bfe3bc5b738f9..fea2df85e5fc5decf7d83e60432e4dfcf4dfce26 100644 (file)
@@ -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
        }
index 9a42b5afd1734dfccaef061777964c3bdc836b59..c4c2db5f78a2d03fdba081786c4d1c4f0c84e790 100644 (file)
@@ -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
index e8dbaac1613e90c4ceebac3ce113668de06fec43..ee9e0e26800d4807eaf4d2d521e27ba94a0e78a4 100644 (file)
@@ -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)
                        }
index 1350c22467f98ac9314e07816d0b86ab9ffda8f6..10861717f3d3f3e7742f2a89041e832452e91b4e 100644 (file)
@@ -643,7 +643,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))
index 32e95bedc23e5f4476b2cc091a7e8ae8baf99d4a..d94482a962d2c4199ba9e5824a630dde390dc19f 100644 (file)
@@ -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 {
diff --git a/src/cmd/compile/internal/pkginit/initAsanGlobals.go b/src/cmd/compile/internal/pkginit/initAsanGlobals.go
new file mode 100644 (file)
index 0000000..7276791
--- /dev/null
@@ -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
+}
index 6fdb4f93a330c2bbedbd8fb9261bb140f19dfb07..7193ab6c7edfad2c4c53ae9b93a2241ba30670ee 100644 (file)
 //     -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 4 and higher.
 //     -v
 //             print the names of packages as they are compiled.
 //     -work
index e9a8ee6cb3a5cc0b7299c22f36fe5abb65ae6aee..d69eb7a9bf7e816882b240a5f45d006ec644bd8a 100644 (file)
@@ -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 4 and higher.
        -v
                print the names of packages as they are compiled.
        -work
index 26192ecaed11cad5ebcbbd6c21ba7b9d0462777c..22e29e87f40efe39721553201c540d733ffe9114 100644 (file)
@@ -14,9 +14,13 @@ import (
        "cmd/internal/quoted"
        "cmd/internal/sys"
        "fmt"
+       exec "internal/execabs"
        "os"
        "path/filepath"
+       "regexp"
        "runtime"
+       "strconv"
+       "strings"
 )
 
 func BuildInit() {
@@ -107,6 +111,15 @@ 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 4,
+       // otherwise a segmentation fault will occur.
+       if cfg.BuildASan {
+               compilerRequiredAsanVersion()
+       }
+
        mode := "race"
        if cfg.BuildMSan {
                mode = "msan"
@@ -310,3 +323,47 @@ func buildModeInit() {
                }
        }
 }
+
+// compilerRequiredAsanVersion checks whether the compiler is the version required by Asan.
+func compilerRequiredAsanVersion() {
+       cc := os.Getenv("CC")
+       isgcc := false
+       if strings.HasPrefix(cc, "gcc") {
+               isgcc = true
+       } else if !strings.HasPrefix(cc, "clang") {
+               fmt.Fprintf(os.Stderr, "-asan requires C compiler is gcc or clang, not %s", cc)
+               base.SetExitStatus(2)
+               base.Exit()
+       }
+       out, err := exec.Command(cc, "-v").CombinedOutput()
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "-asan fails to check C compiler %s version: %v", cc, err)
+               base.SetExitStatus(2)
+               base.Exit()
+       }
+       re := regexp.MustCompile(`version ([0-9]+)\.([0-9]+)\.([0-9]+)`)
+       matches := re.FindSubmatch(out)
+       if len(matches) < 3 {
+               fmt.Fprintf(os.Stderr, "-asan fails to check C compiler %s version: %s", cc, out)
+       }
+       major, err1 := strconv.Atoi(string(matches[1]))
+       minor, err2 := strconv.Atoi(string(matches[2]))
+       if err1 != nil || err2 != nil {
+               fmt.Fprintf(os.Stderr, "-asan fails to check C compiler %s version: %v, %v", cc, err1, err2)
+               base.SetExitStatus(2)
+               base.Exit()
+       }
+       if isgcc {
+               if major < 7 {
+                       fmt.Fprintf(os.Stderr, "-asan is not supported with gcc %d.%d; requires gcc 7 or later", major, minor)
+                       base.SetExitStatus(2)
+                       base.Exit()
+               }
+       } else {
+               if major < 4 {
+                       fmt.Fprintf(os.Stderr, "-asan is not supported with clang %d.%d; requires clang 4 or later", major, minor)
+                       base.SetExitStatus(2)
+                       base.Exit()
+               }
+       }
+}
index 8c41e418f7ce83be0e3dd843929596743a5f439b..25b83277e6d7a1ef638e136ec06a52cd094c2cf7 100644 (file)
@@ -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
index bab2362c51e3125865e88ce9c8b2f21c928e4060..3e41d60d93c7a3344936c317dc304fc6623404ff 100644 (file)
@@ -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"
index d5478d6bee87b48efebbdcba565b98f52243b9d0..0948786200abdd3cc1e002389e9509a6b7a59a44 100644 (file)
@@ -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") }
index 3857350020241c7b5506462610595257362b81ee..0489aa86dd44dc82fec45b6be6632397cc083e4a 100644 (file)
@@ -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)
index 5ed03c932bd8803697e0af5e7679bbc36b527c3c..697c98206ea569d3a6059853544bf1e33eb0a33c 100644 (file)
@@ -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