Skip to content

Commit

Permalink
修复 struct 传值导致泛型打桩卡住的问题
Browse files Browse the repository at this point in the history
Fix #8

Go 会为泛型函数生成中间函数。但实际需要 mock 的是中间函数所
调用的公共函数。为此,我们需要遍历中间函数机器码,通过 CALL
指令确定公共函数的地址。

如果函数使用了 struct 值传递,Go 在一些场景下会插入若干额外
的 CALL 指令。这样 monkey 就会拿到错误的公共函数地址。

为此,我们需要跳过因为 struct 传值而需要执行的 CALL 指令。

通过观察生成的汇编代码,我发现这些 CALL 指令用 BP 寄存器,而
调用公共函数用的是 AX 寄存器。所以可以根据寄存器参数来过滤。
  • Loading branch information
taoso committed Aug 19, 2022
1 parent 2c08d44 commit 15d6e66
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 1 deletion.
6 changes: 5 additions & 1 deletion monkey_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,15 @@ func getFirstCallFunc(from uintptr) uintptr {
f := rawMemoryAccess(from, 1024)

s := 0
var lastOp x86asm.Inst
for {
i, err := x86asm.Decode(f[s:], 64)
if err != nil {
panic(err)
}
if i.Op == x86asm.CALL {
if i.Op == x86asm.CALL &&
lastOp.Op == x86asm.LEA &&
lastOp.Args[0].(x86asm.Reg) == x86asm.RAX {
arg := i.Args[0]
imm := arg.(x86asm.Rel)
next := from + uintptr(s+i.Len)
Expand All @@ -116,5 +119,6 @@ func getFirstCallFunc(from uintptr) uintptr {
if s >= 1024 {
panic("Can not find CALL instruction")
}
lastOp = i
}
}
25 changes: 25 additions & 0 deletions monkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,28 @@ func TestGeneric(t *testing.T) {
g2.Unpatch()
assert(t, 2 == s.Foo())
}

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 }

// https://github.com/go-kiss/monkey/issues/8
func TestIssue8(t *testing.T) {
t1 := TreeNode{ID: 1}
t2 := TreeNode{ID: 2}
monkey.Patch(first[TreeNode], second[TreeNode], monkey.OptGeneric)
assert(t, t2.ID == first(t1, t2).ID)
}

0 comments on commit 15d6e66

Please sign in to comment.