-
Notifications
You must be signed in to change notification settings - Fork 17.4k
/
signal_windows.go
482 lines (433 loc) · 14.5 KB
/
signal_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
// Copyright 2011 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 runtime
import (
"internal/abi"
"internal/runtime/sys"
"unsafe"
)
const (
_SEM_FAILCRITICALERRORS = 0x0001
_SEM_NOGPFAULTERRORBOX = 0x0002
_SEM_NOOPENFILEERRORBOX = 0x8000
_WER_FAULT_REPORTING_NO_UI = 0x0020
)
func preventErrorDialogs() {
errormode := stdcall0(_GetErrorMode)
stdcall1(_SetErrorMode, errormode|_SEM_FAILCRITICALERRORS|_SEM_NOGPFAULTERRORBOX|_SEM_NOOPENFILEERRORBOX)
// Disable WER fault reporting UI.
// Do this even if WER is disabled as a whole,
// as WER might be enabled later with setTraceback("wer")
// and we still want the fault reporting UI to be disabled if this happens.
var werflags uintptr
stdcall2(_WerGetFlags, currentProcess, uintptr(unsafe.Pointer(&werflags)))
stdcall1(_WerSetFlags, werflags|_WER_FAULT_REPORTING_NO_UI)
}
// enableWER re-enables Windows error reporting without fault reporting UI.
func enableWER() {
// re-enable Windows Error Reporting
errormode := stdcall0(_GetErrorMode)
if errormode&_SEM_NOGPFAULTERRORBOX != 0 {
stdcall1(_SetErrorMode, errormode^_SEM_NOGPFAULTERRORBOX)
}
}
// in sys_windows_386.s, sys_windows_amd64.s, sys_windows_arm.s, and sys_windows_arm64.s
func exceptiontramp()
func firstcontinuetramp()
func lastcontinuetramp()
func sehtramp()
func sigresume()
func initExceptionHandler() {
stdcall2(_AddVectoredExceptionHandler, 1, abi.FuncPCABI0(exceptiontramp))
if GOARCH == "386" {
// use SetUnhandledExceptionFilter for windows-386.
// note: SetUnhandledExceptionFilter handler won't be called, if debugging.
stdcall1(_SetUnhandledExceptionFilter, abi.FuncPCABI0(lastcontinuetramp))
} else {
stdcall2(_AddVectoredContinueHandler, 1, abi.FuncPCABI0(firstcontinuetramp))
stdcall2(_AddVectoredContinueHandler, 0, abi.FuncPCABI0(lastcontinuetramp))
}
}
// isAbort returns true, if context r describes exception raised
// by calling runtime.abort function.
//
//go:nosplit
func isAbort(r *context) bool {
pc := r.ip()
if GOARCH == "386" || GOARCH == "amd64" || GOARCH == "arm" {
// In the case of an abort, the exception IP is one byte after
// the INT3 (this differs from UNIX OSes). Note that on ARM,
// this means that the exception IP is no longer aligned.
pc--
}
return isAbortPC(pc)
}
// isgoexception reports whether this exception should be translated
// into a Go panic or throw.
//
// It is nosplit to avoid growing the stack in case we're aborting
// because of a stack overflow.
//
//go:nosplit
func isgoexception(info *exceptionrecord, r *context) bool {
// Only handle exception if executing instructions in Go binary
// (not Windows library code).
// TODO(mwhudson): needs to loop to support shared libs
if r.ip() < firstmoduledata.text || firstmoduledata.etext < r.ip() {
return false
}
// Go will only handle some exceptions.
switch info.exceptioncode {
default:
return false
case _EXCEPTION_ACCESS_VIOLATION:
case _EXCEPTION_IN_PAGE_ERROR:
case _EXCEPTION_INT_DIVIDE_BY_ZERO:
case _EXCEPTION_INT_OVERFLOW:
case _EXCEPTION_FLT_DENORMAL_OPERAND:
case _EXCEPTION_FLT_DIVIDE_BY_ZERO:
case _EXCEPTION_FLT_INEXACT_RESULT:
case _EXCEPTION_FLT_OVERFLOW:
case _EXCEPTION_FLT_UNDERFLOW:
case _EXCEPTION_BREAKPOINT:
case _EXCEPTION_ILLEGAL_INSTRUCTION: // breakpoint arrives this way on arm64
}
return true
}
const (
callbackVEH = iota
callbackFirstVCH
callbackLastVCH
)
// sigFetchGSafe is like getg() but without panicking
// when TLS is not set.
// Only implemented on windows/386, which is the only
// arch that loads TLS when calling getg(). Others
// use a dedicated register.
func sigFetchGSafe() *g
func sigFetchG() *g {
if GOARCH == "386" {
return sigFetchGSafe()
}
return getg()
}
// sigtrampgo is called from the exception handler function, sigtramp,
// written in assembly code.
// Return EXCEPTION_CONTINUE_EXECUTION if the exception is handled,
// else return EXCEPTION_CONTINUE_SEARCH.
//
// It is nosplit for the same reason as exceptionhandler.
//
//go:nosplit
func sigtrampgo(ep *exceptionpointers, kind int) int32 {
gp := sigFetchG()
if gp == nil {
return _EXCEPTION_CONTINUE_SEARCH
}
var fn func(info *exceptionrecord, r *context, gp *g) int32
switch kind {
case callbackVEH:
fn = exceptionhandler
case callbackFirstVCH:
fn = firstcontinuehandler
case callbackLastVCH:
fn = lastcontinuehandler
default:
throw("unknown sigtramp callback")
}
// Check if we are running on g0 stack, and if we are,
// call fn directly instead of creating the closure.
// for the systemstack argument.
//
// A closure can't be marked as nosplit, so it might
// call morestack if we are at the g0 stack limit.
// If that happens, the runtime will call abort
// and end up in sigtrampgo again.
// TODO: revisit this workaround if/when closures
// can be compiled as nosplit.
//
// Note that this scenario should only occur on
// TestG0StackOverflow. Any other occurrence should
// be treated as a bug.
var ret int32
if gp != gp.m.g0 {
systemstack(func() {
ret = fn(ep.record, ep.context, gp)
})
} else {
ret = fn(ep.record, ep.context, gp)
}
if ret == _EXCEPTION_CONTINUE_SEARCH {
return ret
}
// Check if we need to set up the control flow guard workaround.
// On Windows, the stack pointer in the context must lie within
// system stack limits when we resume from exception.
// Store the resume SP and PC in alternate registers
// and return to sigresume on the g0 stack.
// sigresume makes no use of the stack at all,
// loading SP from RX and jumping to RY, being RX and RY two scratch registers.
// Note that blindly smashing RX and RY is only safe because we know sigpanic
// will not actually return to the original frame, so the registers
// are effectively dead. But this does mean we can't use the
// same mechanism for async preemption.
if ep.context.ip() == abi.FuncPCABI0(sigresume) {
// sigresume has already been set up by a previous exception.
return ret
}
prepareContextForSigResume(ep.context)
ep.context.set_sp(gp.m.g0.sched.sp)
ep.context.set_ip(abi.FuncPCABI0(sigresume))
return ret
}
// Called by sigtramp from Windows VEH handler.
// Return value signals whether the exception has been handled (EXCEPTION_CONTINUE_EXECUTION)
// or should be made available to other handlers in the chain (EXCEPTION_CONTINUE_SEARCH).
//
// This is nosplit to avoid growing the stack until we've checked for
// _EXCEPTION_BREAKPOINT, which is raised by abort() if we overflow the g0 stack.
//
//go:nosplit
func exceptionhandler(info *exceptionrecord, r *context, gp *g) int32 {
if !isgoexception(info, r) {
return _EXCEPTION_CONTINUE_SEARCH
}
if gp.throwsplit || isAbort(r) {
// We can't safely sigpanic because it may grow the stack.
// Or this is a call to abort.
// Don't go through any more of the Windows handler chain.
// Crash now.
winthrow(info, r, gp)
}
// After this point, it is safe to grow the stack.
// Make it look like a call to the signal func.
// Have to pass arguments out of band since
// augmenting the stack frame would break
// the unwinding code.
gp.sig = info.exceptioncode
gp.sigcode0 = info.exceptioninformation[0]
gp.sigcode1 = info.exceptioninformation[1]
gp.sigpc = r.ip()
// Only push runtime·sigpanic if r.ip() != 0.
// If r.ip() == 0, probably panicked because of a
// call to a nil func. Not pushing that onto sp will
// make the trace look like a call to runtime·sigpanic instead.
// (Otherwise the trace will end at runtime·sigpanic and we
// won't get to see who faulted.)
// Also don't push a sigpanic frame if the faulting PC
// is the entry of asyncPreempt. In this case, we suspended
// the thread right between the fault and the exception handler
// starting to run, and we have pushed an asyncPreempt call.
// The exception is not from asyncPreempt, so not to push a
// sigpanic call to make it look like that. Instead, just
// overwrite the PC. (See issue #35773)
if r.ip() != 0 && r.ip() != abi.FuncPCABI0(asyncPreempt) {
sp := unsafe.Pointer(r.sp())
delta := uintptr(sys.StackAlign)
sp = add(sp, -delta)
r.set_sp(uintptr(sp))
if usesLR {
*((*uintptr)(sp)) = r.lr()
r.set_lr(r.ip())
} else {
*((*uintptr)(sp)) = r.ip()
}
}
r.set_ip(abi.FuncPCABI0(sigpanic0))
return _EXCEPTION_CONTINUE_EXECUTION
}
// sehhandler is reached as part of the SEH chain.
//
// It is nosplit for the same reason as exceptionhandler.
//
//go:nosplit
func sehhandler(_ *exceptionrecord, _ uint64, _ *context, dctxt *_DISPATCHER_CONTEXT) int32 {
g0 := getg()
if g0 == nil || g0.m.curg == nil {
// No g available, nothing to do here.
return _EXCEPTION_CONTINUE_SEARCH_SEH
}
// The Windows SEH machinery will unwind the stack until it finds
// a frame with a handler for the exception or until the frame is
// outside the stack boundaries, in which case it will call the
// UnhandledExceptionFilter. Unfortunately, it doesn't know about
// the goroutine stack, so it will stop unwinding when it reaches the
// first frame not running in g0. As a result, neither non-Go exceptions
// handlers higher up the stack nor UnhandledExceptionFilter will be called.
//
// To work around this, manually unwind the stack until the top of the goroutine
// stack is reached, and then pass the control back to Windows.
gp := g0.m.curg
ctxt := dctxt.ctx()
var base, sp uintptr
for {
entry := stdcall3(_RtlLookupFunctionEntry, ctxt.ip(), uintptr(unsafe.Pointer(&base)), 0)
if entry == 0 {
break
}
stdcall8(_RtlVirtualUnwind, 0, base, ctxt.ip(), entry, uintptr(unsafe.Pointer(ctxt)), 0, uintptr(unsafe.Pointer(&sp)), 0)
if sp < gp.stack.lo || gp.stack.hi <= sp {
break
}
}
return _EXCEPTION_CONTINUE_SEARCH_SEH
}
// It seems Windows searches ContinueHandler's list even
// if ExceptionHandler returns EXCEPTION_CONTINUE_EXECUTION.
// firstcontinuehandler will stop that search,
// if exceptionhandler did the same earlier.
//
// It is nosplit for the same reason as exceptionhandler.
//
//go:nosplit
func firstcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
if !isgoexception(info, r) {
return _EXCEPTION_CONTINUE_SEARCH
}
return _EXCEPTION_CONTINUE_EXECUTION
}
// lastcontinuehandler is reached, because runtime cannot handle
// current exception. lastcontinuehandler will print crash info and exit.
//
// It is nosplit for the same reason as exceptionhandler.
//
//go:nosplit
func lastcontinuehandler(info *exceptionrecord, r *context, gp *g) int32 {
if islibrary || isarchive {
// Go DLL/archive has been loaded in a non-go program.
// If the exception does not originate from go, the go runtime
// should not take responsibility of crashing the process.
return _EXCEPTION_CONTINUE_SEARCH
}
// VEH is called before SEH, but arm64 MSVC DLLs use SEH to trap
// illegal instructions during runtime initialization to determine
// CPU features, so if we make it to the last handler and we're
// arm64 and it's an illegal instruction and this is coming from
// non-Go code, then assume it's this runtime probing happen, and
// pass that onward to SEH.
if GOARCH == "arm64" && info.exceptioncode == _EXCEPTION_ILLEGAL_INSTRUCTION &&
(r.ip() < firstmoduledata.text || firstmoduledata.etext < r.ip()) {
return _EXCEPTION_CONTINUE_SEARCH
}
winthrow(info, r, gp)
return 0 // not reached
}
// Always called on g0. gp is the G where the exception occurred.
//
//go:nosplit
func winthrow(info *exceptionrecord, r *context, gp *g) {
g0 := getg()
if panicking.Load() != 0 { // traceback already printed
exit(2)
}
panicking.Store(1)
// In case we're handling a g0 stack overflow, blow away the
// g0 stack bounds so we have room to print the traceback. If
// this somehow overflows the stack, the OS will trap it.
g0.stack.lo = 0
g0.stackguard0 = g0.stack.lo + stackGuard
g0.stackguard1 = g0.stackguard0
print("Exception ", hex(info.exceptioncode), " ", hex(info.exceptioninformation[0]), " ", hex(info.exceptioninformation[1]), " ", hex(r.ip()), "\n")
print("PC=", hex(r.ip()), "\n")
if g0.m.incgo && gp == g0.m.g0 && g0.m.curg != nil {
if iscgo {
print("signal arrived during external code execution\n")
}
gp = g0.m.curg
}
print("\n")
g0.m.throwing = throwTypeRuntime
g0.m.caughtsig.set(gp)
level, _, docrash := gotraceback()
if level > 0 {
tracebacktrap(r.ip(), r.sp(), r.lr(), gp)
tracebackothers(gp)
dumpregs(r)
}
if docrash {
dieFromException(info, r)
}
exit(2)
}
func sigpanic() {
gp := getg()
if !canpanic() {
throw("unexpected signal during runtime execution")
}
switch gp.sig {
case _EXCEPTION_ACCESS_VIOLATION, _EXCEPTION_IN_PAGE_ERROR:
if gp.sigcode1 < 0x1000 {
panicmem()
}
if gp.paniconfault {
panicmemAddr(gp.sigcode1)
}
if inUserArenaChunk(gp.sigcode1) {
// We could check that the arena chunk is explicitly set to fault,
// but the fact that we faulted on accessing it is enough to prove
// that it is.
print("accessed data from freed user arena ", hex(gp.sigcode1), "\n")
} else {
print("unexpected fault address ", hex(gp.sigcode1), "\n")
}
throw("fault")
case _EXCEPTION_INT_DIVIDE_BY_ZERO:
panicdivide()
case _EXCEPTION_INT_OVERFLOW:
panicoverflow()
case _EXCEPTION_FLT_DENORMAL_OPERAND,
_EXCEPTION_FLT_DIVIDE_BY_ZERO,
_EXCEPTION_FLT_INEXACT_RESULT,
_EXCEPTION_FLT_OVERFLOW,
_EXCEPTION_FLT_UNDERFLOW:
panicfloat()
}
throw("fault")
}
// Following are not implemented.
func initsig(preinit bool) {
}
func sigenable(sig uint32) {
}
func sigdisable(sig uint32) {
}
func sigignore(sig uint32) {
}
func signame(sig uint32) string {
return ""
}
//go:nosplit
func crash() {
dieFromException(nil, nil)
}
// dieFromException raises an exception that bypasses all exception handlers.
// This provides the expected exit status for the shell.
//
//go:nosplit
func dieFromException(info *exceptionrecord, r *context) {
if info == nil {
gp := getg()
if gp.sig != 0 {
// Try to reconstruct an exception record from
// the exception information stored in gp.
info = &exceptionrecord{
exceptionaddress: gp.sigpc,
exceptioncode: gp.sig,
numberparameters: 2,
}
info.exceptioninformation[0] = gp.sigcode0
info.exceptioninformation[1] = gp.sigcode1
} else {
// By default, a failing Go application exits with exit code 2.
// Use this value when gp does not contain exception info.
info = &exceptionrecord{
exceptioncode: 2,
}
}
}
const FAIL_FAST_GENERATE_EXCEPTION_ADDRESS = 0x1
stdcall3(_RaiseFailFastException, uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(r)), FAIL_FAST_GENERATE_EXCEPTION_ADDRESS)
}
// gsignalStack is unused on Windows.
type gsignalStack struct{}