Skip to content

Commit cf9263d

Browse files
committed
runtime: factor out windows sigtramp
This CL factors out part of the Windows sigtramp implementation, which was duplicated in all four architectures. The new common code is implemented in Go rather than in assembly, which will make Windows error handling easier to reason and maintain. While here, implement the control flow guard workaround on windows/386, which almost comes for free. Change-Id: I0bf38c28c54793225126e161bd95527a62de05e0 Reviewed-on: https://go-review.googlesource.com/c/go/+/458135 Run-TryBot: Quim Muntal <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Alex Brainman <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent e39c7a3 commit cf9263d

10 files changed

+221
-393
lines changed

src/runtime/defs_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ type systeminfo struct {
5757
wprocessorrevision uint16
5858
}
5959

60+
type exceptionpointers struct {
61+
record *exceptionrecord
62+
context *context
63+
}
64+
6065
type exceptionrecord struct {
6166
exceptioncode uint32
6267
exceptionflags uint32

src/runtime/defs_windows_386.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ func (c *context) set_lr(x uintptr) {}
5656
func (c *context) set_ip(x uintptr) { c.eip = uint32(x) }
5757
func (c *context) set_sp(x uintptr) { c.esp = uint32(x) }
5858

59+
func prepareContextForSigResume(c *context) {
60+
c.edx = c.esp
61+
c.ecx = c.eip
62+
}
63+
5964
func dumpregs(r *context) {
6065
print("eax ", hex(r.eax), "\n")
6166
print("ebx ", hex(r.ebx), "\n")

src/runtime/defs_windows_amd64.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ func (c *context) set_lr(x uintptr) {}
7070
func (c *context) set_ip(x uintptr) { c.rip = uint64(x) }
7171
func (c *context) set_sp(x uintptr) { c.rsp = uint64(x) }
7272

73+
func prepareContextForSigResume(c *context) {
74+
c.r8 = c.rsp
75+
c.r9 = c.rip
76+
}
77+
7378
func dumpregs(r *context) {
7479
print("rax ", hex(r.rax), "\n")
7580
print("rbx ", hex(r.rbx), "\n")

src/runtime/defs_windows_arm.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ func (c *context) set_ip(x uintptr) { c.pc = uint32(x) }
5858
func (c *context) set_sp(x uintptr) { c.spr = uint32(x) }
5959
func (c *context) set_lr(x uintptr) { c.lrr = uint32(x) }
6060

61+
func prepareContextForSigResume(c *context) {
62+
c.r0 = c.spr
63+
c.r1 = c.pc
64+
}
65+
6166
func dumpregs(r *context) {
6267
print("r0 ", hex(r.r0), "\n")
6368
print("r1 ", hex(r.r1), "\n")

src/runtime/defs_windows_arm64.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ func (c *context) set_ip(x uintptr) { c.pc = uint64(x) }
4141
func (c *context) set_sp(x uintptr) { c.xsp = uint64(x) }
4242
func (c *context) set_lr(x uintptr) { c.x[30] = uint64(x) }
4343

44+
func prepareContextForSigResume(c *context) {
45+
c.x[0] = c.xsp
46+
c.x[1] = c.pc
47+
}
48+
4449
func dumpregs(r *context) {
4550
print("r0 ", hex(r.x[0]), "\n")
4651
print("r1 ", hex(r.x[1]), "\n")

src/runtime/signal_windows.go

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ func disableWER() {
2222
stdcall1(_SetErrorMode, uintptr(errormode)|SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX|SEM_NOOPENFILEERRORBOX)
2323
}
2424

25-
// in sys_windows_386.s and sys_windows_amd64.s
25+
// in sys_windows_386.s, sys_windows_amd64.s, sys_windows_arm.s, and sys_windows_arm64.s
2626
func exceptiontramp()
2727
func firstcontinuetramp()
2828
func lastcontinuetramp()
29+
func sigresume()
2930

3031
func initExceptionHandler() {
3132
stdcall2(_AddVectoredExceptionHandler, 1, abi.FuncPCABI0(exceptiontramp))
@@ -88,13 +89,105 @@ func isgoexception(info *exceptionrecord, r *context) bool {
8889
return true
8990
}
9091

92+
const (
93+
callbackVEH = iota
94+
callbackFirstVCH
95+
callbackLastVCH
96+
)
97+
98+
// sigFetchGSafe is like getg() but without panicking
99+
// when TLS is not set.
100+
// Only implemented on windows/386, which is the only
101+
// arch that loads TLS when calling getg(). Others
102+
// use a dedicated register.
103+
func sigFetchGSafe() *g
104+
105+
func sigFetchG() *g {
106+
if GOARCH == "386" {
107+
return sigFetchGSafe()
108+
}
109+
return getg()
110+
}
111+
112+
// sigtrampgo is called from the exception handler function, sigtramp,
113+
// written in assembly code.
114+
// Return EXCEPTION_CONTINUE_EXECUTION if the exception is handled,
115+
// else return EXCEPTION_CONTINUE_SEARCH.
116+
//
117+
// It is nosplit for the same reason as exceptionhandler.
118+
//
119+
//go:nosplit
120+
func sigtrampgo(ep *exceptionpointers, kind int) int32 {
121+
gp := sigFetchG()
122+
if gp == nil {
123+
return _EXCEPTION_CONTINUE_SEARCH
124+
}
125+
126+
var fn func(info *exceptionrecord, r *context, gp *g) int32
127+
switch kind {
128+
case callbackVEH:
129+
fn = exceptionhandler
130+
case callbackFirstVCH:
131+
fn = firstcontinuehandler
132+
case callbackLastVCH:
133+
fn = lastcontinuehandler
134+
default:
135+
throw("unknown sigtramp callback")
136+
}
137+
138+
// Check if we are running on g0 stack, and if we are,
139+
// call fn directly instead of creating the closure.
140+
// for the systemstack argument.
141+
//
142+
// A closure can't be marked as nosplit, so it might
143+
// call morestack if we are at the g0 stack limit.
144+
// If that happens, the runtime will call abort
145+
// and end up in sigtrampgo again.
146+
// TODO: revisit this workaround if/when closures
147+
// can be compiled as nosplit.
148+
//
149+
// Note that this scenario should only occur on
150+
// TestG0StackOverflow. Any other occurrence should
151+
// be treated as a bug.
152+
var ret int32
153+
if gp != gp.m.g0 {
154+
systemstack(func() {
155+
ret = fn(ep.record, ep.context, gp)
156+
})
157+
} else {
158+
ret = fn(ep.record, ep.context, gp)
159+
}
160+
if ret == _EXCEPTION_CONTINUE_SEARCH {
161+
return ret
162+
}
163+
164+
// Check if we need to set up the control flow guard workaround.
165+
// On Windows, the stack pointer in the context must lie within
166+
// system stack limits when we resume from exception.
167+
// Store the resume SP and PC in alternate registers
168+
// and return to sigresume on the g0 stack.
169+
// sigresume makes no use of the stack at all,
170+
// loading SP from RX and jumping to RY, being RX and RY two scratch registers.
171+
// Note that blindly smashing RX and RY is only safe because we know sigpanic
172+
// will not actually return to the original frame, so the registers
173+
// are effectively dead. But this does mean we can't use the
174+
// same mechanism for async preemption.
175+
if ep.context.ip() == abi.FuncPCABI0(sigresume) {
176+
// sigresume has already been set up by a previous exception.
177+
return ret
178+
}
179+
prepareContextForSigResume(ep.context)
180+
ep.context.set_sp(gp.m.g0.sched.sp)
181+
ep.context.set_ip(abi.FuncPCABI0(sigresume))
182+
return ret
183+
}
184+
91185
// Called by sigtramp from Windows VEH handler.
92186
// Return value signals whether the exception has been handled (EXCEPTION_CONTINUE_EXECUTION)
93187
// or should be made available to other handlers in the chain (EXCEPTION_CONTINUE_SEARCH).
94188
//
95-
// This is the first entry into Go code for exception handling. This
96-
// is nosplit to avoid growing the stack until we've checked for
97-
// _EXCEPTION_BREAKPOINT, which is raised if we overflow the g0 stack,
189+
// This is nosplit to avoid growing the stack until we've checked for
190+
// _EXCEPTION_BREAKPOINT, which is raised by abort() if we overflow the g0 stack.
98191
//
99192
//go:nosplit
100193
func exceptionhandler(info *exceptionrecord, r *context, gp *g) int32 {

src/runtime/sys_windows_386.s

Lines changed: 28 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,20 @@ TEXT runtime·getlasterror(SB),NOSPLIT,$0
7272
MOVL AX, ret+0(FP)
7373
RET
7474

75+
TEXT runtime·sigFetchGSafe<ABIInternal>(SB),NOSPLIT|NOFRAME,$0
76+
get_tls(AX)
77+
CMPL AX, $0
78+
JE 2(PC)
79+
MOVL g(AX), AX
80+
MOVL AX, ret+0(FP)
81+
RET
82+
7583
// Called by Windows as a Vectored Exception Handler (VEH).
76-
// First argument is pointer to struct containing
84+
// AX is pointer to struct containing
7785
// exception record and context pointers.
78-
// Handler function is stored in AX.
79-
// Return 0 for 'not handled', -1 for handled.
86+
// CX is the kind of sigtramp function.
87+
// Return value of sigtrampgo is stored in AX.
8088
TEXT sigtramp<>(SB),NOSPLIT,$0-0
81-
MOVL ptrs+0(FP), CX
8289
SUBL $40, SP
8390

8491
// save callee-saved registers
@@ -87,58 +94,11 @@ TEXT sigtramp<>(SB),NOSPLIT,$0-0
8794
MOVL SI, 20(SP)
8895
MOVL DI, 24(SP)
8996

90-
MOVL AX, SI // save handler address
91-
92-
// find g
93-
get_tls(DX)
94-
CMPL DX, $0
95-
JNE 3(PC)
96-
MOVL $0, AX // continue
97-
JMP done
98-
MOVL g(DX), DX
99-
CMPL DX, $0
100-
JNE 2(PC)
101-
CALL runtime·badsignal2(SB)
102-
103-
// save g in case of stack switch
104-
MOVL DX, 32(SP) // g
105-
MOVL SP, 36(SP)
106-
107-
// do we need to switch to the g0 stack?
108-
MOVL g_m(DX), BX
109-
MOVL m_g0(BX), BX
110-
CMPL DX, BX
111-
JEQ g0
112-
113-
// switch to the g0 stack
114-
get_tls(BP)
115-
MOVL BX, g(BP)
116-
MOVL (g_sched+gobuf_sp)(BX), DI
117-
// make room for sighandler arguments
118-
// and re-save old SP for restoring later.
119-
// (note that the 36(DI) here must match the 36(SP) above.)
120-
SUBL $40, DI
121-
MOVL SP, 36(DI)
122-
MOVL DI, SP
123-
124-
g0:
125-
MOVL 0(CX), BX // ExceptionRecord*
126-
MOVL 4(CX), CX // Context*
127-
MOVL BX, 0(SP)
97+
MOVL AX, 0(SP)
12898
MOVL CX, 4(SP)
129-
MOVL DX, 8(SP)
130-
CALL SI // call handler
131-
// AX is set to report result back to Windows
132-
MOVL 12(SP), AX
133-
134-
// switch back to original stack and g
135-
// no-op if we never left.
136-
MOVL 36(SP), SP
137-
MOVL 32(SP), DX // note: different SP
138-
get_tls(BP)
139-
MOVL DX, g(BP)
140-
141-
done:
99+
CALL runtime·sigtrampgo(SB)
100+
MOVL 8(SP), AX
101+
142102
// restore callee-saved registers
143103
MOVL 24(SP), DI
144104
MOVL 20(SP), SI
@@ -150,16 +110,27 @@ done:
150110
BYTE $0xC2; WORD $4
151111
RET // unreached; make assembler happy
152112

113+
// Trampoline to resume execution from exception handler.
114+
// This is part of the control flow guard workaround.
115+
// It switches stacks and jumps to the continuation address.
116+
// DX and CX are set above at the end of sigtrampgo
117+
// in the context that starts executing at sigresume.
118+
TEXT runtime·sigresume(SB),NOSPLIT|NOFRAME,$0
119+
MOVL DX, SP
120+
JMP CX
121+
153122
TEXT runtime·exceptiontramp(SB),NOSPLIT,$0
154-
MOVL $runtime·exceptionhandler(SB), AX
123+
MOVL argframe+0(FP), AX
124+
MOVL $const_callbackVEH, CX
155125
JMP sigtramp<>(SB)
156126

157127
TEXT runtime·firstcontinuetramp(SB),NOSPLIT,$0-0
158128
// is never called
159129
INT $3
160130

161131
TEXT runtime·lastcontinuetramp(SB),NOSPLIT,$0-0
162-
MOVL $runtime·lastcontinuehandler(SB), AX
132+
MOVL argframe+0(FP), AX
133+
MOVL $const_callbackLastVCH, CX
163134
JMP sigtramp<>(SB)
164135

165136
GLOBL runtime·cbctxts(SB), NOPTR, $4

0 commit comments

Comments
 (0)