diff --git a/misc/cgo/testsanitizers/asan_test.go b/misc/cgo/testsanitizers/asan_test.go index ff578ac63e1cdd..b5be1ffa278082 100644 --- a/misc/cgo/testsanitizers/asan_test.go +++ b/misc/cgo/testsanitizers/asan_test.go @@ -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) diff --git a/misc/cgo/testsanitizers/cc_test.go b/misc/cgo/testsanitizers/cc_test.go index 05b77932b4cbab..31850e2441783b 100644 --- a/misc/cgo/testsanitizers/cc_test.go +++ b/misc/cgo/testsanitizers/cc_test.go @@ -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 diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go index 39ce8e66f7313e..5e1493e275a34f 100644 --- a/src/cmd/compile/internal/base/base.go +++ b/src/cmd/compile/internal/base/base.go @@ -70,6 +70,7 @@ var NoInstrumentPkgs = []string{ "runtime/msan", "runtime/asan", "internal/cpu", + "buildcfg", } // Don't insert racefuncenter/racefuncexit into the following packages. diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index fe8b6e9d458947..fea2df85e5fc5d 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -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 } diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index 9a42b5afd1734d..c4c2db5f78a2d0 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -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 diff --git a/src/cmd/compile/internal/noder/object.go b/src/cmd/compile/internal/noder/object.go index e8dbaac1613e90..ee9e0e26800d48 100644 --- a/src/cmd/compile/internal/noder/object.go +++ b/src/cmd/compile/internal/noder/object.go @@ -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) } diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index 83ebe2477927d6..4e00dbdfd5117d 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -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)) diff --git a/src/cmd/compile/internal/pkginit/init.go b/src/cmd/compile/internal/pkginit/init.go index 32e95bedc23e5f..d94482a962d2c4 100644 --- a/src/cmd/compile/internal/pkginit/init.go +++ b/src/cmd/compile/internal/pkginit/init.go @@ -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 index 00000000000000..7276791d6e9612 --- /dev/null +++ b/src/cmd/compile/internal/pkginit/initAsanGlobals.go @@ -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 +} diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 6fdb4f93a330c2..15a6ff623ddc6e 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -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 diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index 2f3c8c7554c12a..feb82d8d38ca50 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -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 diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index 26192ecaed11ca..5bf548db32a297 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -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 +} diff --git a/src/cmd/go/testdata/script/install_msan_and_race_require_cgo.txt b/src/cmd/go/testdata/script/install_msan_and_race_and_asan_require_cgo.txt similarity index 63% rename from src/cmd/go/testdata/script/install_msan_and_race_require_cgo.txt rename to src/cmd/go/testdata/script/install_msan_and_race_and_asan_require_cgo.txt index 5e88f7b8dbdc72..d496eaa9cd9890 100644 --- a/src/cmd/go/testdata/script/install_msan_and_race_require_cgo.txt +++ b/src/cmd/go/testdata/script/install_msan_and_race_and_asan_require_cgo.txt @@ -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 -- diff --git a/src/runtime/asan.go b/src/runtime/asan.go index 8c41e418f7ce83..25b83277e6d7a1 100644 --- a/src/runtime/asan.go +++ b/src/runtime/asan.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 diff --git a/src/runtime/asan/asan.go b/src/runtime/asan/asan.go index bab2362c51e312..3e41d60d93c7a3 100644 --- a/src/runtime/asan/asan.go +++ b/src/runtime/asan/asan.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" diff --git a/src/runtime/asan0.go b/src/runtime/asan0.go index d5478d6bee87b4..0948786200abdd 100644 --- a/src/runtime/asan0.go +++ b/src/runtime/asan0.go @@ -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") } diff --git a/src/runtime/asan_amd64.s b/src/runtime/asan_amd64.s index 3857350020241c..0489aa86dd44dc 100644 --- a/src/runtime/asan_amd64.s +++ b/src/runtime/asan_amd64.s @@ -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) diff --git a/src/runtime/asan_arm64.s b/src/runtime/asan_arm64.s index 5ed03c932bd880..697c98206ea569 100644 --- a/src/runtime/asan_arm64.s +++ b/src/runtime/asan_arm64.s @@ -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