runtime: clear X15 before calling cgocallbackg

CL 765581 uses ABIInternal to call from cgocallback to cgocallbackg.
That CL restores the g to R14, but fails to zero X15 as required by
ABIInternal.

As a result, the undefined value of X15 from C leaks into Go code,
causing all sorts of interesting crashes due to the broken invariant.

For #78934.

Change-Id: I70d131be66e971f7c238d18a84cc44b66a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/770560
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Michael Pratt 2026-04-24 14:02:09 -04:00 committed by Gopher Robot
parent 9b3f3ad17a
commit 52fd498a96
5 changed files with 82 additions and 1 deletions

View file

@ -1105,7 +1105,8 @@ havem:
// will seamlessly trace back into the earlier calls.
MOVQ m_curg(BX), SI
MOVQ SI, g(CX)
MOVQ SI, R14 // set the g register
MOVQ SI, R14 // set the g register, as required by ABIInternal.
XORPS X15, X15 // clear X15, as required by ABIInternal.
MOVQ (g_sched+gobuf_sp)(SI), DI // prepare stack as DI
MOVQ (g_sched+gobuf_pc)(SI), BX
MOVQ BX, -8(DI) // "push" return PC on the g stack

View file

@ -103,6 +103,18 @@ func TestCgoCallbackPprof(t *testing.T) {
}
}
func TestCgoCallbackX15(t *testing.T) {
t.Parallel()
if runtime.GOARCH != "amd64" {
t.Skipf("X15 test only relevant on amd64")
}
got := runTestProg(t, "testprogcgo", "CgoCallbackX15")
if want := "OK\n"; got != want {
t.Fatalf("expected %q, but got:\n%s", want, got)
}
}
func TestCgoExternalThreadPanic(t *testing.T) {
t.Parallel()
if runtime.GOOS == "plan9" {

View file

@ -0,0 +1,50 @@
// Copyright 2026 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.
package main
// Regression test to verify that cgocallback restores X15 = 0 as required by
// ABIInternal.
/*
#include <stdint.h>
void go_callback_amd64();
static void call_go_callback_amd64() {
// Clobber X15.
uint64_t val = 42;
asm volatile(
"vmovdqu %0, %%xmm15;"
:
: "m" (val)
: "xmm15");
go_callback_amd64();
}
*/
import "C"
import (
"runtime/testdata/testprogcgo/goasm"
)
func init() {
register("CgoCallbackX15", CgoCallbackX15)
}
//export go_callback_amd64
func go_callback_amd64() {
v := goasm.ReadX15()
if v != 0 {
println("X15 =", v)
panic("non-zero X15")
}
}
func CgoCallbackX15() {
C.call_go_callback_amd64()
println("OK")
}

View file

@ -0,0 +1,10 @@
// Copyright 2026 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.
// Package goasm contains Go assembly functions used by testprogcgo because
// packages using cgo can't also contain Go assembly.
package goasm
// ReadX15 returns the lower 64-bits of X15.
func ReadX15() uint64

View file

@ -0,0 +1,8 @@
// Copyright 2026 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.
// func ReadX15() uint64
TEXT ·ReadX15(SB), $0-8
MOVQ X15, ret+0(FP)
RET