3
3
4
4
package runtime
5
5
6
+ import "unsafe"
7
+
6
8
const GOOS = "darwin"
7
9
8
10
const (
@@ -18,3 +20,93 @@ const (
18
20
clock_REALTIME = 0
19
21
clock_MONOTONIC_RAW = 4
20
22
)
23
+
24
+ // https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
25
+ type machHeader struct {
26
+ magic uint32
27
+ cputype uint32
28
+ cpusubtype uint32
29
+ filetype uint32
30
+ ncmds uint32
31
+ sizeofcmds uint32
32
+ flags uint32
33
+ reserved uint32
34
+ }
35
+
36
+ // Struct for the LC_SEGMENT_64 load command.
37
+ type segmentLoadCommand struct {
38
+ cmd uint32 // LC_SEGMENT_64
39
+ cmdsize uint32
40
+ segname [16 ]byte
41
+ vmaddr uintptr
42
+ vmsize uintptr
43
+ fileoff uintptr
44
+ filesize uintptr
45
+ maxprot uint32
46
+ initprot uint32
47
+ nsects uint32
48
+ flags uint32
49
+ }
50
+
51
+ // MachO header of the currently running process.
52
+ //go:extern _mh_execute_header
53
+ var libc_mh_execute_header machHeader
54
+
55
+ // Mark global variables.
56
+ // The MachO linker doesn't seem to provide symbols for the start and end of the
57
+ // data section. There is get_etext, get_edata, and get_end, but these are
58
+ // undocumented and don't work with ASLR (which is enabled by default).
59
+ // Therefore, read the MachO header directly.
60
+ func markGlobals () {
61
+ // Here is a useful blog post to understand the MachO file format:
62
+ // https://h3adsh0tzz.com/2020/01/macho-file-format/
63
+
64
+ const (
65
+ MH_MAGIC_64 = 0xfeedfacf
66
+ LC_SEGMENT_64 = 0x19
67
+ VM_PROT_WRITE = 0x02
68
+ )
69
+
70
+ // Sanity check that we're actually looking at a MachO header.
71
+ if gcAsserts && libc_mh_execute_header .magic != MH_MAGIC_64 {
72
+ runtimePanic ("gc: unexpected MachO header" )
73
+ }
74
+
75
+ // Iterate through the load commands.
76
+ // Because we're only interested in LC_SEGMENT_64 load commands, cast the
77
+ // pointer to that struct in advance.
78
+ var offset uintptr
79
+ var hasOffset bool
80
+ cmd := (* segmentLoadCommand )(unsafe .Pointer (uintptr (unsafe .Pointer (& libc_mh_execute_header )) + unsafe .Sizeof (machHeader {})))
81
+ for i := libc_mh_execute_header .ncmds ; i != 0 ; i -- {
82
+ if cmd .cmd == LC_SEGMENT_64 {
83
+ if cmd .fileoff == 0 && cmd .nsects != 0 {
84
+ // Detect ASLR offset by checking fileoff and nsects. This
85
+ // locates the __TEXT segment. This matches getsectiondata:
86
+ // https://opensource.apple.com/source/cctools/cctools-973.0.1/libmacho/getsecbyname.c.auto.html
87
+ offset = uintptr (unsafe .Pointer (& libc_mh_execute_header )) - cmd .vmaddr
88
+ hasOffset = true
89
+ }
90
+ if cmd .maxprot & VM_PROT_WRITE != 0 {
91
+ // Found a writable segment, which may contain Go globals.
92
+ if gcAsserts && ! hasOffset {
93
+ // No ASLR offset detected. Did the __TEXT segment come
94
+ // after the __DATA segment?
95
+ // Note that when ASLR is disabled (for example, when
96
+ // running inside lldb), the offset is zero. That's why we
97
+ // need a separate hasOffset for this assert.
98
+ runtimePanic ("gc: did not detect ASLR offset" )
99
+ }
100
+ // Scan this segment for GC roots.
101
+ // This could be improved by only reading the memory areas
102
+ // covered by sections. That would reduce the amount of memory
103
+ // scanned a little bit (up to a single VM page).
104
+ markRoots (offset + cmd .vmaddr , offset + cmd .vmaddr + cmd .vmsize )
105
+ }
106
+ }
107
+
108
+ // Move on to the next load command (wich may or may not be a
109
+ // LC_SEGMENT_64).
110
+ cmd = (* segmentLoadCommand )(unsafe .Pointer (uintptr (unsafe .Pointer (cmd )) + uintptr (cmd .cmdsize )))
111
+ }
112
+ }
0 commit comments