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.
Этот коммит содержится в:
родитель
b52310fed2
коммит
5404c81ffd
4 изменённых файлов: 103 добавлений и 2 удалений
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{})))
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче