github.com/undefinedlabs/go-mpatch@v1.0.8-0.20230904093002-fbac8a0d7853/README.md (about)

     1  # go-mpatch
     2  Go library for monkey patching
     3  
     4  ## Compatibility
     5  
     6  - **Go version:** tested from `go1.7` to `go1.21`
     7  - **Architectures:** `x86`, `amd64`, `arm64` (ARM64 not supported on macos)
     8  - **Operating systems:** tested in `macos`, `linux` and `windows`. 
     9  
    10  ### ARM64 support
    11  The support for ARM64 have some caveats. For example:
    12  - On Windows 11 ARM64 works like any other platform, we test it with tiny methods and worked correctly.
    13  - 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). 
    14  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.
    15  - 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.
    16  
    17  ## Features
    18  
    19  - Can patch package functions, instance functions (by pointer or by value), and create new functions from scratch.
    20  
    21  ## Limitations
    22  
    23  - Target functions could be inlined, making those functions unpatcheables. You can use `//go:noinline` directive or build with the `gcflags=-l`
    24  to disable inlining at compiler level.
    25  
    26  - Write permission to memory pages containing executable code is needed, some operating systems could restrict this access.
    27  
    28  - Not thread safe.
    29  
    30  ## Usage
    31  
    32  ### Patching a func
    33  ```go
    34  //go:noinline
    35  func methodA() int { return 1 }
    36  
    37  //go:noinline
    38  func methodB() int { return 2 }
    39  
    40  func TestPatcher(t *testing.T) {
    41  	patch, err := mpatch.PatchMethod(methodA, methodB)
    42  	if err != nil {
    43  		t.Fatal(err)
    44  	}
    45  	if methodA() != 2 {
    46  		t.Fatal("The patch did not work")
    47  	}
    48  
    49  	err = patch.Unpatch()
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	if methodA() != 1 {
    54  		t.Fatal("The unpatch did not work")
    55  	}
    56  }
    57  ```
    58  
    59  ### Patching using `reflect.ValueOf`
    60  ```go
    61  //go:noinline
    62  func methodA() int { return 1 }
    63  
    64  //go:noinline
    65  func methodB() int { return 2 }
    66  
    67  func TestPatcherUsingReflect(t *testing.T) {
    68  	reflectA := reflect.ValueOf(methodA)
    69  	patch, err := mPatch.PatchMethodByReflectValue(reflectA, methodB)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	if methodA() != 2 {
    74  		t.Fatal("The patch did not work")
    75  	}
    76  
    77  	err = patch.Unpatch()
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	if methodA() != 1 {
    82  		t.Fatal("The unpatch did not work")
    83  	}
    84  }
    85  ```
    86  
    87  ### Patching creating a new func at runtime
    88  ```go
    89  //go:noinline
    90  func methodA() int { return 1 }
    91  
    92  func TestPatcherUsingMakeFunc(t *testing.T) {
    93  	reflectA := reflect.ValueOf(methodA)
    94  	patch, err := PatchMethodWithMakeFuncValue(reflectA,
    95  		func(args []reflect.Value) (results []reflect.Value) {
    96  			return []reflect.Value{reflect.ValueOf(42)}
    97  		})
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	if methodA() != 42 {
   102  		t.Fatal("The patch did not work")
   103  	}
   104  
   105  	err = patch.Unpatch()
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  	if methodA() != 1 {
   110  		t.Fatal("The unpatch did not work")
   111  	}
   112  }
   113  ```
   114  
   115  ### Patching an instance func
   116  ```go
   117  type myStruct struct {
   118  }
   119  
   120  //go:noinline
   121  func (s *myStruct) Method() int {
   122  	return 1
   123  }
   124  
   125  func TestInstancePatcher(t *testing.T) {
   126  	mStruct := myStruct{}
   127  
   128  	var patch *Patch
   129  	var err error
   130  	patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "Method", func(m *myStruct) int {
   131  		patch.Unpatch()
   132  		defer patch.Patch()
   133  		return 41 + m.Method()
   134  	})
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  
   139  	if mStruct.Method() != 42 {
   140  		t.Fatal("The patch did not work")
   141  	}
   142  	err = patch.Unpatch()
   143  	if err != nil {
   144  		t.Fatal(err)
   145  	}
   146  	if mStruct.Method() != 1 {
   147  		t.Fatal("The unpatch did not work")
   148  	}
   149  }
   150  ```
   151  
   152  ### Patching an instance func by Value
   153  ```go
   154  type myStruct struct {
   155  }
   156  
   157  //go:noinline
   158  func (s myStruct) ValueMethod() int {
   159  	return 1
   160  }
   161  
   162  func TestInstanceValuePatcher(t *testing.T) {
   163  	mStruct := myStruct{}
   164  
   165  	var patch *Patch
   166  	var err error
   167  	patch, err = PatchInstanceMethodByName(reflect.TypeOf(mStruct), "ValueMethod", func(m myStruct) int {
   168  		patch.Unpatch()
   169  		defer patch.Patch()
   170  		return 41 + m.Method()
   171  	})
   172  	if err != nil {
   173  		t.Fatal(err)
   174  	}
   175  
   176  	if mStruct.ValueMethod() != 42 {
   177  		t.Fatal("The patch did not work")
   178  	}
   179  	err = patch.Unpatch()
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  	if mStruct.ValueMethod() != 1 {
   184  		t.Fatal("The unpatch did not work")
   185  	}
   186  }
   187  ```
   188  
   189  > Library inspired by the blog post: https://bou.ke/blog/monkey-patching-in-go/