Skip to content

Commit bfd2e1d

Browse files
authored
Pointer safety (#6)
* removes uintptr and uses unsafe.Pointer * experiment test for GC * Update README.md
1 parent 872e88c commit bfd2e1d

File tree

9 files changed

+109
-39
lines changed

9 files changed

+109
-39
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# go-mpatch
22
Go library for monkey patching
33

4+
45
Library inspired by the blog post: https://bou.ke/blog/monkey-patching-in-go/

patcher.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ type (
1515
target *reflect.Value
1616
redirection *reflect.Value
1717
}
18-
pointer struct {
19-
length uintptr
20-
ptr uintptr
18+
sliceHeader struct {
19+
Data unsafe.Pointer
20+
Len int
21+
Cap int
2122
}
2223
)
2324

25+
//go:linkname getInternalPtrFromValue reflect.(*Value).pointer
26+
func getInternalPtrFromValue(v *reflect.Value) unsafe.Pointer
27+
2428
var (
2529
patchLock = sync.Mutex{}
26-
patches = make(map[uintptr]*Patch)
30+
patches = make(map[unsafe.Pointer]*Patch)
2731
pageSize = syscall.Getpagesize()
2832
)
2933

@@ -99,7 +103,7 @@ func isPatchable(target, redirection *reflect.Value) error {
99103
if target.Type() != redirection.Type() {
100104
return errors.New(fmt.Sprintf("the target and/or redirection doesn't have the same type: %s != %s", target.Type(), redirection.Type()))
101105
}
102-
if _, ok := patches[target.Pointer()]; ok {
106+
if _, ok := patches[getSafePointer(target)]; ok {
103107
return errors.New("the target is already patched")
104108
}
105109
return nil
@@ -108,8 +112,8 @@ func isPatchable(target, redirection *reflect.Value) error {
108112
func applyPatch(patch *Patch) error {
109113
patchLock.Lock()
110114
defer patchLock.Unlock()
111-
tPointer := patch.target.Pointer()
112-
rPointer := getInternalPtrFromValue(*patch.redirection)
115+
tPointer := getSafePointer(patch.target)
116+
rPointer := getInternalPtrFromValue(patch.redirection)
113117
rPointerJumpBytes, err := getJumpFuncBytes(rPointer)
114118
if err != nil {
115119
return err
@@ -132,7 +136,7 @@ func applyUnpatch(patch *Patch) error {
132136
if patch.targetBytes == nil || len(patch.targetBytes) == 0 {
133137
return errors.New("the target is not patched")
134138
}
135-
tPointer := patch.target.Pointer()
139+
tPointer := getSafePointer(patch.target)
136140
if _, ok := patches[tPointer]; !ok {
137141
return errors.New("the target is not patched")
138142
}
@@ -144,10 +148,6 @@ func applyUnpatch(patch *Patch) error {
144148
return nil
145149
}
146150

147-
func getInternalPtrFromValue(value reflect.Value) uintptr {
148-
return (*pointer)(unsafe.Pointer(&value)).ptr
149-
}
150-
151151
func getValueFrom(data interface{}) reflect.Value {
152152
if cValue, ok := data.(reflect.Value); ok {
153153
return cValue
@@ -156,14 +156,18 @@ func getValueFrom(data interface{}) reflect.Value {
156156
}
157157
}
158158

159-
func getMemorySliceFromPointer(p uintptr, length int) []byte {
160-
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
159+
func getMemorySliceFromPointer(p unsafe.Pointer, length int) []byte {
160+
return *(*[]byte)(unsafe.Pointer(&sliceHeader{
161161
Data: p,
162162
Len: length,
163163
Cap: length,
164164
}))
165165
}
166166

167-
func getPageStartPtr(ptr uintptr) uintptr {
168-
return ptr & ^(uintptr(pageSize - 1))
167+
func getSafePointer(value *reflect.Value) unsafe.Pointer {
168+
p := getInternalPtrFromValue(value)
169+
if p != nil {
170+
p = *(*unsafe.Pointer)(p)
171+
}
172+
return p
169173
}

patcher.s

Whitespace-only changes.

patcher_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package mpatch
22

33
import (
44
"reflect"
5+
"runtime"
56
"testing"
7+
"time"
68
)
79

810
//go:noinline
@@ -93,3 +95,44 @@ func TestInstanceValuePatcher(t *testing.T) {
9395
t.Fatal("The unpatch did not work")
9496
}
9597
}
98+
99+
var slice []int
100+
101+
//go:noinline
102+
func TestGarbageCollectorExperiment(t *testing.T) {
103+
104+
for i := 0; i < 10000000; i++ {
105+
slice = append(slice, i)
106+
}
107+
go func() {
108+
var sl []int
109+
for i := 0; i < 10000000; i++ {
110+
sl = append(slice, i)
111+
}
112+
_ = sl
113+
}()
114+
<-time.After(time.Second)
115+
116+
aVal := methodA
117+
ptr01 := reflect.ValueOf(aVal).Pointer()
118+
slice = nil
119+
runtime.GC()
120+
for i := 0; i < 10000000; i++ {
121+
slice = append(slice, i)
122+
}
123+
go func() {
124+
var sl []int
125+
for i := 0; i < 10000000; i++ {
126+
sl = append(slice, i)
127+
}
128+
_ = sl
129+
}()
130+
<-time.After(time.Second)
131+
slice = nil
132+
runtime.GC()
133+
ptr02 := reflect.ValueOf(aVal).Pointer()
134+
135+
if ptr01 != ptr02 {
136+
t.Fail()
137+
}
138+
}

patcher_unix.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,28 @@
22

33
package mpatch
44

5-
import "syscall"
5+
import (
6+
"reflect"
7+
"syscall"
8+
"unsafe"
9+
)
610

711
var writeAccess = syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC
812
var readAccess = syscall.PROT_READ | syscall.PROT_EXEC
913

10-
func callMProtect(addr uintptr, length int, prot int) error {
11-
for p := getPageStartPtr(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
12-
page := getMemorySliceFromPointer(p, pageSize)
14+
//go:nosplit
15+
func getMemorySliceFromUintptr(p uintptr, length int) []byte {
16+
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
17+
Data: p,
18+
Len: length,
19+
Cap: length,
20+
}))
21+
}
22+
23+
//go:nosplit
24+
func callMProtect(addr unsafe.Pointer, length int, prot int) error {
25+
for p := uintptr(addr) & ^(uintptr(pageSize - 1)); p < uintptr(addr)+uintptr(length); p += uintptr(pageSize) {
26+
page := getMemorySliceFromUintptr(p, pageSize)
1327
err := syscall.Mprotect(page, prot)
1428
if err != nil {
1529
return err
@@ -18,7 +32,7 @@ func callMProtect(addr uintptr, length int, prot int) error {
1832
return nil
1933
}
2034

21-
func copyDataToPtr(ptr uintptr, data []byte) error {
35+
func copyDataToPtr(ptr unsafe.Pointer, data []byte) error {
2236
dataLength := len(data)
2337
ptrByteSlice := getMemorySliceFromPointer(ptr, len(data))
2438
err := callMProtect(ptr, dataLength, writeAccess)

patcher_unsupported.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"errors"
88
"fmt"
99
"runtime"
10+
"unsafe"
1011
)
1112

1213
// Gets the jump function rewrite bytes
13-
func getJumpFuncBytes(to uintptr) ([]byte, error) {
14+
//go:nosplit
15+
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
1416
return nil, errors.New(fmt.Sprintf("Unsupported architecture: %s", runtime.GOARCH))
1517
}

patcher_windows.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ const pageExecuteReadAndWrite = 0x40
1111

1212
var virtualProtectProc = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
1313

14-
func callVirtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error {
15-
ret, _, _ := virtualProtectProc.Call(lpAddress, uintptr(dwSize), uintptr(flNewProtect), uintptr(lpflOldProtect))
14+
func callVirtualProtect(lpAddress unsafe.Pointer, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error {
15+
ret, _, _ := virtualProtectProc.Call(uintptr(lpAddress), uintptr(dwSize), uintptr(flNewProtect), uintptr(lpflOldProtect))
1616
if ret == 0 {
1717
return syscall.GetLastError()
1818
}
1919
return nil
2020
}
2121

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

patcher_x32.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
package mpatch
44

5+
import "unsafe"
6+
57
// Gets the jump function rewrite bytes
6-
func getJumpFuncBytes(to uintptr) ([]byte, error) {
8+
//go:nosplit
9+
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
710
return []byte{
811
0xBA,
9-
byte(to),
10-
byte(to >> 8),
11-
byte(to >> 16),
12-
byte(to >> 24),
12+
byte(uintptr(to)),
13+
byte(uintptr(to) >> 8),
14+
byte(uintptr(to) >> 16),
15+
byte(uintptr(to) >> 24),
1316
0xFF, 0x22,
1417
}, nil
1518
}

patcher_x64.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22

33
package mpatch
44

5+
import "unsafe"
6+
57
// Gets the jump function rewrite bytes
6-
func getJumpFuncBytes(to uintptr) ([]byte, error) {
8+
//go:nosplit
9+
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
710
return []byte{
811
0x48, 0xBA,
9-
byte(to),
10-
byte(to >> 8),
11-
byte(to >> 16),
12-
byte(to >> 24),
13-
byte(to >> 32),
14-
byte(to >> 40),
15-
byte(to >> 48),
16-
byte(to >> 56),
12+
byte(uintptr(to)),
13+
byte(uintptr(to) >> 8),
14+
byte(uintptr(to) >> 16),
15+
byte(uintptr(to) >> 24),
16+
byte(uintptr(to) >> 32),
17+
byte(uintptr(to) >> 40),
18+
byte(uintptr(to) >> 48),
19+
byte(uintptr(to) >> 56),
1720
0xFF, 0x22,
1821
}, nil
1922
}

0 commit comments

Comments
 (0)