Patch all Go functions for testing

Overview

SuperMonkey

gopher

This lib is inspired by https://github.com/bouk/monkey, and uses some of the code

Introduction

Patch all functions without limits, including which are unexported

Warning : please add -l to your gcflags or add //go:noinline to func which you want to patch.

when running in tests

you should run this lib under a go mod project and provide the full project path

Warning : use go test -ldflags="-s=false" -gcflags="-l" to enable symbol table and disable inline.

when running not in tests

patch private function

normal

package main

import (
	"fmt"

	sm "github.com/cch123/supermonkey"
)

func main() {
	fmt.Println("original function output:")
	heyHey()

	patchGuard := sm.Patch(heyHey, func() {
		fmt.Println("please be polite")
	})
	fmt.Println("after patch, function output:")
	heyHey()

	patchGuard.Unpatch()
	fmt.Println("unpatch, then output:")
	heyHey()
}

//go:noinline
func heyHey() {
	fmt.Println("fake")
}

go run -gcflags="-l" yourfile.go

full symbol name

package main

import (
	"fmt"

	sm "github.com/cch123/supermonkey"
)

func main() {
	fmt.Println("original function output:")
	heyHeyHey()

	patchGuard := sm.PatchByFullSymbolName("main.heyHeyHey", func() {
		fmt.Println("please be polite")
	})
	fmt.Println("after patch, function output:")
	heyHeyHey()

	patchGuard.Unpatch()
	fmt.Println("unpatch, then output:")
	heyHeyHey()
}

//go:noinline
func heyHeyHey() {
	fmt.Println("fake")
}

go run -gcflags="-l" yourfile.go

patch private instance method

normal

package main

import (
	"fmt"

	sm "github.com/cch123/supermonkey"
)

type person struct{ name string }

//go:noinline
func (p *person) speak() {
	fmt.Println("my name is ", p.name)
}

func main() {
	var p = person{"Lance"}
	fmt.Println("original function output:")
	p.speak()

	patchGuard := sm.Patch((*person).speak, func(*person) {
        fmt.Println("we are all the same")
    })
    fmt.Println("after patch, function output:")
    p.speak()

	patchGuard.Unpatch()
	fmt.Println("unpatch, then output:")
	p.speak()
}

go run -gcflags="-l" yourfile.go

full symbol name

package main

import (
	"fmt"
	"unsafe"

	sm "github.com/cch123/supermonkey"
)

type person struct{ name string }

//go:noinline
func (p *person) speak() {
	fmt.Println("my name is ", p.name)
}

func main() {
	var p = person{"Linda"}
	fmt.Println("original function output:")
	p.speak()

	patchGuard := sm.PatchByFullSymbolName("main.(*person).speak", func(ptr uintptr) {
		p = (*person)(unsafe.Pointer(ptr))
		fmt.Println(p.name, ", we are all the same")
	})
	fmt.Println("after patch, function output:")
	p.speak()

	patchGuard.Unpatch()
	fmt.Println("unpatch, then output:")
	p.speak()
}
package main

import (
	"context"
	"fmt"

	sm "github.com/cch123/supermonkey"
)

type Bar struct {
	Name string
}

type Foo struct{}

func (*Foo) MyFunc(ctx context.Context) (*Bar, error) {
	return &Bar{"Bar"}, nil
}

func main() {
	f := &Foo{}
	fmt.Println("original function output:")
	fmt.Println(f.MyFunc(nil))

	//patchGuard := sm.PatchByFullSymbolName("main.(*Foo).MyFunc", func(_ *Foo, ctx context.Context) (*Bar, error) {
	patchGuard := sm.PatchByFullSymbolName("main.(*Foo).MyFunc", func(_ uintptr, ctx context.Context) (*Bar, error) {
		return &Bar{"Not bar"}, nil
	})

	fmt.Println("after patch, function output:")
	fmt.Println(f.MyFunc(nil))

	patchGuard.Unpatch()
	fmt.Println("unpatch, then output:")
	fmt.Println(f.MyFunc(nil))
}

go run -gcflags="-l" yourfile.go

Authors

cch123

Mutated1994

Comments
  • Failed in Test

    Failed in Test

    Hey thanks for the awesome package!

    I met some problems when using supermonkey in test.

    // main.go
    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	heyHey()
    }
    
    //go:noinline
    func heyHey() {
    	fmt.Println("fake")
    }
    
    // main_test.go
    package main
    
    import (
    	"fmt"
    	"testing"
    
    	sm "github.com/cch123/supermonkey"
    )
    
    func Test_HeyHey(t *testing.T) {
    	fmt.Println("original function output:")
    	heyHey()
    	fmt.Println()
    
    	sm.PatchByFullSymbolName("main.heyHey", func() {
    		fmt.Println("please be polite")
    	})
    	fmt.Println("after patch, function output:")
    	heyHey()
    	fmt.Println()
    
    	sm.UnpatchAll()
    	fmt.Println("unpatch all, then output:")
    	heyHey()
    }
    

    And I run the test like

    $go test -gcflags="-l" ./
    original function output:
    fake
    
    The symbol is main.heyHey, and the patch target addr is 0, there may be 2 possible reasons
            1. the function is inlined, please add //go:noinline to function comment or add -l to gcflags
            2. your input for symbolName or pkg/obj/method is wrong, check by using go tool nm {your_bin_file}
    --- FAIL: Test_HeyHey (0.00s)
    panic:  [recovered]
            panic: 
    
    goroutine 20 [running]:
    testing.tRunner.func1.1(0x1193940, 0x1222350)
            /usr/local/Cellar/go/1.14.4/libexec/src/testing/testing.go:940 +0x2f5
    testing.tRunner.func1(0xc0000d0360)
            /usr/local/Cellar/go/1.14.4/libexec/src/testing/testing.go:943 +0x3f9
    panic(0x1193940, 0x1222350)
            /usr/local/Cellar/go/1.14.4/libexec/src/runtime/panic.go:969 +0x166
    github.com/cch123/supermonkey.PatchByFullSymbolName(0x11e749e, 0xb, 0x1191d20, 0x11f4610)
            /Users/kiyon/go/pkg/mod/github.com/cch123/[email protected]/supermonkey.go:33 +0x326
    learn-golang/supermonkey.Test_HeyHey(0xc0000d0360)
            /Users/kiyon/Code/learn/learn-golang/supermonkey/main_test.go:15 +0xac
    testing.tRunner(0xc0000d0360, 0x11f4618)
            /usr/local/Cellar/go/1.14.4/libexec/src/testing/testing.go:991 +0xdc
    created by testing.(*T).Run
            /usr/local/Cellar/go/1.14.4/libexec/src/testing/testing.go:1042 +0x357
    FAIL    learn-golang/supermonkey        0.399s
    FAIL
    

    Did I miss something?

    bug 
    opened by kiyonlin 5
  • feat: Combine SuperMonkey with Monkey.

    feat: Combine SuperMonkey with Monkey.

    Changed

    • goobj、nm、objabi、objfile、src、sys、xcoff 都移到了 internal
    • bouk/monkey 代码移入 internal/bouk
    • 兼容 bouk/monkey 原先的 patch 方法
    • 增加PatchByFullSymbolName失败后相似符号提示
    • 修改 examples
    • 更新 readme

    曹大,我又来了,这次斗胆给您提交个pr。 因为 bouk/monkey 之前大家还是使用的比较多,所以是不是可以考虑兼容一下它?加量不加价,更加美滋滋☺

    opened by Mutated1994 4
  • Suggestions on how to best integrate with Testify?

    Suggestions on how to best integrate with Testify?

    To my amazement and joy this worked from the outset on an M1 Mac in Goland with Go 1.19.1! Unlike another package I won't name.

    My question now is, where the best place would be to integrate with Testify as the test framework?

    Having to set up the mock in every single package that uses my to-be-monkeyed methods/funcs would be highly annoying (doable, but annoying).

    Unfortunately Testify doesn't have a "super-test-setup" that you can do once and every Suite setup receives. At least as far as I saw in the doco.

    Any ideas?

    opened by Marakai 1
  • supermonkey seems to not suit the higher version of golang

    supermonkey seems to not suit the higher version of golang

    Now we are using Go 1.14.2. It seems to be okay with Patch function. But when we need to upgrade our golang version to 1.15 or higher, the supermonkey.Patch seems not okay. Mock is ineffective. Do you konw why, or do you have a plan for suiting golang higher version. Thank you.

    opened by ZenKaiii 3
  • panic: permission denied

    panic: permission denied

    image panic: permission denied [recovered] panic: permission denied

    goroutine 162 [running]: testing.tRunner.func1.2({0x4b819e0, 0x55a90a8}) /usr/local/go/src/testing/testing.go:1209 +0x24e testing.tRunner.func1() /usr/local/go/src/testing/testing.go:1212 +0x218 panic({0x4b819e0, 0x55a90a8}) /usr/local/go/src/runtime/panic.go:1038 +0x215 github.com/cch123/supermonkey/internal/bouk.mprotectCrossPage(0x461f6e0, 0xc, 0x10) /Users/sangtianyu/go/pkg/mod/github.com/cch123/[email protected]/internal/bouk/replace_unix.go:15 +0xc5 github.com/cch123/supermonkey/internal/bouk.CopyToLocation(0x461f6e0, {0xc000651cc4, 0xc, 0xc000384108}) /Users/sangtianyu/go/pkg/mod/github.com/cch123/[email protected]/internal/bouk/replace_unix.go:26 +0x5c github.com/cch123/supermonkey/internal/bouk.ReplaceFunction(0x461f6e0, 0x4cf9f08) /Users/sangtianyu/go/pkg/mod/github.com/cch123/[email protected]/internal/bouk/replace.go:29 +0xb2 github.com/cch123/supermonkey/internal/bouk.patchValue({0x4b4e800, 0x4cf9f08, 0x0}, {0x4b4e800, 0x4cf9c18, 0x13}, 0x0) /Users/sangtianyu/go/pkg/mod/github.com/cch123/[email protected]/internal/bouk/monkey.go:96 +0x2b6 github.com/cch123/supermonkey/internal/bouk.Patch({0x4b4e800, 0x4cf9f08}, {0x4b4e800, 0x4cf9c18}) /Users/sangtianyu/go/pkg/mod/github.com/cch123/[email protected]/internal/bouk/monkey.go:50 +0x17a github.com/cch123/supermonkey.Patch({0x4b4e800, 0x4cf9f08}, {0x4b4e800, 0x4cf9c18}) /Users/sangtianyu/go/pkg/mod/github.com/cch123/[email protected]/supermonkey.go:23 +0x2d

    go env GO111MODULE="on" GOARCH="amd64" GOHOSTARCH="amd64" GOHOSTOS="darwin"

    opened by pherzheyu 4
  • Is there any way to call original function by symbol name?

    Is there any way to call original function by symbol name?

            var guard_down *sm.PatchGuard
    	download_hook := "xxx.DownloadPiece"
    	guard_down = sm.PatchByFullSymbolName(download_hook, func(ctx context.Context, pt peer.Task, request *peer.DownloadPieceRequest) (success bool) {
    		// do something
    
                    // temporary unpatch
    		guard_down.Unpatch()
    		defer guard_down.Restore()
                    
                    // use forceexport to fetch original function
    		var function func(context.Context, peer.Task, *peer.DownloadPieceRequest) (success bool)
    		err := forceexport.GetFunc(&function, download_hook)
    		if err != nil {
    			log.Panicf("connect target error: %s", err)
    		}
    
                    // call original function
    		success = function(ctx, pt, request)
    		return
    	})
    

    If the original function is unexported, and should be called inside the hooked method as above, is supermonkey provide some way to call it by symbol name rather than use forceexport?

    opened by cutecutecat 2
  • undefined: JmpToFunctionValue

    undefined: JmpToFunctionValue

    It's not working on ARM architecture

    github.com/cch123/supermonkey/internal/bouk

    ../../../../go/pkg/mod/github.com/cch123/[email protected]/internal/bouk/replace.go:24:14: undefined: JmpToFunctionValue

    help wanted 
    opened by dgofman 6
  • patch fun after how get origin fun

    patch fun after how get origin fun

    not use UnPatch

    package main
    
    import (
    	"fmt"
    	sm "github.com/cch123/supermonkey"
    	"time"
    )
    //go:noinline
    func Now() time.Time {
    	return time.Time{}
    }
    
    func main() {
    	sm.Patch(time.Now, Now)
    	fmt.Println(time.Now())
    	// how call origin Now fun
    }
    
    question 
    opened by zeromake 3
Releases(v1.0.1)
Owner
Xargin
If you don't keep moving, you'll quickly fall behind.
Xargin
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

Re 290 Nov 26, 2022
A yaml data-driven testing format together with golang testing library

Specimen Yaml-based data-driven testing Specimen is a yaml data format for data-driven testing. This enforces separation between feature being tested

Design it, Run it 1 Nov 24, 2022
Benchmarking hash functions in Go

Experiment: Benchmarks of Hash Functions in Go This repository contains a small experiment of mine, trying out different hash functions and comparing

Roman Atachiants 6 Jun 22, 2022
Benchmarking deferent Fibonacci functions and algorithms with running unit test

GoFibonacciBench Benchmarking deferent Fibonacci functions and algorithms with running unit test ... Introduction: Fibonacci numbers are special kinds

null 2 Feb 27, 2022
Snapshot - snapshot provides a set of utility functions for creating and loading snapshot files for using snapshot tests.

Snapshot - snapshot provides a set of utility functions for creating and loading snapshot files for using snapshot tests.

Daniel J. Rollins 2 Jan 27, 2022
Random - A Golang library that provides functions for generating random values

Random A Golang library that provides functions for generating random values. Us

Fatih Cetinkaya 1 Apr 14, 2022
Create all possible binaries from go files

gopher-build-all Create all possible binaries of a project in go ChangeLog 0.0.2 Flags bin-folder-name ─ With this flag you can rename the output dire

FlamesX128 3 Dec 16, 2021
Fortio load testing library, command line tool, advanced echo server and web UI in go (golang). Allows to specify a set query-per-second load and record latency histograms and other useful stats.

Fortio Fortio (Φορτίο) started as, and is, Istio's load testing tool and now graduated to be its own project. Fortio is also used by, among others, Me

Fortio (Φορτίο) 2.8k Nov 28, 2022
:exclamation:Basic Assertion Library used along side native go testing, with building blocks for custom assertions

Package assert Package assert is a Basic Assertion library used along side native go testing Installation Use go get. go get github.com/go-playground/

Go Playgound 50 Nov 26, 2022
Expressive end-to-end HTTP API testing made easy in Go

baloo Expressive and versatile end-to-end HTTP API testing made easy in Go (golang), built on top of gentleman HTTP client toolkit. Take a look to the

Tom 746 Nov 19, 2022
Simple Go snapshot testing

Incredibly simple Go snapshot testing: cupaloy takes a snapshot of your test output and compares it to a snapshot committed alongside your tests. If t

Bradley Kemp 240 Nov 24, 2022
Clean database for testing, inspired by database_cleaner for Ruby

DbCleaner Clean database for testing, inspired by database_cleaner for Ruby. It uses flock syscall under the hood to make sure the test can runs in pa

Scott Le 146 Nov 17, 2022
Golang HTTP client testing framework

flute Golang HTTP client testing framework Presentation https://speakerdeck.com/szksh/flute-golang-http-client-testing-framework Overview flute is the

Shunsuke Suzuki 17 Sep 27, 2022
API testing framework inspired by frisby-js

frisby REST API testing framework inspired by frisby-js, written in Go Proposals I'm starting to work on frisby again with the following ideas: Read s

_Hofstadter 274 Sep 27, 2022
Mutation testing for Go source code

go-mutesting go-mutesting is a framework for performing mutation testing on Go source code. Its main purpose is to find source code, which is not cove

Markus Zimmermann 563 Nov 18, 2022
Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs

go-testdeep Extremely flexible golang deep comparison, extends the go testing package. Latest news Synopsis Description Installation Functions Availab

Maxime Soulé 329 Nov 26, 2022
Minimal and Beautiful Go testing framework

Goblin A Mocha like BDD testing framework written in Go that requires no additional dependencies. Requires no extensive documentation nor complicated

null 868 Nov 23, 2022
A Comprehensive Coverage Testing System for The Go Programming Language

goc 中文页 | goc is a comprehensive coverage testing system for The Go Programming Language, especially for some complex scenarios, like system testing c

Qiniu Cloud 598 Nov 23, 2022
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.

GoConvey is awesome Go testing Welcome to GoConvey, a yummy Go testing tool for gophers. Works with go test. Use it in the terminal or browser accordi

SmartyStreets 7.5k Nov 27, 2022