return
}
s := fn.Nname.Sym()
- if base.Flag.CompilingRuntime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") {
- // runtime.getg(), getclosureptr(), getcallerpc(), and
- // getcallersp() are not real functions and so do not
- // get funcsyms.
+ if base.Flag.CompilingRuntime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") ||
+ (base.Ctxt.Pkgpath == "internal/abi" && (s.Name == "FuncPCABI0" || s.Name == "FuncPCABIInternal")) {
+ // runtime.getg(), getclosureptr(), getcallerpc(), getcallersp(),
+ // and internal/abi.FuncPCABIxxx() are not real functions and so
+ // do not get funcsyms.
return
}
funcsyms = append(funcsyms, fn.Nname)
directClosureCall(n)
}
+ if isFuncPCIntrinsic(n) {
+ // For internal/abi.FuncPCABIxxx(fn), if fn is a defined function, rewrite
+ // it to the address of the function of the ABI fn is defined.
+ name := n.X.(*ir.Name).Sym().Name
+ arg := n.Args[0]
+ var wantABI obj.ABI
+ switch name {
+ case "FuncPCABI0":
+ wantABI = obj.ABI0
+ case "FuncPCABIInternal":
+ wantABI = obj.ABIInternal
+ }
+ if isIfaceOfFunc(arg) {
+ fn := arg.(*ir.ConvExpr).X.(*ir.Name)
+ abi := fn.Func.ABI
+ if abi != wantABI {
+ base.ErrorfAt(n.Pos(), "internal/abi.%s expects an %v function, %s is defined as %v", name, wantABI, fn.Sym().Name, abi)
+ }
+ var e ir.Node = ir.NewLinksymExpr(n.Pos(), fn.Sym().LinksymABI(abi), types.Types[types.TUINTPTR])
+ e = ir.NewAddrExpr(n.Pos(), e)
+ e.SetType(types.Types[types.TUINTPTR].PtrTo())
+ e = ir.NewConvExpr(n.Pos(), ir.OCONVNOP, n.Type(), e)
+ return e
+ }
+ // fn is not a defined function. It must be ABIInternal.
+ // Read the address from func value, i.e. *(*uintptr)(idata(fn)).
+ if wantABI != obj.ABIInternal {
+ base.ErrorfAt(n.Pos(), "internal/abi.%s does not accept func expression, which is ABIInternal", name)
+ }
+ arg = walkExpr(arg, init)
+ var e ir.Node = ir.NewUnaryExpr(n.Pos(), ir.OIDATA, arg)
+ e.SetType(n.Type().PtrTo())
+ e = ir.NewStarExpr(n.Pos(), e)
+ e.SetType(n.Type())
+ return e
+ }
+
walkCall1(n, init)
return n
}
n := nn.(*ir.CallExpr)
typecheck.FixVariadicCall(n)
+
+ if isFuncPCIntrinsic(n) && isIfaceOfFunc(n.Args[0]) {
+ // For internal/abi.FuncPCABIxxx(fn), if fn is a defined function,
+ // do not introduce temporaries here, so it is easier to rewrite it
+ // to symbol address reference later in walk.
+ return
+ }
+
n.X = o.expr(n.X, nil)
o.exprList(n.Args)
// Finally, point the defer statement at the newly generated call.
n.Call = topcall
}
+
+// isFuncPCIntrinsic returns whether n is a direct call of internal/abi.FuncPCABIxxx functions.
+func isFuncPCIntrinsic(n *ir.CallExpr) bool {
+ if n.Op() != ir.OCALLFUNC || n.X.Op() != ir.ONAME {
+ return false
+ }
+ fn := n.X.(*ir.Name).Sym()
+ return (fn.Name == "FuncPCABI0" || fn.Name == "FuncPCABIInternal") &&
+ (fn.Pkg.Path == "internal/abi" || fn.Pkg == types.LocalPkg && base.Ctxt.Pkgpath == "internal/abi")
+}
+
+// isIfaceOfFunc returns whether n is an interface conversion from a direct reference of a func.
+func isIfaceOfFunc(n ir.Node) bool {
+ return n.Op() == ir.OCONVIFACE && n.(*ir.ConvExpr).X.Op() == ir.ONAME && n.(*ir.ConvExpr).X.(*ir.Name).Class == ir.PFUNC
+}
func (b *IntArgRegBitmap) Get(i int) bool {
return b[i/8]&(uint8(1)<<(i%8)) != 0
}
+
+// FuncPC* intrinsics.
+//
+// CAREFUL: In programs with plugins, FuncPC* can return different values
+// for the same function (because there are actually multiple copies of
+// the same function in the address space). To be safe, don't use the
+// results of this function in any == expression. It is only safe to
+// use the result as an address at which to start executing code.
+
+// FuncPCABI0 returns the entry PC of the function f, which must be a
+// direct reference of a function defined as ABI0. Otherwise it is a
+// compile-time error.
+//
+// Implemented as a compile intrinsic.
+func FuncPCABI0(f interface{}) uintptr
+
+// FuncPCABIInternal returns the entry PC of the function f. If f is a
+// direct reference of a function, it must be defined as ABIInternal.
+// Otherwise it is a compile-time error. If f is not a direct reference
+// of a defined function, it assumes that f is a func value. Otherwise
+// the behavior is undefined.
+//
+// Implemented as a compile intrinsic.
+func FuncPCABIInternal(f interface{}) uintptr
--- /dev/null
+// Copyright 2021 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 abi_test
+
+import (
+ "internal/abi"
+ "internal/testenv"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestFuncPC(t *testing.T) {
+ // Test that FuncPC* can get correct function PC.
+ pcFromAsm := abi.FuncPCTestFnAddr
+
+ // Test FuncPC for locally defined function
+ pcFromGo := abi.FuncPCTest()
+ if pcFromGo != pcFromAsm {
+ t.Errorf("FuncPC returns wrong PC, want %x, got %x", pcFromAsm, pcFromGo)
+ }
+
+ // Test FuncPC for imported function
+ pcFromGo = abi.FuncPCABI0(abi.FuncPCTestFn)
+ if pcFromGo != pcFromAsm {
+ t.Errorf("FuncPC returns wrong PC, want %x, got %x", pcFromAsm, pcFromGo)
+ }
+}
+
+func TestFuncPCCompileError(t *testing.T) {
+ // Test that FuncPC* on a function of a mismatched ABI is rejected.
+ testenv.MustHaveGoBuild(t)
+
+ // We want to test internal package, which we cannot normally import.
+ // Run the assembler and compiler manually.
+ tmpdir := t.TempDir()
+ asmSrc := filepath.Join("testdata", "x.s")
+ goSrc := filepath.Join("testdata", "x.go")
+ symabi := filepath.Join(tmpdir, "symabi")
+ obj := filepath.Join(tmpdir, "x.o")
+
+ // parse assembly code for symabi.
+ cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-gensymabis", "-o", symabi, asmSrc)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool asm -gensymabis failed: %v\n%s", err, out)
+ }
+
+ // compile go code.
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-symabis", symabi, "-o", obj, goSrc)
+ out, err = cmd.CombinedOutput()
+ if err == nil {
+ t.Fatalf("go tool compile did not fail")
+ }
+
+ // Expect errors in line 17, 18, 20, no errors on other lines.
+ want := []string{"x.go:17", "x.go:18", "x.go:20"}
+ got := strings.Split(string(out), "\n")
+ if got[len(got)-1] == "" {
+ got = got[:len(got)-1] // remove last empty line
+ }
+ for i, s := range got {
+ if !strings.Contains(s, want[i]) {
+ t.Errorf("did not error on line %s", want[i])
+ }
+ }
+ if len(got) != len(want) {
+ t.Errorf("unexpected number of errors, want %d, got %d", len(want), len(got))
+ }
+ if t.Failed() {
+ t.Logf("output:\n%s", string(out))
+ }
+}
--- /dev/null
+// Copyright 2021 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.
+
+#include "textflag.h"
+
+#ifdef GOARCH_386
+#define PTRSIZE 4
+#endif
+#ifdef GOARCH_arm
+#define PTRSIZE 4
+#endif
+#ifdef GOARCH_mips
+#define PTRSIZE 4
+#endif
+#ifdef GOARCH_mipsle
+#define PTRSIZE 4
+#endif
+#ifndef PTRSIZE
+#define PTRSIZE 8
+#endif
+
+TEXT internal∕abi·FuncPCTestFn(SB),NOSPLIT,$0-0
+ RET
+
+GLOBL internal∕abi·FuncPCTestFnAddr(SB), NOPTR, $PTRSIZE
+DATA internal∕abi·FuncPCTestFnAddr(SB)/PTRSIZE, $internal∕abi·FuncPCTestFn(SB)
--- /dev/null
+// Copyright 2021 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 abi
+
+func FuncPCTestFn()
+
+var FuncPCTestFnAddr uintptr // address of FuncPCTestFn, directly retrieved from assembly
+
+//go:noinline
+func FuncPCTest() uintptr {
+ return FuncPCABI0(FuncPCTestFn)
+}
--- /dev/null
+// Copyright 2021 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 x
+
+import "internal/abi"
+
+func Fn0() // defined in assembly
+
+func Fn1() {}
+
+var FnExpr func()
+
+func test() {
+ _ = abi.FuncPCABI0(Fn0) // line 16, no error
+ _ = abi.FuncPCABIInternal(Fn0) // line 17, error
+ _ = abi.FuncPCABI0(Fn1) // line 18, error
+ _ = abi.FuncPCABIInternal(Fn1) // line 19, no error
+ _ = abi.FuncPCABI0(FnExpr) // line 20, error
+ _ = abi.FuncPCABIInternal(FnExpr) // line 21, no error
+}
--- /dev/null
+// Copyright 2021 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.
+
+TEXT ·Fn0(SB), 0, $0-0
+ RET
RET
// The top-most function running on a goroutine
-// returns to goexit+PCQuantum. Defined as ABIInternal
-// so as to make it identifiable to traceback (this
-// function it used as a sentinel; traceback wants to
-// see the func PC, not a wrapper PC).
-TEXT runtime·goexit<ABIInternal>(SB),NOSPLIT|TOPFRAME,$0-0
+// returns to goexit+PCQuantum.
+TEXT runtime·goexit(SB),NOSPLIT|TOPFRAME,$0-0
BYTE $0x90 // NOP
CALL runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
package runtime
import (
+ "internal/abi"
"internal/cpu"
"internal/goexperiment"
"runtime/internal/atomic"
// the goroutine stack ends.
mp := allocm(nil, nil, -1)
gp := malg(4096)
- gp.sched.pc = funcPC(goexit) + sys.PCQuantum
+ gp.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum
gp.sched.sp = gp.stack.hi
gp.sched.sp -= 4 * sys.PtrSize // extra space in case of reads slightly beyond frame
gp.sched.lr = 0
memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
newg.sched.sp = sp
newg.stktopsp = sp
- newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
+ newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
newg.sched.g = guintptr(unsafe.Pointer(newg))
gostartcallfn(&newg.sched, fn)
newg.gopc = callerpc