gomonkey: macOS permission denied

Line 19: - permission denied goroutine 4 [running]:

发生在

err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
if err != nil {
    panic(err)
}

环境

  • MacOS 11.6.1 (ARM64 M1 以及 X86都如此)
  • 运行GoMonkey的最新Demo
  • 使用的gomonkey 2.2.0

描述:

  • 使用GOARCH=amd64可以解决, 但是无法debug很不幸.
  • 在Linux/Windows都是正常
  • macos-golink-wrapper 方案可以解决x86上此问题, 但是arm64依然不行
  • 如果使用单元测试框架结果导致无法断点调试也是太影响了

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 2
  • Comments: 59 (5 by maintainers)

Most upvoted comments

Suggest to give up

换armd64无法断点调试, 而且编译性能差, 基本上等于没解决

on macos, you can try command [go test -ldflags=“-extldflags=”-Wl,-segprot,__TEXT,rwx,rx""],go test command with option extldflags. which have same effect with https://github.com/eisenxp/macos-golink-wrapper, but do not need wrap go link

Waiting for a solution

it is fixed by a very easy way.

err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)

change to

err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE)

in the function `func modifyBinary(target uintptr, bytes []byte) { function := entryAddress(target, len(bytes))

page := entryAddress(pageStart(target), syscall.Getpagesize())
err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
if err != nil {
    panic(err)
}
copy(function, bytes)

err = syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
if err != nil {
    panic(err)
}

}`

“github.com/agiledragon/gomonkey/v2” modify_binary_darwin.go

Test ok at Mac M1 13.2.1 Please follow step below to solve problem “permission denied” follow this issues steam solution and test at Mac M1.

  • Enjoy it and save your life, I donnot know why!
  1. find this file modify_binary_darwin.go in your GoProjects/pkg
  2. currently gomonkey version is v2@v2.9.0, please replace this file with code below directly
package gomonkey

import (
	"syscall"
	"time"
)

func modifyBinary(target uintptr, bytes []byte) {
	function := entryAddress(target, len(bytes))
	err := mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_WRITE)
	if err != nil {
		panic(err)
	}
	copy(function, bytes)
	err = mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_EXEC)
	if err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond)
}

func mprotectCrossPage(addr uintptr, length int, prot int) error {
	pageSize := syscall.Getpagesize()
	for p := pageStart(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
		page := entryAddress(p, pageSize)
		if err := syscall.Mprotect(page, prot); err != nil {
			return err
		}
	}
	return nil
}

这个测试可用,但是这里的表述有点问题,我补充一下: 1、只需要修改文件modify_binary_darwin.go第7行,删除参数:syscall.PROT_EXEC 最终:err := mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_WRITE) 2、使用go test -gcflags=all=-l 进行测试

v2.11.0 has been released!

on macos, you can try command [go test -ldflags=“-extldflags=”-Wl,-segprot,__TEXT,rwx,rx""],go test command with option extldflags. which have same effect with https://github.com/eisenxp/macos-golink-wrapper, but do not need wrap go link

not work for me

I do some test of mprotect on m1.

conclusion: m1 memory page can’t has W+X at same time, and TEXT segment must have X, so we can’t write TEXT segment😭

GOARCH=amd64 is the only way

I tried this and it…sort of worked? However, there seemed to be some sort of timing issue - I noticed that the patch reset wasn’t taking effect.

Adding a small sleep seemed to fix that, but without understanding the problem, I don’t trust that as a solution. For reference, here was my full modifyBinary() from modify_binary_darwin.go:

func modifyBinary(target uintptr, bytes []byte) {
	function := entryAddress(target, len(bytes))
	err := mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_WRITE)
	if err != nil {
		panic(err)
	}
	copy(function, bytes)
	err = mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_EXEC)
	if err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond)
}

After this change, the tests in gomonkey/test suite failed in similar ways between my M1 and non-M1 machines.

More specifically, everything passed except apply_private_method_test.go. On M1, I get an unexpected fault address, whereas on non-M1 I get retrieve method by name failed.

I update gomonkey from v2.9.0 to v2.11.0 and finally fix this problem!

Additional info:

  1. m1 pro
  2. go 1.12
  3. goconvey 1.7.2

v2.11.0 has been released!

It works on my M2 Mac! 🎉

Test ok at Mac M1 13.2.1 Please follow step below to solve problem “permission denied” follow this issues steam solution and test at Mac M1.

  • Enjoy it and save your life, I donnot know why!
  1. find this file modify_binary_darwin.go in your GoProjects/pkg
  2. currently gomonkey version is v2@v2.9.0, please replace this file with code below directly
package gomonkey

import (
	"syscall"
	"time"
)

func modifyBinary(target uintptr, bytes []byte) {
	function := entryAddress(target, len(bytes))
	err := mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_WRITE)
	if err != nil {
		panic(err)
	}
	copy(function, bytes)
	err = mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_EXEC)
	if err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond)
}

func mprotectCrossPage(addr uintptr, length int, prot int) error {
	pageSize := syscall.Getpagesize()
	for p := pageStart(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
		page := entryAddress(p, pageSize)
		if err := syscall.Mprotect(page, prot); err != nil {
			return err
		}
	}
	return nil
}

Not work for me 😦

My env: M1 Pro, 13.3.1, Go, go1.20.1 darwin/arm64

I tried with this file

package main

import (
	"fmt"
	"github.com/agiledragon/gomonkey/v2"
)

//go:noinline
func Foo() {
	fmt.Println("not patch")
}

func main() {
	gomonkey.ApplyFunc(Foo, func() {
		fmt.Println("patched")
	})
	Foo()
}

when I run this file, I got segmentation fault

$ go run test_gomonkey.go 
signal: segmentation fault

Test ok at Mac M1 13.2.1 Please follow step below to solve problem “permission denied” follow this issues steam solution and test at Mac M1.

  • Enjoy it and save your life, I donnot know why!

  1. find this file modify_binary_darwin.go in your GoProjects/pkg
  2. currently gomonkey version is v2@v2.9.0, please replace this file with code below directly
package gomonkey

import (
	"syscall"
	"time"
)

func modifyBinary(target uintptr, bytes []byte) {
	function := entryAddress(target, len(bytes))
	err := mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_WRITE)
	if err != nil {
		panic(err)
	}
	copy(function, bytes)
	err = mprotectCrossPage(target, len(bytes), syscall.PROT_READ|syscall.PROT_EXEC)
	if err != nil {
		panic(err)
	}
	time.Sleep(time.Millisecond)
}

func mprotectCrossPage(addr uintptr, length int, prot int) error {
	pageSize := syscall.Getpagesize()
	for p := pageStart(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
		page := entryAddress(p, pageSize)
		if err := syscall.Mprotect(page, prot); err != nil {
			return err
		}
	}
	return nil
}


we have implemented a compile-time code rewrite to support any function mock, in general it does not rely on any architecture hack, anyone interested ? we’re using it to bootstrap our test cases, wish it could finally be a solution to function-level mock problems.

arm64 MacOS can use amd64 version go, I change to go1.16.11 darwin/amd64,and https://github.com/eisenxp/macos-golink-wrapper

This will make it impossible to debug with breakpoints

in mac M1 I get Process finished with the exit code 139 (interrupted by signal 11: SIGSEGV)

my code is


import (
	"fmt"
	"github.com/agiledragon/gomonkey/v2"
)

func add(a, b int) int {
	return a + b
}

func main() {
	fmt.Println(add(5, 2))
	gomonkey.ApplyFunc(add, func(a, b int) int {
		return a * b
	})
	fmt.Println(add(5, 2))
}

im using version 2.11.0, golang 1.20.5

The solution for mac with intel: add go tool args: -gcflags "all=-N -l" -ldflags="-extldflags="-Wl,-segprot,__TEXT,rwx,rx""

收到!

@xhd2015 Show your code.

@agiledragon Hi guy I made it here: https://github.com/xhd2015/go-mock, the brief idea is take from https://go.dev/blog/cover, that we insert a Trap call in beginning of each function’s body, to catch the control flow of the original function, meanwhile does not change the line layout of the original files so that debugging info are still accurate.

@xhd2015 Show your code.

arm64 MacOS can use amd64 version go, I change to go1.16.11 darwin/amd64,and https://github.com/eisenxp/macos-golink-wrapper

it is fixed by a very easy way.

err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)

change to

err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE)

in the function `func modifyBinary(target uintptr, bytes []byte) { function := entryAddress(target, len(bytes))

page := entryAddress(pageStart(target), syscall.Getpagesize())
err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
if err != nil {
    panic(err)
}
copy(function, bytes)

err = syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
if err != nil {
    panic(err)
}

}`

“github.com/agiledragon/gomonkey/v2” modify_binary_darwin.go

still got “permission denied”

image