Skip to content

Commit

Permalink
Add ARM64 support (#13)
Browse files Browse the repository at this point in the history
* Add arm64 support
  • Loading branch information
tonyredondo authored Jan 27, 2023
1 parent 3f23ca2 commit 03333c6
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 16 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ Go library for monkey patching
## Compatibility

- **Go version:** tested from `go1.7` to `go1.19`
- **Architectures:** `x86`, `amd64`
- **Architectures:** `x86`, `amd64`, `arm64` (ARM64 not supported on macos)
- **Operating systems:** tested in `macos`, `linux` and `windows`.

### ARM64 support
The support for ARM64 have some caveats. For example:
- On Windows 11 ARM64 works like any other platform, we test it with tiny methods and worked correctly.
- On Linux ARM64 the minimum target and source method size must be > 24 bytes. This means a simple 1 liner method will silently fail. In our tests we have methods at least with 3 lines and works correctly (beware of the compiler optimizations).
This doesn't happen in any other platform because the assembly code we emit is really short (x64: 12 bytes / x86: 7 bytes), but for ARM64 is exactly 24 bytes.
- On MacOS ARM64 the patching fails with `EACCES: permission denied` when calling `syscall.Mprotect`. There's no current workaround for this issue, if you use an Apple Silicon Mac you can use a docker container or docker dev environment.

## Features

- Can patch package functions, instance functions (by pointer or by value), and create new functions from scratch.
Expand Down
2 changes: 1 addition & 1 deletion patcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func PatchMethod(target, redirection interface{}) (*Patch, error) {
}

// Patches an instance func by using two parameters, the target struct type and the method name inside that type,
//this func will be redirected to the "redirection" func. Note: The first parameter of the redirection func must be the object instance.
// this func will be redirected to the "redirection" func. Note: The first parameter of the redirection func must be the object instance.
func PatchInstanceMethodByName(target reflect.Type, methodName string, redirection interface{}) (*Patch, error) {
method, ok := target.MethodByName(methodName)
if !ok && target.Kind() == reflect.Struct {
Expand Down
42 changes: 42 additions & 0 deletions patcher_arm64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//go:build arm64
// +build arm64

package mpatch

import "unsafe"

// Code from: https://github.com/agiledragon/gomonkey/blob/master/jmp_arm64.go

// Gets the jump function rewrite bytes
//
//go:nosplit
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
res := make([]byte, 0, 24)
d0d1 := uintptr(to) & 0xFFFF
d2d3 := uintptr(to) >> 16 & 0xFFFF
d4d5 := uintptr(to) >> 32 & 0xFFFF
d6d7 := uintptr(to) >> 48 & 0xFFFF

res = append(res, movImm(0b10, 0, d0d1)...) // MOVZ x26, double[16:0]
res = append(res, movImm(0b11, 1, d2d3)...) // MOVK x26, double[32:16]
res = append(res, movImm(0b11, 2, d4d5)...) // MOVK x26, double[48:32]
res = append(res, movImm(0b11, 3, d6d7)...) // MOVK x26, double[64:48]
res = append(res, []byte{0x4A, 0x03, 0x40, 0xF9}...) // LDR x10, [x26]
res = append(res, []byte{0x40, 0x01, 0x1F, 0xD6}...) // BR x10

return res, nil
}

func movImm(opc, shift int, val uintptr) []byte {
var m uint32 = 26 // rd
m |= uint32(val) << 5 // imm16
m |= uint32(shift&3) << 21 // hw
m |= 0b100101 << 23 // const
m |= uint32(opc&0x3) << 29 // opc
m |= 0b1 << 31 // sf

res := make([]byte, 4)
*(*uint32)(unsafe.Pointer(&res[0])) = m

return res
}
24 changes: 16 additions & 8 deletions patcher_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package mpatch

import (
"math/rand"
"reflect"
"testing"
)

//go:noinline
func methodA() int { return 1 }
func methodA() int {
x := rand.Int() >> 48
y := rand.Int() >> 48
return x + y
}

//go:noinline
func methodB() int { return 2 }
func methodB() int {
x := rand.Int() >> 48
y := rand.Int() >> 48
return -(x + y)
}

type myStruct struct {
}
Expand All @@ -29,15 +38,14 @@ func TestPatcher(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if methodA() != 2 {
if methodA() > 0 {
t.Fatal("The patch did not work")
}

err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if methodA() != 1 {
if methodA() < 0 {
t.Fatal("The unpatch did not work")
}
}
Expand All @@ -48,15 +56,15 @@ func TestPatcherUsingReflect(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if methodA() != 2 {
if methodA() > 0 {
t.Fatal("The patch did not work")
}

err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if methodA() != 1 {
if methodA() < 0 {
t.Fatal("The unpatch did not work")
}
}
Expand All @@ -78,7 +86,7 @@ func TestPatcherUsingMakeFunc(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if methodA() != 1 {
if methodA() < 0 {
t.Fatal("The unpatch did not work")
}
}
Expand Down
3 changes: 3 additions & 0 deletions patcher_unix.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//go:build !windows
// +build !windows

package mpatch

import (
"reflect"
"syscall"
"time"
"unsafe"
)

Expand Down Expand Up @@ -41,5 +43,6 @@ func writeDataToPointer(ptr unsafe.Pointer, data []byte) error {
if err := callMProtect(ptr, dataLength, readAccess); err != nil {
return err
}
<-time.After(100 * time.Microsecond) // If we remove this line then it fails to unpatch on ARM64
return nil
}
5 changes: 3 additions & 2 deletions patcher_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// +build !386
// +build !amd64
//go:build !386 && !amd64 && !arm64
// +build !386,!amd64,!arm64

package mpatch

Expand All @@ -11,6 +11,7 @@ import (
)

// Gets the jump function rewrite bytes
//
//go:nosplit
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
return nil, errors.New(fmt.Sprintf("unsupported architecture: %s", runtime.GOARCH))
Expand Down
1 change: 1 addition & 0 deletions patcher_windows.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build windows
// +build windows

package mpatch
Expand Down
4 changes: 2 additions & 2 deletions patcher_x32.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//go:build 386
// +build 386

package mpatch

import "unsafe"

const jumpLength = 7

// Gets the jump function rewrite bytes
//
//go:nosplit
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
return []byte{
Expand Down
4 changes: 2 additions & 2 deletions patcher_x64.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//go:build amd64
// +build amd64

package mpatch

import "unsafe"

const jumpLength = 12

// Gets the jump function rewrite bytes
//
//go:nosplit
func getJumpFuncBytes(to unsafe.Pointer) ([]byte, error) {
return []byte{
Expand Down

0 comments on commit 03333c6

Please sign in to comment.