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

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

このコミットは、Goランタイムにおけるコアダンプ生成時のシグナル処理に関する改善です。具体的には、コアダンプを試みる際にブロックされているシグナルをアンブロックする機能を追加しています。これにより、プログラムがクラッシュしてコアダンプを生成しようとする際に、シグナルが原因でその処理が妨げられることを防ぎ、より確実にコアダンプが生成されるようにします。

コミット

commit 0097d30c9715a524aabf62778927d2cac8b7dd35
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Thu Dec 19 20:45:05 2013 -0500

    runtime: unblock signals when we try to core dump
    Fixes #6988.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/44070046

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

https://github.com/golang/go/commit/0097d30c9715a524aabf62778927d2cac8b7dd35

元コミット内容

runtime: unblock signals when we try to core dump
Fixes #6988.

変更の背景

このコミットの背景には、Goプログラムがクラッシュした際に、デバッグのために生成されるコアダンプが、特定の条件下で正しく生成されないという問題がありました。具体的には、Issue #6988で報告された問題に対応しています。

Goランタイムは、プログラムの異常終了時にデバッグ情報を収集するためにコアダンプを生成しようとします。しかし、このコアダンプ生成処理中に、プロセスが特定のシグナル(例えば、SIGSEGVSIGABRTなど、クラッシュを引き起こしたシグナル自身や、それ以外のシグナル)をブロックしている場合、コアダンプの生成が妨げられる可能性がありました。シグナルがブロックされていると、関連するシグナルハンドラが実行されず、システムコールが中断されたり、予期せぬ動作を引き起こしたりすることがあります。

この問題を解決するため、コアダンプを試みる直前に、すべてのシグナルをアンブロックする(ブロックを解除する)ことで、コアダンプ生成処理がシグナルによって中断されることなく、確実に実行されるようにする変更が導入されました。これにより、デバッグに必要なコアダンプが安定して取得できるようになります。

前提知識の解説

このコミットを理解するためには、以下のOSおよびGoランタイムに関する前提知識が必要です。

1. シグナル (Signals)

Unix系OSにおいて、シグナルはソフトウェア割り込みの一種であり、プロセスに対して非同期的にイベントを通知するメカニズムです。シグナルは、以下のような様々なイベントによって生成されます。

  • ハードウェア例外: 無効なメモリアクセス (SIGSEGV)、ゼロ除算 (SIGFPE) など。
  • ソフトウェアイベント: ユーザーからの割り込み (SIGINT)、子プロセスの終了 (SIGCHLD)、タイマーの満了 (SIGALRM) など。
  • システムコール: kill() 関数による明示的なシグナル送信。

プロセスは、シグナルを受信した際に以下のいずれかの動作を行います。

  • デフォルト動作: OSが定義した標準的な動作(例: プロセス終了、コアダンプ生成)。
  • シグナルハンドラ: プログラマが定義した関数を実行。
  • シグナルの無視: シグナルを無視する。ただし、SIGKILLSIGSTOPは無視できない。

2. シグナルマスク (Signal Mask)

各プロセスは「シグナルマスク」と呼ばれるシグナルの集合を持っています。シグナルマスクに含まれるシグナルは「ブロックされている」と見なされ、それらのシグナルがプロセスに送信されても、すぐに配送されず、保留状態になります。シグナルがブロックされている間に複数回同じシグナルが送信されても、通常は1回しか配送されません(リアルタイムシグナルを除く)。シグナルがアンブロックされると、保留されていたシグナルが配送されます。

シグナルマスクは、sigprocmask()システムコール(またはそのバリアント)によって操作されます。

  • SIG_BLOCK: 現在のシグナルマスクに指定されたシグナルを追加する。
  • SIG_UNBLOCK: 現在のシグナルマスクから指定されたシグナルを削除する。
  • SIG_SETMASK: 現在のシグナルマスクを指定されたシグナルセットで完全に置き換える。

3. コアダンプ (Core Dump)

コアダンプは、プログラムがクラッシュした際に、その時点でのプロセスのメモリイメージ(レジスタの値、スタック、ヒープなど)をファイルに書き出したものです。このファイルは、デバッガ(例: GDB)を用いて、クラッシュの原因を特定するための詳細な分析に利用されます。コアダンプは、通常、SIGSEGV (セグメンテーション違反)、SIGABRT (異常終了)、SIGBUS (バスエラー) などのシグナルによってプロセスが終了する際に生成されます。

4. Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理する非常に重要な部分です。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、システムコールインターフェース、シグナルハンドリングなどが含まれます。Goプログラムがクラッシュした場合、Goランタイムはデバッグを支援するために、スタックトレースの出力やコアダンプの生成を試みます。Goランタイムは、OSの低レベルな機能(シグナル処理など)を直接操作することが多く、OSごとの差異を吸収する役割も担っています。

5. sigset_none

sigset_noneは、すべてのシグナルがクリアされた(どのシグナルも含まれていない)シグナルセットを表す定数または変数です。sigprocmask関数でこのセットを使用すると、現在のプロセスのシグナルマスクを空に設定し、すべてのシグナルをアンブロックする効果があります。

技術的詳細

このコミットの技術的詳細は、Goランタイムがコアダンプを生成する際のシグナル処理の堅牢性を高めることにあります。

Goランタイムは、プログラムがクラッシュした際に、runtime·crash()関数を呼び出します。この関数は、デバッグ情報を収集し、最終的にコアダンプを生成するためにSIGABRTシグナルを自身に送信することがあります。しかし、もしこの時点で何らかのシグナルがブロックされていると、SIGABRTの配送が遅延したり、コアダンプ生成に必要な他のシステムコールがシグナルによって中断されたりする可能性がありました。

このコミットでは、runtime·crash()関数内でruntime·unblocksignals()という新しい関数を呼び出すように変更されています。runtime·unblocksignals()関数は、各OS(Darwin, Dragonfly, FreeBSD, Linux, NetBSD, OpenBSD)のランタイムコードに実装され、その役割は、現在のプロセスのシグナルマスクをクリアし、すべてのシグナルをアンブロックすることです。

具体的には、ほとんどのUnix系OSではsigprocmask(SIG_SETMASK, &sigset_none, nil)というシステムコール(またはそれに相当するGoランタイム内のラッパー関数)が呼び出されます。

  • SIG_SETMASK: シグナルマスクを完全に置き換える操作を指定します。
  • &sigset_none: すべてのシグナルがクリアされたシグナルセットへのポインタを渡します。これにより、新しいシグナルマスクは空になります。
  • nil: 以前のシグナルマスクを保存するための引数ですが、ここでは不要なためnilが渡されます。

Linuxの場合、runtime·rtsigprocmaskという関数が使用されています。これは、リアルタイムシグナルを考慮したsigprocmaskのバリアントであり、sizeof sigset_noneという引数も追加されています。これは、rt_sigprocmaskシステムコールがシグナルセットのサイズを明示的に要求するためです。

この変更により、runtime·crash()が実行される際には、プロセスがシグナルによって妨げられることなく、コアダンプ生成処理を続行できるようになります。これにより、デバッグの信頼性が向上し、クラッシュ解析がより容易になります。

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

このコミットのコアとなる変更は、以下の2点です。

  1. runtime·unblocksignals() 関数の追加: 各OS固有のランタイムファイル (os_darwin.c, os_dragonfly.c, os_freebsd.c, os_linux.c, os_netbsd.c, os_openbsd.c) に、runtime·unblocksignals() という新しい関数が追加されました。この関数は、OSのシグナルマスクをクリアする役割を担います。
  2. runtime·crash() から runtime·unblocksignals() の呼び出し: src/pkg/runtime/signal_unix.c 内の runtime·crash() 関数に、runtime·unblocksignals() の呼び出しが追加されました。

以下に、主要な変更箇所の抜粋を示します。

src/pkg/runtime/os_darwin.c (例: Darwin)

void
runtime·unblocksignals(void)
{
	runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);
}

src/pkg/runtime/os_linux.c (例: Linux)

void
runtime·unblocksignals(void)
{
	runtime·rtsigprocmask(SIG_SETMASK, &sigset_none, nil, sizeof sigset_none);
}

src/pkg/runtime/signal_unix.c

void
runtime·crash(void)
{
	// ... (既存のコード) ...

	runtime·unblocksignals(); // ★ この行が追加された
	runtime·setsig(SIGABRT, SIG_DFL, false);
	runtime·raise(SIGABRT);
}

また、各OSのヘッダーファイル (os_darwin.h など) にも runtime·unblocksignals の関数プロトタイプが追加されています。

コアとなるコードの解説

runtime·unblocksignals() 関数

この関数は、Goランタイムが提供するOS固有のシグナル処理ラッパー関数です。その目的は、現在のプロセスのシグナルマスクをリセットし、すべてのシグナルが配送可能な状態にすることです。

  • runtime·sigprocmask(SIG_SETMASK, &sigset_none, nil);:

    • runtime·sigprocmask: Goランタイムが提供するsigprocmaskシステムコールのラッパーです。
    • SIG_SETMASK: シグナルマスクの操作モードを指定します。これは、現在のシグナルマスクを、指定されたシグナルセットで完全に置き換えることを意味します。
    • &sigset_none: sigset_noneは、すべてのシグナルがクリアされた(どのシグナルも含まれていない)シグナルセットを表します。この引数を渡すことで、新しいシグナルマスクは空になり、結果としてすべてのシグナルがアンブロックされます。
    • nil: 通常、この引数には以前のシグナルマスクを保存するためのポインタを渡しますが、ここでは以前のマスクを保存する必要がないためnilが指定されています。
  • runtime·rtsigprocmask(SIG_SETMASK, &sigset_none, nil, sizeof sigset_none); (Linuxの場合):

    • Linuxでは、リアルタイムシグナルをサポートするためにrt_sigprocmaskシステムコールが導入されています。Goランタイムはこれをruntime·rtsigprocmaskとしてラップしています。
    • 基本的な機能はsigprocmaskと同じですが、最後の引数としてシグナルセットのサイズ(バイト単位)を明示的に渡す必要があります。これは、異なるバージョンのsigset_t構造体に対応するためです。

runtime·crash() 関数

runtime·crash()関数は、Goプログラムが致命的なエラー(パニックなど)に遭遇した際に呼び出されるGoランタイムの内部関数です。この関数は、デバッグを支援するために、スタックトレースの出力やコアダンプの生成を試みます。

このコミットによって追加されたruntime·unblocksignals();の呼び出しは、runtime·crash()がコアダンプを生成するためにSIGABRTシグナルを自身に送信する直前に行われます。これにより、SIGABRTシグナルがブロックされることなく、確実にプロセスに配送され、デフォルトの動作(コアダンプの生成とプロセス終了)が実行されることが保証されます。

この変更は、Goランタイムの堅牢性を高め、デバッグ時の情報収集能力を向上させる上で非常に重要です。

関連リンク

参考にした情報源リンク

  • sigprocmask man page: https://man7.org/linux/man-pages/man2/sigprocmask.2.html
  • rt_sigprocmask man page: https://man7.org/linux/man-pages/man2/rt_sigprocmask.2.html
  • Signals (Unix): https://en.wikipedia.org/wiki/Signal_(IPC)
  • Core dump: https://en.wikipedia.org/wiki/Core_dump
  • Go Runtime Source Code (relevant files):
    • src/runtime/signal_unix.go (Go言語側のシグナル処理)
    • src/runtime/os_darwin.go (Go言語側のDarwin OS固有処理)
    • src/runtime/os_linux.go (Go言語側のLinux OS固有処理)
    • (C言語で書かれたランタイム部分は、Goのソースツリー内のsrc/runtime/以下の*.cファイル群に分散しています。)
  • Go言語のシグナル処理に関するブログ記事やドキュメント (一般的な情報源)
    • 例: "Go and Signals" (Go公式ブログや関連技術ブログ)
    • 例: "Understanding Go's Runtime Scheduler" (Goのスケジューラとシグナル処理の関連性)
    • 例: "Go's low-level system calls" (Goがどのようにシステムコールをラップしているか)
    • (これらの具体的なURLはコミット時点のものではないため、一般的な情報源として記載)