Skip to content

Commit b295ad1

Browse files
authored
Library documentation (#8)
* code and readme docs update * readme update
1 parent 608c12d commit b295ad1

File tree

5 files changed

+203
-14
lines changed

5 files changed

+203
-14
lines changed

README.md

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,182 @@
11
# go-mpatch
22
Go library for monkey patching
33

4+
## Compatibility
45

5-
Library inspired by the blog post: https://bou.ke/blog/monkey-patching-in-go/
6+
- **Go version:** tested from `go1.7` to `go1.15-beta`
7+
- **Architectures:** `x86`, `amd64`
8+
- **Operating systems:** tested in `macos`, `linux` and `windows`.
9+
10+
## Features
11+
12+
- Can patch package functions, instance functions (by pointer or by value), and create new functions from scratch.
13+
14+
## Limitations
15+
16+
- Target functions could be inlined, making those functions unpatcheables. You can use `//go:noinline` directive or build with the `gcflags=-l`
17+
to disable inlining at compiler level.
18+
19+
- Write permission to memory pages containing executable code is needed, some operating systems could restrict this access.
20+
21+
- Not thread safe.
22+
23+
## Usage
24+
25+
### Patching a func
26+
```go
27+
//go:noinline
28+
func methodA() int { return 1 }
29+
30+
//go:noinline
31+
func methodB() int { return 2 }
32+
33+
func TestPatcher(t *testing.T) {
34+
patch, err := mpatch.PatchMethod(methodA, methodB)
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
if methodA() != 2 {
39+
t.Fatal("The patch did not work")
40+
}
41+
42+
err = patch.Unpatch()
43+
if err != nil {
44+
t.Fatal(err)
45+
}
46+
if methodA() != 1 {
47+
t.Fatal("The unpatch did not work")
48+
}
49+
}
50+
```
51+
52+
### Patching using `reflect.ValueOf`
53+
```go
54+
//go:noinline
55+
func methodA() int { return 1 }
56+
57+
//go:noinline
58+
func methodB() int { return 2 }
59+
60+
func TestPatcherUsingReflect(t *testing.T) {
61+
reflectA := reflect.ValueOf(methodA)
62+
patch, err := mPatch.PatchMethodByReflect(reflectA, methodB)
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
if methodA() != 2 {
67+
t.Fatal("The patch did not work")
68+
}
69+
70+
err = patch.Unpatch()
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
if methodA() != 1 {
75+
t.Fatal("The unpatch did not work")
76+
}
77+
}
78+
```
79+
80+
### Patching creating a new func at runtime
81+
```go
82+
//go:noinline
83+
func methodA() int { return 1 }
84+
85+
func TestPatcherUsingMakeFunc(t *testing.T) {
86+
reflectA := reflect.ValueOf(methodA)
87+
patch, err := PatchMethodWithMakeFunc(reflectA,
88+
func(args []reflect.Value) (results []reflect.Value) {
89+
return []reflect.Value{reflect.ValueOf(42)}
90+
})
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
if methodA() != 42 {
95+
t.Fatal("The patch did not work")
96+
}
97+
98+
err = patch.Unpatch()
99+
if err != nil {
100+
t.Fatal(err)
101+
}
102+
if methodA() != 1 {
103+
t.Fatal("The unpatch did not work")
104+
}
105+
}
106+
```
107+
108+
### Patching an instance func
109+
```go
110+
type myStruct struct {
111+
}
112+
113+
//go:noinline
114+
func (s *myStruct) Method() int {
115+
return 1
116+
}
117+
118+
func TestInstancePatcher(t *testing.T) {
119+
mStruct := myStruct{}
120+
121+
var patch *Patch
122+
var err error
123+
patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "Method", func(m *myStruct) int {
124+
patch.Unpatch()
125+
defer patch.Patch()
126+
return 41 + m.Method()
127+
})
128+
if err != nil {
129+
t.Fatal(err)
130+
}
131+
132+
if mStruct.Method() != 42 {
133+
t.Fatal("The patch did not work")
134+
}
135+
err = patch.Unpatch()
136+
if err != nil {
137+
t.Fatal(err)
138+
}
139+
if mStruct.Method() != 1 {
140+
t.Fatal("The unpatch did not work")
141+
}
142+
}
143+
```
144+
145+
### Patching an instance func by Value
146+
```go
147+
type myStruct struct {
148+
}
149+
150+
//go:noinline
151+
func (s myStruct) ValueMethod() int {
152+
return 1
153+
}
154+
155+
func TestInstanceValuePatcher(t *testing.T) {
156+
mStruct := myStruct{}
157+
158+
var patch *Patch
159+
var err error
160+
patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "ValueMethod", func(m myStruct) int {
161+
patch.Unpatch()
162+
defer patch.Patch()
163+
return 41 + m.Method()
164+
})
165+
if err != nil {
166+
t.Fatal(err)
167+
}
168+
169+
if mStruct.ValueMethod() != 42 {
170+
t.Fatal("The patch did not work")
171+
}
172+
err = patch.Unpatch()
173+
if err != nil {
174+
t.Fatal(err)
175+
}
176+
if mStruct.ValueMethod() != 1 {
177+
t.Fatal("The unpatch did not work")
178+
}
179+
}
180+
```
181+
182+
> Library inspired by the blog post: https://bou.ke/blog/monkey-patching-in-go/

patcher.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var (
3131
pageSize = syscall.Getpagesize()
3232
)
3333

34+
// Patches a target func to redirect calls to "redirection" func. Both function must have same arguments and return types.
3435
func PatchMethod(target, redirection interface{}) (*Patch, error) {
3536
tValue := getValueFrom(target)
3637
rValue := getValueFrom(redirection)
@@ -43,6 +44,8 @@ func PatchMethod(target, redirection interface{}) (*Patch, error) {
4344
}
4445
return patch, nil
4546
}
47+
// Patches an instance func by using two parameters, the target struct type and the method name inside that type,
48+
//this func will be redirected to the "redirection" func. Note: The first parameter of the redirection func must be the object instance.
4649
func PatchInstanceMethodByName(target reflect.Type, methodName string, redirection interface{}) (*Patch, error) {
4750
method, ok := target.MethodByName(methodName)
4851
if !ok && target.Kind() == reflect.Struct {
@@ -54,6 +57,8 @@ func PatchInstanceMethodByName(target reflect.Type, methodName string, redirecti
5457
}
5558
return PatchMethodByReflect(method.Func, redirection)
5659
}
60+
// Patches a target func by passing the reflect.ValueOf of the func. The target func will be redirected to the "redirection" func.
61+
// Both function must have same arguments and return types.
5762
func PatchMethodByReflect(target reflect.Value, redirection interface{}) (*Patch, error) {
5863
tValue := &target
5964
rValue := getValueFrom(redirection)
@@ -66,10 +71,11 @@ func PatchMethodByReflect(target reflect.Value, redirection interface{}) (*Patch
6671
}
6772
return patch, nil
6873
}
74+
// Patches a target func with a "redirection" function created at runtime by using "reflect.MakeFunc".
6975
func PatchMethodWithMakeFunc(target reflect.Value, fn func(args []reflect.Value) (results []reflect.Value)) (*Patch, error) {
7076
return PatchMethodByReflect(target, reflect.MakeFunc(target.Type(), fn))
7177
}
72-
78+
// Patch the target func with the redirection func.
7379
func (p *Patch) Patch() error {
7480
if p == nil {
7581
return errors.New("patch is nil")
@@ -82,6 +88,7 @@ func (p *Patch) Patch() error {
8288
}
8389
return nil
8490
}
91+
// Unpatch the target func and recover the original func.
8592
func (p *Patch) Unpatch() error {
8693
if p == nil {
8794
return errors.New("patch is nil")
@@ -96,7 +103,7 @@ func isPatchable(target, redirection *reflect.Value) error {
96103
if target.Type() != redirection.Type() {
97104
return errors.New(fmt.Sprintf("the target and/or redirection doesn't have the same type: %s != %s", target.Type(), redirection.Type()))
98105
}
99-
if _, ok := patches[getSafeCodePointer(target)]; ok {
106+
if _, ok := patches[getCodePointer(target)]; ok {
100107
return errors.New("the target is already patched")
101108
}
102109
return nil
@@ -105,7 +112,7 @@ func isPatchable(target, redirection *reflect.Value) error {
105112
func applyPatch(patch *Patch) error {
106113
patchLock.Lock()
107114
defer patchLock.Unlock()
108-
tPointer := getSafeCodePointer(patch.target)
115+
tPointer := getCodePointer(patch.target)
109116
rPointer := getInternalPtrFromValue(patch.redirection)
110117
rPointerJumpBytes, err := getJumpFuncBytes(rPointer)
111118
if err != nil {
@@ -114,7 +121,7 @@ func applyPatch(patch *Patch) error {
114121
tPointerBytes := getMemorySliceFromPointer(tPointer, len(rPointerJumpBytes))
115122
targetBytes := make([]byte, len(tPointerBytes))
116123
copy(targetBytes, tPointerBytes)
117-
if err := copyDataToPtr(tPointer, rPointerJumpBytes); err != nil {
124+
if err := writeDataToPointer(tPointer, rPointerJumpBytes); err != nil {
118125
return err
119126
}
120127
patch.targetBytes = targetBytes
@@ -128,12 +135,12 @@ func applyUnpatch(patch *Patch) error {
128135
if patch.targetBytes == nil || len(patch.targetBytes) == 0 {
129136
return errors.New("the target is not patched")
130137
}
131-
tPointer := getSafeCodePointer(patch.target)
138+
tPointer := getCodePointer(patch.target)
132139
if _, ok := patches[tPointer]; !ok {
133140
return errors.New("the target is not patched")
134141
}
135142
delete(patches, tPointer)
136-
err := copyDataToPtr(tPointer, patch.targetBytes)
143+
err := writeDataToPointer(tPointer, patch.targetBytes)
137144
if err != nil {
138145
return err
139146
}
@@ -148,6 +155,7 @@ func getValueFrom(data interface{}) reflect.Value {
148155
}
149156
}
150157

158+
// Extracts a memory slice from a pointer
151159
func getMemorySliceFromPointer(p unsafe.Pointer, length int) []byte {
152160
return *(*[]byte)(unsafe.Pointer(&sliceHeader{
153161
Data: p,
@@ -156,7 +164,8 @@ func getMemorySliceFromPointer(p unsafe.Pointer, length int) []byte {
156164
}))
157165
}
158166

159-
func getSafeCodePointer(value *reflect.Value) unsafe.Pointer {
167+
// Gets the code pointer of a func
168+
func getCodePointer(value *reflect.Value) unsafe.Pointer {
160169
p := getInternalPtrFromValue(value)
161170
if p != nil {
162171
p = *(*unsafe.Pointer)(p)

patcher_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ func TestPatcher(t *testing.T) {
4545
}
4646

4747
func TestPatcherUsingReflect(t *testing.T) {
48-
patch, err := PatchMethodByReflect(reflect.ValueOf(methodA), methodB)
48+
reflectA := reflect.ValueOf(methodA)
49+
patch, err := PatchMethodByReflect(reflectA, methodB)
4950
if err != nil {
5051
t.Fatal(err)
5152
}
@@ -63,9 +64,11 @@ func TestPatcherUsingReflect(t *testing.T) {
6364
}
6465

6566
func TestPatcherUsingMakeFunc(t *testing.T) {
66-
patch, err := PatchMethodWithMakeFunc(reflect.ValueOf(methodA), func(args []reflect.Value) (results []reflect.Value) {
67-
return []reflect.Value{reflect.ValueOf(42)}
68-
})
67+
reflectA := reflect.ValueOf(methodA)
68+
patch, err := PatchMethodWithMakeFunc(reflectA,
69+
func(args []reflect.Value) (results []reflect.Value) {
70+
return []reflect.Value{reflect.ValueOf(42)}
71+
})
6972
if err != nil {
7073
t.Fatal(err)
7174
}

patcher_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func callMProtect(addr unsafe.Pointer, length int, prot int) error {
3131
return nil
3232
}
3333

34-
func copyDataToPtr(ptr unsafe.Pointer, data []byte) error {
34+
func writeDataToPointer(ptr unsafe.Pointer, data []byte) error {
3535
dataLength := len(data)
3636
ptrByteSlice := getMemorySliceFromPointer(ptr, len(data))
3737
if err := callMProtect(ptr, dataLength, writeAccess); err != nil {

patcher_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func callVirtualProtect(lpAddress unsafe.Pointer, dwSize int, flNewProtect uint3
1919
return nil
2020
}
2121

22-
func copyDataToPtr(ptr unsafe.Pointer, data []byte) error {
22+
func writeDataToPointer(ptr unsafe.Pointer, data []byte) error {
2323
var oldPerms, tmp uint32
2424
dataLength := len(data)
2525
ptrByteSlice := getMemorySliceFromPointer(ptr, len(data))

0 commit comments

Comments
 (0)