mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
16fc1ea2bb
Match the Go runtime by terminating for deadlocks, stack overflows, runtime and GC invariants, invalid lock operations, and platform initialization failures instead of routing them through panic/recover. Keep language-level runtime errors and unsupported user operations recoverable. Add crash coverage that verifies fatal errors bypass deferred recover calls.
203 lines
5.7 KiB
Go
203 lines
5.7 KiB
Go
//go:build darwin
|
|
|
|
package runtime
|
|
|
|
import "unsafe"
|
|
|
|
const GOOS = "darwin"
|
|
|
|
const zeroSizeAllocPtr uintptr = 16 // part of the first protected page
|
|
|
|
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_SIGFPE = 8
|
|
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 {
|
|
runtimeFatal("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.
|
|
runtimeFatal("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())
|
|
}
|
|
|
|
// 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
|
|
|
|
//go:linkname os_runtime_executable_path os.runtime_executable_path
|
|
func os_runtime_executable_path() string {
|
|
argv := (*unsafe.Pointer)(unsafe.Pointer(main_argv))
|
|
|
|
// skip over argv
|
|
argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), (uintptr(main_argc)+1)*unsafe.Sizeof(argv)))
|
|
|
|
// skip over envv
|
|
for (*argv) != nil {
|
|
argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
|
|
}
|
|
|
|
// next string is exe path
|
|
argv = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(argv), unsafe.Sizeof(argv)))
|
|
|
|
cstr := unsafe.Pointer(*argv)
|
|
length := strlen(cstr)
|
|
argString := _string{
|
|
length: length,
|
|
ptr: (*byte)(cstr),
|
|
}
|
|
executablePath := *(*string)(unsafe.Pointer(&argString))
|
|
|
|
// strip "executable_path=" prefix if available, it's added after OS X 10.11.
|
|
executablePath = stringsTrimPrefix(executablePath, "executable_path=")
|
|
return executablePath
|
|
}
|
|
|
|
func stringsTrimPrefix(s, prefix string) string {
|
|
if len(s) >= len(prefix) && s[:len(prefix)] == prefix {
|
|
return s[len(prefix):]
|
|
}
|
|
return s
|
|
}
|