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