runtime: add support for panic/recover in Plan 9 note handler
This change also resolves some issues with note handling: we now make
sure that there is enough room at the bottom of every goroutine to
execute the note handler, and the `exitstatus' is no longer a global
entity, which resolves some race conditions.
R=rminnich, npe, rsc, ality
CC=golang-dev
https://golang.org/cl/6569068
diff --git a/src/cmd/dist/buildruntime.c b/src/cmd/dist/buildruntime.c
index 52a69be..6c07e2a 100644
--- a/src/cmd/dist/buildruntime.c
+++ b/src/cmd/dist/buildruntime.c
@@ -105,9 +105,15 @@
"#define m(r) 4(r)\n"
},
{"386", "plan9",
+ "// Plan 9 does not have per-process segment descriptors with\n"
+ "// which to do thread-local storage. Instead, we will use a\n"
+ "// fixed offset from the per-process TOS struct address for\n"
+ "// the local storage. Since the process ID is contained in the\n"
+ "// TOS struct, we specify an offset for that here as well.\n"
"#define get_tls(r) MOVL _tos(SB), r \n"
"#define g(r) -8(r)\n"
"#define m(r) -4(r)\n"
+ "#define procid(r) 48(r)\n"
},
{"386", "linux",
"// On Linux systems, what we call 0(GS) and 4(GS) for g and m\n"
diff --git a/src/pkg/runtime/defs_plan9_386.h b/src/pkg/runtime/defs_plan9_386.h
index 3874ad2..bde299d 100644
--- a/src/pkg/runtime/defs_plan9_386.h
+++ b/src/pkg/runtime/defs_plan9_386.h
@@ -1,3 +1,29 @@
-// nothing to see here
-#define tos_pid 48
#define PAGESIZE 0x1000
+
+typedef struct Ureg Ureg;
+
+struct Ureg
+{
+ uint32 di; /* general registers */
+ uint32 si; /* ... */
+ uint32 bp; /* ... */
+ uint32 nsp;
+ uint32 bx; /* ... */
+ uint32 dx; /* ... */
+ uint32 cx; /* ... */
+ uint32 ax; /* ... */
+ uint32 gs; /* data segments */
+ uint32 fs; /* ... */
+ uint32 es; /* ... */
+ uint32 ds; /* ... */
+ uint32 trap; /* trap type */
+ uint32 ecode; /* error code (or zero) */
+ uint32 pc; /* pc */
+ uint32 cs; /* old context */
+ uint32 flags; /* old flags */
+ union {
+ uint32 usp;
+ uint32 sp;
+ };
+ uint32 ss; /* old stack segment */
+};
diff --git a/src/pkg/runtime/defs_plan9_amd64.h b/src/pkg/runtime/defs_plan9_amd64.h
index d5d19f8..d8fec67 100644
--- a/src/pkg/runtime/defs_plan9_amd64.h
+++ b/src/pkg/runtime/defs_plan9_amd64.h
@@ -1,2 +1,34 @@
-// nothing to see here
#define PAGESIZE 0x200000ULL
+
+typedef struct Ureg Ureg;
+
+struct Ureg {
+ uint64 ax;
+ uint64 bx;
+ uint64 cx;
+ uint64 dx;
+ uint64 si;
+ uint64 di;
+ uint64 bp;
+ uint64 r8;
+ uint64 r9;
+ uint64 r10;
+ uint64 r11;
+ uint64 r12;
+ uint64 r13;
+ uint64 r14;
+ uint64 r15;
+
+ uint16 ds;
+ uint16 es;
+ uint16 fs;
+ uint16 gs;
+
+ uint64 type;
+ uint64 error; /* error code (or zero) */
+ uint64 ip; /* pc */
+ uint64 cs; /* old context */
+ uint64 flags; /* old flags */
+ uint64 sp; /* sp */
+ uint64 ss; /* old stack segment */
+};
diff --git a/src/pkg/runtime/os_plan9.h b/src/pkg/runtime/os_plan9.h
index b1dc815..c2cdf5b 100644
--- a/src/pkg/runtime/os_plan9.h
+++ b/src/pkg/runtime/os_plan9.h
@@ -16,9 +16,12 @@
int32 runtime·plan9_semacquire(uint32 *addr, int32 block);
int32 runtime·plan9_tsemacquire(uint32 *addr, int32 ms);
int32 runtime·plan9_semrelease(uint32 *addr, int32 count);
-int32 runtime·notify(void (*fn)(void*, byte*));
+int32 runtime·notify(void (*fn)(void*, int8*));
int32 runtime·noted(int32);
-void runtime·gonote(void*, byte*);
+void runtime·sigtramp(void*, int8*);
+int32 runtime·sighandler(void*, int8*, G*);
+void runtime·sigpanic(void);
+void runtime·goexitsall(int8*);
void runtime·setfpmasks(void);
/* open */
@@ -79,4 +82,5 @@
/* top of stack is here */
};
-#define NSIG 1
+#define NSIG 5 /* number of signals in runtime·SigTab array */
+#define ERRMAX 128 /* max length of note string */
diff --git a/src/pkg/runtime/runtime.h b/src/pkg/runtime/runtime.h
index 22aead7..187a827 100644
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -296,6 +296,9 @@
#ifdef GOOS_windows
void* thread; // thread handle
#endif
+#ifdef GOOS_plan9
+ int8* notesig;
+#endif
SEH* seh;
uintptr end[];
};
diff --git a/src/pkg/runtime/signal_plan9_386.c b/src/pkg/runtime/signal_plan9_386.c
index d266885..17bc117 100644
--- a/src/pkg/runtime/signal_plan9_386.c
+++ b/src/pkg/runtime/signal_plan9_386.c
@@ -3,6 +3,107 @@
// license that can be found in the LICENSE file.
#include "runtime.h"
+#include "defs_GOOS_GOARCH.h"
+#include "os_GOOS.h"
+#include "signals_GOOS.h"
+
+void
+runtime·dumpregs(Ureg *u)
+{
+ runtime·printf("ax %X\n", u->ax);
+ runtime·printf("bx %X\n", u->bx);
+ runtime·printf("cx %X\n", u->cx);
+ runtime·printf("dx %X\n", u->dx);
+ runtime·printf("di %X\n", u->di);
+ runtime·printf("si %X\n", u->si);
+ runtime·printf("bp %X\n", u->bp);
+ runtime·printf("sp %X\n", u->sp);
+ runtime·printf("pc %X\n", u->pc);
+ runtime·printf("flags %X\n", u->flags);
+ runtime·printf("cs %X\n", u->cs);
+ runtime·printf("fs %X\n", u->fs);
+ runtime·printf("gs %X\n", u->gs);
+}
+
+int32
+runtime·sighandler(void *v, int8 *s, G *gp)
+{
+ Ureg *ureg;
+ uintptr *sp;
+ SigTab *sig, *nsig;
+ int32 len, i;
+
+ if(!s)
+ return NCONT;
+
+ len = runtime·findnull((byte*)s);
+ if(len <= 4 || runtime·mcmp((byte*)s, (byte*)"sys:", 4) != 0)
+ return NDFLT;
+
+ nsig = nil;
+ sig = runtime·sigtab;
+ for(i=0; i < NSIG; i++) {
+ if(runtime·strstr((byte*)s, (byte*)sig->name)) {
+ nsig = sig;
+ break;
+ }
+ sig++;
+ }
+
+ if(nsig == nil)
+ return NDFLT;
+
+ ureg = v;
+ if(nsig->flags & SigPanic) {
+ if(gp == nil || m->notesig == 0)
+ goto Throw;
+
+ // Save error string from sigtramp's stack,
+ // into gsignal->sigcode0, so we can reliably
+ // access it from the panic routines.
+ if(len > ERRMAX)
+ len = ERRMAX;
+ runtime·memmove((void*)m->notesig, (void*)s, len);
+
+ gp->sig = i;
+ gp->sigpc = ureg->pc;
+
+ // Only push runtime·sigpanic if ureg->pc != 0.
+ // If ureg->pc == 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.)
+ if(ureg->pc != 0) {
+ sp = (uintptr*)ureg->sp;
+ *--sp = ureg->pc;
+ ureg->sp = (uint32)sp;
+ }
+ ureg->pc = (uintptr)runtime·sigpanic;
+ return NCONT;
+ }
+
+ if(!(nsig->flags & SigThrow))
+ return NDFLT;
+
+Throw:
+ runtime·startpanic();
+
+ runtime·printf("%s\n", s);
+ runtime·printf("PC=%X\n", ureg->pc);
+ runtime·printf("\n");
+
+ if(runtime·gotraceback()) {
+ runtime·traceback((void*)ureg->pc, (void*)ureg->sp, 0, gp);
+ runtime·tracebackothers(gp);
+ runtime·dumpregs(ureg);
+ }
+ runtime·goexitsall("");
+ runtime·exits(s);
+
+ return 0;
+}
+
void
runtime·sigenable(uint32 sig)
diff --git a/src/pkg/runtime/signal_plan9_amd64.c b/src/pkg/runtime/signal_plan9_amd64.c
index d266885..e4f946a 100644
--- a/src/pkg/runtime/signal_plan9_amd64.c
+++ b/src/pkg/runtime/signal_plan9_amd64.c
@@ -3,6 +3,114 @@
// license that can be found in the LICENSE file.
#include "runtime.h"
+#include "defs_GOOS_GOARCH.h"
+#include "os_GOOS.h"
+#include "signals_GOOS.h"
+
+void
+runtime·dumpregs(Ureg *u)
+{
+ runtime·printf("ax %X\n", u->ax);
+ runtime·printf("bx %X\n", u->bx);
+ runtime·printf("cx %X\n", u->cx);
+ runtime·printf("dx %X\n", u->dx);
+ runtime·printf("di %X\n", u->di);
+ runtime·printf("si %X\n", u->si);
+ runtime·printf("bp %X\n", u->bp);
+ runtime·printf("sp %X\n", u->sp);
+ runtime·printf("r8 %X\n", u->r8);
+ runtime·printf("r9 %X\n", u->r9);
+ runtime·printf("r10 %X\n", u->r10);
+ runtime·printf("r11 %X\n", u->r11);
+ runtime·printf("r12 %X\n", u->r12);
+ runtime·printf("r13 %X\n", u->r13);
+ runtime·printf("r14 %X\n", u->r14);
+ runtime·printf("r15 %X\n", u->r15);
+ runtime·printf("ip %X\n", u->ip);
+ runtime·printf("flags %X\n", u->flags);
+ runtime·printf("cs %X\n", (uint64)u->cs);
+ runtime·printf("fs %X\n", (uint64)u->fs);
+ runtime·printf("gs %X\n", (uint64)u->gs);
+}
+
+int32
+runtime·sighandler(void *v, int8 *s, G *gp)
+{
+ Ureg *ureg;
+ uintptr *sp;
+ SigTab *sig, *nsig;
+ int32 len, i;
+
+ if(!s)
+ return NCONT;
+
+ len = runtime·findnull((byte*)s);
+ if(len <= 4 || runtime·mcmp((byte*)s, (byte*)"sys:", 4) != 0)
+ return NDFLT;
+
+ nsig = nil;
+ sig = runtime·sigtab;
+ for(i=0; i < NSIG; i++) {
+ if(runtime·strstr((byte*)s, (byte*)sig->name)) {
+ nsig = sig;
+ break;
+ }
+ sig++;
+ }
+
+ if(nsig == nil)
+ return NDFLT;
+
+ ureg = v;
+ if(nsig->flags & SigPanic) {
+ if(gp == nil || m->notesig == 0)
+ goto Throw;
+
+ // Save error string from sigtramp's stack,
+ // into gsignal->sigcode0, so we can reliably
+ // access it from the panic routines.
+ if(len > ERRMAX)
+ len = ERRMAX;
+ runtime·memmove((void*)m->notesig, (void*)s, len);
+
+ gp->sig = i;
+ gp->sigpc = ureg->ip;
+
+ // Only push runtime·sigpanic if ureg->ip != 0.
+ // If ureg->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.)
+ if(ureg->ip != 0) {
+ sp = (uintptr*)ureg->sp;
+ *--sp = ureg->ip;
+ ureg->sp = (uint64)sp;
+ }
+ ureg->ip = (uintptr)runtime·sigpanic;
+ return NCONT;
+ }
+
+ if(!(nsig->flags & SigThrow))
+ return NDFLT;
+
+Throw:
+ runtime·startpanic();
+
+ runtime·printf("%s\n", s);
+ runtime·printf("PC=%X\n", ureg->ip);
+ runtime·printf("\n");
+
+ if(runtime·gotraceback()) {
+ runtime·traceback((void*)ureg->ip, (void*)ureg->sp, 0, gp);
+ runtime·tracebackothers(gp);
+ runtime·dumpregs(ureg);
+ }
+ runtime·goexitsall("");
+ runtime·exits(s);
+
+ return 0;
+}
void
runtime·sigenable(uint32 sig)
diff --git a/src/pkg/runtime/signals_plan9.h b/src/pkg/runtime/signals_plan9.h
index 5df7576..0f1165e 100644
--- a/src/pkg/runtime/signals_plan9.h
+++ b/src/pkg/runtime/signals_plan9.h
@@ -1 +1,24 @@
-// nothing to see here
+#define N SigNotify
+#define T SigThrow
+#define P SigPanic
+
+SigTab runtime·sigtab[] = {
+ P, "sys: fp:",
+
+ // Go libraries expect to be able
+ // to recover from memory
+ // read/write errors, so we flag
+ // those as panics. All other traps
+ // are generally more serious and
+ // should immediately throw an
+ // exception.
+ P, "sys: trap: fault read addr",
+ P, "sys: trap: fault write addr",
+ T, "sys: trap:",
+
+ N, "sys: bad sys call",
+};
+
+#undef N
+#undef T
+#undef P
diff --git a/src/pkg/runtime/stack.h b/src/pkg/runtime/stack.h
index d42385d..06b0c56 100644
--- a/src/pkg/runtime/stack.h
+++ b/src/pkg/runtime/stack.h
@@ -55,13 +55,19 @@
enum {
// StackSystem is a number of additional bytes to add
// to each stack below the usual guard area for OS-specific
- // purposes like signal handling. Used on Windows because
- // it does not use a separate stack.
+ // purposes like signal handling. Used on Windows and on
+ // Plan 9 because they do not use a separate stack.
#ifdef GOOS_windows
StackSystem = 512 * sizeof(uintptr),
#else
+#ifdef GOOS_plan9
+ // The size of the note handler frame varies among architectures,
+ // but 512 bytes should be enough for every implementation.
+ StackSystem = 512,
+#else
StackSystem = 0,
-#endif
+#endif // Plan 9
+#endif // Windows
// The amount of extra stack to allocate beyond the size
// needed for the single frame that triggered the split.
diff --git a/src/pkg/runtime/sys_plan9_386.s b/src/pkg/runtime/sys_plan9_386.s
index f8034d4..3385b08 100644
--- a/src/pkg/runtime/sys_plan9_386.s
+++ b/src/pkg/runtime/sys_plan9_386.s
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include "defs_GOOS_GOARCH.h"
#include "zasm_GOOS_GOARCH.h"
// setldt(int entry, int address, int limit)
@@ -102,9 +101,8 @@
MOVL DX, g(AX)
MOVL BX, m(AX)
- // Initialize AX from _tos->pid
- MOVL _tos(SB), AX
- MOVL tos_pid(AX), AX
+ // Initialize AX from TOS struct.
+ MOVL procid(AX), AX
MOVL AX, m_procid(BX) // save pid as m->procid
CALL runtime·stackcheck(SB) // smashes AX, CX
@@ -121,6 +119,54 @@
CALL runtime·exit(SB)
RET
+// void sigtramp(void *ureg, int8 *note)
+TEXT runtime·sigtramp(SB),7,$0
+ get_tls(AX)
+
+ // check that m exists
+ MOVL m(AX), BX
+ CMPL BX, $0
+ JNE 3(PC)
+ CALL runtime·badsignal(SB) // will exit
+ RET
+
+ // save args
+ MOVL ureg+4(SP), CX
+ MOVL note+8(SP), DX
+
+ // change stack
+ MOVL m_gsignal(BX), BP
+ MOVL g_stackbase(BP), BP
+ MOVL BP, SP
+
+ // make room for args and g
+ SUBL $16, SP
+
+ // save g
+ MOVL g(AX), BP
+ MOVL BP, 12(SP)
+
+ // g = m->gsignal
+ MOVL m_gsignal(BX), DI
+ MOVL DI, g(AX)
+
+ // load args and call sighandler
+ MOVL CX, 0(SP)
+ MOVL DX, 4(SP)
+ MOVL BP, 8(SP)
+
+ CALL runtime·sighandler(SB)
+
+ // restore g
+ get_tls(BX)
+ MOVL 12(SP), BP
+ MOVL BP, g(BX)
+
+ // call noted(AX)
+ MOVL AX, 0(SP)
+ CALL runtime·noted(SB)
+ RET
+
// Only used by the 64-bit runtime.
TEXT runtime·setfpmasks(SB),7,$0
RET
diff --git a/src/pkg/runtime/sys_plan9_amd64.s b/src/pkg/runtime/sys_plan9_amd64.s
index b5e8c59..be164a0 100644
--- a/src/pkg/runtime/sys_plan9_amd64.s
+++ b/src/pkg/runtime/sys_plan9_amd64.s
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include "defs_GOOS_GOARCH.h"
#include "zasm_GOOS_GOARCH.h"
// setldt(int entry, int address, int limit)
@@ -104,7 +103,7 @@
MOVQ $38, BP
SYSCALL
RET
-
+
TEXT runtime·rfork(SB),7,$0
MOVQ $0x8000, AX
MOVQ $19, BP // rfork
@@ -146,6 +145,53 @@
TEXT runtime·settls(SB),7,$0
RET
+// void sigtramp(void *ureg, int8 *note)
+TEXT runtime·sigtramp(SB),7,$0
+ get_tls(AX)
+
+ // check that m exists
+ MOVQ m(AX), BX
+ CMPQ BX, $0
+ JNE 3(PC)
+ CALL runtime·badsignal(SB) // will exit
+ RET
+
+ // save args
+ MOVQ ureg+8(SP), CX
+ MOVQ note+16(SP), DX
+
+ // change stack
+ MOVQ m_gsignal(BX), R10
+ MOVQ g_stackbase(R10), BP
+ MOVQ BP, SP
+
+ // make room for args and g
+ SUBQ $32, SP
+
+ // save g
+ MOVQ g(AX), BP
+ MOVQ BP, 24(SP)
+
+ // g = m->gsignal
+ MOVQ R10, g(AX)
+
+ // load args and call sighandler
+ MOVQ CX, 0(SP)
+ MOVQ DX, 8(SP)
+ MOVQ BP, 16(SP)
+
+ CALL runtime·sighandler(SB)
+
+ // restore g
+ get_tls(BX)
+ MOVQ 24(SP), R10
+ MOVQ R10, g(BX)
+
+ // call noted(AX)
+ MOVQ AX, 0(SP)
+ CALL runtime·noted(SB)
+ RET
+
TEXT runtime·setfpmasks(SB),7,$8
STMXCSR 0(SP)
MOVL 0(SP), AX
diff --git a/src/pkg/runtime/thread_plan9.c b/src/pkg/runtime/thread_plan9.c
index b7a7de7..932135d 100644
--- a/src/pkg/runtime/thread_plan9.c
+++ b/src/pkg/runtime/thread_plan9.c
@@ -7,13 +7,17 @@
#include "arch_GOARCH.h"
int8 *goos = "plan9";
-int8 *runtime·exitstatus;
+extern SigTab runtime·sigtab[];
int32 runtime·postnote(int32, int8*);
void
runtime·minit(void)
{
+ // Initialize stack and goroutine for note handling.
+ m->gsignal = runtime·malg(32*1024);
+ m->notesig = (int8*)runtime·malloc(ERRMAX*sizeof(int8));
+
// Mask all SSE floating-point exceptions
// when running on the 64-bit kernel.
runtime·setfpmasks();
@@ -65,7 +69,7 @@
{
runtime·ncpu = getproccount();
m->procid = getpid();
- runtime·notify(runtime·gonote);
+ runtime·notify(runtime·sigtramp);
}
void
@@ -169,7 +173,7 @@
}
void
-goexitsall(void)
+runtime·goexitsall(int8 *status)
{
M *mp;
int32 pid;
@@ -177,31 +181,7 @@
pid = getpid();
for(mp=runtime·atomicloadp(&runtime·allm); mp; mp=mp->alllink)
if(mp->procid != pid)
- runtime·postnote(mp->procid, "gointr");
-}
-
-void
-runtime·gonote(void*, byte *s)
-{
- uint8 buf[128];
- int32 l;
-
- l = runtime·findnull(s);
- if(l > 4 && runtime·mcmp(s, (byte*)"sys:", 4) == 0) {
- runtime·memclr(buf, sizeof buf);
- runtime·memmove((void*)buf, (void*)s, runtime·findnull(s));
- runtime·exitstatus = (int8*)buf;
- goexitsall();
- runtime·noted(NDFLT);
- }
-
- if(runtime·exitstatus)
- runtime·exits(runtime·exitstatus);
-
- if(runtime·strcmp(s, (byte*)"gointr") == 0)
- runtime·noted(NCONT);
-
- runtime·noted(NDFLT);
+ runtime·postnote(mp->procid, status);
}
int32
@@ -240,17 +220,18 @@
runtime·exit(int32 e)
{
byte tmp[16];
-
+ int8 *status;
+
if(e == 0)
- runtime·exitstatus = "";
+ status = "";
else {
/* build error string */
runtime·itoa(e, tmp, sizeof tmp);
- runtime·exitstatus = (int8*)tmp;
+ status = (int8*)tmp;
}
- goexitsall();
- runtime·exits(runtime·exitstatus);
+ runtime·goexitsall(status);
+ runtime·exits(status);
}
void
@@ -307,15 +288,15 @@
runtime·throw("too many writes on closed pipe");
}
-/*
- * placeholder - once notes are implemented,
- * a signal generating a panic must appear as
- * a call to this function for correct handling by
- * traceback.
- */
void
runtime·sigpanic(void)
{
+ if(g->sigpc == 0)
+ runtime·panicstring("call of nil func value");
+ runtime·panicstring(m->notesig);
+
+ if(g->sig == 1 || g->sig == 2)
+ runtime·throw("fault");
}
int32
@@ -357,8 +338,8 @@
// This runs on a foreign stack, without an m or a g. No stack split.
#pragma textflag 7
void
-runtime·badsignal(int32 sig)
+runtime·badsignal(void)
{
- USED(sig);
runtime·pwrite(2, badsignal, sizeof badsignal - 1, -1LL);
+ runtime·exits(badsignal);
}