首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >golang源码分析:gomonkey(2)

golang源码分析:gomonkey(2)

作者头像
golangLeetcode
发布2026-03-18 18:10:43
发布2026-03-18 18:10:43
820
举报

在介绍完如何使用和核心函数后,这里通过分析源码,看看它的实现原理。常见接口的定义位于:

github.com/agiledragon/gomonkey@v2.0.2+incompatible/patch.go

代码语言:javascript
复制
func ApplyFunc(target, double interface{}) *Patches {
    return create().ApplyFunc(target, double)
}
代码语言:javascript
复制
func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches {
    return create().ApplyMethod(target, methodName, double)
}
代码语言:javascript
复制
func ApplyGlobalVar(target, double interface{}) *Patches {
    return create().ApplyGlobalVar(target, double)
}
代码语言:javascript
复制
func ApplyFuncVar(target, double interface{}) *Patches {
    return create().ApplyFuncVar(target, double)
}

首先通过create创建patch对象,然后应用对应的方法:

代码语言:javascript
复制
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也是调用同样的方法

代码语言:javascript
复制
func NewPatches() *Patches {
    return create()
}

其结构体定义如下:

代码语言:javascript
复制
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来实现替换。

代码语言:javascript
复制
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)
}

同理

代码语言:javascript
复制
func (this *Patches) ApplyFunc(target, double interface{}) *Patches {
    t := reflect.ValueOf(target)
    d := reflect.ValueOf(double)
    return this.ApplyCore(t, d)
}

ApplyGlobalVar不同的是,它替换的是变量,所以使用的反射方法不一样

代码语言:javascript
复制
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变量里。

代码语言:javascript
复制
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
}

替换的时候使用了指针转化,获取函数的原始地址

代码语言:javascript
复制
func getPointer(v reflect.Value) unsafe.Pointer {
    return (*funcValue)(unsafe.Pointer(&v)).p
}

replace的过程就是指针的替换

代码语言:javascript
复制
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进行原始值的替换

代码语言:javascript
复制
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 函数来修改内存区域的保护属性。

至此核心流程介绍完了,批量逻辑是类似的

代码语言:javascript
复制
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)
}

只不过,它将目标值进行了封装,变成了一个函数,函数内部其实也是用了反射逻辑

代码语言:javascript
复制
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")
    })
}

整体代码不多,只不过用了很多反射函数,本质上就是一个地址替换。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档