Skip to content

Commit 2048d31

Browse files
committed
chore: attempt to fix clone on Darwin
- Use MAP_JIT for cloned function area on Darwin - Verify the address from mmap is in the acceptable range (it never is on Darwin, but I think this should help other OS's) - Call `pthread_jit_write_protect_np()` on Darwin None of these things actually make any difference, but I think they're necessary.
1 parent ddc1de1 commit 2048d31

10 files changed

Lines changed: 83 additions & 28 deletions

clone.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (a *allocator) init(startSize int) error {
8383
}
8484

8585
if protBE, ok := be.(malloc.ProtectedArenaBackend); ok {
86-
a.mprotect = protBE.Protect
86+
a.mprotect = mprotectHook(protBE.Protect)
8787
} else {
8888
// No real mprotect for some reason. This shouldn't
8989
// really happen, but continue with a no-op mprotect.
@@ -127,34 +127,48 @@ func initMallocBackend() (malloc.ArenaBackend, error) {
127127
// space right before the text segment but that's not guaranteed
128128
// (particularly when buildmode=pie).
129129

130-
// The minimum acceptable address is where the first
131-
// instruction in the code segment can still reach the final
132-
// address before end. These are unsigned so watch for wrap-around.
130+
// The minimum acceptable address is where the first instruction in the
131+
// code segment can still reach the final address before end. The
132+
// maximum address is similar. These are unsigned so watch for
133+
// wrap-around.
133134
minAddress := end - maxCloneDistance
134135
if minAddress > end || minAddress < absMinAddress {
135136
minAddress = absMinAddress
136137
}
137-
for addr := text - pageSize - size; addr >= minAddress; addr -= 0x100000 {
138-
be, err := malloc.VirtBackend(size, malloc.MmapAddr(addr), malloc.MmapProt(mprotectExec), malloc.MmapFlags(_MAP_FIXED_NOREPLACE))
138+
maxAddress := text + maxCloneDistance - size
139+
if maxAddress < text {
140+
maxAddress = math.MaxUint
141+
}
142+
143+
for addr := text - pageSize - size; addr >= minAddress; addr -= pageSize {
144+
be, err := malloc.VirtBackend(size, malloc.MmapAddr(addr), malloc.MmapProt(mprotectExec), malloc.MmapFlags(_MMAP_FLAGS))
139145
if err == nil {
140-
return be, nil
146+
if be.Addr() < minAddress || be.Addr() > maxAddress {
147+
// No good, try again.
148+
be.Release()
149+
} else {
150+
return be, nil
151+
}
141152
}
142153
}
143154

144155
// Nothing was found before the text segment, repeat the process for
145156
// the space after end.
146-
maxAddress := text + maxCloneDistance - size
147-
if maxAddress < text {
148-
maxAddress = math.MaxUint
149-
}
150-
for addr := end; addr <= maxAddress; addr += 0x100000 {
151-
be, err := malloc.VirtBackend(size, malloc.MmapAddr(addr), malloc.MmapProt(mprotectExec), malloc.MmapFlags(_MAP_FIXED_NOREPLACE))
157+
for addr := end; addr <= maxAddress; addr += pageSize {
158+
be, err := malloc.VirtBackend(size, malloc.MmapAddr(addr), malloc.MmapProt(mprotectExec), malloc.MmapFlags(_MMAP_FLAGS))
152159
if err == nil {
153-
return be, nil
160+
if be.Addr() < minAddress || be.Addr() > maxAddress {
161+
// No good, try again.
162+
be.Release()
163+
} else {
164+
return be, nil
165+
}
154166
}
155167
}
156168

157-
return nil, errors.New("no suitable virtual memory space found")
169+
// Well, we tried. We tried really hard. There's nothing left to do but
170+
// take whatever address the OS gives us.
171+
return malloc.VirtBackend(size, malloc.MmapAddr(minAddress), malloc.MmapProt(mprotectExec), malloc.MmapFlags(_MMAP_FLAGS))
158172
}
159173

160174
func (a *allocator) BeginMutate() error {

clone_mprotect.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !(darwin && arm64)
2+
3+
package redefine
4+
5+
func mprotectHook(inner func(int) error) func(int) error {
6+
return inner
7+
}

clone_mprotect_darwin.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//go:build darwin && arm64
2+
3+
package redefine
4+
5+
import "golang.org/x/sys/unix"
6+
7+
/*
8+
#include <pthread.h>
9+
*/
10+
import "C"
11+
12+
func mprotectHook(inner func(int) error) func(int) error {
13+
// TODO: Figure out if this is really needed.
14+
return func(prot int) error {
15+
if prot&unix.PROT_WRITE != 0 {
16+
C.pthread_jit_write_protect_np(0)
17+
} else {
18+
C.pthread_jit_write_protect_np(1)
19+
}
20+
return inner(prot)
21+
}
22+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/pboyd/redefine
33
go 1.25.0
44

55
require (
6-
github.com/pboyd/malloc v1.2.0
6+
github.com/pboyd/malloc v1.2.1
77
github.com/stretchr/testify v1.11.1
88
golang.org/x/arch v0.23.0
99
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/pboyd/malloc v1.1.0 h1:xVOGFPmrMYU+uV5YnIHcvaXeZi8ERNE/umKwv9aXqKs=
66
github.com/pboyd/malloc v1.1.0/go.mod h1:YGRIeEWvukIMTTZffUkV74qEnoRmuAp9mPrw0LDk3SE=
77
github.com/pboyd/malloc v1.2.0 h1:YrXyBimK7NsNiUVqKznnKir9aCCmMN5+/tzu5qvEu8M=
88
github.com/pboyd/malloc v1.2.0/go.mod h1:YGRIeEWvukIMTTZffUkV74qEnoRmuAp9mPrw0LDk3SE=
9+
github.com/pboyd/malloc v1.2.1 h1:hRQuCrDsKufuO3WA9z6AM1OXpGhRBvVjsyF64ja+JRw=
10+
github.com/pboyd/malloc v1.2.1/go.mod h1:YGRIeEWvukIMTTZffUkV74qEnoRmuAp9mPrw0LDk3SE=
911
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1012
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1113
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=

mmap_flags_darwin.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build darwin
2+
3+
package redefine
4+
5+
import "golang.org/x/sys/unix"
6+
7+
// Darwin has no equivalent to MAP_FIXED_NOREPLACE. But MAP_JIT is required to
8+
// use PROT_WRITE and PROT_EXEC together.
9+
const _MMAP_FLAGS = unix.MAP_JIT

mmap_flags_fallback.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
//go:build darwin || netbsd || openbsd || windows
1+
//go:build netbsd || openbsd || windows
22

33
package redefine
44

5-
// Darwin, NetBSD, OpenBSD and Windows don't have an equivalent to
6-
// MAP_FIXED_NOREPLACE. On BSD, MAP_FIXED would almost work except that it
7-
// would replace existing mappings. We'll have to trust the OS to give us a
8-
// suitable address based on our request.
5+
// NetBSD, OpenBSD and Windows don't have an equivalent to MAP_FIXED_NOREPLACE.
6+
// On BSD, MAP_FIXED would almost work except that it would replace existing
7+
// mappings. We'll have to trust the OS to give us a suitable address based on
8+
// our request.
99
//
10-
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/mmap.2.html
1110
// https://man.netbsd.org/mmap.2
1211
// https://man.openbsd.org/mmap.2
1312
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
14-
const _MAP_FIXED_NOREPLACE = 0
13+
const _MMAP_FLAGS = 0

mmap_flags_freebsd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ import "golang.org/x/sys/unix"
88
// Linux
99
//
1010
// https://man.freebsd.org/cgi/man.cgi?mmap(2)
11-
const _MAP_FIXED_NOREPLACE = unix.MAP_FIXED | unix.MAP_EXCL
11+
const _MMAP_FLAGS = unix.MAP_FIXED | unix.MAP_EXCL

mmap_flags_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ package redefine
44

55
import "golang.org/x/sys/unix"
66

7-
const _MAP_FIXED_NOREPLACE = unix.MAP_FIXED_NOREPLACE
7+
const _MMAP_FLAGS = unix.MAP_FIXED_NOREPLACE

redefine.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ func Original[T any](fn T) T {
115115
}
116116

117117
if clonedType, ok := cloned.(*clonedFunc[T]); ok {
118-
return clonedType.Func
118+
if clonedType != nil {
119+
return clonedType.Func
120+
}
119121
}
120122

121123
return *((*T)(nil))
@@ -183,13 +185,13 @@ func unsafeFunc[T any](fn T, newFn any) error {
183185
redefined[addr], err = cloneFunc(fn)
184186
if err != nil {
185187
// TODO: Should this be fatal?
186-
return err
188+
return fmt.Errorf("unable to clone function: %w", err)
187189
}
188190
}
189191

190192
err = mprotect(code, mprotectRWX)
191193
if err != nil {
192-
return err
194+
return fmt.Errorf("mprotect: %w", err)
193195
}
194196
defer mprotect(code, mprotectRX)
195197

0 commit comments

Comments
 (0)