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

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

このコミットは、Go言語のランタイムにおけるシグナルハンドリングの修正に関するものです。具体的には、NetBSDオペレーティングシステム上でのGoのビルドが正しく行われるように、シグナル情報の取得方法とレジスタ定義を調整しています。これにより、NetBSD環境でのGoプログラムの安定性と互換性が向上しました。

コミット

commit e67f19851624aef42749f188462ded277ce2d57
Author: Joel Sing <jsing@google.com>
Date:   Fri Mar 15 11:43:43 2013 -0400

    runtime: unbreak netbsd builds

    Fix signal handling so that Go builds on NetBSD.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/7759048

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

https://github.com/golang/go/commit/e67f19851624aef42749f188462ded277ce2d57

元コミット内容

GoランタイムにおけるNetBSDビルドの破損を修正。 GoがNetBSD上でビルドできるようにシグナルハンドリングを修正。

変更の背景

このコミットの背景には、Go言語のランタイムがNetBSDオペレーティングシステム上で正しく動作しないという問題がありました。特に、シグナルハンドリングのメカニズムに不整合があり、これが原因でGoプログラムがクラッシュしたり、予期せぬ動作をしたりしていました。

Goランタイムは、プログラムの実行中に発生する様々なシグナル(例えば、セグメンテーション違反、浮動小数点例外、ユーザー定義シグナルなど)を適切に処理する必要があります。これらのシグナルは、オペレーティングシステムによってプロセスに通知され、ランタイムはそれらを捕捉し、適切なハンドラを実行することで、プログラムの堅牢性を保ちます。

NetBSDのような特定のOS環境では、シグナルハンドラに渡されるコンテキスト情報(ucontext構造体など)の構造や、シグナルコード(si_code)の解釈、あるいはレジスタ情報へのアクセス方法が他のOS(LinuxやFreeBSDなど)と異なる場合があります。この差異が、Goランタイムが期待する情報とNetBSDが提供する情報の間にギャップを生み出し、結果としてビルドの失敗や実行時の問題を引き起こしていました。

このコミットは、NetBSD固有のシグナルハンドリングの差異を吸収し、GoランタイムがNetBSD環境でも期待通りにシグナルを処理できるようにするための修正です。これにより、NetBSDユーザーがGo言語を問題なく利用できるようになります。

前提知識の解説

Goランタイム (Go Runtime)

Goランタイムは、Go言語で書かれたプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、そしてシグナルハンドリングなどが含まれます。Goプログラムは、OSの機能に直接依存する部分(システムコールなど)を除き、ほとんどがGoランタイムによって抽象化されています。

シグナルハンドリング (Signal Handling)

シグナルは、オペレーティングシステムがプロセスに非同期的に通知するイベントです。例えば、プログラムが不正なメモリアドレスにアクセスしようとした場合(SIGSEGV)、Ctrl+Cが押された場合(SIGINT)、子プロセスが終了した場合(SIGCHLD)などにシグナルが送られます。シグナルハンドリングは、これらのシグナルを捕捉し、プログラムがそれらにどのように応答するかを定義するメカニズムです。

siginfo_t (Siginfo) 構造体

siginfo_t(GoランタイムではSiginfoとして扱われることが多い)は、シグナルに関する詳細情報を提供する構造体です。この構造体には、シグナル番号、シグナルを送信したプロセスのID、そしてシグナルが発生した原因を示すsi_codeフィールドなどが含まれます。

si_code

si_codesiginfo_t構造体の一部であり、シグナルが生成された原因を示すコードです。例えば、SI_USERはユーザープロセスによってkill()システムコールなどで明示的に送信されたシグナルを示します。他の値は、ハードウェア例外(例: SEGV_MAPERR for SIGSEGV)、タイマーの期限切れ、I/Oイベントなど、様々な原因を示します。

ucontext_t (Ucontext) 構造体

ucontext_t(GoランタイムではUcontextとして扱われることが多い)は、シグナルハンドラが呼び出された時点でのプロセスのコンテキスト(CPUレジスタの状態、シグナルマスク、スタック情報など)を保存する構造体です。これにより、シグナルハンドラはプログラムの状態を検査したり、必要に応じて変更したりすることができます。特に、uc_mcontextフィールドはマシン依存のコンテキスト情報(レジスタ値など)を保持します。

SIG_CODE0 マクロ

このコミットで導入されたSIG_CODE0マクロは、シグナルハンドラに渡されるSiginfoctxtucontext)から、シグナルコードを安全かつプラットフォーム依存の方法で取得するための抽象化です。これは、異なるOSやアーキテクチャ間でsi_codeの取得方法が異なる場合に、コードの移植性を高めるために使用されます。

SigPanicSigNotify

これらはGoランタイム内部でシグナルの種類を分類するために使用されるフラグです。

  • SigPanic: このフラグが設定されたシグナルは、Goランタイムがパニック(プログラムの異常終了)を引き起こすべきシグナルであることを示します。例えば、セグメンテーション違反のような致命的なエラーがこれに該当します。
  • SigNotify: このフラグが設定されたシグナルは、Goランタイムがユーザーに通知すべきシグナルであることを示します。例えば、os/signalパッケージを通じてGoプログラムが捕捉できるシグナルがこれに該当します。

レジスタ定義 (REG_EAX, REG_ESP, REG_EFL など)

CPUのレジスタは、プログラムの実行中にデータを一時的に保持する高速な記憶領域です。シグナルハンドラが呼び出された際、ucontext_t構造体内のuc_mcontextフィールドには、シグナル発生時のCPUレジスタの値が保存されます。REG_EAX(32ビットシステムでの汎用レジスタ)、REG_ESP(スタックポインタ)、REG_EFL(EFLAGSレジスタ、CPUの状態フラグを保持)などは、これらのレジスタにアクセスするためのマクロや定義です。OSやアーキテクチャによって、これらのレジスタの名称やucontext構造体内のオフセットが異なる場合があります。

技術的詳細

このコミットは、主にGoランタイムのシグナルハンドリング部分におけるNetBSD固有の互換性問題を解決しています。変更は大きく分けて二つの側面があります。

  1. si_codeの取得方法の変更: src/pkg/runtime/signal_386.csrc/pkg/runtime/signal_amd64.csrc/pkg/runtime/signal_arm.cの各ファイルにおいて、info->si_codeを直接参照していた箇所がSIG_CODE0(info, ctxt)マクロに置き換えられました。 これは、NetBSD環境においてsiginfo_t構造体のsi_codeフィールドが、特定の状況下で期待通りに設定されない、あるいはアクセス方法が他のOSと異なる可能性があるためと考えられます。SIG_CODE0マクロは、infosiginfo_t)とctxtucontext_t)の両方を利用して、プラットフォームに依存しない形で正確なシグナルコードを取得するための抽象化を提供します。これにより、NetBSD上でのシグナルハンドリングがより堅牢になります。

  2. NetBSD固有のucontext型とレジスタ定義の修正: src/pkg/runtime/signal_netbsd_386.hsrc/pkg/runtime/signal_netbsd_amd64.hsrc/pkg/runtime/signal_netbsd_arm.hの各ファイルで、SIG_REGSマクロの定義が変更されました。

    • 変更前: #define SIG_REGS(ctxt) (((Ucontext*)(ctxt))->uc_mcontext)
    • 変更後: #define SIG_REGS(ctxt) (((UcontextT*)(ctxt))->uc_mcontext) これは、NetBSDの特定のバージョンやアーキテクチャにおいて、ucontext_t構造体の型エイリアスがUcontextではなくUcontextTとして定義されているか、あるいはGoランタイムが内部的に使用するucontextのラッパー型がUcontextTに変更されたことを示唆しています。型が一致しないと、uc_mcontextフィールドへのアクセスが不正なメモリアドレスを参照し、クラッシュの原因となります。この修正により、正しい型キャストが適用され、uc_mcontextへのアクセスが保証されます。

    さらに、src/pkg/runtime/signal_netbsd_386.hでは、32ビットNetBSD環境における特定のレジスタ定義が修正されました。

    • SIG_ESP (スタックポインタ) の定義が REG_ESP から REG_UESP へ変更。
    • SIG_EFLAGS (EFLAGSレジスタ) の定義が REG_EFLAGS から REG_EFL へ変更。 これは、NetBSDの386アーキテクチャにおけるucontext_t構造体内のレジスタ配列のインデックスや、レジスタ名の定義が、他のシステムやGoランタイムが以前想定していたものと異なっていたためです。REG_UESPREG_EFLは、NetBSDのシステムヘッダで定義されている、より正確なレジスタの識別子である可能性が高いです。これらの修正により、シグナル発生時のCPUレジスタの状態が正しく取得・復元できるようになり、Goプログラムの実行が安定します。

これらの変更は、GoランタイムがNetBSDのシステムコールインターフェースやABI(Application Binary Interface)の細かな差異に適切に対応するためのものであり、クロスプラットフォーム互換性を維持する上で非常に重要です。

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

src/pkg/runtime/signal_386.c, src/pkg/runtime/signal_amd64.c, src/pkg/runtime/signal_arm.c

--- a/src/pkg/runtime/signal_386.c
+++ b/src/pkg/runtime/signal_386.c
@@ -45,7 +45,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *ctxt, G *gp)
 	}
 
 	t = &runtime·sigtab[sig];
-	if(info->si_code != SI_USER && (t->flags & SigPanic)) {
+	if(SIG_CODE0(info, ctxt) != SI_USER && (t->flags & SigPanic)) {
 		if(gp == nil || gp == m->g0)
 			goto Throw;
 
@@ -87,7 +87,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *ctxt, G *gp)
 		return;
 	}
 
-	if(info->si_code == SI_USER || (t->flags & SigNotify))
+	if(SIG_CODE0(info, ctxt) == SI_USER || (t->flags & SigNotify))
 		if(runtime·sigsend(sig))
 			return;
 	if(t->flags & SigKill)

signal_amd64.csignal_arm.cも同様の変更)

src/pkg/runtime/signal_netbsd_386.h

--- a/src/pkg/runtime/signal_netbsd_386.h
+++ b/src/pkg/runtime/signal_netbsd_386.h
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-#define SIG_REGS(ctxt) (((Ucontext*)(ctxt))->uc_mcontext)
+#define SIG_REGS(ctxt) (((UcontextT*)(ctxt))->uc_mcontext)
 
 #define SIG_EAX(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EAX])
 #define SIG_EBX(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EBX])
@@ -11,9 +11,9 @@
 #define SIG_EDI(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EDI])
 #define SIG_ESI(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_ESI])
 #define SIG_EBP(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EBP])
-#define SIG_ESP(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_ESP])
+#define SIG_ESP(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_UESP])
 #define SIG_EIP(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EIP])
-#define SIG_EFLAGS(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EFLAGS])
+#define SIG_EFLAGS(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EFL])
 
 #define SIG_CS(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_CS])
 #define SIG_FS(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_FS])

src/pkg/runtime/signal_netbsd_amd64.h, src/pkg/runtime/signal_netbsd_arm.h

--- a/src/pkg/runtime/signal_netbsd_amd64.h
+++ b/src/pkg/runtime/signal_netbsd_amd64.h
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-#define SIG_REGS(ctxt) (((Ucontext*)(ctxt))->uc_mcontext)
+#define SIG_REGS(ctxt) (((UcontextT*)(ctxt))->uc_mcontext)
 
 #define SIG_RAX(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_RAX])
 #define SIG_RBX(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_RBX])

signal_netbsd_arm.hも同様の変更)

コアとなるコードの解説

info->si_code から SIG_CODE0(info, ctxt) への変更

  • 変更前: if(info->si_code != SI_USER && (t->flags & SigPanic))
  • 変更後: if(SIG_CODE0(info, ctxt) != SI_USER && (t->flags & SigPanic))

この変更は、シグナルハンドラ内でシグナルコード(si_code)を取得する方法を修正しています。infosiginfo_t構造体へのポインタであり、si_codeはそのメンバーです。しかし、NetBSD環境では、特定の状況下でinfo->si_codeが正しく設定されない、あるいはアクセス方法が他のOSと異なる場合があります。 SIG_CODE0(info, ctxt)は、siginfo_tucontext_tの両方の情報を使用して、より堅牢かつプラットフォームに依存しない形でシグナルコードを取得するためのマクロです。これにより、NetBSD上でのシグナルハンドリングが、シグナル発生の原因を正確に識別できるようになり、SigPanicSigNotifyといったGoランタイムの内部ロジックが適切に機能するようになります。

Ucontext から UcontextT への型キャスト変更

  • 変更前: #define SIG_REGS(ctxt) (((Ucontext*)(ctxt))->uc_mcontext)
  • 変更後: #define SIG_REGS(ctxt) (((UcontextT*)(ctxt))->uc_mcontext)

この変更は、ucontext_t構造体へのポインタをキャストする際の型名をUcontextからUcontextTに変更しています。Goランタイムは、C言語の構造体をGoの型にマッピングする際に、特定の型エイリアスを使用することがあります。NetBSDの特定のバージョンやアーキテクチャにおいて、Goランタイムが期待するucontextの型がUcontextTとして定義されていたか、あるいはGoランタイム内部の型定義が変更された可能性があります。この修正により、ctxtポインタが正しい型にキャストされ、uc_mcontextフィールドへのアクセスが正しく行われるようになります。uc_mcontextは、シグナル発生時のCPUレジスタの状態を含む重要な情報です。

レジスタ定義の修正 (REG_ESP -> REG_UESP, REG_EFLAGS -> REG_EFL)

  • 変更前:
    • #define SIG_ESP(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_ESP])
    • #define SIG_EFLAGS(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EFLAGS])
  • 変更後:
    • #define SIG_ESP(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_UESP])
    • #define SIG_EFLAGS(info, ctxt) (SIG_REGS(ctxt).__gregs[REG_EFL])

これらの変更は、32ビットNetBSD環境におけるスタックポインタ(ESP)とEFLAGSレジスタへのアクセス方法を修正しています。ucontext_t構造体内の__gregs配列は、CPUレジスタの値を格納していますが、そのインデックスやレジスタのシンボリック名(REG_ESP, REG_EFLAGSなど)はOSやアーキテクチャによって異なります。 NetBSDの386アーキテクチャでは、スタックポインタはREG_UESPとして、EFLAGSレジスタはREG_EFLとして定義されていることが判明したため、これらのマクロが修正されました。これにより、シグナルハンドラがシグナル発生時の正確なスタックポインタとCPUフラグの状態を取得できるようになり、Goプログラムのスタックトレースや実行コンテキストの復元が正しく行われるようになります。

これらの修正は、GoランタイムがNetBSDのシステムレベルのインターフェースと正確に連携し、シグナルハンドリングの堅牢性とクロスプラットフォーム互換性を確保するために不可欠です。

関連リンク

参考にした情報源リンク