From 4717d9a1a21dfe051a14f033615218d833371d68 Mon Sep 17 00:00:00 2001
From: jingrui <jingrui@huawei.com>
Date: Wed, 27 Nov 2019 10:19:13 +0800
Subject: [PATCH 3/6] runtime: save/fetch g register during VDSO on ARM and
 ARM64

On ARM and ARM64, during a VDSO call, the g register may be
temporarily clobbered by the VDSO code. If a signal is received
during the execution of VDSO code, we may not find a valid g
reading the g register. In CL 192937, we conservatively assume
g is nil. But this approach has a problem: we cannot handle
the signal in this case. Further, if the signal is not a
profiling signal, we'll call badsignal, which calls needm, which
wants to get an extra m, but we don't have one in a non-cgo
binary, which cuases the program to hang.

This is even more of a problem with async preemption, where we
will receive more signals than before. I ran into this problem
while working on async preemption support on ARM64.

In this CL, before making a VDSO call, we save the g on the
gsignal stack. When we receive a signal, we will be running on
the gsignal stack, so we can fetch the g from there and move on.

We probably want to do the same for PPC64. Currently we rely on
that the VDSO code doesn't actually clobber the g register, but
this is not guaranteed and we don't have control with.

Idea from discussion with Dan Cross and Austin.

Should fix #34391.

Change-Id: Idbefc5e4c2f4373192c2be797be0140ae08b26e3
Reviewed-on: https://go-review.googlesource.com/c/go/+/202759
Run-TryBot: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Austin Clements <austin@google.com>
---
 src/os/signal/signal_test.go  | 49 +++++++++++++++++++++++++++++++++++
 src/runtime/proc.go           |  3 +++
 src/runtime/signal_unix.go    | 24 ++++++++++++-----
 src/runtime/sys_linux_arm.s   | 32 +++++++++++++++++++++++
 src/runtime/sys_linux_arm64.s | 28 ++++++++++++++++++++
 5 files changed, 129 insertions(+), 7 deletions(-)

diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go
index 6ea59f4..c8274ea 100644
--- a/src/os/signal/signal_test.go
+++ b/src/os/signal/signal_test.go
@@ -453,3 +453,52 @@ func atomicStopTestProgram() {
 
 	os.Exit(0)
 }
+
+func TestTime(t *testing.T) {
+	// Test that signal works fine when we are in a call to get time,
+	// which on some platforms is using VDSO. See issue #34391.
+	dur := 3 * time.Second
+	if testing.Short() {
+		dur = 100 * time.Millisecond
+	}
+	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+	done := make(chan bool)
+	finished := make(chan bool)
+	go func() {
+		sig := make(chan os.Signal, 1)
+		Notify(sig, syscall.SIGUSR1)
+		defer Stop(sig)
+	Loop:
+		for {
+			select {
+			case <-sig:
+			case <-done:
+				break Loop
+			}
+		}
+		finished <- true
+	}()
+	go func() {
+	Loop:
+		for {
+			select {
+			case <-done:
+				break Loop
+			default:
+				syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+				runtime.Gosched()
+			}
+		}
+		finished <- true
+	}()
+	t0 := time.Now()
+	for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() {
+	} // hammering on getting time
+	close(done)
+	<-finished
+	<-finished
+	// When run with 'go test -cpu=1,2,4' SIGUSR1 from this test can slip
+	// into subsequent TestSignal() causing failure.
+	// Sleep for a while to reduce the possibility of the failure.
+	time.Sleep(10 * time.Millisecond)
+}
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 93d329d..1487647 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -3237,6 +3237,9 @@ func malg(stacksize int32) *g {
 		})
 		newg.stackguard0 = newg.stack.lo + _StackGuard
 		newg.stackguard1 = ^uintptr(0)
+		// Clear the bottom word of the stack. We record g
+		// there on gsignal stack during VDSO on ARM and ARM64.
+		*(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
 	}
 	return newg
 }
diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go
index 63fb07f..2cf1e3b 100644
--- a/src/runtime/signal_unix.go
+++ b/src/runtime/signal_unix.go
@@ -280,13 +280,23 @@ func sigpipe() {
 //
 //go:nosplit
 func sigFetchG(c *sigctxt) *g {
-    switch GOARCH {
-    case "arm", "arm64", "ppc64", "ppc64le":
-        if inVDSOPage(c.sigpc()) {
-            return nil
-        }
-    }
-    return getg()
+	switch GOARCH {
+	case "arm", "arm64":
+		if inVDSOPage(c.sigpc()) {
+			// Before making a VDSO call we save the g to the bottom of the
+			// signal stack. Fetch from there.
+			// TODO: in efence mode, stack is sysAlloc'd, so this wouldn't
+			// work.
+			sp := getcallersp()
+			s := spanOf(sp)
+			if s != nil && s.state == mSpanManual && s.base() < sp && sp < s.limit {
+				gp := *(**g)(unsafe.Pointer(s.base()))
+				return gp
+			}
+			return nil
+		}
+	}
+	return getg()
 }
 
 // sigtrampgo is called from the signal handler function, sigtramp,
diff --git a/src/runtime/sys_linux_arm.s b/src/runtime/sys_linux_arm.s
index 9c73984..26e12a8 100644
--- a/src/runtime/sys_linux_arm.s
+++ b/src/runtime/sys_linux_arm.s
@@ -246,7 +246,23 @@ noswitch:
 	CMP	$0, R11
 	B.EQ	fallback
 
+	// Store g on gsignal's stack, so if we receive a signal
+	// during VDSO code we can find the g.
+	// If we don't have a signal stack, we won't receive signal,
+	// so don't bother saving g.
+	MOVW	m_gsignal(R5), R6          // g.m.gsignal
+	CMP	$0, R6
+	BEQ	3(PC)
+	MOVW	(g_stack+stack_lo)(R6), R6 // g.m.gsignal.stack.lo
+	MOVW	g, (R6)
+
 	BL	(R11)
+
+	CMP	$0, R6   // R6 is unchanged by C code
+	BEQ	3(PC)
+	MOVW	$0, R1
+	MOVW	R1, (R6) // clear g slot
+
 	JMP	finish
 
 fallback:
@@ -297,7 +313,23 @@ noswitch:
 	CMP	$0, R11
 	B.EQ	fallback
 
+	// Store g on gsignal's stack, so if we receive a signal
+	// during VDSO code we can find the g.
+	// If we don't have a signal stack, we won't receive signal,
+	// so don't bother saving g.
+	MOVW	m_gsignal(R5), R6          // g.m.gsignal
+	CMP	$0, R6
+	BEQ	3(PC)
+	MOVW	(g_stack+stack_lo)(R6), R6 // g.m.gsignal.stack.lo
+	MOVW	g, (R6)
+
 	BL	(R11)
+
+	CMP	$0, R6   // R6 is unchanged by C code
+	BEQ	3(PC)
+	MOVW	$0, R1
+	MOVW	R1, (R6) // clear g slot
+
 	JMP	finish
 
 fallback:
diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s
index 2835b6c..fd40bf9 100644
--- a/src/runtime/sys_linux_arm64.s
+++ b/src/runtime/sys_linux_arm64.s
@@ -207,7 +207,21 @@ noswitch:
 	MOVW	$CLOCK_REALTIME, R0
 	MOVD	runtime·vdsoClockgettimeSym(SB), R2
 	CBZ	R2, fallback
+
+	// Store g on gsignal's stack, so if we receive a signal
+	// during VDSO code we can find the g.
+	// If we don't have a signal stack, we won't receive signal,
+	// so don't bother saving g.
+	MOVD	m_gsignal(R21), R22          // g.m.gsignal
+	CBZ	R22, 3(PC)
+	MOVD	(g_stack+stack_lo)(R22), R22 // g.m.gsignal.stack.lo
+	MOVD	g, (R22)
+
 	BL	(R2)
+
+	CBZ	R22, 2(PC) // R22 is unchanged by C code
+	MOVD	ZR, (R22)  // clear g slot
+
 	B	finish
 
 fallback:
@@ -250,7 +264,21 @@ noswitch:
 	MOVW	$CLOCK_MONOTONIC, R0
 	MOVD	runtime·vdsoClockgettimeSym(SB), R2
 	CBZ	R2, fallback
+
+	// Store g on gsignal's stack, so if we receive a signal
+	// during VDSO code we can find the g.
+	// If we don't have a signal stack, we won't receive signal,
+	// so don't bother saving g.
+	MOVD	m_gsignal(R21), R22          // g.m.gsignal
+	CBZ	R22, 3(PC)
+	MOVD	(g_stack+stack_lo)(R22), R22 // g.m.gsignal.stack.lo
+	MOVD	g, (R22)
+
 	BL	(R2)
+
+	CBZ	R22, 2(PC) // R22 is unchanged by C code
+	MOVD	ZR, (R22)  // clear g slot
+
 	B	finish
 
 fallback:
-- 
2.17.1

