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