mirror of
https://github.com/golang/go
synced 2024-11-26 15:46:54 -07:00
cmd/compile, internal/abi: add FuncPCABIxxx intrinsics
When ABI wrappers are used, there are cases where in Go code we need the PC of the defined function instead of the ABI wrapper. Currently we work around this by define such functions as ABIInternal, even if they do not actually follow the internal ABI. This CL introduces internal/abi.FuncPCABIxxx functions as compiler intrinsics, which return the underlying defined function's entry PC if the argument is a direct reference of a function of the expected ABI, and reject it if it is of a different ABI. As a proof of concept, change runtime.goexit back to ABI0 and use internal/abi.FuncPCABI0 to retrieve its PC. Updates #44065. Change-Id: I02286f0f9d99e6a3090f9e8169dbafc6804a2da6 Reviewed-on: https://go-review.googlesource.com/c/go/+/304232 Trust: Cherry Zhang <cherryyz@google.com> Run-TryBot: Cherry Zhang <cherryyz@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
parent
691e1b84c1
commit
1421516973
@ -287,10 +287,11 @@ func NeedFuncSym(fn *ir.Func) {
|
||||
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)
|
||||
|
@ -497,6 +497,43 @@ func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node {
|
||||
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
|
||||
}
|
||||
|
@ -544,6 +544,14 @@ func (o *orderState) call(nn ir.Node) {
|
||||
|
||||
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)
|
||||
|
||||
@ -1796,3 +1804,18 @@ func (o *orderState) wrapGoDefer(n *ir.GoDeferStmt) {
|
||||
// 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
|
||||
}
|
||||
|
@ -51,3 +51,27 @@ func (b *IntArgRegBitmap) Set(i int) {
|
||||
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
|
||||
|
76
src/internal/abi/abi_test.go
Normal file
76
src/internal/abi/abi_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
// 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))
|
||||
}
|
||||
}
|
27
src/internal/abi/abi_test.s
Normal file
27
src/internal/abi/abi_test.s
Normal file
@ -0,0 +1,27 @@
|
||||
// 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)
|
14
src/internal/abi/export_test.go
Normal file
14
src/internal/abi/export_test.go
Normal file
@ -0,0 +1,14 @@
|
||||
// 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)
|
||||
}
|
22
src/internal/abi/testdata/x.go
vendored
Normal file
22
src/internal/abi/testdata/x.go
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// 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
|
||||
}
|
6
src/internal/abi/testdata/x.s
vendored
Normal file
6
src/internal/abi/testdata/x.s
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// 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
|
@ -1576,11 +1576,8 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$0
|
||||
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
|
||||
|
@ -5,6 +5,7 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/cpu"
|
||||
"internal/goexperiment"
|
||||
"runtime/internal/atomic"
|
||||
@ -2022,7 +2023,7 @@ func oneNewExtraM() {
|
||||
// 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
|
||||
@ -4310,7 +4311,7 @@ func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerp
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user