Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18861] ファイルの概要

このコミットは、GoランタイムにおけるPlan 9オペレーティングシステム上でのシグナルハンドリングの修正と改善に焦点を当てています。具体的には、シグナル(Plan 9では「ノート」と呼ばれる)の処理方法、パニック発生時の挙動、およびプロセス終了時の処理が変更されています。

変更されたファイルは以下の通りです。

  • src/pkg/runtime/os_plan9.c: Plan 9固有のOSレベルのランタイム関数が含まれています。シグナルハンドリングの主要なロジックが変更されています。
  • src/pkg/runtime/os_plan9.h: Plan 9固有のランタイム定義が含まれています。シグナル関連の定数と構造体が更新されています。
  • src/pkg/runtime/os_plan9_386.c: Plan 9上の386アーキテクチャ固有のランタイム関数が含まれています。シグナルハンドラが変更されています。
  • src/pkg/runtime/os_plan9_amd64.c: Plan 9上のAMD64アーキテクチャ固有のランタイム関数が含まれています。シグナルハンドラが変更されています。
  • src/pkg/runtime/print.c: ランタイムのデバッグプリント関数が含まれています。runtime·snprintf関数が追加されています。
  • src/pkg/runtime/runtime.h: Goランタイムのグローバルな定義が含まれています。シグナル関連のフラグや関数のプロトタイプが追加・変更されています。
  • src/pkg/runtime/signals_plan9.h: Plan 9固有のシグナルテーブル定義が含まれています。シグナルテーブルの内容が大幅に拡張・再編成されています。
  • src/pkg/runtime/string.goc: ランタイムの文字列操作関数が含まれています。runtime·strncmp関数が追加されています。

コミット

runtime: fix signal handling on Plan 9

LGTM=rsc
R=rsc, 0intro, aram, jeremyjackins, iant
CC=golang-codereviews, lucio.dere, minux.ma, paurea, r
https://golang.org/cl/9796043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/41aa887be5981844a425c8c71aa7e24cea21a258

元コミット内容

commit 41aa887be5981844a425c8c71aa7e24cea21a258
Author: Anthony Martin <ality@pbrane.org>
Date:   Thu Mar 13 09:00:12 2014 -0700

    runtime: fix signal handling on Plan 9
    
    LGTM=rsc
    R=rsc, 0intro, aram, jeremyjackins, iant
    CC=golang-codereviews, lucio.dere, minux.ma, paurea, r
    https://golang.org/cl/9796043
---
 src/pkg/runtime/os_plan9.c       |  82 ++++++++++++++++++++---
 src/pkg/runtime/os_plan9.h       |   9 ++-
 src/pkg/runtime/os_plan9_386.c   | 140 +++++++++++++++++++++------------------
 src/pkg/runtime/os_plan9_amd64.c | 113 +++++++++++++++++--------------
 src/pkg/runtime/print.c          |  18 +++++
 src/pkg/runtime/runtime.h        |   3 +\
 src/pkg/runtime/signals_plan9.h  |  61 ++++++++++++-----\
 src/pkg/runtime/string.goc       |  19 ++++++\
 8 files changed, 307 insertions(+), 138 deletions(-)

diff --git a/src/pkg/runtime/os_plan9.c b/src/pkg/runtime/os_plan9.c
index 214cb224ba..af20ce8db9 100644
--- a/src/pkg/runtime/os_plan9.c
+++ b/src/pkg/runtime/os_plan9.c
@@ -193,13 +193,15 @@ runtime·itoa(int32 n, byte *p, uint32 len)
 void
 runtime·goexitsall(int8 *status)
 {
+	int8 buf[ERRMAX];
 	M *mp;
 	int32 pid;
 
+	runtime·snprintf((byte*)buf, sizeof buf, "go: exit %s", status);
 	pid = getpid();
 	for(mp=runtime·atomicloadp(&runtime·allm); mp; mp=mp->alllink)
 		if(mp->procid != pid)
-			runtime·postnote(mp->procid, status);
+			runtime·postnote(mp->procid, buf);
 }
 
 int32
@@ -305,19 +307,79 @@ os·sigpipe(void)
 	runtime·throw("too many writes on closed pipe");
 }
 
+static int64
+atolwhex(byte *p)
+{
+	int64 n;
+	int32 f;
+
+	n = 0;
+	f = 0;
+	while(*p == ' ' || *p == '\t')
+		p++;
+	if(*p == '-' || *p == '+') {
+		if(*p++ == '-')
+			f = 1;
+		while(*p == ' ' || *p == '\t')
+			p++;
+	}
+	if(p[0] == '0' && p[1]) {
+		if(p[1] == 'x' || p[1] == 'X') {
+			p += 2;
+			for(;;) {
+				if('0' <= *p && *p <= '9')
+					n = n*16 + *p++ - '0';
+				else if('a' <= *p && *p <= 'f')
+					n = n*16 + *p++ - 'a' + 10;
+				else if('A' <= *p && *p <= 'F')
+					n = n*16 + *p++ - 'A' + 10;
+				else
+					break;
+			}
+		} else
+			while('0' <= *p && *p <= '7')
+				n = n*8 + *p++ - '0';
+	} else
+		while('0' <= *p && *p <= '9')
+			n = n*10 + *p++ - '0';
+	if(f)
+		n = -n;
+	return n;
+}
+
 void
 runtime·sigpanic(void)
 {
-	if(g->sigpc == 0)
-		runtime·panicstring("call of nil func value");
-	if(runtime·strcmp((byte*)m->notesig, (byte*)"sys: trap: fault read addr") >= 0 || runtime·strcmp((byte*)m->notesig, (byte*)"sys: trap: fault write addr") >= 0)
-		runtime·panicstring("invalid memory address or nil pointer dereference");
-	if(runtime·strcmp((byte*)m->notesig, (byte*)"sys: trap: divide error") >= 0)
-		runtime·panicstring("integer divide by zero");
-	runtime·panicstring(m->notesig);
-
-	if(g->sig == 1 || g->sig == 2)
+	byte *p;
+
+	switch(g->sig) {
+	case SIGRFAULT:
+	case SIGWFAULT:
+		p = runtime·strstr((byte*)m->notesig, (byte*)"addr=")+5;
+		g->sigcode1 = atolwhex(p);
+		if(g->sigcode1 < 0x1000 || g->paniconfault) {
+			if(g->sigpc == 0)
+				runtime·panicstring("call of nil func value");
+			runtime·panicstring("invalid memory address or nil pointer dereference");
+		}
+		runtime·printf("unexpected fault address %p\n", g->sigcode1);
 	runtime·throw("fault");
+		break;
+	case SIGTRAP:
+		if(g->paniconfault)
+			runtime·panicstring("invalid memory address or nil pointer dereference");
+		runtime·throw(m->notesig);
+		break;
+	case SIGINTDIV:
+		runtime·panicstring("integer divide by zero");
+		break;
+	case SIGFLOAT:
+		runtime·panicstring("floating point error");
+		break;
+	default:
+		runtime·panicstring(m->notesig);
+		break;
+	}
 }
 
 int32
diff --git a/src/pkg/runtime/os_plan9.h b/src/pkg/runtime/os_plan9.h
index f0474cda54..00ea8366d7 100644
--- a/src/pkg/runtime/os_plan9.h
+++ b/src/pkg/runtime/os_plan9.h
@@ -78,5 +78,12 @@ struct Tos {
 	/* top of stack is here */
 };
 
-#define	NSIG	5	/* number of signals in runtime·SigTab array */
+#define	NSIG	14	/* number of signals in runtime·SigTab array */
 #define	ERRMAX	128	/* max length of note string */
+
+/* Notes in runtime·sigtab that are handled by runtime·sigpanic. */
+#define	SIGRFAULT	2
+#define	SIGWFAULT	3
+#define	SIGINTDIV	4
+#define	SIGFLOAT	5
+#define	SIGTRAP		6
diff --git a/src/pkg/runtime/os_plan9_386.c b/src/pkg/runtime/os_plan9_386.c
index 3a17b33b84..04be91bf4e 100644
--- a/src/pkg/runtime/os_plan9_386.c
+++ b/src/pkg/runtime/os_plan9_386.c
@@ -10,77 +10,83 @@
 void
 runtime·dumpregs(Ureg *u)
 {
-	runtime·printf("ax\t%X\n", u->ax);
-	runtime·printf("bx\t%X\n", u->bx);
-	runtime·printf("cx\t%X\n", u->cx);
-	runtime·printf("dx\t%X\n", u->dx);
-	runtime·printf("di\t%X\n", u->di);
-	runtime·printf("si\t%X\n", u->si);
-	runtime·printf("bp\t%X\n", u->bp);
-	runtime·printf("sp\t%X\n", u->sp);
-	runtime·printf("pc\t%X\n", u->pc);
-	runtime·printf("flags\t%X\n", u->flags);
-	runtime·printf("cs\t%X\n", u->cs);
-	runtime·printf("fs\t%X\n", u->fs);
-	runtime·printf("gs\t%X\n", u->gs);
+	runtime·printf("ax\t%x\n", u->ax);
+	runtime·printf("bx\t%x\n", u->bx);
+	runtime·printf("cx\t%x\n", u->cx);
+	runtime·printf("dx\t%x\n", u->dx);
+	runtime·printf("di\t%x\n", u->di);
+	runtime·printf("si\t%x\n", u->si);
+	runtime·printf("bp\t%x\n", u->bp);
+	runtime·printf("sp\t%x\n", u->sp);
+	runtime·printf("pc\t%x\n", u->pc);
+	runtime·printf("flags\t%x\n", u->flags);
+	runtime·printf("cs\t%x\n", u->cs);
+	runtime·printf("fs\t%x\n", u->fs);
+	runtime·printf("gs\t%x\n", u->gs);
 }
 
 int32
-runtime·sighandler(void *v, int8 *s, G *gp)
+runtime·sighandler(void *v, int8 *note, G *gp)
 {
+	uintptr *sp;
+	SigTab *t;
 	bool crash;
 	Ureg *ureg;
-	uintptr *sp;
-	SigTab *sig, *nsig;
-	intgo len, i;
+	intgo len, n;
+	int32 sig, flags;
 
-	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;
+	ureg = (Ureg*)v;
+
+	// The kernel will never pass us a nil note or ureg so we probably
+	// made a mistake somewhere in runtime·sigtramp.
+	if(ureg == nil || note == nil) {
+		runtime·printf("sighandler: ureg %p note %p\n", ureg, note);
+		goto Throw;
+	}
+
+	// Check that the note is no more than ERRMAX bytes (including
+	// the trailing NUL). We should never receive a longer note.
+	len = runtime·findnull((byte*)note);
+	if(len > ERRMAX-1) {
+		runtime·printf("sighandler: note is longer than ERRMAX\n");
+		goto Throw;
+	}
+
+	// See if the note matches one of the patterns in runtime·sigtab.
+	// Notes that do not match any pattern can be handled at a higher
+	// level by the program but will otherwise be ignored.
+	flags = SigNotify;
+	for(sig = 0; sig < nelem(runtime·sigtab); sig++) {
+		t = &runtime·sigtab[sig];
+		n = runtime·findnull((byte*)t->name);
+		if(len < n)
+			continue;
+		if(runtime·strncmp((byte*)note, (byte*)t->name, n) == 0) {
+			flags = t->flags;
 			break;
 		}
-		sig++;
 	}
 
-	if(nsig == nil)
-		return NDFLT;
-
-	ureg = v;
-	if(nsig->flags & SigPanic) {
-		if(gp == nil || m->notesig == 0)
+	if(flags & SigGoExit)
+		runtime·exits(note+9); // Strip "go: exit " prefix.
+
+	if(flags & SigPanic) {
+		if(!runtime·canpanic(gp))
 			goto Throw;
 
 		// Copy the error string from sigtramp's stack into m->notesig so
-		// we can reliably access it from the panic routines. We can't use
-		// runtime·memmove here since it will use SSE instructions for big
-		// copies. The Plan 9 kernel doesn't allow floating point in note
-		// handlers.
-		//
-		// TODO(ality): revert back to memmove when the kernel is fixed.
-		if(len >= ERRMAX)
-			len = ERRMAX-1;
-		for(i = 0; i < len; i++)
-			m->notesig[i] = s[i];
-		m->notesig[i] = '\0';
-
-		gp->sig = i;
+		// we can reliably access it from the panic routines.
+		runtime·memmove(m->notesig, note, len+1);
+
+		gp->sig = sig;
 		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.)
+		// Only push runtime·sigpanic if PC != 0.
+		//
+		// If 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;
@@ -90,34 +96,42 @@ runtime·sighandler(void *v, int8 *s, G *gp)
 		return NCONT;
 	}
 
-	if(!(nsig->flags & SigThrow))
-		return NDFLT;
+	if(flags & SigNotify) {
+		// TODO(ality): See if os/signal wants it.
+		//if(runtime·sigsend(...))
+		//	return NCONT;
+	}
+	if(flags & SigKill)
+		goto Exit;
+	if(!(flags & SigThrow))
+		return NCONT;
 
 Throw:
 	m->throwing = 1;
 	m->caughtsig = gp;
 	runtime·startpanic();
 
-	runtime·printf("%s\n", s);
-	runtime·printf("PC=%X\n", ureg->pc);
+	runtime·printf("%s\n", note);
+	runtime·printf("PC=%x\n", ureg->pc);
 	runtime·printf("\n");
 
 	if(runtime·gotraceback(&crash)) {
+		runtime·goroutineheader(gp);
 		runtime·traceback(ureg->pc, ureg->sp, 0, gp);
 		runtime·tracebackothers(gp);
+		runtime·printf("\n");
 		runtime·dumpregs(ureg);
 	}
 	
 	if(crash)
 		runtime·crash();
 
-	runtime·goexitsall("");
-	runtime·exits(s);
-
-	return 0;
+	runtime·goexitsall(note);
+	runtime·exits(note);
+	return NDFLT; // not reached
 }
 
-
 void
 runtime·sigenable(uint32 sig)
 {
diff --git a/src/pkg/runtime/os_plan9_amd64.c b/src/pkg/runtime/os_plan9_amd64.c
index 4847dc6cef..7f4e1187fd 100644
--- a/src/pkg/runtime/os_plan9_amd64.c
+++ b/src/pkg/runtime/os_plan9_amd64.c
@@ -34,95 +34,110 @@ runtime·dumpregs(Ureg *u)
 }
 
 int32
-runtime·sighandler(void *v, int8 *s, G *gp)
+runtime·sighandler(void *v, int8 *note, G *gp)
 {
+	uintptr *sp;
+	SigTab *t;
 	bool crash;
 	Ureg *ureg;
-	uintptr *sp;
-	SigTab *sig, *nsig;
-	intgo i, len;
+	intgo len, n;
+	int32 sig, flags;
 
-	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;
+	ureg = (Ureg*)v;
+
+	// The kernel will never pass us a nil note or ureg so we probably
+	// made a mistake somewhere in runtime·sigtramp.
+	if(ureg == nil || note == nil) {
+		runtime·printf("sighandler: ureg %p note %p\n", ureg, note);
+		goto Throw;
+	}
+
+	// Check that the note is no more than ERRMAX bytes (including
+	// the trailing NUL). We should never receive a longer note.
+	len = runtime·findnull((byte*)note);
+	if(len > ERRMAX-1) {
+		runtime·printf("sighandler: note is longer than ERRMAX\n");
+		goto Throw;
+	}
+
+	// See if the note matches one of the patterns in runtime·sigtab.
+	// Notes that do not match any pattern can be handled at a higher
+	// level by the program but will otherwise be ignored.
+	flags = SigNotify;
+	for(sig = 0; sig < nelem(runtime·sigtab); sig++) {
+		t = &runtime·sigtab[sig];
+		n = runtime·findnull((byte*)t->name);
+		if(len < n)
+			continue;
+		if(runtime·strncmp((byte*)note, (byte*)t->name, n) == 0) {
+			flags = t->flags;
 			break;
 		}
-		sig++;
 	}
 
-	if(nsig == nil)
-		return NDFLT;
-
-	ureg = v;
-	if(nsig->flags & SigPanic) {
-		if(gp == nil || m->notesig == 0)
+	if(flags & SigGoExit)
+		runtime·exits(note+9); // Strip "go: exit " prefix.
+
+	if(flags & SigPanic) {
+		if(!runtime·canpanic(gp))
 			goto Throw;
 
 		// Copy the error string from sigtramp's stack into m->notesig so
-		// we can reliably access it from the panic routines. We can't use
-		// runtime·memmove here since it will use SSE instructions for big
-		// copies. The Plan 9 kernel doesn't allow floating point in note
-		// handlers.
-		//
-		// TODO(ality): revert back to memmove when the kernel is fixed.
-		if(len >= ERRMAX)
-			len = ERRMAX-1;
-		for(i = 0; i < len; i++)
-			m->notesig[i] = s[i];
-		m->notesig[i] = '\0';
-
-		gp->sig = i;
+		// we can reliably access it from the panic routines.
+		runtime·memmove(m->notesig, note, len+1);
+
+		gp->sig = sig;
 		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.)
+		// Only push runtime·sigpanic if PC != 0.
+		//
+		// If 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->ip != 0) {
 			sp = (uintptr*)ureg->sp;
 			*--sp = ureg->ip;
-			ureg->sp = (uint64)sp;
+			ureg->sp = (uint32)sp;
 		}
 		ureg->ip = (uintptr)runtime·sigpanic;
 		return NCONT;
 	}
 
-	if(!(nsig->flags & SigThrow))
-		return NDFLT;
+	if(flags & SigNotify) {
+		// TODO(ality): See if os/signal wants it.
+		//if(runtime·sigsend(...))
+		//	return NCONT;
+	}
+	if(flags & SigKill)
+		goto Exit;
+	if(!(flags & SigThrow))
+		return NCONT;
 
 Throw:
 	m->throwing = 1;
 	m->caughtsig = gp;
 	runtime·startpanic();
 
-	runtime·printf("%s\n", s);
+	runtime·printf("%s\n", note);
 	runtime·printf("PC=%X\n", ureg->ip);
 	runtime·printf("\n");
 
 	if(runtime·gotraceback(&crash)) {
+		runtime·goroutineheader(gp);
 		runtime·traceback(ureg->ip, ureg->sp, 0, gp);
 		runtime·tracebackothers(gp);
+		runtime·printf("\n");
 		runtime·dumpregs(ureg);
 	}
 	
 	if(crash)
 		runtime·crash();
 
-	runtime·goexitsall("");
-	runtime·exits(s);
-
-	return 0;
+	runtime·goexitsall(note);
+	runtime·exits(note);
+	return NDFLT; // not reached
 }
 
 void
diff --git a/src/pkg/runtime/print.c b/src/pkg/runtime/print.c
index 2a772ea340..e58c8bf3e6 100644
--- a/src/pkg/runtime/print.c
+++ b/src/pkg/runtime/print.c
@@ -63,6 +63,24 @@ runtime·printf(int8 *s, ...)
 	vprintf(s, arg);
 }
 
+#pragma textflag NOSPLIT
+int32
+runtime·snprintf(byte *buf, int32 n, int8 *s, ...)
+{
+	byte *arg;
+	int32 m;
+
+	arg = (byte*)(&s+1);
+	g->writebuf = buf;
+	g->writenbuf = n-1;
+	vprintf(s, arg);
+	*g->writebuf = '\0';
+	m = g->writebuf - buf;
+	g->writenbuf = 0;
+	g->writebuf = nil;
+	return m;
+}
+
 // Very simple printf.  Only for debugging prints.
 // Do not add to this without checking with Rob.
 static void
diff --git a/src/pkg/runtime/runtime.h b/src/pkg/runtime/runtime.h
index 8e5e9a1294..01294b70a0 100644
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -468,6 +468,7 @@ enum
 	SigDefault = 1<<4,	// if the signal isn't explicitly requested, don't monitor it
 	SigHandling = 1<<5,	// our signal handler is registered
 	SigIgnored = 1<<6,	// the signal was ignored before we registered for it
+	SigGoExit = 1<<7,	// cause all runtime procs to exit (only used on Plan 9).
 };
 
 // Layout of in-memory per-function information prepared by linker
@@ -792,6 +793,7 @@ extern	uintptr	runtime·maxstacksize;
  * common functions and data
  */
 int32	runtime·strcmp(byte*, byte*);
+int32	runtime·strncmp(byte*, byte*, uintptr);
 byte*	runtime·strstr(byte*, byte*);
 intgo	runtime·findnull(byte*);
 intgo	runtime·findnullw(uint16*);
@@ -840,6 +842,7 @@ void	runtime·panicstring(int8*);
 bool	runtime·canpanic(G*);
 void	runtime·prints(int8*);
 void	runtime·printf(int8*, ...);
+int32	runtime·snprintf(byte*, int32, int8*, ...);
 byte*	runtime·mchr(byte*, byte, byte*);
 int32	runtime·mcmp(byte*, byte*, uintptr);
 void	runtime·memmove(void*, void*, uintptr);
diff --git a/src/pkg/runtime/signals_plan9.h b/src/pkg/runtime/signals_plan9.h
index b16ecafd10..818f508cf3 100644
--- a/src/pkg/runtime/signals_plan9.h
+++ b/src/pkg/runtime/signals_plan9.h
@@ -3,27 +3,58 @@
 // license that can be found in the LICENSE file.
 
 #define N SigNotify
+#define K SigKill
 #define T SigThrow
 #define P SigPanic
+#define E SigGoExit
+
+// Incoming notes are compared against this table using strncmp, so the
+// order matters: longer patterns must appear before their prefixes.
+// There are #defined SIG constants in os_plan9.h for the table index of
+// some of these.
+//
+// If you add entries to this table, you must respect the prefix ordering
+// and also update the constant values is os_plan9.h.
 
 SigTab runtime·sigtab[] = {
-\tP, "sys: fp:",
-\
-\t// Go libraries expect to be able
-\t// to recover from memory
-\t// read/write errors, so we flag
-\t// those as panics. All other traps
-\t// are generally more serious and
-\t// should immediately throw an
-\t// exception.
-\tP, "sys: trap: fault read addr",
-\tP, "sys: trap: fault write addr",
-\tP, "sys: trap: divide error",
-\tT, "sys: trap:",
-\
-\tN, "sys: bad sys call",
+\t// Traps that we cannot be recovered.
+\tT,	"sys: trap: debug exception",
+\tT,	"sys: trap: invalid opcode",
+\
+\t// We can recover from some memory errors in runtime·sigpanic.
+\tP,	"sys: trap: fault read addr",	// SIGRFAULT
+\tP,	"sys: trap: fault write addr",	// SIGWFAULT
+\
+\t// We can also recover from math errors.
+\tP,	"sys: trap: divide error",	// SIGINTDIV
+\tP,	"sys: fp:",	// SIGFLOAT
+\
+\t// All other traps are normally handled as if they were marked SigThrow.
+\t// We mark them SigPanic here so that debug.SetPanicOnFault will work.
+\tP,	"sys: trap:",	// SIGTRAP
+\
+\t// Writes to a closed pipe can be handled if desired, otherwise they're ignored.
+\tN,	"sys: write on closed pipe",
+\
+\t// Other system notes are more serious and cannot be recovered.
+\tT,	"sys:",
+\
+\t// Issued to all other procs when calling runtime·exit.
+\tE,	"go: exit ",
+\
+\t// Kill is sent by external programs to cause an exit.
+\tK,	"kill",
+\
+\t// Interrupts can be handled if desired, otherwise they cause an exit.
+\tN+K,	"interrupt",
+\tN+K,	"hangup",
+\
+\t// Alarms can be handled if desired, otherwise they're ignored.
+\tN,	"alarm",
 };
 
 #undef N
+#undef K
 #undef T
 #undef P
+#undef E
diff --git a/src/pkg/runtime/string.goc b/src/pkg/runtime/string.goc
index 8bdaf9d654..89b9130c08 100644
--- a/src/pkg/runtime/string.goc
+++ b/src/pkg/runtime/string.goc
@@ -236,6 +236,25 @@ runtime·strcmp(byte *s1, byte *s2)
 	}
 }
 
+int32
+runtime·strncmp(byte *s1, byte *s2, uintptr n)
+{
+	uintptr i;
+	byte c1, c2;
+
+	for(i=0; i<n; i++) {
+		c1 = s1[i];
+		c2 = s2[i];
+		if(c1 < c2)
+			return -1;
+		if(c1 > c2)
+			return +1;
+		if(c1 == 0)
+			break;
+	}
+	return 0;
+}
+
 byte*
 runtime·strstr(byte *s1, byte *s2)
 {

変更の背景

このコミットの背景には、Plan 9オペレーティングシステムにおけるGoプログラムのシグナルハンドリングの堅牢性と正確性の向上が挙げられます。Plan 9は従来のUnix系OSとは異なる「ノート(note)」と呼ばれるメカニズムで非同期イベント(シグナルに相当)を扱います。Goランタイムは、このノートシステムを適切に解釈し、Goのパニックやエラー処理メカニズムにマッピングする必要があります。

以前の実装では、特定のノート(例えば、メモリ不正アクセスやゼロ除算)が適切にパニックに変換されなかったり、シグナルハンドラがノートの内容を安全に処理できなかったりする問題があったと考えられます。特に、ノート文字列の長さ制限や、浮動小数点演算がノートハンドラ内で許可されないというPlan 9カーネルの制約が、既存のruntime·memmoveの使用を妨げていた可能性があります。

このコミットは、これらの問題を解決し、GoプログラムがPlan 9上でより予測可能で堅牢なシグナル処理を行えるようにすることを目的としています。具体的には、ノートの解析を改善し、より多くの種類のノートをGoのパニックとして適切に処理できるようにすることで、デバッグのしやすさやプログラムの安定性を向上させています。また、プロセス終了時のノート送信も改善されています。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  • Goランタイム (Go Runtime): Goプログラムは、OSが提供する機能の上に、Go言語独自のランタイムシステム(ガベージコレクション、スケジューラ、メモリ管理、シグナルハンドリングなど)を構築しています。このランタイムは、Goプログラムの実行環境全体を管理します。
  • Goroutine (G): Go言語における軽量な並行処理の単位です。Goランタイムによってスケジューリングされます。
  • Machine (M): OSのスレッドに相当します。Goランタイムは、M上でGを実行します。
  • Processor (P): MとGを仲介する論理的なプロセッサです。Goスケジューラは、PにGを割り当て、MがP上でGを実行します。
  • Plan 9のノート (Notes): Plan 9オペレーティングシステムにおける非同期イベント通知メカニズムです。Unix系のシグナルに似ていますが、より柔軟な文字列ベースのメッセージ(ノート文字列)を送信できます。例えば、メモリ不正アクセスは"sys: trap: fault read addr"のようなノートとして通知されます。
  • runtime·panicstring: Goランタイム内部で文字列を引数としてパニックを発生させる関数です。
  • runtime·throw: Goランタイム内部で回復不可能なエラーが発生した際に呼び出される関数で、プログラムを異常終了させます。
  • runtime·exits: Plan 9において、現在のプロセスを終了させるシステムコールです。引数として終了ステータス(ノート文字列)を取ります。
  • runtime·memmove: メモリブロックをコピーするランタイム内部関数です。
  • runtime·printf: ランタイム内部のデバッグ用printf関数です。
  • runtime·snprintf: ランタイム内部のデバッグ用snprintf関数です。指定されたバッファにフォーマットされた文字列を書き込みます。
  • SigTab: GoランタイムがPlan 9のノートを処理するために使用するテーブルです。各エントリはノートのパターンと、それに対応する処理フラグ(パニック、スロー、通知など)を定義します。
  • Ureg: Plan 9のシグナルハンドラに渡されるレジスタの状態を保持する構造体です。プログラムカウンタ(PC)やスタックポインタ(SP)などの情報が含まれます。
  • ERRMAX: Plan 9のノート文字列の最大長を定義する定数です。
  • runtime·atomicloadp: アトミックにポインタをロードする関数。並行処理環境でのデータ競合を防ぎます。
  • runtime·postnote: Plan 9において、指定されたプロセスにノートを送信するシステムコールです。
  • runtime·strcmp: ランタイム内部の文字列比較関数。
  • runtime·strstr: ランタイム内部の文字列検索関数。
  • runtime·findnull: ヌル終端文字列の長さを返す関数。
  • runtime·mcmp: メモリブロックを比較する関数。
  • runtime·strncmp: 指定された長さまで文字列を比較する関数。

技術的詳細

このコミットは、Plan 9におけるGoランタイムのシグナルハンドリングを多岐にわたって改善しています。

  1. runtime·goexitsall の改善:

    • 以前は、runtime·goexitsallが他のM(OSスレッド)にノートをポストする際に、引数として受け取ったstatus文字列をそのまま使用していました。
    • 変更後、runtime·snprintfを使用して"go: exit %s"という形式の新しいノート文字列を生成し、それをポストするようになりました。これにより、他のプロセスがGoプログラムの終了をより明確に識別できるようになります。
  2. atolwhex 関数の追加:

    • この新しい静的関数は、文字列から16進数を含む数値を解析してint64として返すユーティリティです。
    • これは主にruntime·sigpanic内で、メモリ不正アクセスノート(例: "sys: trap: fault read addr addr=0x12345")からアドレスを抽出するために使用されます。
  3. runtime·sigpanic の大幅なリファクタリング:

    • 以前は、m->notesigの内容をruntime·strcmpで直接比較し、特定の文字列パターンに基づいてパニックメッセージを決定していました。これは柔軟性に欠け、新しいノートタイプへの対応が困難でした。
    • 変更後、g->sigruntime·sighandlerで設定されるシグナルインデックス)に基づいてswitch文を使用するようになりました。
    • SIGRFAULT(読み取りフォルト)とSIGWFAULT(書き込みフォルト)の場合、atolwhexを使用してノート文字列から不正アクセスアドレスを抽出し、g->sigcode1に保存します。これにより、不正なアドレスが0x1000未満の場合やg->paniconfaultが設定されている場合に、より具体的なパニックメッセージ("invalid memory address or nil pointer dereference")を生成できるようになりました。
    • SIGTRAPSIGINTDIV(ゼロ除算)、SIGFLOAT(浮動小数点エラー)に対しても、それぞれ適切なパニックメッセージが設定されるようになりました。
    • これにより、より多くの種類のノートがGoのパニックとして適切に処理され、デバッグ情報がより詳細になります。
  4. os_plan9.h のシグナル定数拡張:

    • NSIGruntime·SigTab配列内のシグナル数)が5から14に増加しました。
    • SIGRFAULT, SIGWFAULT, SIGINTDIV, SIGFLOAT, SIGTRAPといった、runtime·sigpanicで処理される特定のノートに対応する定数が追加されました。これらはruntime·sigtab内のインデックスに対応します。
  5. runtime.h の変更:

    • 新しいSigGoExitフラグ(1<<7)が追加されました。これはPlan 9でのみ使用され、すべてのランタイムプロセスを終了させるノートを示します。
    • runtime·strncmpruntime·snprintfのプロトタイプが追加されました。
  6. runtime·sighandler の堅牢化と改善 (386/AMD64共通):

    • noteuregnilでないことのチェックが追加され、ランタイム内部のエラーを早期に検出できるようになりました。
    • ノート文字列の長さがERRMAXを超えないことのチェックが追加されました。
    • ノートをruntime·sigtabと比較するロジックが改善されました。以前はruntime·strstrを使用していましたが、これは部分一致を許容するため、より厳密なruntime·strncmpを使用するように変更されました。これにより、より正確なノートのマッチングが可能になります。
    • SigTabのエントリをループし、ノートがどのパターンに一致するかを判断するようになりました。
    • SigGoExitフラグが設定されている場合、"go: exit "プレフィックスを削除してruntime·exitsを呼び出すようになりました。
    • SigPanicフラグが設定されている場合、runtime·canpanic(gp)でパニック可能かチェックし、ノート文字列をm->notesigruntime·memmoveでコピーするようになりました。以前はループで1バイトずつコピーしていましたが、これはPlan 9カーネルの浮動小数点制約を回避するための一時的な措置だったようです。カーネルが修正されたか、あるいはruntime·memmoveが浮動小数点命令を使用しないように変更されたため、より効率的なruntime·memmoveに戻されました。
    • パニック発生時のスタックトレース出力にruntime·goroutineheader(gp)が追加され、ゴルーチンヘッダが出力されるようになりました。
    • runtime·printfのフォーマット指定子が%Xから%xに変更され、レジスタ値の出力が小文字の16進数になりました。
  7. signals_plan9.hSigTab の再編成と拡張:

    • SigTabが大幅に拡張され、より多くのノートパターンとそれに対応するフラグ(SigNotify, SigKill, SigThrow, SigPanic, SigGoExit)が定義されました。
    • SigTabの順序が重要であることが明記されました。strncmpを使用するため、より長いパターンがそのプレフィックスよりも前に来るように並べ替える必要があります。
    • 回復不可能なトラップ(デバッグ例外、不正オペコード)はSigThrowとしてマークされました。
    • メモリエラー(読み取り/書き込みフォルト)と数学エラー(ゼロ除算、浮動小数点エラー)はSigPanicとしてマークされ、runtime·sigpanicで回復可能になりました。
    • "sys: trap:"のような一般的なトラップもSigPanicとしてマークされ、debug.SetPanicOnFaultが機能するように変更されました。
    • "go: exit "ノートがSigGoExitフラグを持つようになりました。
    • "kill", "interrupt", "hangup", "alarm"などのノートも追加され、それぞれ適切なフラグが設定されました。
  8. string.gocruntime·strncmp の追加:

    • この関数は、指定された長さまで2つの文字列を比較します。これはruntime·sighandlerでノートをSigTabと比較する際に使用されます。

これらの変更により、Plan 9上でのGoプログラムのシグナルハンドリングは、より正確で、堅牢で、デバッグが容易になりました。特に、パニックの発生条件とメッセージが改善され、開発者が問題の原因を特定しやすくなっています。

コアとなるコードの変更箇所

このコミットのコアとなるコードの変更箇所は以下の3点です。

  1. src/pkg/runtime/os_plan9.c 内の runtime·sigpanic 関数のリファクタリング: 以前は文字列比較でノートの種類を判別していましたが、switch文と新しいシグナル定数(SIGRFAULT, SIGWFAULT, SIGINTDIV, SIGFLOAT, SIGTRAP)を使用して、より構造化された方法でパニックを処理するようになりました。特に、メモリ不正アクセスのアドレスを解析するatolwhex関数の導入と、それに基づく詳細なパニックメッセージの生成が重要です。

    void
    runtime·sigpanic(void)
    {
    	byte *p;
    
    	switch(g->sig) {
    	case SIGRFAULT:
    	case SIGWFAULT:
    		p = runtime·strstr((byte*)m->notesig, (byte*)"addr=")+5;
    		g->sigcode1 = atolwhex(p);
    		if(g->sigcode1 < 0x1000 || g->paniconfault) {
    			if(g->sigpc == 0)
    				runtime·panicstring("call of nil func value");
    			runtime·panicstring("invalid memory address or nil pointer dereference");
    		}
    		runtime·printf("unexpected fault address %p\n", g->sigcode1);
    		runtime·throw("fault");
    		break;
    	case SIGTRAP:
    		if(g->paniconfault)
    			runtime·panicstring("invalid memory address or nil pointer dereference");
    		runtime·throw(m->notesig);
    		break;
    	case SIGINTDIV:
    		runtime·panicstring("integer divide by zero");
    		break;
    	case SIGFLOAT:
    		runtime·panicstring("floating point error");
    		break;
    	default:
    		runtime·panicstring(m->notesig);
    		break;
    	}
    }
    
  2. src/pkg/runtime/os_plan9_386.c および src/pkg/runtime/os_plan9_amd64.c 内の runtime·sighandler 関数の改善: ノート文字列の処理がより堅牢になり、runtime·sigtabとの比較にruntime·strncmpを使用するようになりました。また、SigPanicフラグが設定されている場合のm->notesigへのコピーがruntime·memmoveに戻され、効率が向上しました。

    // 共通の変更点 (386とAMD64で同様)
    int32
    runtime·sighandler(void *v, int8 *note, G *gp)
    {
    	// ... (省略) ...
    	// See if the note matches one of the patterns in runtime·sigtab.
    	// Notes that do not match any pattern can be handled at a higher
    	// level by the program but will otherwise be ignored.
    	flags = SigNotify;
    	for(sig = 0; sig < nelem(runtime·sigtab); sig++) {
    		t = &runtime·sigtab[sig];
    		n = runtime·findnull((byte*)t->name);
    		if(len < n)
    			continue;
    		if(runtime·strncmp((byte*)note, (byte*)t->name, n) == 0) {
    			flags = t->flags;
    			break;
    		}
    	}
    
    	if(flags & SigGoExit)
    		runtime·exits(note+9); // Strip "go: exit " prefix.
    
    	if(flags & SigPanic) {
    		if(!runtime·canpanic(gp))
    			goto Throw;
    
    		// Copy the error string from sigtramp's stack into m->notesig so
    		// we can reliably access it from the panic routines.
    		runtime·memmove(m->notesig, note, len+1);
    
    		gp->sig = sig;
    		gp->sigpc = ureg->pc; // または ureg->ip for amd64
    
    		// ... (省略) ...
    	}
    	// ... (省略) ...
    }
    
  3. src/pkg/runtime/signals_plan9.h 内の SigTab の再編成と拡張: SigTabが大幅に拡張され、より多くのノートパターンとそれに対応するフラグが追加されました。特に、パターンマッチングの順序が重要であることがコメントで強調されています。

    SigTab runtime·sigtab[] = {
    	// Traps that we cannot be recovered.
    	T,	"sys: trap: debug exception",
    	T,	"sys: trap: invalid opcode",
    
    	// We can recover from some memory errors in runtime·sigpanic.
    	P,	"sys: trap: fault read addr",	// SIGRFAULT
    	P,	"sys: trap: fault write addr",	// SIGWFAULT
    
    	// We can also recover from math errors.
    	P,	"sys: trap: divide error",	// SIGINTDIV
    	P,	"sys: fp:",	// SIGFLOAT
    
    	// All other traps are normally handled as if they were marked SigThrow.
    	// We mark them SigPanic here so that debug.SetPanicOnFault will work.
    	P,	"sys: trap:",	// SIGTRAP
    
    	// Writes to a closed pipe can be handled if desired, otherwise they're ignored.
    	N,	"sys: write on closed pipe",
    
    	// Other system notes are more serious and cannot be recovered.
    	T,	"sys:",
    
    	// Issued to all other procs when calling runtime·exit.
    	E,	"go: exit ",
    
    	// Kill is sent by external programs to cause an exit.
    	K,	"kill",
    
    	// Interrupts can be handled if desired, otherwise they cause an exit.
    	N+K,	"interrupt",
    	N+K,	"hangup",
    
    	// Alarms can be handled if desired, otherwise they're ignored.
    	N,	"alarm",
    };
    

コアとなるコードの解説

runtime·sigpanic のリファクタリング

この変更は、GoプログラムがPlan 9上で遭遇する可能性のある様々な低レベルのシステムエラー(ノート)を、Goのパニックとしてより正確かつ詳細に報告できるようにすることを目的としています。

  • switch(g->sig) の導入: 以前はノート文字列の直接比較に依存していましたが、これはエラーの種類を厳密に区別するのが困難でした。新しいswitch文は、runtime·sighandlerによって事前に分類されたシグナルインデックスg->sigを使用することで、より明確で効率的な処理パスを提供します。
  • メモリ不正アクセス (SIGRFAULT, SIGWFAULT) の詳細化:
    • atolwhex関数を導入することで、ノート文字列(例: "sys: trap: fault read addr addr=0x12345")から実際に不正アクセスが発生したメモリアドレスを抽出できるようになりました。
    • このアドレスが0x1000未満の場合(通常、ヌルポインタ参照や非常に低いアドレスへのアクセスを示す)や、g->paniconfaultフラグが設定されている場合(デバッグ目的で全てのフォルトをパニックにする設定)には、一般的な「無効なメモリアドレスまたはnilポインタデリファレンス」というパニックメッセージを生成します。
    • それ以外の場合(予期せぬ高位アドレスでのフォルトなど)は、抽出したアドレスをruntime·printfで出力し、runtime·throw("fault")で回復不能なエラーとして扱います。これにより、開発者はより具体的なデバッグ情報を得ることができます。
  • その他のエラーの明確化:
    • SIGTRAP(トラップ)、SIGINTDIV(ゼロ除算)、SIGFLOAT(浮動小数点エラー)といった一般的なエラーに対しても、それぞれ専用のパニックメッセージが割り当てられました。これにより、パニックの原因がより明確になります。
  • デフォルトのパニック: 上記のいずれにも該当しないノートは、以前と同様にノート文字列自体をパニックメッセージとして使用します。

このリファクタリングにより、Goプログラムのクラッシュレポートが大幅に改善され、開発者がPlan 9環境での低レベルな問題を診断する際の効率が向上します。

runtime·sighandler の堅牢化と SigTab との連携

runtime·sighandlerは、Plan 9カーネルからノートを受け取り、それをGoランタイムの内部処理にマッピングする重要な役割を担っています。この変更は、ノートの解析と処理をより正確かつ安全に行うことを目的としています。

  • 入力検証の強化: uregnotenilでないこと、およびノート文字列の長さがERRMAXを超えないことのチェックが追加されました。これにより、不正な入力に対するランタイムの堅牢性が向上します。
  • SigTabruntime·strncmp を用いたノートマッチング:
    • 以前はruntime·strstr(部分文字列検索)を使用していましたが、これは意図しないノートにマッチする可能性がありました。
    • 新しい実装では、runtime·sigtab配列をループし、各エントリのnameフィールドと入力ノートをruntime·strncmp(指定された長さまでの文字列比較)で比較します。これにより、より厳密なプレフィックスマッチングが可能になり、ノートの分類精度が向上します。
    • signals_plan9.hのコメントにあるように、SigTab内のエントリの順序が重要になります(長いパターンが短いプレフィックスパターンより前に来る必要がある)。
  • SigGoExit フラグの導入と処理:
    • SigGoExitフラグが設定されたノート(主にruntime·goexitsallから送信される"go: exit "ノート)を受け取った場合、"go: exit "プレフィックスを削除した上でruntime·exitsを呼び出し、プロセスを正常に終了させます。これにより、Goプログラムが他のGoプロセスに終了を通知するメカニズムが明確化されました。
  • SigPanic 処理の改善:
    • runtime·canpanic(gp)のチェックが追加され、現在のゴルーチンがパニックを処理できる状態にあるかを確認します。
    • ノート文字列をm->notesigにコピーする際に、以前の1バイトずつのループからruntime·memmoveに戻されました。これは、Plan 9カーネルの制約が緩和されたか、またはruntime·memmoveの実装が改善され、ノートハンドラ内で安全に使用できるようになったためと考えられます。これにより、コピー処理の効率が向上します。
    • gp->sig = sig;により、runtime·sigpanicswitch文で利用するシグナルインデックスが正確に設定されます。
  • トレースバック出力の改善: パニック発生時のトレースバックにruntime·goroutineheader(gp)が追加され、どのゴルーチンでパニックが発生したかの情報がより明確に表示されるようになりました。

これらの変更により、runtime·sighandlerはPlan 9のノートをより正確に解釈し、Goランタイムのパニックおよび終了処理と適切に連携できるようになりました。

SigTab の再編成と拡張

SigTabは、Plan 9のノートとGoランタイムのシグナル処理ロジックをマッピングする中心的なデータ構造です。この変更は、Goランタイムが認識し、適切に処理できるノートの種類を大幅に増やし、それぞれのノートに対する処理方法をより細かく制御できるようにすることを目的としています。

  • シグナルフラグの追加: K (SigKill) と E (SigGoExit) の新しいフラグが追加され、ノートがプロセス終了を引き起こすか、またはGoプログラムの終了ノートであるかを示すことができるようになりました。
  • ノートパターンの拡張:
    • 以前は少数の一般的なノートしか定義されていませんでしたが、この変更により、デバッグ例外、不正オペコード、読み取り/書き込みフォルト、ゼロ除算、浮動小数点エラー、パイプへの書き込み、一般的なシステムノート、Go独自の終了ノート、killシグナル、割り込み、ハングアップ、アラームなど、より多くの具体的なノートパターンが追加されました。
  • 処理フラグの細分化: 各ノートパターンに対して、SigThrow(回復不能なエラー)、SigPanic(Goパニックとして回復可能)、SigNotify(通知のみ)、SigKill(プロセス終了)、SigGoExit(Goプログラムの終了)といった適切なフラグが割り当てられました。これにより、ランタイムは各ノートに対してよりきめ細やかな対応が可能になります。
  • 順序の重要性: strncmpを使用するため、SigTab内のエントリの順序が重要であることが明記されました。より具体的な(長い)パターンが、その一般的な(短い)プレフィックスパターンよりも前に来るように配置する必要があります。例えば、"sys: trap: fault read addr""sys: trap:"よりも前に来る必要があります。

このSigTabの拡張と再編成により、GoランタイムはPlan 9環境における様々なシステムイベントに対して、よりインテリジェントかつ柔軟に対応できるようになり、Goプログラムの堅牢性と互換性が向上しました。

関連リンク

参考にした情報源リンク

  • コミットデータ: /home/orange/Project/comemo/commit_data/18861.txt
  • GitHub上のコミットページ: https://github.com/golang/go/commit/41aa887be5981844a425c8c71aa7e24cea21a258
  • Goランタイムの一般的な知識
  • Plan 9オペレーティングシステムのノートメカニズムに関する一般的な知識
  • C言語の標準ライブラリ関数(strcmp, strstr, memmove, printf, snprintfなど)に関する一般的な知識
  • Goの内部構造(M, G, Pなど)に関する一般的な知識
  • Goのパニックとエラー処理に関する一般的な知識