blob: 591c55823e70b3abcf5aa31235c78aadd8182f45 (
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
|
//go:build darwin
// +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
)
// 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
// Mark global variables.
// 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 markGlobals() {
// 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).
markRoots(offset+cmd.vmaddr, offset+cmd.vmaddr+cmd.vmsize)
}
}
// Move on to the next load command (wich may or may not be a
// LC_SEGMENT_64).
cmd = (*segmentLoadCommand)(unsafe.Pointer(uintptr(unsafe.Pointer(cmd)) + uintptr(cmd.cmdsize)))
}
}
|