runtime,runtime/cgo: save all necessary registers on entry to Go on Windows

There are several assembly functions that transition from the Windows
ABI to the Go ABI. These all need to save all registers that are
callee-save in the Windows ABI and caller-save in the Go ABI and
prepare the register state for Go. However, they all do this slightly
differently and most of them don't save the necessary XMM registers
for this transition (which could corrupt them in the C caller).
Furthermore, now that we have a carefully specified Go ABI, it's clear
that none of these actually get all of the details 100% right.

So, unify this code into two macros in a shared header in
runtime/cgo/abi_amd64.h that handle all necessary registers and setup
and use these macros everywhere on Windows that handles transitions
from C to Go.

Change-Id: I62f41345a507aad1ca383814ac8b7e2a9ffb821e
Reviewed-on: https://go-review.googlesource.com/c/go/+/309769
Trust: Austin Clements <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Austin Clements 2021-04-13 09:06:50 -04:00
parent 3e0b1cdb5d
commit dba2eab826
4 changed files with 113 additions and 99 deletions

View file

@ -912,7 +912,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
} }
if autoffset != deltasp { if autoffset != deltasp {
ctxt.Diag("unbalanced PUSH/POP") ctxt.Diag("%s: unbalanced PUSH/POP", cursym)
} }
if autoffset != 0 { if autoffset != 0 {

View file

@ -0,0 +1,69 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Macros for transitioning from the host ABI to Go ABI0.
//
// TODO(austin): Define these for ELF platforms as well.
#ifdef GOOS_windows
// REGS_HOST_TO_ABI0_STACK is the stack bytes used by
// PUSH_REGS_HOST_TO_ABI0.
#define REGS_HOST_TO_ABI0_STACK (28*8 + 8)
// PUSH_REGS_HOST_TO_ABI0 prepares for transitioning from
// the host ABI to Go ABI0 code. It saves all registers that are
// callee-save in the host ABI and caller-save in Go ABI0 and prepares
// for entry to Go.
//
// Save DI SI BP BX R12 R13 R14 R15 X6-X15 registers and the DF flag.
// Clear the DF flag for the Go ABI.
// MXCSR matches the Go ABI, so we don't have to set that,
// and Go doesn't modify it, so we don't have to save it.
#define PUSH_REGS_HOST_TO_ABI0() \
PUSHFQ \
CLD \
ADJSP $(REGS_HOST_TO_ABI0_STACK - 8) \
MOVQ DI, (0*0)(SP) \
MOVQ SI, (1*8)(SP) \
MOVQ BP, (2*8)(SP) \
MOVQ BX, (3*8)(SP) \
MOVQ R12, (4*8)(SP) \
MOVQ R13, (5*8)(SP) \
MOVQ R14, (6*8)(SP) \
MOVQ R15, (7*8)(SP) \
MOVUPS X6, (8*8)(SP) \
MOVUPS X7, (10*8)(SP) \
MOVUPS X8, (12*8)(SP) \
MOVUPS X9, (14*8)(SP) \
MOVUPS X10, (16*8)(SP) \
MOVUPS X11, (18*8)(SP) \
MOVUPS X12, (20*8)(SP) \
MOVUPS X13, (22*8)(SP) \
MOVUPS X14, (24*8)(SP) \
MOVUPS X15, (26*8)(SP)
#define POP_REGS_HOST_TO_ABI0() \
MOVQ (0*0)(SP), DI \
MOVQ (1*8)(SP), SI \
MOVQ (2*8)(SP), BP \
MOVQ (3*8)(SP), BX \
MOVQ (4*8)(SP), R12 \
MOVQ (5*8)(SP), R13 \
MOVQ (6*8)(SP), R14 \
MOVQ (7*8)(SP), R15 \
MOVUPS (8*8)(SP), X6 \
MOVUPS (10*8)(SP), X7 \
MOVUPS (12*8)(SP), X8 \
MOVUPS (14*8)(SP), X9 \
MOVUPS (16*8)(SP), X10 \
MOVUPS (18*8)(SP), X11 \
MOVUPS (20*8)(SP), X12 \
MOVUPS (22*8)(SP), X13 \
MOVUPS (24*8)(SP), X14 \
MOVUPS (26*8)(SP), X15 \
ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8) \
POPFQ
#endif

View file

@ -3,6 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
#include "textflag.h" #include "textflag.h"
#include "abi_amd64.h"
// Called by C code generated by cmd/cgo. // Called by C code generated by cmd/cgo.
// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr)
@ -11,57 +12,18 @@
// This signature is known to SWIG, so we can't change it. // This signature is known to SWIG, so we can't change it.
#ifndef GOOS_windows #ifndef GOOS_windows
TEXT crosscall2(SB),NOSPLIT,$0x50-0 /* keeps stack pointer 32-byte aligned */ TEXT crosscall2(SB),NOSPLIT,$0x50-0 /* keeps stack pointer 32-byte aligned */
#else
TEXT crosscall2(SB),NOSPLIT,$0x110-0 /* also need to save xmm6 - xmm15 */
#endif
MOVQ BX, 0x18(SP) MOVQ BX, 0x18(SP)
MOVQ R12, 0x28(SP) MOVQ R12, 0x28(SP)
MOVQ R13, 0x30(SP) MOVQ R13, 0x30(SP)
MOVQ R14, 0x38(SP) MOVQ R14, 0x38(SP)
MOVQ R15, 0x40(SP) MOVQ R15, 0x40(SP)
#ifdef GOOS_windows
// Win64 save RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 and XMM6 -- XMM15.
MOVQ DI, 0x48(SP)
MOVQ SI, 0x50(SP)
MOVUPS X6, 0x60(SP)
MOVUPS X7, 0x70(SP)
MOVUPS X8, 0x80(SP)
MOVUPS X9, 0x90(SP)
MOVUPS X10, 0xa0(SP)
MOVUPS X11, 0xb0(SP)
MOVUPS X12, 0xc0(SP)
MOVUPS X13, 0xd0(SP)
MOVUPS X14, 0xe0(SP)
MOVUPS X15, 0xf0(SP)
MOVQ CX, 0x0(SP) /* fn */
MOVQ DX, 0x8(SP) /* arg */
// Skip n in R8.
MOVQ R9, 0x10(SP) /* ctxt */
CALL runtime·cgocallback(SB)
MOVQ 0x48(SP), DI
MOVQ 0x50(SP), SI
MOVUPS 0x60(SP), X6
MOVUPS 0x70(SP), X7
MOVUPS 0x80(SP), X8
MOVUPS 0x90(SP), X9
MOVUPS 0xa0(SP), X10
MOVUPS 0xb0(SP), X11
MOVUPS 0xc0(SP), X12
MOVUPS 0xd0(SP), X13
MOVUPS 0xe0(SP), X14
MOVUPS 0xf0(SP), X15
#else
MOVQ DI, 0x0(SP) /* fn */ MOVQ DI, 0x0(SP) /* fn */
MOVQ SI, 0x8(SP) /* arg */ MOVQ SI, 0x8(SP) /* arg */
// Skip n in DX. // Skip n in DX.
MOVQ CX, 0x10(SP) /* ctxt */ MOVQ CX, 0x10(SP) /* ctxt */
CALL runtime·cgocallback(SB) CALL runtime·cgocallback(SB)
#endif
MOVQ 0x18(SP), BX MOVQ 0x18(SP), BX
MOVQ 0x28(SP), R12 MOVQ 0x28(SP), R12
@ -70,3 +32,21 @@ TEXT crosscall2(SB),NOSPLIT,$0x110-0 /* also need to save xmm6 - xmm15 */
MOVQ 0x40(SP), R15 MOVQ 0x40(SP), R15
RET RET
#else
TEXT crosscall2(SB),NOSPLIT,$0-0
PUSH_REGS_HOST_TO_ABI0()
// Make room for arguments to cgocallback.
ADJSP $0x18
MOVQ CX, 0x0(SP) /* fn */
MOVQ DX, 0x8(SP) /* arg */
// Skip n in R8.
MOVQ R9, 0x10(SP) /* ctxt */
CALL runtime·cgocallback(SB)
ADJSP $-0x18
POP_REGS_HOST_TO_ABI0()
RET
#endif

View file

@ -5,6 +5,7 @@
#include "go_asm.h" #include "go_asm.h"
#include "go_tls.h" #include "go_tls.h"
#include "textflag.h" #include "textflag.h"
#include "cgo/abi_amd64.h"
// maxargs should be divisible by 2, as Windows stack // maxargs should be divisible by 2, as Windows stack
// must be kept 16-byte aligned on syscall entry. // must be kept 16-byte aligned on syscall entry.
@ -111,18 +112,10 @@ TEXT runtime·getlasterror(SB),NOSPLIT,$0
TEXT sigtramp<>(SB),NOSPLIT|NOFRAME,$0-0 TEXT sigtramp<>(SB),NOSPLIT|NOFRAME,$0-0
// CX: PEXCEPTION_POINTERS ExceptionInfo // CX: PEXCEPTION_POINTERS ExceptionInfo
// DI SI BP BX R12 R13 R14 R15 registers and DF flag are preserved // Switch from the host ABI to the Go ABI.
// as required by windows callback convention. PUSH_REGS_HOST_TO_ABI0()
PUSHFQ // Make stack space for the rest of the function.
SUBQ $112, SP ADJSP $48
MOVQ DI, 80(SP)
MOVQ SI, 72(SP)
MOVQ BP, 64(SP)
MOVQ BX, 56(SP)
MOVQ R12, 48(SP)
MOVQ R13, 40(SP)
MOVQ R14, 32(SP)
MOVQ R15, 88(SP)
MOVQ AX, R15 // save handler address MOVQ AX, R15 // save handler address
@ -138,8 +131,8 @@ TEXT sigtramp<>(SB),NOSPLIT|NOFRAME,$0-0
CALL runtime·badsignal2(SB) CALL runtime·badsignal2(SB)
// save g and SP in case of stack switch // save g and SP in case of stack switch
MOVQ DX, 96(SP) // g MOVQ DX, 32(SP) // g
MOVQ SP, 104(SP) MOVQ SP, 40(SP)
// do we need to switch to the g0 stack? // do we need to switch to the g0 stack?
MOVQ g_m(DX), BX MOVQ g_m(DX), BX
@ -153,9 +146,11 @@ TEXT sigtramp<>(SB),NOSPLIT|NOFRAME,$0-0
MOVQ (g_sched+gobuf_sp)(BX), DI MOVQ (g_sched+gobuf_sp)(BX), DI
// make room for sighandler arguments // make room for sighandler arguments
// and re-save old SP for restoring later. // and re-save old SP for restoring later.
// (note that the 104(DI) here must match the 104(SP) above.) // Adjust g0 stack by the space we're using and
SUBQ $120, DI // save SP at the same place on the g0 stack.
MOVQ SP, 104(DI) // The 32(DI) here must match the 32(SP) above.
SUBQ $(REGS_HOST_TO_ABI0_STACK + 48), DI
MOVQ SP, 40(DI)
MOVQ DI, SP MOVQ DI, SP
g0: g0:
@ -170,23 +165,14 @@ g0:
// switch back to original stack and g // switch back to original stack and g
// no-op if we never left. // no-op if we never left.
MOVQ 104(SP), SP MOVQ 40(SP), SP
MOVQ 96(SP), DX MOVQ 32(SP), DX
get_tls(BP) get_tls(BP)
MOVQ DX, g(BP) MOVQ DX, g(BP)
done: done:
// restore registers as required for windows callback ADJSP $-48
MOVQ 88(SP), R15 POP_REGS_HOST_TO_ABI0()
MOVQ 32(SP), R14
MOVQ 40(SP), R13
MOVQ 48(SP), R12
MOVQ 56(SP), BX
MOVQ 64(SP), BP
MOVQ 72(SP), SI
MOVQ 80(SP), DI
ADDQ $112, SP
POPFQ
RET RET
@ -230,21 +216,8 @@ TEXT runtime·callbackasm1(SB),NOSPLIT,$0
DIVL CX DIVL CX
SUBQ $1, AX // subtract 1 because return PC is to the next slot SUBQ $1, AX // subtract 1 because return PC is to the next slot
// DI SI BP BX R12 R13 R14 R15 registers and DF flag are preserved // Switch from the host ABI to the Go ABI.
// as required by windows callback convention. PUSH_REGS_HOST_TO_ABI0()
PUSHFQ
SUBQ $64, SP
MOVQ DI, 56(SP)
MOVQ SI, 48(SP)
MOVQ BP, 40(SP)
MOVQ BX, 32(SP)
MOVQ R12, 24(SP)
MOVQ R13, 16(SP)
MOVQ R14, 8(SP)
MOVQ R15, 0(SP)
// Go ABI requires DF flag to be cleared.
CLD
// Create a struct callbackArgs on our stack to be passed as // Create a struct callbackArgs on our stack to be passed as
// the "frame" to cgocallback and on to callbackWrap. // the "frame" to cgocallback and on to callbackWrap.
@ -263,23 +236,16 @@ TEXT runtime·callbackasm1(SB),NOSPLIT,$0
MOVQ (24+callbackArgs_result)(SP), AX MOVQ (24+callbackArgs_result)(SP), AX
ADDQ $(24+callbackArgs__size), SP ADDQ $(24+callbackArgs__size), SP
// restore registers as required for windows callback POP_REGS_HOST_TO_ABI0()
MOVQ 0(SP), R15
MOVQ 8(SP), R14
MOVQ 16(SP), R13
MOVQ 24(SP), R12
MOVQ 32(SP), BX
MOVQ 40(SP), BP
MOVQ 48(SP), SI
MOVQ 56(SP), DI
ADDQ $64, SP
POPFQ
// The return value was placed in AX above. // The return value was placed in AX above.
RET RET
// uint32 tstart_stdcall(M *newm); // uint32 tstart_stdcall(M *newm);
TEXT runtime·tstart_stdcall<ABIInternal>(SB),NOSPLIT,$0 TEXT runtime·tstart_stdcall<ABIInternal>(SB),NOSPLIT,$0
// Switch from the host ABI to the Go ABI.
PUSH_REGS_HOST_TO_ABI0()
// CX contains first arg newm // CX contains first arg newm
MOVQ m_g0(CX), DX // g MOVQ m_g0(CX), DX // g
@ -298,12 +264,11 @@ TEXT runtime·tstart_stdcall<ABIInternal>(SB),NOSPLIT,$0
MOVQ CX, g_m(DX) MOVQ CX, g_m(DX)
MOVQ DX, g(SI) MOVQ DX, g(SI)
// Someday the convention will be D is always cleared.
CLD
CALL runtime·stackcheck(SB) // clobbers AX,CX CALL runtime·stackcheck(SB) // clobbers AX,CX
CALL runtime·mstart(SB) CALL runtime·mstart(SB)
POP_REGS_HOST_TO_ABI0()
XORL AX, AX // return 0 == success XORL AX, AX // return 0 == success
RET RET