254 lines
7.2 KiB
Diff
254 lines
7.2 KiB
Diff
|
|
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
|
||
|
|
|