From de00f6ef4e386925fd7864638686067179bc15a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Wed, 4 Oct 2023 15:42:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=94=AF=E6=8C=81=20go1.20=20=E4=B8=8B?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E6=89=93=E6=A1=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Go 调用泛型函数时会先执行 LEA 指令,再执行 CALL 指令。我在上次提交 1f9d72d787e9444e1c249acee84a2b31b5c10485 中基于此特点跳过了跟 struct 传值产生的 CALL 指令,修复了 struct 传值导致 泛型打桩卡住的问题。 判断的依据是看 CALL 指令前面是否还有 LEA 指令且第一个参数为 RAX。 根据 Ruomenger 的调查,Go1.20 在执行 LEA 指令时,其目的寄存器由 RAX 改为 RBX,从而导致泛型 mock 出错。我还没找到对应的改动以及改动的原因。 Ruomenger 提议在检查 LEA 指令时同时支持 RAX 和 RBX 两种情况。但我认为 这样做不能根除问题,以后 Go 编译器改版可能还会引出新问题。 经过一番研究,我感觉使用函数名来判断可能是一个比较稳定的的办法。 getFirstCallFunc 最终找到泛型底层函数的地址。该地址对应的函数名包含`[...]`: ``` github.com/go-kiss/monkey_test.add2[...] github.com/go-kiss/monkey/demo.Add[...] github.com/go-kiss/monkey_test.(*S2__monkey__[...]).Foo github.com/go-kiss/monkey/demo.(*S2[...]).Foo ``` 以此为基准来确定函数指针应该没问题。而且 Go 语言提供 runtime.FuncForPC 非常方便。 Fix #12 --- .github/workflows/go.yml | 18 +++++++++--------- go.sum | 1 - monkey_amd64.go | 16 +++++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a6dc87b..961e325 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,38 +5,38 @@ jobs: name: Test on Linux runs-on: ubuntu-latest steps: - - name: Set up Go 1.18 + - name: Set up Go 1.20 uses: actions/setup-go@v1 with: - go-version: 1.18 + go-version: 1.20 id: go - name: Check out code into the Go module directory uses: actions/checkout@v1 - name: Test - run: go test -gcflags=-l + run: go test -gcflags='all=-N -l' test-macos: name: Test on Mac runs-on: macos-latest steps: - - name: Set up Go 1.18 + - name: Set up Go 1.20 uses: actions/setup-go@v1 with: - go-version: 1.18 + go-version: 1.20 id: go - name: Check out code into the Go module directory uses: actions/checkout@v1 - name: Test - run: go test -gcflags=-l + run: go test -gcflags='all=-N -l' test-windows: name: Test on Windows runs-on: windows-latest steps: - - name: Set up Go 1.18 + - name: Set up Go 1.20 uses: actions/setup-go@v1 with: - go-version: 1.18 + go-version: 1.20 id: go - name: Check out code into the Go module directory uses: actions/checkout@v1 - name: Test - run: go test -gcflags=-l + run: go test -gcflags='all=-N -l' diff --git a/go.sum b/go.sum index db5cb5f..75a53c4 100644 --- a/go.sum +++ b/go.sum @@ -3,4 +3,3 @@ github.com/huandu/go-tls v1.0.1/go.mod h1:WeItecBdaIdUBRb7cSMMk+rq41iFKhf6Q9mDRD golang.org/x/arch v0.0.0-20210901143047-ebb09ed340f1 h1:MwxAfiDvuwX8Nnnc6iRDhzyMyyc2tz5tYyCP/pZcPCg= golang.org/x/arch v0.0.0-20210901143047-ebb09ed340f1/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/monkey_amd64.go b/monkey_amd64.go index 66efaf3..c5511d0 100644 --- a/monkey_amd64.go +++ b/monkey_amd64.go @@ -1,6 +1,9 @@ package monkey import ( + "runtime" + "strings" + "golang.org/x/arch/x86/x86asm" ) @@ -95,13 +98,12 @@ func getFirstCallFunc(from uintptr) uintptr { f := rawMemoryAccess(from, 1024) s := 0 - var lastLea x86asm.Inst for { i, err := x86asm.Decode(f[s:], 64) if err != nil { panic(err) } - if i.Op == x86asm.CALL && lastLea.Args[0].(x86asm.Reg) == x86asm.RAX { + if i.Op == x86asm.CALL { arg := i.Args[0] imm := arg.(x86asm.Rel) next := from + uintptr(s+i.Len) @@ -111,15 +113,15 @@ func getFirstCallFunc(from uintptr) uintptr { } else { to = next - uintptr(-imm) } - return to + f := runtime.FuncForPC(to) + // 泛型函数的名字中包含 [...] + if strings.Index(f.Name(), "[") > 0 { + return to + } } s += i.Len if s >= 1024 { panic("Can not find CALL instruction") } - // 有些情况下会生成 NOPW 指令,需要跳过 - if i.Op == x86asm.LEA { - lastLea = i - } } } From dd261b2a1670a533512e0cdfabaf4933b0978b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Sat, 7 Oct 2023 20:53:38 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=90=8C=E6=97=B6=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=A4=9A=E4=B8=AA=20Go=20=E7=89=88=E6=9C=AC=E5=B9=B6=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E7=A7=BB=E9=99=A4=20Win=20=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/go.yml | 52 +++++++++++----------------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 961e325..5bb21b9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,42 +1,18 @@ name: Go on: [pull_request] jobs: - test-linux: - name: Test on Linux - runs-on: ubuntu-latest + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + version: [1.18, 1.19, "1.20", 1.21] + runs-on: ${{ matrix.os }} steps: - - name: Set up Go 1.20 - uses: actions/setup-go@v1 - with: - go-version: 1.20 - id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v1 - - name: Test - run: go test -gcflags='all=-N -l' - test-macos: - name: Test on Mac - runs-on: macos-latest - steps: - - name: Set up Go 1.20 - uses: actions/setup-go@v1 - with: - go-version: 1.20 - id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v1 - - name: Test - run: go test -gcflags='all=-N -l' - test-windows: - name: Test on Windows - runs-on: windows-latest - steps: - - name: Set up Go 1.20 - uses: actions/setup-go@v1 - with: - go-version: 1.20 - id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v1 - - name: Test - run: go test -gcflags='all=-N -l' + - name: Set up Go ${{ matrix.version }} + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.version }} + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + - name: Test + run: go test -gcflags='all=-N -l' From 82cfbaaf6b8ae840d232e3a0b49561df36d8b056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=95=E6=B5=B7=E6=B6=9B?= Date: Sat, 7 Oct 2023 21:04:59 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20win=20=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a8724e5..f033834 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,6 @@ Go 语言猴子补丁(monkey patching)框架。 Bouke 已经不再维护原项目,所以只能开一个新项目了🤣。 -有兴趣的同学也可以加微信 `taoshu-in` 讨论,拉你进群。 - ## 快速入门 首先,引入 monkey 包 @@ -62,7 +60,9 @@ func main() { ## 注意事项 -1. Monkey 需要关闭 Go 语言的内联优化才能生效,比如测试的时候需要:`go test -gcflags=all=-l`。 +1. Monkey 需要关闭 Go 语言的内联优化才能生效,比如测试的时候需要:`go test -gcflags='all=-N -l'`。 2. Monkey 需要在运行的时候修改内存代码段,因而无法在一些对安全性要求比较高的系统上工作。 3. Monkey 不应该用于生产系统,但用来 mock 测试代码还是没有问题的。 -4. Monkey 目前仅支持 amd64 指令架构,支持 linux/macos/windows 平台。 +4. Monkey 目前仅支持 amd64 指令架构,支持 linux/macos/~~windows~~[^win] 平台。 + +[^win]: 目前在 Win 平台下 Go 1.20 版本之后会报错。欢迎熟悉 Win 平台的同学提供 PR。问题解决之前建议 Win 用户使用 WSL。