github.com/undefinedlabs/go-mpatch@v1.0.8-0.20230904093002-fbac8a0d7853/README.md (about) 1 # go-mpatch 2 Go library for monkey patching 3 4 ## Compatibility 5 6 - **Go version:** tested from `go1.7` to `go1.21` 7 - **Architectures:** `x86`, `amd64`, `arm64` (ARM64 not supported on macos) 8 - **Operating systems:** tested in `macos`, `linux` and `windows`. 9 10 ### ARM64 support 11 The support for ARM64 have some caveats. For example: 12 - On Windows 11 ARM64 works like any other platform, we test it with tiny methods and worked correctly. 13 - On Linux ARM64 the minimum target and source method size must be > 24 bytes. This means a simple 1 liner method will silently fail. In our tests we have methods at least with 3 lines and works correctly (beware of the compiler optimizations). 14 This doesn't happen in any other platform because the assembly code we emit is really short (x64: 12 bytes / x86: 7 bytes), but for ARM64 is exactly 24 bytes. 15 - On MacOS ARM64 the patching fails with `EACCES: permission denied` when calling `syscall.Mprotect`. There's no current workaround for this issue, if you use an Apple Silicon Mac you can use a docker container or docker dev environment. 16 17 ## Features 18 19 - Can patch package functions, instance functions (by pointer or by value), and create new functions from scratch. 20 21 ## Limitations 22 23 - Target functions could be inlined, making those functions unpatcheables. You can use `//go:noinline` directive or build with the `gcflags=-l` 24 to disable inlining at compiler level. 25 26 - Write permission to memory pages containing executable code is needed, some operating systems could restrict this access. 27 28 - Not thread safe. 29 30 ## Usage 31 32 ### Patching a func 33 ```go 34 //go:noinline 35 func methodA() int { return 1 } 36 37 //go:noinline 38 func methodB() int { return 2 } 39 40 func TestPatcher(t *testing.T) { 41 patch, err := mpatch.PatchMethod(methodA, methodB) 42 if err != nil { 43 t.Fatal(err) 44 } 45 if methodA() != 2 { 46 t.Fatal("The patch did not work") 47 } 48 49 err = patch.Unpatch() 50 if err != nil { 51 t.Fatal(err) 52 } 53 if methodA() != 1 { 54 t.Fatal("The unpatch did not work") 55 } 56 } 57 ``` 58 59 ### Patching using `reflect.ValueOf` 60 ```go 61 //go:noinline 62 func methodA() int { return 1 } 63 64 //go:noinline 65 func methodB() int { return 2 } 66 67 func TestPatcherUsingReflect(t *testing.T) { 68 reflectA := reflect.ValueOf(methodA) 69 patch, err := mPatch.PatchMethodByReflectValue(reflectA, methodB) 70 if err != nil { 71 t.Fatal(err) 72 } 73 if methodA() != 2 { 74 t.Fatal("The patch did not work") 75 } 76 77 err = patch.Unpatch() 78 if err != nil { 79 t.Fatal(err) 80 } 81 if methodA() != 1 { 82 t.Fatal("The unpatch did not work") 83 } 84 } 85 ``` 86 87 ### Patching creating a new func at runtime 88 ```go 89 //go:noinline 90 func methodA() int { return 1 } 91 92 func TestPatcherUsingMakeFunc(t *testing.T) { 93 reflectA := reflect.ValueOf(methodA) 94 patch, err := PatchMethodWithMakeFuncValue(reflectA, 95 func(args []reflect.Value) (results []reflect.Value) { 96 return []reflect.Value{reflect.ValueOf(42)} 97 }) 98 if err != nil { 99 t.Fatal(err) 100 } 101 if methodA() != 42 { 102 t.Fatal("The patch did not work") 103 } 104 105 err = patch.Unpatch() 106 if err != nil { 107 t.Fatal(err) 108 } 109 if methodA() != 1 { 110 t.Fatal("The unpatch did not work") 111 } 112 } 113 ``` 114 115 ### Patching an instance func 116 ```go 117 type myStruct struct { 118 } 119 120 //go:noinline 121 func (s *myStruct) Method() int { 122 return 1 123 } 124 125 func TestInstancePatcher(t *testing.T) { 126 mStruct := myStruct{} 127 128 var patch *Patch 129 var err error 130 patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "Method", func(m *myStruct) int { 131 patch.Unpatch() 132 defer patch.Patch() 133 return 41 + m.Method() 134 }) 135 if err != nil { 136 t.Fatal(err) 137 } 138 139 if mStruct.Method() != 42 { 140 t.Fatal("The patch did not work") 141 } 142 err = patch.Unpatch() 143 if err != nil { 144 t.Fatal(err) 145 } 146 if mStruct.Method() != 1 { 147 t.Fatal("The unpatch did not work") 148 } 149 } 150 ``` 151 152 ### Patching an instance func by Value 153 ```go 154 type myStruct struct { 155 } 156 157 //go:noinline 158 func (s myStruct) ValueMethod() int { 159 return 1 160 } 161 162 func TestInstanceValuePatcher(t *testing.T) { 163 mStruct := myStruct{} 164 165 var patch *Patch 166 var err error 167 patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "ValueMethod", func(m myStruct) int { 168 patch.Unpatch() 169 defer patch.Patch() 170 return 41 + m.Method() 171 }) 172 if err != nil { 173 t.Fatal(err) 174 } 175 176 if mStruct.ValueMethod() != 42 { 177 t.Fatal("The patch did not work") 178 } 179 err = patch.Unpatch() 180 if err != nil { 181 t.Fatal(err) 182 } 183 if mStruct.ValueMethod() != 1 { 184 t.Fatal("The unpatch did not work") 185 } 186 } 187 ``` 188 189 > Library inspired by the blog post: https://bou.ke/blog/monkey-patching-in-go/