os_darwin.mx raw
1 //go:build darwin
2
3 package runtime
4
5 import "unsafe"
6
7 const GOOS = "darwin"
8
9 const (
10 // See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go
11 flag_PROT_READ = 0x1
12 flag_PROT_WRITE = 0x2
13 flag_MAP_PRIVATE = 0x2
14 flag_MAP_ANONYMOUS = 0x1000 // MAP_ANON
15 )
16
17 // Source: https://opensource.apple.com/source/Libc/Libc-1439.100.3/include/time.h.auto.html
18 const (
19 clock_REALTIME = 0
20 clock_MONOTONIC_RAW = 4
21 )
22
23 // Source:
24 // https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/signal.h.auto.html
25 const (
26 sig_SIGBUS = 10
27 sig_SIGILL = 4
28 sig_SIGSEGV = 11
29 )
30
31 // https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
32 type machHeader struct {
33 magic uint32
34 cputype uint32
35 cpusubtype uint32
36 filetype uint32
37 ncmds uint32
38 sizeofcmds uint32
39 flags uint32
40 reserved uint32
41 }
42
43 // Struct for the LC_SEGMENT_64 load command.
44 type segmentLoadCommand struct {
45 cmd uint32 // LC_SEGMENT_64
46 cmdsize uint32
47 segname [16]byte
48 vmaddr uintptr
49 vmsize uintptr
50 fileoff uintptr
51 filesize uintptr
52 maxprot uint32
53 initprot uint32
54 nsects uint32
55 flags uint32
56 }
57
58 // MachO header of the currently running process.
59 //
60 //go:extern _mh_execute_header
61 var libc_mh_execute_header machHeader
62
63 // Find global variables in .data/.bss sections.
64 // The MachO linker doesn't seem to provide symbols for the start and end of the
65 // data section. There is get_etext, get_edata, and get_end, but these are
66 // undocumented and don't work with ASLR (which is enabled by default).
67 // Therefore, read the MachO header directly.
68 func findGlobals(found func(start, end uintptr)) {
69 // Here is a useful blog post to understand the MachO file format:
70 // https://h3adsh0tzz.com/2020/01/macho-file-format/
71
72 const (
73 MH_MAGIC_64 = 0xfeedfacf
74 LC_SEGMENT_64 = 0x19
75 VM_PROT_WRITE = 0x02
76 )
77
78 // Sanity check that we're actually looking at a MachO header.
79 if gcAsserts && libc_mh_execute_header.magic != MH_MAGIC_64 {
80 runtimePanic("gc: unexpected MachO header")
81 }
82
83 // Iterate through the load commands.
84 // Because we're only interested in LC_SEGMENT_64 load commands, cast the
85 // pointer to that struct in advance.
86 var offset uintptr
87 var hasOffset bool
88 cmd := (*segmentLoadCommand)(unsafe.Pointer(uintptr(unsafe.Pointer(&libc_mh_execute_header)) + unsafe.Sizeof(machHeader{})))
89 for i := libc_mh_execute_header.ncmds; i != 0; i-- {
90 if cmd.cmd == LC_SEGMENT_64 {
91 if cmd.fileoff == 0 && cmd.nsects != 0 {
92 // Detect ASLR offset by checking fileoff and nsects. This
93 // locates the __TEXT segment. This matches getsectiondata:
94 // https://opensource.apple.com/source/cctools/cctools-973.0.1/libmacho/getsecbyname.c.auto.html
95 offset = uintptr(unsafe.Pointer(&libc_mh_execute_header)) - cmd.vmaddr
96 hasOffset = true
97 }
98 if cmd.maxprot&VM_PROT_WRITE != 0 {
99 // Found a writable segment, which may contain Go globals.
100 if gcAsserts && !hasOffset {
101 // No ASLR offset detected. Did the __TEXT segment come
102 // after the __DATA segment?
103 // Note that when ASLR is disabled (for example, when
104 // running inside lldb), the offset is zero. That's why we
105 // need a separate hasOffset for this assert.
106 runtimePanic("gc: did not detect ASLR offset")
107 }
108 // Scan this segment for GC roots.
109 // This could be improved by only reading the memory areas
110 // covered by sections. That would reduce the amount of memory
111 // scanned a little bit (up to a single VM page).
112 found(offset+cmd.vmaddr, offset+cmd.vmaddr+cmd.vmsize)
113 }
114 }
115
116 // Move on to the next load command (which may or may not be a
117 // LC_SEGMENT_64).
118 cmd = (*segmentLoadCommand)(unsafe.Add(unsafe.Pointer(cmd), cmd.cmdsize))
119 }
120 }
121
122 func hardwareRand() (n uint64, ok bool) {
123 n |= uint64(libc_arc4random())
124 n |= uint64(libc_arc4random()) << 32
125 return n, true
126 }
127
128 //go:linkname syscall_Getpagesize syscall.Getpagesize
129 func syscall_Getpagesize() int {
130 return int(libc_getpagesize())
131 }
132
133 // Call "system calls" (actually: libc functions) in a special way.
134 // - Most calls calls return a C int (which is 32-bits), and -1 on failure.
135 // - syscallX* is for functions that return a 64-bit integer (and also return
136 // -1 on failure).
137 // - syscallPtr is for functions that return a pointer on success or NULL on
138 // failure.
139 // - rawSyscall seems to avoid some stack modifications, which isn't relevant
140 // to Moxie.
141
142 //go:linkname syscall_syscall syscall.syscall
143 func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
144 // For Moxie we don't need to do anything special to call C functions.
145 return syscall_rawSyscall(fn, a1, a2, a3)
146 }
147
148 //go:linkname syscall_rawSyscall syscall.rawSyscall
149 func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
150 result := call_syscall(fn, a1, a2, a3)
151 r1 = uintptr(result)
152 if result == -1 {
153 // Syscall returns -1 on failure.
154 err = uintptr(*libc_errno_location())
155 }
156 return
157 }
158
159 //go:linkname syscall_syscallX syscall.syscallX
160 func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
161 r1 = call_syscallX(fn, a1, a2, a3)
162 if int64(r1) == -1 {
163 // Syscall returns -1 on failure.
164 err = uintptr(*libc_errno_location())
165 }
166 return
167 }
168
169 //go:linkname syscall_syscallPtr syscall.syscallPtr
170 func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
171 r1 = call_syscallX(fn, a1, a2, a3)
172 if r1 == 0 {
173 // Syscall returns a pointer on success, or NULL on failure.
174 err = uintptr(*libc_errno_location())
175 }
176 return
177 }
178
179 //go:linkname syscall_syscall6 syscall.syscall6
180 func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
181 result := call_syscall6(fn, a1, a2, a3, a4, a5, a6)
182 r1 = uintptr(result)
183 if result == -1 {
184 // Syscall returns -1 on failure.
185 err = uintptr(*libc_errno_location())
186 }
187 return
188 }
189
190 //go:linkname syscall_syscall6X syscall.syscall6X
191 func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
192 r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6)
193 if int64(r1) == -1 {
194 // Syscall returns -1 on failure.
195 err = uintptr(*libc_errno_location())
196 }
197 return
198 }
199
200 // uint32_t arc4random(void);
201 //
202 //export arc4random
203 func libc_arc4random() uint32
204
205 // int getpagesize(void);
206 //
207 //export getpagesize
208 func libc_getpagesize() int32
209
210 // This function returns the error location in the darwin ABI.
211 // Discovered by compiling the following code using Clang:
212 //
213 // #include <errno.h>
214 // int getErrno() {
215 // return errno;
216 // }
217 //
218 //export __error
219 func libc_errno_location() *int32
220
221 //export moxie_syscall
222 func call_syscall(fn, a1, a2, a3 uintptr) int32
223
224 //export moxie_syscallX
225 func call_syscallX(fn, a1, a2, a3 uintptr) uintptr
226
227 //export moxie_syscall6
228 func call_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) int32
229
230 //export moxie_syscall6X
231 func call_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr
232
233 //go:linkname os_runtime_executable_path os.runtime_executable_path
234 func os_runtime_executable_path() string {
235 argv := (*unsafe.Pointer)(unsafe.Pointer(main_argv))
236
237 // skip over argv
238 argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), (uintptr(main_argc)+1)*unsafe.Sizeof(argv)))
239
240 // skip over envv
241 for (*argv) != nil {
242 argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
243 }
244
245 // next string is exe path
246 argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
247
248 cstr := unsafe.Pointer(*argv)
249 length := strlen(cstr)
250 argString := _string{
251 length: length,
252 cap: length,
253 ptr: (*byte)(cstr),
254 }
255 executablePath := *(*string)(unsafe.Pointer(&argString))
256
257 // strip "executable_path=" prefix if available, it's added after OS X 10.11.
258 executablePath = stringsTrimPrefix(executablePath, "executable_path=")
259 return executablePath
260 }
261
262 func stringsTrimPrefix(s, prefix string) string {
263 if len(s) >= len(prefix) && s[:len(prefix)] == prefix {
264 return s[len(prefix):]
265 }
266 return s
267 }
268