trap

package
v1.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2024 License: MIT Imports: 14 Imported by: 4

README

Problems

Infinite loop: trap into interceptor, should not be allowed

findfunc uses linear search, which might be slow when there are many modules, maybe a cache would help?

TODO

Is there a way to enumerate all functions?

What is a moduledata in the runtime's perspective, is it a go package or a go module(not likely)?

Maybe go link is better than init?Given that init needs some extra effort to hack.

Knowledge

runtime.Callers(skip, []pc) returns a slice of pcs of current stack

runtime.CallersFrame() returns an iterator over a slice of pcs which can be used to retrieve all frames as needed.

the runtime.moduledata

minpc,maxpc ---> used to search pc inittasks -> a list of init tasks modulename --->

ftab --> a list of offset and entry info of all funcs, offset are to be used in pclntable pclntable --> pclntable[funcOff] is type of _func example: f1 := funcInfo{(*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[i].funcoff])), datap}

ptab -> a list of exported functions

itablinks []*itab -> interface,type table

funcnametab

this section are all function names separated by \x00.

func printFuncNames(funcnametab []byte) {
	n := len(funcnametab)
	last := -1
	for i := 0; i < n; i++ {
		if funcnametab[i] == '\x00' {
			println(string(funcnametab[last+1 : i]))
			last = i
		}
	}
}

Would print about 2931 names, like:

go:buildid
...
slices.Grow[go.shape.[]uint8,go.shape.uint8]
...
encoding/json.appendString[go.shape.string]
slices.SortFunc[go.shape.[]encoding/json.reflectWithString,go.shape.struct { encoding/json.v reflect.Value; encoding/json.ks string }]
type:.eq.encoding/json.reflectWithString
type:.eq.struct { encoding/json.ptr interface {}; encoding/json.len int }
type:.eq.go.shape.struct { encoding/json.v reflect.Value; encoding/json.ks string }
github.com/xhd2015/xgo/runtime/pkg.Hello
github.com/xhd2015/xgo/runtime/pkg.Mass.Print
github.com/xhd2015/xgo/runtime/pkg.(*Person).Greet
github.com/xhd2015/xgo/runtime/pkg.Hello.func1
main.init.0
main.main
main.testArgs
main.num.add

In brief, it contains all functions compiled/linked into the binary, so that gives us a chance to list all functions.

Note, there are forms like go:buildid,slices.Grow[go.shape.[]uint8,go.shape.uint8], the [...] denotes instantiated generic params.

runtime._func

// Layout of in-memory per-function information prepared by linker
// See https://golang.org/s/go12symtab.
type _func struct {
...

pc(entryOff) = datap.text + entryOff

func (f funcInfo) entry() uintptr {
	return f.datap.textAddr(f.entryOff)
}

reflect.Func

// Non-nil func value points at data block.
// First word of data block is actual code.

NOTE: cannot take address of a function

p := &testReflect
ERROR: invalid operation: cannot take address of testReflect (value of type func())

A function symbol is itself a pointer to the function entry.

// f itself is a named variable in some place, its type is *byte
var v interface{} = f  ----> v.word = &f
reflect.ValueOf(f)  ---> 

A reflect.ValueOf(v) is just a wrapper around interface{}

Test pc meaning:

func main() {
	testReflect()
	fnWord := getReflectWord(testReflect)
	fmt.Printf("testReflect word: %x\n", fnWord)
	fnAddrPtr := (*unsafe.Pointer)((unsafe.Pointer)(fnWord))
	fmt.Printf("testReflect word target: %x\n", *fnAddrPtr)
	fmt.Println(testReflect)
}
func testReflect() {
	pc := runtime.Getcallerpc()
	entryPC := runtime.GetcallerFuncPC()

	fmt.Printf("testReflect caller pc: %x\n", pc)
	fmt.Printf("testReflect caller entry pc: %x\n", entryPC)
}

Output:

testReflect caller pc: c6423b5
testReflect caller entry pc: c642300
testReflect word: c678298
testReflect word target: c642300
0xc642300

Found that entryPC is the same thing with function symbol, this is a very important observation.

Explanation: a function symbol is entry to the function body, function types are either inserted by compiler statically or carried by interface dynamically. So a function symbol is considered *byte=PC, pointer to a readonly part.

an interface is a {type,word}, the ptr itself is allocated on heap, it has type *PC, i.e. {type:funcType, word: *PC}

getReflectWord

Get address of an interface

func getReflectWord(i interface{}) uintptr {
	type IHeader struct {
		typ  uintptr
		word uintptr
	}

	return (*IHeader)(unsafe.Pointer(&i)).word
}

How to list all functions at runtime?

func printFTab(m *moduledata, ftab []functab) {
	println("ftab len:", len(ftab))
	for i, f := range ftab {
		// funcoff -> offset to function info, like name
		// pc,_ := m.textOff(uintptr(f.entryoff))
		pc := m.textAddr(f.entryoff)
		fnInfo := funcInfo{(*_func)(unsafe.Pointer(&m.pclntable[f.funcoff])), m}
		print("ftab:", i)
		printsp()
		printhex(uint64(pc))
		printsp()
		println(m.funcName(fnInfo.nameOff))
	}
}

Output:

ftab len: 2933
ftab:0 0xe06e000 
ftab:1 0xe06e080 internal/abi.(*RegArgs).IntRegArgAddr
...
ftab:48 0xe06f5c0 type:.eq.internal/abi.UncommonType
ftab:49 0xe06f600 type:.eq.internal/abi.RegArgs
...
ftab:2930 0xe153f60 main.testArgs
ftab:2931 0xe154300 main.num.add
ftab:2932 0xe15465f lBreak

NOTE: there are some names starting with prefix type:.

How to get runtime type of a function

What is a type? Look at the interface{} structure:


Use runtime.resolveTypeOff

type moduledata{
	// ...
    types, etypes         uintptr
	// ...
}
types and etypes are the range of type data

How to construct an interface{} for a func using pc?

How to invoke a function

First, construct an interface with type set to func type, word set pointer to pc.

What about parameter names

symtab

../../runtime/symtab.go

How to construct a reflect.Value from pc?

Through my investigation, there is no type info from a PC value. Types are inserted at compile time by compiler.

A workaround: when calling __x_trap(), carry the function symbol with itself.

And for registration and invoking purepose, we make the program register the types automatically.

func init(){
	// building a PC -> type mapping
	registerFunc(A)
	registerFunc(B)
}

func A(){
 ...
}
type T struct{}
func (c *T) A(){
 ...
}

// T.A
// *T.A

Empty interface vs interface with methods

The reflect implementation:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
	typ  *abi.Type
	word unsafe.Pointer
}

// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
	// see ../runtime/iface.go:/Itab
	itab *struct {
		ityp *abi.Type // static interface type
		typ  *abi.Type // dynamic concrete type
		hash uint32    // copy of typ.hash
		_    [4]byte
		fun  [100000]unsafe.Pointer // method table
	}
	word unsafe.Pointer
}

funcType

// returns a function of
reflect.FuncOf = func(in, out []Type, variadic bool) Type

funcType

// funcType represents a function type.
//
// A *rtype for each in and out parameter is stored in an array that
// directly follows the funcType (and possibly its uncommonType). So
// a function type with one method, one input, and one output is:
//
//	struct {
//		funcType
//		uncommonType
//		[2]*rtype    // [0] is in, [1] is out
//	}
type funcType = abi.FuncType

type at runtime

// reflectOffs holds type offsets defined at run time by the reflect package.
//
// When a type is defined at run time, its *rtype data lives on the heap.
// There are a wide range of possible addresses the heap may use, that
// may not be representable as a 32-bit offset. Moreover the GC may
// one day start moving heap memory, in which case there is no stable
// offset that can be defined.
//
// To provide stable offsets, we add pin *rtype objects in a global map
// and treat the offset as an identifier. We use negative offsets that
// do not overlap with any compile-time module offsets.
//
// Entries are created by reflect.addReflectOff.
var reflectOffs struct {
	lock mutex
	next int32
	m    map[int32]unsafe.Pointer
	minv map[unsafe.Pointer]int32
}

FuncData

Including args info.

see go/src/internal/abi/symtab.go

FUNCDATA_ArgsPointerMaps    = 0
FUNCDATA_LocalsPointerMaps  = 1
FUNCDATA_StackObjects       = 2
FUNCDATA_InlTree            = 3
FUNCDATA_OpenCodedDeferInfo = 4
FUNCDATA_ArgInfo            = 5
FUNCDATA_ArgLiveInfo        = 6
FUNCDATA_WrapInfo           = 7

PCDATA

UnsafePoint is for gc?

PCDATA_UnsafePoint   = 0
PCDATA_StackMapIndex = 1
PCDATA_InlTreeIndex  = 2
PCDATA_ArgLiveIndex  = 3

IR

insert a function call

func addPrint(){
	for _, fn := range typecheck.Target.Funcs {
		callPrint := ir.NewCallExpr(base.AutogeneratedPos, ir.OCALL, typecheck.LookupRuntime("printstring"), []ir.Node{
			ir.NewBasicLit(base.AutogeneratedPos, types.Types[types.TSTRING], constant.MakeString("hello init\n")),
		})
	    callPrint = typecheck.Expr(callPrint)
		fn.Body.Prepend(callPrint)
	}
}

add an init function

  • use types.LocalPkg to create a pkg scope symbol
  • use ir.NewFunc to create the function, and create its body
  • use typecheck.Stmts to typecheck it's body(which will probably normalize expr to statement if needed)
  • append to typecheck.Target.Inits and typecheck.Target.Funcs
func addInit(){
	// init names are usually init.0, init.1, ...
	sym,exists := types.LocalPkg.LookupOK(fmt.Sprintf("init.%d", len(typecheck.Target.Inits)))
	if exists {
		panic(fmt.Errorf("init name error"))
	}
	regFuncs := ir.NewFunc(base.AutogeneratedPos, base.AutogeneratedPos, sym, types.NewSignature(nil, nil, nil))

	regFuncs.Body = []ir.Node{
		ir.NewCallExpr(base.AutogeneratedPos, ir.OCALL, typecheck.LookupRuntime("printstring"), []ir.Node{
			ir.NewBasicLit(base.AutogeneratedPos, types.Types[types.TSTRING], constant.MakeString("hello init\n")),
		}),
	}

	// this typecheck is required
	// to make subsequent steps work
	typecheck.Stmts(regFuncs.Body)

	typecheck.Target.Inits = append(typecheck.Target.Inits, regFuncs)
	typecheck.Target.Funcs = append(typecheck.Target.Funcs, regFuncs)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrAbort error = errors.New("abort trap interceptor")

Functions

func AddInterceptor

func AddInterceptor(interceptor *Interceptor) func()

func ClearLocalInterceptors

func ClearLocalInterceptors()

func Inspect added in v1.0.8

func Inspect(f interface{}) (recvPtr interface{}, funcInfo *core.FuncInfo)

Inspect make a call to f to capture its receiver pointer if is is bound method It can be used to get the unwrapped innermost function of a method wrapper. if f is a bound method, then guranteed that recvPtr cannot be nil

func Skip

func Skip()

Skip serves as mark to tell xgo not insert trap instructions for the function that calls Skip() NOTE: the function body is intenionally leave empty as trap.Skip() is just a mark that makes sense at compile time.

func WithInterceptor

func WithInterceptor(interceptor *Interceptor, f func())

WithInterceptor executes given f with interceptor setup. It can be used from init phase safely. it clears the interceptor after f finishes. the interceptor will be added to head, so it will gets firstly invoked. f cannot be nil.

NOTE: the implementation uses addLocalInterceptor even from init because it will be soon cleared without causing concurrent issues.

Types

type Interceptor

type Interceptor struct {
	Pre  func(ctx context.Context, f *core.FuncInfo, args core.Object, result core.Object) (data interface{}, err error)
	Post func(ctx context.Context, f *core.FuncInfo, args core.Object, result core.Object, data interface{}) error
}

func GetAllInterceptors

func GetAllInterceptors() []*Interceptor

func GetInterceptors

func GetInterceptors() []*Interceptor

func GetLocalInterceptors

func GetLocalInterceptors() []*Interceptor

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL