Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go! #1

Open
fodhelper opened this issue Jul 24, 2024 · 4 comments
Open

Go! #1

fodhelper opened this issue Jul 24, 2024 · 4 comments
Assignees
Labels
question Further information is requested

Comments

@fodhelper
Copy link

Hello
I saw your old tweet on Twitter "I made the latest version of golang able to run on Windows XP!!"
I was wondered, latest versions of Go can't even run on Windows 7, how did you made them run in Windows XP!
Could you please give more info about this experience?

@iDigitalFlame iDigitalFlame self-assigned this Aug 29, 2024
@iDigitalFlame iDigitalFlame added the question Further information is requested label Aug 29, 2024
@iDigitalFlame
Copy link
Owner

Hey sorry for the delayed response, before and after Defcon/BSLV takes a number on me.

Anyway, so the interesting thing about Go's OS support is based on the underlying syscalls and functions it uses under the hood. These are mainly stored in the syscall_(os).go and os_(os).go files, with a few exceptions.

This goal was done as I was working on a variant of Garble that would do these inline replacements (similar to how Gable changes some core-stdlib code before compile time using things called "overlays"). The first goal was to be sure that even if I could make it do that, would it work?

Well I cloned my on-device GOROOT and replaced many of the functions not available in Xp with ones that are. I don't have the exact diff handy, but from my notes, it looks something like this. (Also some of my fixes to the stdlib I wanted to make, like actually de-commiting reserved memory which Go does not do in Windows). These are specific to Go 1.22, so it may be different in Go 1.23.


Files that need to be updated:

  • mem_windows.go
    • Replace all virtual* memory calls with ntdll.Rtl* calls
    • Should sysUnusedOS map to sysFreeOS? (DECOMMIT -> FREE)
  • netpoll_windows.go
    • Update netpoll(delay int64)
    • Update netpollBreak() ? (Maybe)
    • Update netpollinit() ? (Maybe)
    • Update netpollopen(fd uintptr, pd *pollDesc) ? (Maybe)
  • os_windows_arm.go / os_windows_arm64.go
    • Update cputicks() ? (Maybe)
  • os_windows.go
    • ASM to access the GS segment register?
      Or use RtlGetCurrentPeb?
    • Maybe change the loader from static to direct?
      Could copy what we did in Rust and load the Kernel32 memory mapping funcs only. Dynamically load everything else.
    • Add _LdrLoadDll
    • Add _LdrGetProcedureAddress
    • Remove _CreateWaitableTimerExW
    • Change _CloseHandle to _NtCloseHandle
    • Change _CreateEventA to _NtCreateEvent
    • Remove _LoadLibraryExW / _LoadLibraryW
    • Remove _RaiseFailFastException
    • Change _SetEvent to _NtSetEvent
    • Change _DuplicateHandle to _NtDuplicateObject
    • Change CreateThread to NtCreateThreadEx
    • Change _Virtual{Allow,Free,Query} with Nt* counterparts
    • Change _WaitForSingleObject to _NtWaitForSingleObject
    • Change _WaitForMultipleObjects to _NtWaitForMultipleObjects
    • Change _ProcessPrng to _SystemFunction036
    • Change _SuspendThread to _NtSuspendThread
    • Change _ResumeThread to _NtResumeThread
    • Remove bcryptprimitivesdll
    • Find a replacement for timeBeginPeriod and timeEndPeriod
    • Nuke monitorSuspendResume()
    • Update getproccount() ? (Maybe)
    • Update getPageSize() ? (Maybe)
    • Update osRelax(relax bool)
    • Update / Nuke createHighResTimer() / initHighResTimer()
    • Nuke initLongPathSupport()
    • Remove calls to _SetProcessPriorityBoost? (Maybe)
    • Handle _SetConsoleCtrlHandler? (Non WinXp Fix)
    • Fucking fix exit(code int32)
    • Update semasleep(ns int64)
    • Update semawakeup(mp *m)
    • Update semacreate(mp *m)
    • Update newosproc(mp *m)
    • Update minit()
    • Update unminit()
    • Update mdestroy(mp *m)
    • Update osyield_no_g() / osyield()
    • Update usleep_no_g(us uint32)
    • Update usleep(us uint32)
    • Fucking Fix ctrlHandler(_type uint32)
    • Do we need?
      • profilem(mp *m, thread uintptr)
      • setProcessCPUProfiler(hz int32)
      • profileLoop()
      • setThreadCPUProfiler(hz int32)
  • signal_windows.go
    • Fix preventErrorDialogs()
    • Fix enableWER()
    • Fix initExceptionHandler()
    • Nuke dieFromException(info _exceptionrecord, r context)
  • syscall_windows.go
    • Fix syscall_loadsystemlibrary(filename *uint16)
    • Fix syscall_loadlibrary(filename *uint16)
    • Fix syscall_getprocaddress(handle uintptr, procname *byte)

You'll notice that some of the replaced functions are in Xp. However, it's 1000% easier to use all the Nt* variants as they have long support. (Minus NtCreateThreadEx needs Vista+)

Currently, the compatible builder is paused as work is focused on the Rust variant XrMT as I have full control instead of having to deal with the Go runtime and the Go devs disdain of people modifying how it works.

I hope that helps!

@iDigitalFlame
Copy link
Owner

Also found my diff, it's very hacky looking but it replaces the incompatible function calls.

diff '--color=auto' -r goxp/src/runtime/netpoll_windows.go gostd/src/runtime/netpoll_windows.go
88c88,89
< 	var wait, qty, key uint32
---
> 	var entries [64]overlappedEntry
> 	var wait, qty, flags, n, i uint32
111,115c112,116
< 	delta := int32(0)
< retry:
< 	op = nil
< 	errno = 0
< 	qty = 0
---
> 
> 	n = uint32(len(entries) / int(gomaxprocs))
> 	if n < 8 {
> 		n = 8
> 	}
119c120
< 	if stdcall5(_GetQueuedCompletionStatus, iocphandle, uintptr(unsafe.Pointer(&qty)), uintptr(unsafe.Pointer(&key)), uintptr(unsafe.Pointer(&op)), uintptr(wait)) == 0 {
---
> 	if stdcall6(_GetQueuedCompletionStatusEx, iocphandle, uintptr(unsafe.Pointer(&entries[0])), uintptr(n), uintptr(unsafe.Pointer(&n)), uintptr(wait), 0) == 0 {
125,130c126,127
< 		if op == nil {
< 			println("runtime: GetQueuedCompletionStatus failed (errno=", errno, ")")
< 			throw("runtime: netpoll failed")
< 		}
< 		mp.blocked = false
< 		delta += handlecompletion(&toRun, op, errno, qty)
---
> 		println("runtime: GetQueuedCompletionStatusEx failed (errno=", errno, ")")
> 		throw("runtime: netpoll failed")
132,133c129,147
< 	if delay != 0 && toRun.empty() {
< 		goto retry
---
> 	mp.blocked = false
> 	delta := int32(0)
> 	for i = 0; i < n; i++ {
> 		op = entries[i].op
> 		if op != nil && op.pd == entries[i].key {
> 			errno = 0
> 			qty = 0
> 			if stdcall5(_WSAGetOverlappedResult, op.pd.fd, uintptr(unsafe.Pointer(op)), uintptr(unsafe.Pointer(&qty)), 0, uintptr(unsafe.Pointer(&flags))) == 0 {
> 				errno = int32(getlasterror())
> 			}
> 			delta += handlecompletion(&toRun, op, errno, qty)
> 		} else {
> 			netpollWakeSig.Store(0)
> 			if delay == 0 {
> 				// Forward the notification to the
> 				// blocked poller.
> 				netpollBreak()
> 			}
> 		}
diff '--color=auto' -r goxp/src/runtime/os_windows.go gostd/src/runtime/os_windows.go
26d25
< 
29d27
< 
36d33
< 
38d34
< 
41d36
< 
43,45d37
< 
< //go:cgo_import_dynamic runtime._GetQueuedCompletionStatus GetQueuedCompletionStatus%5 "kernel32.dll"
< 
51d42
< 
54d44
< 
80,92d69
< //go:cgo_import_dynamic runtime._LdrGetProcedureAddress LdrGetProcedureAddress%4 "ntdll.dll"
< //go:cgo_import_dynamic runtime._LdrLoadDll LdrLoadDll%4 "ntdll.dll"
< 
< type uns struct {
< 	length uint16
< 	max    uint16
< 	buffer *uint16
< }
< type ans struct {
< 	length uint16
< 	max    uint16
< 	buffer *uint8
< }
97,99d73
< 	_LdrLoadDll,
< 	_LdrGetProcedureAddress,
< 
111c85
< 	// _CreateWaitableTimerExW,
---
> 	_CreateWaitableTimerExW,
118c92
< 	//_GetErrorMode,
---
> 	_GetErrorMode,
121,122c95
< 	//_GetQueuedCompletionStatusEx,
< 	_GetQueuedCompletionStatus,
---
> 	_GetQueuedCompletionStatusEx,
132c105
< 	//_RaiseFailFastException,
---
> 	_RaiseFailFastException,
179d151
< 	advapi32dll         = [...]uint16{'a', 'd', 'v', 'a', 'p', 'i', '3', '2', '.', 'd', 'l', 'l', 0}
245d216
< 
249,257c220,221
< 	n := len(name) - 1
< 
< 	aa := ans{length: uint16(n), max: uint16(n), buffer: &name[0]}
< 
< 	var ff stdFunction
< 
< 	stdcall4(_LdrGetProcedureAddress, lib, uintptr(unsafe.Pointer(&aa)), 0, uintptr(unsafe.Pointer(&ff)))
< 
< 	return ff
---
> 	f := stdcall2(_GetProcAddress, lib, uintptr(unsafe.Pointer(&name[0])))
> 	return stdFunction(unsafe.Pointer(f))
278,279d241
< var winDir = []uint16{'C', ':', '\\', 'W', 'i', 'n', 'd', 'o', 'w', 's', '\\', 's', 'y', 's', 't', 'e', 'm', '3', '2'}
< 
281,289c243
< 	nn := len(name)
< 	if name[nn-1] == 0 {
< 		nn--
< 	}
< 	u := uns{length: uint16(nn * 2), max: uint16(nn * 2), buffer: &name[0]}
< 	var hh uint32
< 	var hd uintptr
< 	stdcall4(_LdrLoadDll, uintptr(unsafe.Pointer(&winDir[0])), uintptr(unsafe.Pointer(&hh)), uintptr(unsafe.Pointer(&u)), uintptr(unsafe.Pointer(&hd)))
< 	return hd
---
> 	return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
293c247
< 	/*bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
---
> 	bcryptPrimitives := windowsLoadSystemLib(bcryptprimitivesdll[:])
297,303c251
< 	_ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))*/
< 
< 	a32 := windowsLoadSystemLib(advapi32dll[:])
< 	if a32 == 0 {
< 		throw("advapi32.dll not found")
< 	}
< 	_ProcessPrng = windowsFindfunc(a32, []byte("SystemFunction036\000"))
---
> 	_ProcessPrng = windowsFindfunc(bcryptPrimitives, []byte("ProcessPrng\000"))
333c281,310
< 	return
---
> 	const (
> 		_DEVICE_NOTIFY_CALLBACK = 2
> 	)
> 	type _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS struct {
> 		callback uintptr
> 		context  uintptr
> 	}
> 
> 	powrprof := windowsLoadSystemLib(powrprofdll[:])
> 	if powrprof == 0 {
> 		return // Running on Windows 7, where we don't need it anyway.
> 	}
> 	powerRegisterSuspendResumeNotification := windowsFindfunc(powrprof, []byte("PowerRegisterSuspendResumeNotification\000"))
> 	if powerRegisterSuspendResumeNotification == nil {
> 		return // Running on Windows 7, where we don't need it anyway.
> 	}
> 	var fn any = func(context uintptr, changeType uint32, setting uintptr) uintptr {
> 		for mp := (*m)(atomic.Loadp(unsafe.Pointer(&allm))); mp != nil; mp = mp.alllink {
> 			if mp.resumesema != 0 {
> 				stdcall1(_SetEvent, mp.resumesema)
> 			}
> 		}
> 		return 0
> 	}
> 	params := _DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS{
> 		callback: compileCallback(*efaceOf(&fn), true),
> 	}
> 	handle := uintptr(0)
> 	stdcall3(powerRegisterSuspendResumeNotification, _DEVICE_NOTIFY_CALLBACK,
> 		uintptr(unsafe.Pointer(&params)), uintptr(unsafe.Pointer(&handle)))
386,396d362
< func getprocessid() uint32
< func getprocesspeb() uintptr
< 
< func GetPEB() uintptr {
< 	return getprocesspeb()
< }
< 
< func GetProcID() uint32 {
< 	return getprocessid()
< }
< 
439c405
< 	/*const (
---
> 	const (
450,451c416
< 		_SYNCHRONIZE|_TIMER_QUERY_STATE|_TIMER_MODIFY_STATE)*/
< 	return 0
---
> 		_SYNCHRONIZE|_TIMER_QUERY_STATE|_TIMER_MODIFY_STATE)
455c420
< 	/*h := createHighResTimer()
---
> 	h := createHighResTimer()
459c424
< 	}*/
---
> 	}
480c445,495
< 	return
---
> 	const (
> 		IsLongPathAwareProcess = 0x80
> 		PebBitFieldOffset      = 3
> 		OPEN_EXISTING          = 3
> 		ERROR_PATH_NOT_FOUND   = 3
> 	)
> 
> 	// Check that we're ≥ 10.0.15063.
> 	var maj, min, build uint32
> 	stdcall3(_RtlGetNtVersionNumbers, uintptr(unsafe.Pointer(&maj)), uintptr(unsafe.Pointer(&min)), uintptr(unsafe.Pointer(&build)))
> 	if maj < 10 || (maj == 10 && min == 0 && build&0xffff < 15063) {
> 		return
> 	}
> 
> 	// Set the IsLongPathAwareProcess flag of the PEB's bit field.
> 	bitField := (*byte)(unsafe.Pointer(stdcall0(_RtlGetCurrentPeb) + PebBitFieldOffset))
> 	originalBitField := *bitField
> 	*bitField |= IsLongPathAwareProcess
> 
> 	// Check that this actually has an effect, by constructing a large file
> 	// path and seeing whether we get ERROR_PATH_NOT_FOUND, rather than
> 	// some other error, which would indicate the path is too long, and
> 	// hence long path support is not successful. This whole section is NOT
> 	// strictly necessary, but is a nice validity check for the near to
> 	// medium term, when this functionality is still relatively new in
> 	// Windows.
> 	targ := longFileName[len(longFileName)-33 : len(longFileName)-1]
> 	if readRandom(targ) != len(targ) {
> 		readTimeRandom(targ)
> 	}
> 	start := copy(longFileName[:], sysDirectory[:sysDirectoryLen])
> 	const dig = "0123456789abcdef"
> 	for i := 0; i < 32; i++ {
> 		longFileName[start+i*2] = dig[longFileName[len(longFileName)-33+i]>>4]
> 		longFileName[start+i*2+1] = dig[longFileName[len(longFileName)-33+i]&0xf]
> 	}
> 	start += 64
> 	for i := start; i < len(longFileName)-1; i++ {
> 		longFileName[i] = 'A'
> 	}
> 	stdcall7(_CreateFileA, uintptr(unsafe.Pointer(&longFileName[0])), 0, 0, 0, OPEN_EXISTING, 0, 0)
> 	// The ERROR_PATH_NOT_FOUND error value is distinct from
> 	// ERROR_FILE_NOT_FOUND or ERROR_INVALID_NAME, the latter of which we
> 	// expect here due to the final component being too long.
> 	if getlasterror() == ERROR_PATH_NOT_FOUND {
> 		*bitField = originalBitField
> 		println("runtime: warning: IsLongPathAwareProcess failed to enable long paths; proceeding in fixup mode")
> 		return
> 	}
> 
> 	canUseLongPaths = true
diff '--color=auto' -r goxp/src/runtime/signal_windows.go gostd/src/runtime/signal_windows.go
22c22
< 	/*errormode := stdcall0(_GetErrorMode)
---
> 	errormode := stdcall0(_GetErrorMode)
31c31
< 	stdcall1(_WerSetFlags, werflags|_WER_FAULT_REPORTING_NO_UI)*/
---
> 	stdcall1(_WerSetFlags, werflags|_WER_FAULT_REPORTING_NO_UI)
36c36
< 	/*// re-enable Windows Error Reporting
---
> 	// re-enable Windows Error Reporting
40c40
< 	}*/
---
> 	}
478c478
< 	//stdcall3(_RaiseFailFastException, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(r)), FAIL_FAST_GENERATE_EXCEPTION_ADDRESS)
---
> 	stdcall3(_RaiseFailFastException, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(r)), FAIL_FAST_GENERATE_EXCEPTION_ADDRESS)
diff '--color=auto' -r goxp/src/runtime/sys_windows_amd64.s gostd/src/runtime/sys_windows_amd64.s
100,113d99
< // faster get/set last error
< TEXT runtime·getprocessid(SB),NOSPLIT,$0
< 	MOVQ	0x30(GS), AX
< 	MOVL	0x40(AX), AX
< 	MOVL	AX, ret+0(FP)
< 	RET
< 
< // faster get/set last error
< TEXT runtime·getprocesspeb(SB),NOSPLIT,$0
< 	MOVQ	0x30(GS), AX
< 	MOVQ	0x60(AX), AX
< 	MOVQ	AX, ret+0(FP)
< 	RET
< 
Only in goxp/src/time/tzdata: zzipdata.go

@wwqgtxx
Copy link

wwqgtxx commented Aug 30, 2024

Have you considered open-sourcing your golang branch?

Maybe it will be helpful to others who need it.

@iDigitalFlame
Copy link
Owner

Oh absolutely! It will be, it'll be included as part of my fork of Garble. Revisiting it is on my todo list!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants