Browse Source

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.
pull/2857/head
Ayke van Laethem 2 years ago
committed by Ron Evans
parent
commit
5404c81ffd
  1. 4
      src/runtime/gc_globals_precise.go
  2. 6
      src/runtime/gc_leaking.go
  3. 6
      src/runtime/gc_none.go
  4. 89
      src/runtime/os_windows.go

4
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

6
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
}

6
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
}

89
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{})))
}
}

Loading…
Cancel
Save