Skip to content

Commit 5404c81

Browse files
aykevldeadprogram
authored andcommitted
windows: scan globals conservatively
Scan globals conservatively by reading writable sections from the PE header. I'd like to get rid of needing to precisely scan globals eventually, and this brings us one step closer. It also avoids a bug with ThinLTO on Windows.
1 parent b52310f commit 5404c81

File tree

4 files changed

+103
-2
lines changed

4 files changed

+103
-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
2-
// +build gc.conservative,!baremetal,!tinygo.wasm
1+
//go:build gc.conservative && !baremetal && !tinygo.wasm && !windows
2+
// +build gc.conservative,!baremetal,!tinygo.wasm,!windows
33

44
package runtime
55

src/runtime/gc_leaking.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"unsafe"
1212
)
1313

14+
const gcAsserts = false // perform sanity checks
15+
1416
// Ever-incrementing pointer: no memory is freed.
1517
var heapptr = heapStart
1618

@@ -76,3 +78,7 @@ func setHeapEnd(newHeapEnd uintptr) {
7678
// enough.
7779
heapEnd = newHeapEnd
7880
}
81+
82+
func markRoots(start, end uintptr) {
83+
// dummy, so that markGlobals will compile
84+
}

src/runtime/gc_none.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"unsafe"
1212
)
1313

14+
const gcAsserts = false // perform sanity checks
15+
1416
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
1517

1618
func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer
@@ -38,3 +40,7 @@ func initHeap() {
3840
func setHeapEnd(newHeapEnd uintptr) {
3941
// Nothing to do here, this function is never actually called.
4042
}
43+
44+
func markRoots(start, end uintptr) {
45+
// dummy, so that markGlobals will compile
46+
}

src/runtime/os_windows.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,92 @@
11
package runtime
22

3+
import "unsafe"
4+
35
const GOOS = "windows"
6+
7+
//export GetModuleHandleExA
8+
func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool
9+
10+
// MS-DOS stub with PE header offset:
11+
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only
12+
type exeHeader struct {
13+
signature uint16
14+
_ [58]byte // skip DOS header
15+
peHeader uint32 // at offset 0x3C
16+
}
17+
18+
// COFF file header:
19+
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#file-headers
20+
type peHeader struct {
21+
magic uint32
22+
machine uint16
23+
numberOfSections uint16
24+
timeDateStamp uint32
25+
pointerToSymbolTable uint32
26+
numberOfSymbols uint32
27+
sizeOfOptionalHeader uint16
28+
characteristics uint16
29+
}
30+
31+
// COFF section header:
32+
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers
33+
type peSection struct {
34+
name [8]byte
35+
virtualSize uint32
36+
virtualAddress uint32
37+
sizeOfRawData uint32
38+
pointerToRawData uint32
39+
pointerToRelocations uint32
40+
pointerToLinenumbers uint32
41+
numberOfRelocations uint16
42+
numberOfLinenumbers uint16
43+
characteristics uint32
44+
}
45+
46+
var module *exeHeader
47+
48+
// Mark global variables.
49+
// Unfortunately, the linker doesn't provide symbols for the start and end of
50+
// the data/bss sections. Therefore these addresses need to be determined at
51+
// runtime. This might seem complex and it kind of is, but it only compiles to
52+
// around 160 bytes of amd64 instructions.
53+
// Most of this function is based on the documentation in
54+
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format.
55+
func markGlobals() {
56+
// Constants used in this function.
57+
const (
58+
// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexa
59+
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002
60+
61+
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
62+
IMAGE_SCN_MEM_WRITE = 0x80000000
63+
)
64+
65+
if module == nil {
66+
// Obtain a handle to the currently executing image. What we're getting
67+
// here is really just __ImageBase, but it's probably better to obtain
68+
// it using GetModuleHandle to account for ASLR etc.
69+
result := _GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, &module)
70+
if gcAsserts && (!result || module.signature != 0x5A4D) { // 0x4D5A is "MZ"
71+
runtimePanic("cannot get module handle")
72+
}
73+
}
74+
75+
// Find the PE header at offset 0x3C.
76+
pe := (*peHeader)(unsafe.Pointer(uintptr(unsafe.Pointer(module)) + uintptr(module.peHeader)))
77+
if gcAsserts && pe.magic != 0x00004550 { // 0x4550 is "PE"
78+
runtimePanic("cannot find PE header")
79+
}
80+
81+
// Iterate through sections.
82+
section := (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(pe)) + uintptr(pe.sizeOfOptionalHeader) + unsafe.Sizeof(peHeader{})))
83+
for i := 0; i < int(pe.numberOfSections); i++ {
84+
if section.characteristics&IMAGE_SCN_MEM_WRITE != 0 {
85+
// Found a writable section. Scan the entire section for roots.
86+
start := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress)
87+
end := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress) + uintptr(section.virtualSize)
88+
markRoots(start, end)
89+
}
90+
section = (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(section)) + unsafe.Sizeof(peSection{})))
91+
}
92+
}

0 commit comments

Comments
 (0)