From 5404c81ffd23f890a3a4be3ca614b7c5a4b967d1 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 22 May 2022 22:56:49 +0200 Subject: [PATCH] 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. --- src/runtime/gc_globals_precise.go | 4 +- src/runtime/gc_leaking.go | 6 +++ src/runtime/gc_none.go | 6 +++ src/runtime/os_windows.go | 89 +++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/runtime/gc_globals_precise.go b/src/runtime/gc_globals_precise.go index 9b07b396..3cdba741 100644 --- a/src/runtime/gc_globals_precise.go +++ b/src/runtime/gc_globals_precise.go @@ -1,5 +1,5 @@ -//go:build gc.conservative && !baremetal && !tinygo.wasm -// +build gc.conservative,!baremetal,!tinygo.wasm +//go:build gc.conservative && !baremetal && !tinygo.wasm && !windows +// +build gc.conservative,!baremetal,!tinygo.wasm,!windows package runtime diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 88d52eca..7d5e2c31 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -11,6 +11,8 @@ import ( "unsafe" ) +const gcAsserts = false // perform sanity checks + // Ever-incrementing pointer: no memory is freed. var heapptr = heapStart @@ -76,3 +78,7 @@ func setHeapEnd(newHeapEnd uintptr) { // enough. heapEnd = newHeapEnd } + +func markRoots(start, end uintptr) { + // dummy, so that markGlobals will compile +} diff --git a/src/runtime/gc_none.go b/src/runtime/gc_none.go index fc791008..a27b57a2 100644 --- a/src/runtime/gc_none.go +++ b/src/runtime/gc_none.go @@ -11,6 +11,8 @@ import ( "unsafe" ) +const gcAsserts = false // perform sanity checks + func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer @@ -38,3 +40,7 @@ func initHeap() { func setHeapEnd(newHeapEnd uintptr) { // Nothing to do here, this function is never actually called. } + +func markRoots(start, end uintptr) { + // dummy, so that markGlobals will compile +} diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index 85282cc3..f6ae6864 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -1,3 +1,92 @@ package runtime +import "unsafe" + const GOOS = "windows" + +//export GetModuleHandleExA +func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool + +// MS-DOS stub with PE header offset: +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only +type exeHeader struct { + signature uint16 + _ [58]byte // skip DOS header + peHeader uint32 // at offset 0x3C +} + +// COFF file header: +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#file-headers +type peHeader struct { + magic uint32 + machine uint16 + numberOfSections uint16 + timeDateStamp uint32 + pointerToSymbolTable uint32 + numberOfSymbols uint32 + sizeOfOptionalHeader uint16 + characteristics uint16 +} + +// COFF section header: +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers +type peSection struct { + name [8]byte + virtualSize uint32 + virtualAddress uint32 + sizeOfRawData uint32 + pointerToRawData uint32 + pointerToRelocations uint32 + pointerToLinenumbers uint32 + numberOfRelocations uint16 + numberOfLinenumbers uint16 + characteristics uint32 +} + +var module *exeHeader + +// Mark global variables. +// Unfortunately, the linker doesn't provide symbols for the start and end of +// the data/bss sections. Therefore these addresses need to be determined at +// runtime. This might seem complex and it kind of is, but it only compiles to +// around 160 bytes of amd64 instructions. +// Most of this function is based on the documentation in +// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format. +func markGlobals() { + // Constants used in this function. + const ( + // https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexa + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002 + + // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format + IMAGE_SCN_MEM_WRITE = 0x80000000 + ) + + if module == nil { + // Obtain a handle to the currently executing image. What we're getting + // here is really just __ImageBase, but it's probably better to obtain + // it using GetModuleHandle to account for ASLR etc. + result := _GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, &module) + if gcAsserts && (!result || module.signature != 0x5A4D) { // 0x4D5A is "MZ" + runtimePanic("cannot get module handle") + } + } + + // Find the PE header at offset 0x3C. + pe := (*peHeader)(unsafe.Pointer(uintptr(unsafe.Pointer(module)) + uintptr(module.peHeader))) + if gcAsserts && pe.magic != 0x00004550 { // 0x4550 is "PE" + runtimePanic("cannot find PE header") + } + + // Iterate through sections. + section := (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(pe)) + uintptr(pe.sizeOfOptionalHeader) + unsafe.Sizeof(peHeader{}))) + for i := 0; i < int(pe.numberOfSections); i++ { + if section.characteristics&IMAGE_SCN_MEM_WRITE != 0 { + // Found a writable section. Scan the entire section for roots. + start := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress) + end := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress) + uintptr(section.virtualSize) + markRoots(start, end) + } + section = (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(section)) + unsafe.Sizeof(peSection{}))) + } +}