Giter Site home page Giter Site logo

monkey's People

Contributors

allenluce avatar andoryuuta avatar bouk avatar kkbblzq avatar ruomenger avatar samanhappy avatar streamlet avatar taoso avatar x1nchen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

monkey's Issues

如何做到在patch状态下调用原函数

刚刚睡不着,画了大概10多分钟看了go夜读的
#119 Go monkey patch 的原理及应用
录播,发现都有一个问题对于类似这种情况见: cch123/supermonkey#12
我看了原理以后感觉应该还是有办法能解决的,例如给开始加个汇编的判断,然后强制走回原函数,不过这个判断的值就比较麻烦了,如果引用一个全局变量倒是比较简单,但是这样频繁修改也不是太合理,要是能直接判断出父级方法是指定的方法然后调用原函数就好了。

顺便放一下应用场景: https://blog.zeromake.com/pages/replace-now-offset/

能否恢复 PatchInstanceMethod 函数

项目中使用了 bou.ke/monkey,但由于其不再维护需要寻找一个替代方案。

另外,项目代码中大量使用了 PatchInstanceMethod 函数,但是这个函数在 重新支持全局打桩 PR#7 被移除了。

所以想问,能否恢复 PatchInstanceMethod 函数,保持向前兼容,这样其他项目只需要替换引用的包就可以了?

结构体字段多导致 go test 卡住

// main.go
package main

type TreeNode struct {
	Name     string
	ID       int
	Children []*TreeNode
	A        int
	B        int
	C        int
	// 注释掉 go test 可以正常运行
	A1        int
	B2        int
	C1        int
}

func first[T any](a, b T) T { return a }

func second[T any](a, b T) T { return b }

// main_test.go
package main

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/go-kiss/monkey"
)

func Test_main(t *testing.T) {
	t1 := TreeNode{ ID: 1 }
	t2 := TreeNode{ ID: 2 }

	monkey.Patch(first[TreeNode], second[TreeNode], monkey.OptGeneric)
	if reflect.DeepEqual(t2, first(t1, t2)) {
		fmt.Println("equal")
	} else {
		fmt.Println("not equal")
		t.Fatalf("not equal")
	}
}

signal SIGSEGV: segmentation violation

使用monkey配合plugin,实现一个简单的热修复功能会发生崩溃,刚开始patching之后一切正常,运行一会以后就会发生错误

另外: agiledragon/gomonkey/#132 存在同样的问题

测试环境:

  • go version go1.20.4 linux/amd64
  • Ubuntu 20.04.5 LTS (WSL2)

main.go

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"plugin"
	"reflect"
	"sync/atomic"
	"time"

	"github.com/go-kiss/monkey"
	"gocase/gopatch/route"
)

var HotfixVersion = "main"

func IndexHandler() func(http.ResponseWriter, *http.Request) {
	//return route.Index
	var counter int32
	return func(writer http.ResponseWriter, request *http.Request) {
		fmt.Fprintln(writer, "plugin handle:", atomic.AddInt32(&counter, 1))
	}
}

func main() {
	http.HandleFunc("/", route.Index)

	// 热修复 route.Index 逻辑
	{
		p, err := plugin.Open("http_v1.so")
		if nil != err {
			panic(err)
		}

		sym, err := p.Lookup("IndexHandler")
		if nil != err {
			panic(err)
		}

		fn := sym.(func() func(http.ResponseWriter, *http.Request))
		handler := fn()

		fmt.Println("patching...", reflect.ValueOf(route.Index).Pointer(), " => ", reflect.ValueOf(handler).Pointer())

		monkey.Patch(route.Index, handler, monkey.OptGlobal)
	}

	// 等3s后开始启动客户端访问接口
	time.AfterFunc(time.Second*3, func() {

		fmt.Println("start client...")
		for i := 0; i < 3; i++ {
			go startClient()
		}
	})

	// 启动http服务器
	fmt.Println("http server listen...")
	err := http.ListenAndServe(":8080", nil)
	fmt.Println("http server listen failed: ", err)
}

func startClient() {
	for {
		resp, err := http.Get("http://127.0.0.1:8080/")
		if nil != err {
			fmt.Println("http.Get: ", err.Error())
			return
		}
		io.Copy(os.Stdout, resp.Body)
		resp.Body.Close()
	}
}

route.go

var counter int32
func Index(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprintln(writer, "main: hello gopatch/", atomic.AddInt32(&counter, 1))
}

run_http.sh

#!/bin/bash

set -e

echo "build main program..."
go build -gcflags=all=-l -ldflags="-X main.HotfixVersion=main" -o http_main main.go

echo "please modify v1 plugin, press enter key to continue..."
read input

echo "build plugin v1..."
go build -gcflags=all=-l -buildmode=plugin -ldflags="-X main.HotfixVersion=v1" -o http_v1.so main.go

echo "run main program..."
./http_main

输出结果

patching... 8645408  =>  140521878991296
http server listen...
start client...
plugin handle: 1
plugin handle: 3
plugin handle: 2
plugin handle: 4
plugin handle: 6
plugin handle: 7
plugin handle: 5
plugin handle: 8
plugin handle: 9
plugin handle: 11
plugin handle: 10
plugin handle: 12
plugin handle: 13
plugin handle: 14
plugin handle: 15
plugin handle: 16
plugin handle: 17
plugin handle: 18
plugin handle: 19
plugin handle: 20
plugin handle: 22
plugin handle: 21
plugin handle: 23
plugin handle: 24
plugin handle: 25
plugin handle: 26
plugin handle: 27
plugin handle: 28
plugin handle: 30
plugin handle: 29
plugin handle: 31
plugin handle: 32
plugin handle: 33
plugin handle: 34
plugin handle: 35
plugin handle: 36
plugin handle: 37
plugin handle: 38
plugin handle: 41
plugin handle: 39
plugin handle: 40
plugin handle: 12346
plugin handle: 12347
plugin handle: 12348
plugin handle: 12349
plugin handle: 12350
plugin handle: 12351
unexpected fault address 0x931cc0
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0x931cc0 pc=0x931cc0]

goroutine 182 [running]:
runtime.throw({0x8e39fd?, 0xc00022a000?})
        /usr/local/go/src/runtime/panic.go:1047 +0x5d fp=0xc0004f4a70 sp=0xc0004f4a40 pc=0x5906dd
runtime.sigpanic()
        /usr/local/go/src/runtime/signal_unix.go:851 +0x1e5 fp=0xc0004f4aa0 sp=0xc0004f4a70 pc=0x5a7465
net/http.HandlerFunc.ServeHTTP(0x688a65?, {0x934080?, 0xc00058a0e0?}, 0x92ceb8?)
        /usr/local/go/src/net/http/server.go:2123 +0x2f fp=0xc0004f4ac8 sp=0xc0004f4aa0 pc=0x80644f
net/http.(*ServeMux).ServeHTTP(0x0?, {0x934080, 0xc00058a0e0}, 0xc0001d2300)
        /usr/local/go/src/net/http/server.go:2500 +0xc2 fp=0xc0004f4b00 sp=0xc0004f4ac8 pc=0x8079a2
net/http.serverHandler.ServeHTTP({0xc000482a80?}, {0x934080?, 0xc00058a0e0?}, 0xc0001d2300?)
        /usr/local/go/src/net/http/server.go:2936 +0x23c fp=0xc0004f4b98 sp=0xc0004f4b00 pc=0x808fdc
net/http.(*conn).serve(0xc0004847e0, {0x934488, 0xc00011af30})
        /usr/local/go/src/net/http/server.go:1995 +0xad0 fp=0xc0004f4fb8 sp=0xc0004f4b98 pc=0x8055b0
net/http.(*Server).Serve.func3()
        /usr/local/go/src/net/http/server.go:3089 +0x2e fp=0xc0004f4fe0 sp=0xc0004f4fb8 pc=0x80992e
runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:1598 +0x1 fp=0xc0004f4fe8 sp=0xc0004f4fe0 pc=0x5c4861
created by net/http.(*Server).Serve
        /usr/local/go/src/net/http/server.go:3089 +0x45f
...

在go版本>=1.20时generic mock失败

env

  • wsl2 Arch Linux on win11
  • go version:1.21.1 & 1.20.8 & 1.19.8 & 1.18.8

problem

使用go1.21.1和go1.20.8编译(编译命令禁止了内联和优化go build -gcflags '-N -l' -o generic-go1.21.1)运行examples中的generic时报错,使用go1.18.8和go1.19.8编译后执行可以正常输出结果为2。

error:

panic: Can not find CALL instruction

goroutine 1 [running]:
github.com/go-kiss/monkey.getFirstCallFunc(0x487be0)
        /home/ruo/go/src/monkey/monkey_amd64.go:118 +0x277
github.com/go-kiss/monkey.patchValue({0x48fd60?, 0x4a9cc8?, 0x1?}, {0x48fe20?, 0x4a9cd0?, 0x48f300?}, 0xc0000a6000)
        /home/ruo/go/src/monkey/monkey.go:145 +0x58a
github.com/go-kiss/monkey.Patch({0x48fd60?, 0x4a9cc8?}, {0x48fe20?, 0x4a9cd0?}, {0xc00004a6f0, 0x1, 0x7fb6b70ee5b8?})
        /home/ruo/go/src/monkey/monkey.go:54 +0x19e
main.main()
        /home/ruo/go/src/monkey/examples/generic/generic.go:26 +0xb2

generic.go代码

package main

import (
	"fmt"

	"github.com/go-kiss/monkey"
)

type S1[T int | float64] struct {
	i T
}

func (s *S1[T]) Get() T {
	return s.i
}

type S1__monkey__[T int | float64] struct {
	S1[T]
}

func (s *S1__monkey__[T]) Get() T {
	return s.i + 1
}

func main() {
	monkey.Patch((*S1[int]).Get, (*S1__monkey__[int]).Get, monkey.OptGeneric)
	s := S1[int]{i: 1}
	fmt.Println(s.Get()) // display 2
}

问题分析

明确了问题出现的场景及看了getFirstCallFunc的实现后,我用gdb对go1.18.8编译出的generic-go1.18.8和go1.21.1编译出的generic-go1.21.1进行了调试

go1.18.8反编译如下所示:

image

go1.21.1反编译如下所示:
image

而在getFirstCallFunc中的判断逻辑是要求call指令的上一条指令使用RAX寄存器

if i.Op == x86asm.CALL && lastLea.Args[0].(x86asm.Reg) == x86asm.RAX

所以问题应该出现在这里,我将这里的判断逻辑修改为使用RAX或RBX寄存器后可以正常执行了,但是还不确定go1.20进行了什么改动导致的这一问题

panic: permission denied [recovered]

=== RUN TestName1
--- FAIL: TestName1 (0.00s)
panic: permission denied [recovered]
panic: permission denied

goroutine 83 [running]:
testing.tRunner.func1.2({0x100ff16a0, 0x1018edb88})
/usr/local/go/src/testing/testing.go:1545 +0x48a
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1548 +0x63f
panic({0x100ff16a0?, 0x1018edb88?})
/usr/local/go/src/runtime/panic.go:920 +0x290
github.com/go-kiss/monkey.mprotectCrossPage(0x100ee1c00, 0xd, 0x7)
/Users/amos/go/pkg/mod/github.com/go-kiss/[email protected]/replace_unix.go:16 +0xf9
github.com/go-kiss/monkey.copyToLocation(0x100ee1c00, {0xc00052fe30, 0xd, 0xd})
/Users/amos/go/pkg/mod/github.com/go-kiss/[email protected]/replace_unix.go:27 +0x75
github.com/go-kiss/monkey.(*patch).Apply(0xc00022d090)
/Users/amos/go/pkg/mod/github.com/go-kiss/[email protected]/monkey.go:269 +0x1be
github.com/go-kiss/monkey.patchValue({0x100f92de0, 0x101190540, 0x13}, {0x100f92de0, 0x101190520, 0x13}, 0xc00024f6a0)
/Users/amos/go/pkg/mod/github.com/go-kiss/[email protected]/monkey.go:150 +0x6fc
github.com/go-kiss/monkey.Patch({0x100f92de0, 0x101190540}, {0x100f92de0, 0x101190520}, {0x0, 0x0, 0x0})
/Users/amos/go/pkg/mod/github.com/go-kiss/[email protected]/monkey.go:54 +0x1e5
data-factory/core/manager.TestName1(0xc000501520)
/Users/amos/Code/104-Golang/data-factory/core/manager/import_test.go:24 +0x5f
testing.tRunner(0xc000501520, 0x101190460)
/usr/local/go/src/testing/testing.go:1595 +0x1c2
created by testing.(*T).Run in goroutine 1
/usr/local/go/src/testing/testing.go:1648 +0x7e5

已经添加 -gcflags='all=-N -l' 参数

关于 getg 的一些疑问

目前monkeygetg函数的实现在windows平台下是

mov r13,QWORD PTR gs:0x28
mov r12,QWORD PTR [r13]

在linux平台下的实现是

mov r12,QWORD PTR fs:0xfffffffffffffff8

我在 https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md 和自己的测试中发现go使用r14寄存器
存储当前的goroutine指针,那么为何不能直接获取r14的值来用呢?

1696777792787

image

我将getg在windows和linux的实现修改为了

func getg() []byte {
	return []byte{
		// mov r12, r14
		0x4D, 0x89, 0xF4,
	}
}

都是可以正常跑通测试的,并不会有问题,那么之前的这种写法是有什么别的考虑吗?如果没有的话用这种方法应该可以统一win/linux(应该也包括intel版mac,但是m1芯片的mac是arm应该不行)的getg实现,且解决之前的windows平台下panic的问题。

image

@taoso FYI

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.