Go实现猴子补丁

本文是对涛叔在Go夜读119期119 Go monkey patch 的原理及应用的学习与记录

相关ppt


关于猴子补丁


Python异步库gevent有一个猴子补丁模块from gevent import monkey; monkey.patch_all(),可以将python标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和select等,变为协作式运行

猴子补丁的这个叫法起源于Zope框架(1998年就诞生的一个Python Web框架),大家在修正Zope的Bug的时候经常在程序后面追加更新部分,这些被称作是“杂牌军补丁(guerilla patch)”,后来guerilla就渐渐的写成了gorllia(猩猩),再后来就写了monkey(猴子),所以猴子补丁的叫法是这么莫名其妙的得来的。


后来在动态语言中,不改变源代码而对功能进行追加和变更,统称为“猴子补丁”。所以猴子补丁并不是Python中专有的。猴子补丁这种东西充分利用了动态语言的灵活性,可以对现有的语言Api进行追加,替换,修改Bug,甚至性能优化等等。
使用猴子补丁的方式,gevent能够修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。也就是通过猴子补丁的monkey.patch_xxx()来将python标准库中模块或函数改成gevent中的响应的具有协程的协作式对象。这样在不改变原有代码的情况下,将应用的阻塞式方法,变成协程式的。

python协程初步–gevent库使用以及解释什么是猴子补丁monkey_patch

Gevent和猴子补丁


Go实现猴子补丁


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"math/rand"
"time"
)

func main() {
n := foo()

fmt.Println(n)
}

// 有没有办法让 time.Now和rand.Int63返回一个固定的数字,让单测能够重复跑
func foo() int64 {

n := time.Now()
r := rand.Int63()

return n.UnixNano() + r

}

最好的方式是找到main()调foo()的地方,将其指向bar().

但Go中实现不了这种效果,只能找到foo()指向的内存区域,将该区域前面的指令替换为跳转到bar()

不同CPU的跳转指令不同,下面以amd64架构为例


只要内存布局一样,不同类型的指针是可以通过unsafe.Pointor强转的,如float64类型的指针可以转为int64类型的指针

unsafe包的用处就是绕过编译器对指针类型转换的检测逻辑,但程序员需自己保证两种指针的底层数据结构一致(即不让编译器做类型检查)

//todo


一些相关的库


最早有个bouk/monkey项目,作者是荷兰人,但已经不维护了

打桩神器gomonkey

gomonkey 全面支持 arm64 了

Go语言实现猴子补丁
Go语言实现猴子补丁【二】
Go语言实现猴子补丁【三】

go-kiss/monkey