init
This commit is contained in:
30
vendor/github.com/bouk/monkey/BUILD.bazel
generated
vendored
Normal file
30
vendor/github.com/bouk/monkey/BUILD.bazel
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"monkey.go",
|
||||
"monkey_386.go",
|
||||
"monkey_amd64.go",
|
||||
"replace.go",
|
||||
"replace_unix.go",
|
||||
"replace_windows.go",
|
||||
],
|
||||
importmap = "go-common/vendor/github.com/bouk/monkey",
|
||||
importpath = "github.com/bouk/monkey",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
3
vendor/github.com/bouk/monkey/LICENSE.md
generated
vendored
Normal file
3
vendor/github.com/bouk/monkey/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
Copyright Bouke van der Bijl
|
||||
|
||||
I do not give anyone permissions to use this tool for any purpose. Don't use it.
|
112
vendor/github.com/bouk/monkey/README.md
generated
vendored
Normal file
112
vendor/github.com/bouk/monkey/README.md
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
# Go monkeypatching :monkey_face: :monkey:
|
||||
|
||||
Actual arbitrary monkeypatching for Go. Yes really.
|
||||
|
||||
Read this blogpost for an explanation on how it works: http://bouk.co/blog/monkey-patching-in-go/
|
||||
|
||||
## I thought that monkeypatching in Go is impossible?
|
||||
|
||||
It's not possible through regular language constructs, but we can always bend computers to our will! Monkey implements monkeypatching by rewriting the running executable at runtime and inserting a jump to the function you want called instead. **This is as unsafe as it sounds and I don't recommend anyone do it outside of a testing environment.**
|
||||
|
||||
Make sure you read the notes at the bottom of the README if you intend to use this library.
|
||||
|
||||
## Using monkey
|
||||
|
||||
Monkey's API is very simple and straightfoward. Call `monkey.Patch(<target function>, <replacement function>)` to replace a function. For example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bouk/monkey"
|
||||
)
|
||||
|
||||
func main() {
|
||||
monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {
|
||||
s := make([]interface{}, len(a))
|
||||
for i, v := range a {
|
||||
s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)
|
||||
}
|
||||
return fmt.Fprintln(os.Stdout, s...)
|
||||
})
|
||||
fmt.Println("what the hell?") // what the *bleep*?
|
||||
}
|
||||
```
|
||||
|
||||
You can then call `monkey.Unpatch(<target function>)` to unpatch the method again. The replacement function can be any function value, whether it's anonymous, bound or otherwise.
|
||||
|
||||
If you want to patch an instance method you need to use `monkey.PatchInstanceMethod(<type>, <name>, <replacement>)`. You get the type by using `reflect.TypeOf`, and your replacement function simply takes the instance as the first argument. To disable all network connections, you can do as follows for example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/bouk/monkey"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var d *net.Dialer // Has to be a pointer to because `Dial` has a pointer receiver
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(d), "Dial", func(_ *net.Dialer, _, _ string) (net.Conn, error) {
|
||||
return nil, fmt.Errorf("no dialing allowed")
|
||||
})
|
||||
_, err := http.Get("http://google.com")
|
||||
fmt.Println(err) // Get http://google.com: no dialing allowed
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Note that patching the method for just one instance is currently not possible, `PatchInstanceMethod` will patch it for all instances. Don't bother trying `monkey.Patch(instance.Method, replacement)`, it won't work. `monkey.UnpatchInstanceMethod(<type>, <name>)` will undo `PatchInstanceMethod`.
|
||||
|
||||
If you want to remove all currently applied monkeypatches simply call `monkey.UnpatchAll`. This could be useful in a test teardown function.
|
||||
|
||||
If you want to call the original function from within the replacement you need to use a `monkey.PatchGuard`. A patchguard allows you to easily remove and restore the patch so you can call the original function. For example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/bouk/monkey"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var guard *monkey.PatchGuard
|
||||
guard = monkey.PatchInstanceMethod(reflect.TypeOf(http.DefaultClient), "Get", func(c *http.Client, url string) (*http.Response, error) {
|
||||
guard.Unpatch()
|
||||
defer guard.Restore()
|
||||
|
||||
if !strings.HasPrefix(url, "https://") {
|
||||
return nil, fmt.Errorf("only https requests allowed")
|
||||
}
|
||||
|
||||
return c.Get(url)
|
||||
})
|
||||
|
||||
_, err := http.Get("http://google.com")
|
||||
fmt.Println(err) // only https requests allowed
|
||||
resp, err := http.Get("https://google.com")
|
||||
fmt.Println(resp.Status, err) // 200 OK <nil>
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. Monkey sometimes fails to patch a function if inlining is enabled. Try running your tests with inlining disabled, for example: `go test -gcflags=-l`. The same command line argument can also be used for build.
|
||||
2. Monkey won't work on some security-oriented operating system that don't allow memory pages to be both write and execute at the same time. With the current approach there's not really a reliable fix for this.
|
||||
3. Monkey is not threadsafe. Or any kind of safe.
|
||||
4. I've tested monkey on OSX 10.10.2 and Ubuntu 14.04. It should work on any unix-based x86 or x86-64 system.
|
||||
|
||||
© Bouke van der Bijl
|
3
vendor/github.com/bouk/monkey/circle.yml
generated
vendored
Normal file
3
vendor/github.com/bouk/monkey/circle.yml
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
test:
|
||||
override:
|
||||
- script/test
|
133
vendor/github.com/bouk/monkey/monkey.go
generated
vendored
Normal file
133
vendor/github.com/bouk/monkey/monkey.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// patch is an applied patch
|
||||
// needed to undo a patch
|
||||
type patch struct {
|
||||
originalBytes []byte
|
||||
replacement *reflect.Value
|
||||
}
|
||||
|
||||
var (
|
||||
lock = sync.Mutex{}
|
||||
|
||||
patches = make(map[reflect.Value]patch)
|
||||
)
|
||||
|
||||
type value struct {
|
||||
_ uintptr
|
||||
ptr unsafe.Pointer
|
||||
}
|
||||
|
||||
func getPtr(v reflect.Value) unsafe.Pointer {
|
||||
return (*value)(unsafe.Pointer(&v)).ptr
|
||||
}
|
||||
|
||||
type PatchGuard struct {
|
||||
target reflect.Value
|
||||
replacement reflect.Value
|
||||
}
|
||||
|
||||
func (g *PatchGuard) Unpatch() {
|
||||
unpatchValue(g.target)
|
||||
}
|
||||
|
||||
func (g *PatchGuard) Restore() {
|
||||
patchValue(g.target, g.replacement)
|
||||
}
|
||||
|
||||
// Patch replaces a function with another
|
||||
func Patch(target, replacement interface{}) *PatchGuard {
|
||||
t := reflect.ValueOf(target)
|
||||
r := reflect.ValueOf(replacement)
|
||||
patchValue(t, r)
|
||||
|
||||
return &PatchGuard{t, r}
|
||||
}
|
||||
|
||||
// PatchInstanceMethod replaces an instance method methodName for the type target with replacement
|
||||
// Replacement should expect the receiver (of type target) as the first argument
|
||||
func PatchInstanceMethod(target reflect.Type, methodName string, replacement interface{}) *PatchGuard {
|
||||
m, ok := target.MethodByName(methodName)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown method %s", methodName))
|
||||
}
|
||||
r := reflect.ValueOf(replacement)
|
||||
patchValue(m.Func, r)
|
||||
|
||||
return &PatchGuard{m.Func, r}
|
||||
}
|
||||
|
||||
func patchValue(target, replacement reflect.Value) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if target.Kind() != reflect.Func {
|
||||
panic("target has to be a Func")
|
||||
}
|
||||
|
||||
if replacement.Kind() != reflect.Func {
|
||||
panic("replacement has to be a Func")
|
||||
}
|
||||
|
||||
if target.Type() != replacement.Type() {
|
||||
panic(fmt.Sprintf("target and replacement have to have the same type %s != %s", target.Type(), replacement.Type()))
|
||||
}
|
||||
|
||||
if patch, ok := patches[target]; ok {
|
||||
unpatch(target, patch)
|
||||
}
|
||||
|
||||
bytes := replaceFunction(*(*uintptr)(getPtr(target)), uintptr(getPtr(replacement)))
|
||||
patches[target] = patch{bytes, &replacement}
|
||||
}
|
||||
|
||||
// Unpatch removes any monkey patches on target
|
||||
// returns whether target was patched in the first place
|
||||
func Unpatch(target interface{}) bool {
|
||||
return unpatchValue(reflect.ValueOf(target))
|
||||
}
|
||||
|
||||
// UnpatchInstanceMethod removes the patch on methodName of the target
|
||||
// returns whether it was patched in the first place
|
||||
func UnpatchInstanceMethod(target reflect.Type, methodName string) bool {
|
||||
m, ok := target.MethodByName(methodName)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown method %s", methodName))
|
||||
}
|
||||
return unpatchValue(m.Func)
|
||||
}
|
||||
|
||||
// UnpatchAll removes all applied monkeypatches
|
||||
func UnpatchAll() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
for target, p := range patches {
|
||||
unpatch(target, p)
|
||||
delete(patches, target)
|
||||
}
|
||||
}
|
||||
|
||||
// Unpatch removes a monkeypatch from the specified function
|
||||
// returns whether the function was patched in the first place
|
||||
func unpatchValue(target reflect.Value) bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
patch, ok := patches[target]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
unpatch(target, patch)
|
||||
delete(patches, target)
|
||||
return true
|
||||
}
|
||||
|
||||
func unpatch(target reflect.Value, p patch) {
|
||||
copyToLocation(*(*uintptr)(getPtr(target)), p.originalBytes)
|
||||
}
|
13
vendor/github.com/bouk/monkey/monkey_386.go
generated
vendored
Normal file
13
vendor/github.com/bouk/monkey/monkey_386.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package monkey
|
||||
|
||||
// Assembles a jump to a function value
|
||||
func jmpToFunctionValue(to uintptr) []byte {
|
||||
return []byte{
|
||||
0xBA,
|
||||
byte(to),
|
||||
byte(to >> 8),
|
||||
byte(to >> 16),
|
||||
byte(to >> 24), // mov edx,to
|
||||
0xFF, 0x22, // jmp DWORD PTR [edx]
|
||||
}
|
||||
}
|
17
vendor/github.com/bouk/monkey/monkey_amd64.go
generated
vendored
Normal file
17
vendor/github.com/bouk/monkey/monkey_amd64.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package monkey
|
||||
|
||||
// Assembles a jump to a function value
|
||||
func jmpToFunctionValue(to uintptr) []byte {
|
||||
return []byte{
|
||||
0x48, 0xBA,
|
||||
byte(to),
|
||||
byte(to >> 8),
|
||||
byte(to >> 16),
|
||||
byte(to >> 24),
|
||||
byte(to >> 32),
|
||||
byte(to >> 40),
|
||||
byte(to >> 48),
|
||||
byte(to >> 56), // movabs rdx,to
|
||||
0xFF, 0x22, // jmp QWORD PTR [rdx]
|
||||
}
|
||||
}
|
31
vendor/github.com/bouk/monkey/replace.go
generated
vendored
Normal file
31
vendor/github.com/bouk/monkey/replace.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func rawMemoryAccess(p uintptr, length int) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: p,
|
||||
Len: length,
|
||||
Cap: length,
|
||||
}))
|
||||
}
|
||||
|
||||
func pageStart(ptr uintptr) uintptr {
|
||||
return ptr & ^(uintptr(syscall.Getpagesize() - 1))
|
||||
}
|
||||
|
||||
// from is a pointer to the actual function
|
||||
// to is a pointer to a go funcvalue
|
||||
func replaceFunction(from, to uintptr) (original []byte) {
|
||||
jumpData := jmpToFunctionValue(to)
|
||||
f := rawMemoryAccess(from, len(jumpData))
|
||||
original = make([]byte, len(f))
|
||||
copy(original, f)
|
||||
|
||||
copyToLocation(from, jumpData)
|
||||
return
|
||||
}
|
26
vendor/github.com/bouk/monkey/replace_unix.go
generated
vendored
Normal file
26
vendor/github.com/bouk/monkey/replace_unix.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
//+build !windows
|
||||
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// this function is super unsafe
|
||||
// aww yeah
|
||||
// It copies a slice to a raw memory location, disabling all memory protection before doing so.
|
||||
func copyToLocation(location uintptr, data []byte) {
|
||||
f := rawMemoryAccess(location, len(data))
|
||||
|
||||
page := rawMemoryAccess(pageStart(location), syscall.Getpagesize())
|
||||
err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
copy(f, data[:])
|
||||
|
||||
err = syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
44
vendor/github.com/bouk/monkey/replace_windows.go
generated
vendored
Normal file
44
vendor/github.com/bouk/monkey/replace_windows.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package monkey
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const PAGE_EXECUTE_READWRITE = 0x40
|
||||
|
||||
var procVirtualProtect = syscall.NewLazyDLL("kernel32.dll").NewProc("VirtualProtect")
|
||||
|
||||
func virtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldProtect unsafe.Pointer) error {
|
||||
ret, _, _ := procVirtualProtect.Call(
|
||||
lpAddress,
|
||||
uintptr(dwSize),
|
||||
uintptr(flNewProtect),
|
||||
uintptr(lpflOldProtect))
|
||||
if ret == 0 {
|
||||
return syscall.GetLastError()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// this function is super unsafe
|
||||
// aww yeah
|
||||
// It copies a slice to a raw memory location, disabling all memory protection before doing so.
|
||||
func copyToLocation(location uintptr, data []byte) {
|
||||
f := rawMemoryAccess(location, len(data))
|
||||
|
||||
var oldPerms uint32
|
||||
err := virtualProtect(location, len(data), PAGE_EXECUTE_READWRITE, unsafe.Pointer(&oldPerms))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
copy(f, data[:])
|
||||
|
||||
// VirtualProtect requires you to pass in a pointer which it can write the
|
||||
// current memory protection permissions to, even if you don't want them.
|
||||
var tmp uint32
|
||||
err = virtualProtect(location, len(data), oldPerms, unsafe.Pointer(&tmp))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user