go syscall: 不支持在Windows/arm64上使用浮点参数

yx2lnoni  于 4个月前  发布在  Go
关注(0)|答案(8)|浏览(59)

你正在使用的Go版本是什么( go version )?

$ go version
go version go1.21.1 linux/amd64

这个问题在最新版本的发布中是否会重现?

是的。

你正在使用什么操作系统和处理器架构( go env )?

go env 输出

$ go env
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/agiacomolli/.cache/go-build'
GOENV='/home/agiacomolli/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/agiacomolli/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/agiacomolli/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/tmp/go1.21.1'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/tmp/go1.21.1/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.1'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/tmp/windows-crash/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build63189677=/tmp/go-build -gno-record-gcc-switches'

你做了什么?

从Linux机器上交叉构建Windows。amd64 在运行在amd64arm64机器上的构建工作正常。
arm64 版本在运行时抛出异常导致崩溃。

package main

import (
        "log"
        "math"
        "syscall"
        "time"
        "unsafe"

        "golang.org/x/sys/windows"
)

var (
        modoleaut32 = windows.NewLazySystemDLL("oleaut32.dll")

        procVariantTimeToSystemTime = modoleaut32.NewProc("VariantTimeToSystemTime")
)

func main() {
        t, err := GetVariantDate(45000.0)
        if err != nil {
                log.Fatal(err)
        }

        log.Printf("time = %v", t)
}

func GetVariantDate(value float64) (time.Time, error) {
        var st syscall.Systemtime

        r, _, err := procVariantTimeToSystemTime.Call(
                uintptr(math.Float64bits(value)),
                uintptr(unsafe.Pointer(&st)),
        )
        if r == 0 {
                return time.Time{}, err
        }

        return time.Date(
                int(st.Year),
                time.Month(st.Month),
                int(st.Day),
                int(st.Hour),
                int(st.Minute),
                int(st.Second),
                int(st.Milliseconds/1000),
                time.UTC,
        ), nil
}

你期望看到什么?

在Linux上构建:

GOOS=windows GOARCH=arm64 go build -o crash.exe main.go

在Windows ARM64上运行:

PS C:\tmp> .\crash.exe
2023/09/11 19:30:33 time = 2023-03-15 00:00:00 +0000 UTC

你实际上看到了什么?

PS C:\tmp> .\crash.exe
Exception 0xc0000005 0x1 0x40e5f9000000000e 0x7ffb0b780d30
PC=0x7ffb0b780d30

runtime.cgocall(0x7ff784c919c0, 0x7ff784da1348)
        /tmp/go1.21.1/src/runtime/cgocall.go:157 +0x38 fp=0x40000b7c80 sp=0x40000b7c40 pc=0x7ff784c33b98
syscall.SyscallN(0x7ffb0b780cf0?, {0x40000b7d30?, 0x3?, 0x7ff784ccb3a8?})
        /tmp/go1.21.1/src/runtime/syscall_windows.go:544 +0xe8 fp=0x40000b7d00 sp=0x40000b7c80 pc=0x7ff784c8d9f8
syscall.Syscall(0x40000b7d88?, 0x7ff784ccb3d4?, 0x40000b7d88?, 0x7ff784ccb3f4?, 0x7ff784d92134?)
        /tmp/go1.21.1/src/runtime/syscall_windows.go:482 +0x30 fp=0x40000b7d50 sp=0x40000b7d00 pc=0x7ff784c8d750
golang.org/x/sys/windows.(*Proc).Call(0x7ff784d92160?, {0x400009a0f0?, 0x4000047e01?, 0x7ff784c476e0?})
        /home/agiacomolli/go/pkg/mod/golang.org/x/sys@v0.12.0/windows/dll_windows.go:172 +0xe0 fp=0x40000b7e00 sp=0x40000b7d50 pc=0x7ff784ccaa80
golang.org/x/sys/windows.(*LazyProc).Call(0x7ff784d92160, {0x400009a0f0, 0x2, 0x2})
        /home/agiacomolli/go/pkg/mod/golang.org/x/sys@v0.12.0/windows/dll_windows.go:348 +0x4c fp=0x40000b7e30 sp=0x40000b7e00 pc=0x7ff784ccb53c
main.GetVariantDate(0x40e5f90000000000)
        /tmp/windows-crash/main.go:31 +0x6c fp=0x40000b7e90 sp=0x40000b7e30 pc=0x7ff784cd187c
main.main()
        /tmp/windows-crash/main.go:20 +0x28 fp=0x40000b7f30 sp=0x40000b7e90 pc=0x7ff784cd16d8
runtime.main()
        /tmp/go1.21.1/src/runtime/proc.go:267 +0x2a4 fp=0x40000b7fd0 sp=0x40000b7f30 pc=0x7ff784c66d74
runtime.goexit()
        /tmp/go1.21.1/src/runtime/asm_arm64.s:1197 +0x4 fp=0x40000b7fd0 sp=0x40000b7fd0 pc=0x7ff784c90d14

goroutine 2 [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /tmp/go1.21.1/src/runtime/proc.go:398 +0xc8 fp=0x4000043f90 sp=0x4000043f70 pc=0x7ff784c67188
runtime.goparkunlock(...)
        /tmp/go1.21.1/src/runtime/proc.go:404
runtime.forcegchelper()
        /tmp/go1.21.1/src/runtime/proc.go:322 +0xb8 fp=0x4000043fd0 sp=0x4000043f90 pc=0x7ff784c67018
runtime.goexit()
        /tmp/go1.21.1/src/runtime/asm_arm64.s:1197 +0x4 fp=0x4000043fd0 sp=0x4000043fd0 pc=0x7ff784c90d14
created by runtime.init.6 in goroutine 1
        /tmp/go1.21.1/src/runtime/proc.go:310 +0x24

goroutine 3 [GC sweep wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /tmp/go1.21.1/src/runtime/proc.go:398 +0xc8 fp=0x4000045f60 sp=0x4000045f40 pc=0x7ff784c67188
runtime.goparkunlock(...)
        /tmp/go1.21.1/src/runtime/proc.go:404
runtime.bgsweep(0x0?)
        /tmp/go1.21.1/src/runtime/mgcsweep.go:280 +0xa0 fp=0x4000045fb0 sp=0x4000045f60 pc=0x7ff784c527a0
runtime.gcenable.func1()
        /tmp/go1.21.1/src/runtime/mgc.go:200 +0x28 fp=0x4000045fd0 sp=0x4000045fb0 pc=0x7ff784c474e8
runtime.goexit()
        /tmp/go1.21.1/src/runtime/asm_arm64.s:1197 +0x4 fp=0x4000045fd0 sp=0x4000045fd0 pc=0x7ff784c90d14
created by runtime.gcenable in goroutine 1
        /tmp/go1.21.1/src/runtime/mgc.go:200 +0x6c

goroutine 4 [GC scavenge wait]:
runtime.gopark(0x4000016070?, 0x7ff784d17d18?, 0x1?, 0x0?, 0x4000040b60?)
        /tmp/go1.21.1/src/runtime/proc.go:398 +0xc8 fp=0x4000055f50 sp=0x4000055f30 pc=0x7ff784c67188
runtime.goparkunlock(...)
        /tmp/go1.21.1/src/runtime/proc.go:404
runtime.(*scavengerState).park(0x7ff784da0b80)
        /tmp/go1.21.1/src/runtime/mgcscavenge.go:425 +0x5c fp=0x4000055f80 sp=0x4000055f50 pc=0x7ff784c5003c
runtime.bgscavenge(0x0?)
        /tmp/go1.21.1/src/runtime/mgcscavenge.go:653 +0x44 fp=0x4000055fb0 sp=0x4000055f80 pc=0x7ff784c50584
runtime.gcenable.func2()
        /tmp/go1.21.1/src/runtime/mgc.go:201 +0x28 fp=0x4000055fd0 sp=0x4000055fb0 pc=0x7ff784c47488
runtime.goexit()
        /tmp/go1.21.1/src/runtime/asm_arm64.s:1197 +0x4 fp=0x4000055fd0 sp=0x4000055fd0 pc=0x7ff784c90d14
created by runtime.gcenable in goroutine 1
        /tmp/go1.21.1/src/runtime/mgc.go:201 +0xac

goroutine 18 [finalizer wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /tmp/go1.21.1/src/runtime/proc.go:398 +0xc8 fp=0x4000047d80 sp=0x4000047d60 pc=0x7ff784c67188
runtime.runfinq()
        /tmp/go1.21.1/src/runtime/mfinal.go:193 +0x108 fp=0x4000047fd0 sp=0x4000047d80 pc=0x7ff784c46618
runtime.goexit()
        /tmp/go1.21.1/src/runtime/asm_arm64.s:1197 +0x4 fp=0x4000047fd0 sp=0x4000047fd0 pc=0x7ff784c90d14
created by runtime.createfing in goroutine 1
        /tmp/go1.21.1/src/runtime/mfinal.go:163 +0x80
r0   0x0
r1   0x2
r2   0x4
r3   0x0
r4   0x0
r5   0x1
r6   0xf1f09ff972
r7   0x1e
r8   0x0
r9   0xc
r10  0x1e
r11  0x7ffb0b874308
r12  0x16b
r13  0xf1f09ff9d0
r14  0x20
r15  0x16c
r16  0x40000b13a0
r17  0x40000b7dd0
r18  0x0
r19  0x40e5f90000000000
r20  0xf1f09ff9b0
r21  0xf1f09ff818
r22  0x0
r23  0x0
r24  0x0
r25  0x0
r26  0x40000b7de0
r27  0x1488
r28  0x7ff784da0c20
r29  0xf1f09ff960
lr   0x7ffb0b780d20
sp   0xf1f09ff960
pc   0x7ffb0b780d30
cpsr 0x80000000
sshcrbum

sshcrbum1#

看起来windows/arm64似乎没有将浮点参数设置到专用的浮点寄存器中,这会导致后续出现异常。
例如,这里是第一个cgo参数被设置的地方:
go/src/runtime/sys_windows_arm64.s
第83行 905b58b
| | MOVD (0*8)(R12), R0 |
但它应该是这样的:

MOVD(0*8)(R12),R0
FMOVD    R0,F0

不幸的是,修复方法并不那么简单。AArch64调用约定为整数和浮点数分别分配寄存器。也就是说,在VariantTimeToSystemTime(arg0 float64, arg1 uintptr)中,arg0将存储在V0中,arg1将存储在R0中,而不是R1中。这意味着需要知道每个参数的值类,才能从Go调用约定转换为AArch64。
我想知道其他arm64操作系统是如何实现这一点的。@golang/compiler

rbl8hiat

rbl8hiat2#

另外,值得注意的是,在windows/arm64系统上跳过了TestFloatArgs:
go/src/runtime/syscall_windows_test.go
第824行到第830行的代码:
| | funcTestFloatArgs(t*testing.T) { |
| | if_, err:=exec.LookPath("gcc"); err!=nil { |
| | t.Skip("skipping test: gcc is missing") |
| | } |
| | ifruntime.GOARCH!="amd64" { |
| | t.Skipf("skipping test: GOARCH=%s", runtime.GOARCH) |
| | } |
#6510相关。

bnlyeluc

bnlyeluc3#

在其他平台上,我们通常假设系统调用不接受浮点数参数。syscall.Syscall 显然也是这样认为的。但是如果我们需要浮点数参数,它有一个特殊的代码路径,例如 https://cs.opensource.google/go/go/+/master:src/runtime/sys_darwin.go;l=105 ,我们称之为带有浮点数参数的macOS系统函数。它的签名不同,并且有不同的汇编实现。

avwztpqn

avwztpqn4#

如果浮点参数是Windows系统调用的一般情况,我们可能需要一些其他机制,除了syscall.Syscall。也许对于具有1、2、... ... FP参数的系统调用,可以使用syscall.SyscallFP1syscall.SyscallFP2等。

ygya80vv

ygya80vv5#

@cherrymui,既然我们已经有了syscall.SyscallN,那么我们是否可以换成syscall.SyscallFPN,这样就更具未来性?

rdrgkggo

rdrgkggo6#

我认为我们不能有两个可变参数,一个用于整数,另一个用于浮点数。如果有一种方法可以编写这样的可变函数并实现它,我会完全没问题。

jchrr9hc

jchrr9hc7#

嗯,这个API接受整数和浮点数参数:

// SyscallFPN is like SyscallN but for functions that accept floating point arguments.
// To flag an argument as floating point, set the corresponding bit in the floats bitmask.
// If floats is 1, all arguments are assumed to be floating point.
//
// For example, if the second and third argument are floating points, use
//
//	syscall.SyscallFPN(trap, 1<<2|1<<3, a0, uintptr(math.Float64bits(a1)), uintptr(math.Float64bits(a2)), a3)
func SyscallFPN(trap uintptr, floats byte, args ...uintptr) (r1, r2 uintptr, err Errno)
ejk8hzay

ejk8hzay8#

相关问题,但对于Windows C API调用回Go:#45300
我更喜欢有类型化的API,或许是这样的:

// Get assigns this Proc as a Go function to *f. f must be a pointer to a func-typed variable.
// f's arguments may have type uintptr or float64 and it may have up to two results,
// each of type uintptr or float64. (I made up these constraints, but something like that.)
func (p *Proc) Get(f any) error

这基本上是syscall.NewCallback的逆操作。我没有想过如何实现这个。这可能会很棘手,看起来像是NewCallback和reflect.makeMethodValue之间的交叉。
总的来说,我非常不喜欢Windows syscall.Syscall*函数,因为它们与Windows上的事物并不完全匹配,尤其是它们将“trap”参数作为函数PC进行重载的方式。如果我们能避免的话,我不想深入研究这些函数。

相关问题