
在介绍完如何使用和核心函数后,这里通过分析源码,看看它的实现原理。常见接口的定义位于:
github.com/agiledragon/gomonkey@v2.0.2+incompatible/patch.go
func ApplyFunc(target, double interface{}) *Patches {
return create().ApplyFunc(target, double)
}func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches {
return create().ApplyMethod(target, methodName, double)
}func ApplyGlobalVar(target, double interface{}) *Patches {
return create().ApplyGlobalVar(target, double)
}func ApplyFuncVar(target, double interface{}) *Patches {
return create().ApplyFuncVar(target, double)
}首先通过create创建patch对象,然后应用对应的方法:
func create() *Patches {
return &Patches{originals: make(map[reflect.Value][]byte), values: make(map[reflect.Value]reflect.Value), valueHolders: make(map[reflect.Value]reflect.Value)}
}NewPatches也是调用同样的方法
func NewPatches() *Patches {
return create()
}其结构体定义如下:
type Patches struct {
originals map[reflect.Value][]byte
values map[reflect.Value]reflect.Value
valueHolders map[reflect.Value]reflect.Value
}对应具体ApplyMethod方法,首先通过反射方法ApplyMethod根据方法名称获取对应的方法,接着通过反射方法ValueOf获取替换模板的reflect.Value,最后通过ApplyCore来实现替换。
func (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches {
m, ok := target.MethodByName(methodName)
if !ok {
panic("retrieve method by name failed")
}
d := reflect.ValueOf(double)
return this.ApplyCore(m.Func, d)
}同理
func (this *Patches) ApplyFunc(target, double interface{}) *Patches {
t := reflect.ValueOf(target)
d := reflect.ValueOf(double)
return this.ApplyCore(t, d)
}ApplyGlobalVar不同的是,它替换的是变量,所以使用的反射方法不一样
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches {
t := reflect.ValueOf(target)
if t.Type().Kind() != reflect.Ptr {
panic("target is not a pointer")
}
this.values[t] = reflect.ValueOf(t.Elem().Interface())
d := reflect.ValueOf(double)
t.Elem().Set(d)
return this
}接着看下ApplyCore方法,首先使用check方法检查下两者是不是都是方法,两者的类型是否一样。然后通过replace方法,用输入值替换原始值,并把原始值存储在originals变量里。
func (this *Patches) ApplyCore(target, double reflect.Value) *Patches {
this.check(target, double)
if _, ok := this.originals[target]; ok {
panic("patch has been existed")
}
this.valueHolders[double] = double
original := replace(*(*uintptr)(getPointer(target)), uintptr(getPointer(double)))
this.originals[target] = original
return this
}替换的时候使用了指针转化,获取函数的原始地址
func getPointer(v reflect.Value) unsafe.Pointer {
return (*funcValue)(unsafe.Pointer(&v)).p
}replace的过程就是指针的替换
func replace(target, double uintptr) []byte {
code := buildJmpDirective(double)
bytes := entryAddress(target, len(code))
original := make([]byte, len(bytes))
copy(original, bytes)
modifyBinary(target, code)
return original
}最后使用modifyBinary进行原始值的替换
func modifyBinary(target uintptr, bytes []byte) {
function := entryAddress(target, len(bytes))
page := entryAddress(pageStart(target), syscall.Getpagesize())
err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
if err != nil {
panic(err)
}
copy(function, bytes)
err = syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
if err != nil {
panic(err)
}
}使用 unix.Mprotect 函数来修改内存区域的保护属性。
至此核心流程介绍完了,批量逻辑是类似的
func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {
funcType := reflect.TypeOf(target)
t := reflect.ValueOf(target)
d := getDoubleFunc(funcType, outputs)
return this.ApplyCore(t, d)
}只不过,它将目标值进行了封装,变成了一个函数,函数内部其实也是用了反射逻辑
func getDoubleFunc(funcType reflect.Type, outputs []OutputCell) reflect.Value {
if funcType.NumOut() != len(outputs[0].Values) {
panic(fmt.Sprintf("func type has %v return values, but only %v values provided as double",
funcType.NumOut(), len(outputs[0].Values)))
}
slice := make([]Params, 0)
for _, output := range outputs {
t := 0
if output.Times <= 1 {
t = 1
} else {
t = output.Times
}
for j := 0; j < t; j++ {
slice = append(slice, output.Values)
}
}
i := 0
len := len(slice)
return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {
if i < len {
i++
return GetResultValues(funcType, slice[i-1]...)
}
panic("double seq is less than call seq")
})
}整体代码不多,只不过用了很多反射函数,本质上就是一个地址替换。
本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!