aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/runtime/os_darwin.go
blob: e7f7b368fbe6dd68a11d6b64e4f6d01cd643a35e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//go:build darwin

package runtime

import "unsafe"

const GOOS = "darwin"

const (
	// See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go
	flag_PROT_READ     = 0x1
	flag_PROT_WRITE    = 0x2
	flag_MAP_PRIVATE   = 0x2
	flag_MAP_ANONYMOUS = 0x1000 // MAP_ANON
)

// Source: https://opensource.apple.com/source/Libc/Libc-1439.100.3/include/time.h.auto.html
const (
	clock_REALTIME      = 0
	clock_MONOTONIC_RAW = 4
)

// Source:
// https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/signal.h.auto.html
const (
	sig_SIGBUS  = 10
	sig_SIGILL  = 4
	sig_SIGSEGV = 11
)

// https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
type machHeader struct {
	magic      uint32
	cputype    uint32
	cpusubtype uint32
	filetype   uint32
	ncmds      uint32
	sizeofcmds uint32
	flags      uint32
	reserved   uint32
}

// Struct for the LC_SEGMENT_64 load command.
type segmentLoadCommand struct {
	cmd      uint32 // LC_SEGMENT_64
	cmdsize  uint32
	segname  [16]byte
	vmaddr   uintptr
	vmsize   uintptr
	fileoff  uintptr
	filesize uintptr
	maxprot  uint32
	initprot uint32
	nsects   uint32
	flags    uint32
}

// MachO header of the currently running process.
//
//go:extern _mh_execute_header
var libc_mh_execute_header machHeader

// Find global variables in .data/.bss sections.
// The MachO linker doesn't seem to provide symbols for the start and end of the
// data section. There is get_etext, get_edata, and get_end, but these are
// undocumented and don't work with ASLR (which is enabled by default).
// Therefore, read the MachO header directly.
func findGlobals(found func(start, end uintptr)) {
	// Here is a useful blog post to understand the MachO file format:
	// https://h3adsh0tzz.com/2020/01/macho-file-format/

	const (
		MH_MAGIC_64   = 0xfeedfacf
		LC_SEGMENT_64 = 0x19
		VM_PROT_WRITE = 0x02
	)

	// Sanity check that we're actually looking at a MachO header.
	if gcAsserts && libc_mh_execute_header.magic != MH_MAGIC_64 {
		runtimePanic("gc: unexpected MachO header")
	}

	// Iterate through the load commands.
	// Because we're only interested in LC_SEGMENT_64 load commands, cast the
	// pointer to that struct in advance.
	var offset uintptr
	var hasOffset bool
	cmd := (*segmentLoadCommand)(unsafe.Pointer(uintptr(unsafe.Pointer(&libc_mh_execute_header)) + unsafe.Sizeof(machHeader{})))
	for i := libc_mh_execute_header.ncmds; i != 0; i-- {
		if cmd.cmd == LC_SEGMENT_64 {
			if cmd.fileoff == 0 && cmd.nsects != 0 {
				// Detect ASLR offset by checking fileoff and nsects. This
				// locates the __TEXT segment. This matches getsectiondata:
				// https://opensource.apple.com/source/cctools/cctools-973.0.1/libmacho/getsecbyname.c.auto.html
				offset = uintptr(unsafe.Pointer(&libc_mh_execute_header)) - cmd.vmaddr
				hasOffset = true
			}
			if cmd.maxprot&VM_PROT_WRITE != 0 {
				// Found a writable segment, which may contain Go globals.
				if gcAsserts && !hasOffset {
					// No ASLR offset detected. Did the __TEXT segment come
					// after the __DATA segment?
					// Note that when ASLR is disabled (for example, when
					// running inside lldb), the offset is zero. That's why we
					// need a separate hasOffset for this assert.
					runtimePanic("gc: did not detect ASLR offset")
				}
				// Scan this segment for GC roots.
				// This could be improved by only reading the memory areas
				// covered by sections. That would reduce the amount of memory
				// scanned a little bit (up to a single VM page).
				found(offset+cmd.vmaddr, offset+cmd.vmaddr+cmd.vmsize)
			}
		}

		// Move on to the next load command (which may or may not be a
		// LC_SEGMENT_64).
		cmd = (*segmentLoadCommand)(unsafe.Add(unsafe.Pointer(cmd), cmd.cmdsize))
	}
}

func hardwareRand() (n uint64, ok bool) {
	n |= uint64(libc_arc4random())
	n |= uint64(libc_arc4random()) << 32
	return n, true
}

//go:linkname syscall_Getpagesize syscall.Getpagesize
func syscall_Getpagesize() int {
	return int(libc_getpagesize())
}

// Call "system calls" (actually: libc functions) in a special way.
//   - Most calls calls return a C int (which is 32-bits), and -1 on failure.
//   - syscallX* is for functions that return a 64-bit integer (and also return
//     -1 on failure).
//   - syscallPtr is for functions that return a pointer on success or NULL on
//     failure.
//   - rawSyscall seems to avoid some stack modifications, which isn't relevant
//     to TinyGo.

//go:linkname syscall_syscall syscall.syscall
func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
	// For TinyGo we don't need to do anything special to call C functions.
	return syscall_rawSyscall(fn, a1, a2, a3)
}

//go:linkname syscall_rawSyscall syscall.rawSyscall
func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
	result := call_syscall(fn, a1, a2, a3)
	r1 = uintptr(result)
	if result == -1 {
		// Syscall returns -1 on failure.
		err = uintptr(*libc_errno_location())
	}
	return
}

//go:linkname syscall_syscallX syscall.syscallX
func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
	r1 = call_syscallX(fn, a1, a2, a3)
	if int64(r1) == -1 {
		// Syscall returns -1 on failure.
		err = uintptr(*libc_errno_location())
	}
	return
}

//go:linkname syscall_syscallPtr syscall.syscallPtr
func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) {
	r1 = call_syscallX(fn, a1, a2, a3)
	if r1 == 0 {
		// Syscall returns a pointer on success, or NULL on failure.
		err = uintptr(*libc_errno_location())
	}
	return
}

//go:linkname syscall_syscall6 syscall.syscall6
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
	result := call_syscall6(fn, a1, a2, a3, a4, a5, a6)
	r1 = uintptr(result)
	if result == -1 {
		// Syscall returns -1 on failure.
		err = uintptr(*libc_errno_location())
	}
	return
}

//go:linkname syscall_syscall6X syscall.syscall6X
func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) {
	r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6)
	if int64(r1) == -1 {
		// Syscall returns -1 on failure.
		err = uintptr(*libc_errno_location())
	}
	return
}

// uint32_t arc4random(void);
//
//export arc4random
func libc_arc4random() uint32

// int getpagesize(void);
//
//export getpagesize
func libc_getpagesize() int32

// This function returns the error location in the darwin ABI.
// Discovered by compiling the following code using Clang:
//
//	#include <errno.h>
//	int getErrno() {
//	    return errno;
//	}
//
//export __error
func libc_errno_location() *int32

//export tinygo_syscall
func call_syscall(fn, a1, a2, a3 uintptr) int32

//export tinygo_syscallX
func call_syscallX(fn, a1, a2, a3 uintptr) uintptr

//export tinygo_syscall6
func call_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) int32

//export tinygo_syscall6X
func call_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr