Skip to content

Commit 48242ba

Browse files
aykevldeadprogram
authored andcommitted
darwin: scan globals by reading MachO header
This replaces "precise" global scanning in LLVM with conservative scanning of writable MachO segments. Eventually I'd like to get rid of the AddGlobalsBitmap pass, and this is one step towards that goal.
1 parent 7ea9eff commit 48242ba

File tree

2 files changed

+94
-2
lines changed

2 files changed

+94
-2
lines changed

src/runtime/gc_globals_precise.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//go:build gc.conservative && !baremetal && !tinygo.wasm && !windows
2-
// +build gc.conservative,!baremetal,!tinygo.wasm,!windows
1+
//go:build gc.conservative && !baremetal && !darwin && !tinygo.wasm && !windows
2+
// +build gc.conservative,!baremetal,!darwin,!tinygo.wasm,!windows
33

44
package runtime
55

src/runtime/os_darwin.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
package runtime
55

6+
import "unsafe"
7+
68
const GOOS = "darwin"
79

810
const (
@@ -18,3 +20,93 @@ const (
1820
clock_REALTIME = 0
1921
clock_MONOTONIC_RAW = 4
2022
)
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

Comments
 (0)