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

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

このコミットは、GoランタイムにおけるLinux/ARMアーキテクチャでのSigaction構造体の定義と、rt_sigactionシステムコールの使用方法に関するバグを修正するものです。具体的には、Sigaction構造体内のsa_maskフィールドのサイズが誤っていた点と、rt_sigactionシステムコールの最後の引数に固定値8が渡されていた点を修正し、より堅牢なシグナルハンドリングを実現しています。また、Linux/386およびLinux/amd64アーキテクチャの関連ファイルも同様の修正が適用され、rt_sigactionの戻り値のチェックも追加されています。

コミット

commit d4c4f4d2c4cdfc9e713383d419b528726cfbbb20
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Jun 20 01:17:03 2012 +0800

    runtime: fix struct Sigaction for Linux/ARM
            if we were to use sizeof(sa.sa_mask) instead of 8 as the last argument
            to rt_sigaction, we would have already fixed this bug, so also updated
            Linux/386 and Linux/amd64 files to use that; also test the return value
            of rt_sigaction.
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/6297087

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

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

元コミット内容

runtime: fix struct Sigaction for Linux/ARM
        if we were to use sizeof(sa.sa_mask) instead of 8 as the last argument
        to rt_sigaction, we would have already fixed this bug, so also updated
        Linux/386 and Linux/amd64 files to use that; also test the return value
        of rt_sigaction.

R=dave, rsc
CC=golang-dev
https://golang.org/cl/6297087

変更の背景

この変更の背景には、Linuxシステムコールであるrt_sigactionの正しい使用方法と、異なるアーキテクチャ間でのシグナルマスクのサイズの違いがあります。

rt_sigactionシステムコールは、特定のシグナルを受信した際のプロセスの動作を変更するために使用されます。このシステムコールは4つの引数を取りますが、その最後の引数sigsetsizeは、actおよびoldact構造体内のシグナルセット(sa_mask)のサイズをバイト単位で指定する必要があります。この値は、現在のところsizeof(sigset_t)である必要があります。

元のコードでは、Linux/ARMアーキテクチャにおいてSigaction構造体のsa_maskフィールドがuint32(4バイト)として定義されていました。しかし、Linuxのsigset_t型は通常64ビット(8バイト)のサイズを持つことが多く、特にARMアーキテクチャにおいても同様です。この不一致により、rt_sigactionシステムコールに渡されるsa_maskのサイズが正しくなく、シグナルハンドリングが意図した通りに機能しない可能性がありました。

さらに、rt_sigactionシステムコールの最後の引数に、マジックナンバーである固定値8が直接渡されていました。これは、sigset_tのサイズが8バイトであることを前提としたものでしたが、この固定値を使用する代わりにsizeof(sa.sa_mask)を使用していれば、sa_maskの定義が誤っていたとしても、このバグは早期に発見され、修正されていた可能性がありました。

このコミットは、これらの問題を解決し、GoランタイムがLinuxシステムコールをより正確かつ堅牢に利用できるようにすることを目的としています。また、Linux/386およびLinux/amd64アーキテクチャの関連ファイルにも同様の修正を適用することで、コードの一貫性と移植性を向上させています。

前提知識の解説

1. シグナル (Signal)

シグナルは、Unix系OSにおいてプロセス間で非同期にイベントを通知するためのメカニズムです。例えば、Ctrl+Cを押すとSIGINTシグナルがプロセスに送られ、プロセスを終了させることができます。シグナルには様々な種類があり、それぞれ異なるイベントに対応しています。

2. sigactionrt_sigaction システムコール

  • sigaction: 古いシグナルハンドリングのシステムコールです。シグナルに対するアクション(シグナルを無視する、デフォルトの動作を行う、カスタムのハンドラ関数を実行する)を設定するために使用されます。
  • rt_sigaction: sigactionの改良版で、リアルタイムシグナルや拡張されたsigset_t型をサポートするために導入されました。Goランタイムのような低レベルのシステムプログラミングでは、通常こちらのシステムコールが使用されます。

rt_sigactionシステムコールのプロトタイプは以下のようになります(簡略化):

int rt_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact, size_t sigsetsize);
  • signum: 処理するシグナル番号。
  • act: 新しいシグナルアクションを指定するstruct sigactionへのポインタ。
  • oldact: 以前のシグナルアクションを保存するstruct sigactionへのポインタ(不要な場合はNULL)。
  • sigsetsize: actおよびoldact構造体内のシグナルセット(sa_mask)のサイズをバイト単位で指定します。この値は、sizeof(sigset_t)である必要があります。

3. struct sigaction

struct sigactionは、シグナルハンドリングの詳細を設定するための構造体です。主要なフィールドは以下の通りです。

  • sa_handler または sa_sigaction: シグナルハンドラ関数へのポインタ。
  • sa_mask: シグナルハンドラが実行されている間にブロックされるシグナルのセットを指定するsigset_t型の変数。
  • sa_flags: シグナルハンドリングの動作を変更するためのフラグ。

4. sigset_tsa_mask

  • sigset_t: シグナルのセットを表すデータ型です。通常、ビットマスクとして実装され、各ビットが特定のシグナルに対応します。
  • sa_mask: struct sigaction内のフィールドで、シグナルハンドラが実行されている間に追加でブロックされるシグナルを指定します。これにより、シグナルハンドラが実行中に他のシグナルによって中断されるのを防ぐことができます。sa_maskのサイズは、システムによって定義されるsigset_tのサイズと一致している必要があります。Linuxでは、多くのアーキテクチャでsigset_tは64ビット(8バイト)です。

5. Goランタイムにおけるシグナルハンドリング

Goプログラムは、os/signalパッケージを通じてOSシグナルと対話できます。しかし、Goランタイム自体は、ガベージコレクション、プロファイリング、パニック処理などの内部的な目的のために、低レベルでシグナルを処理します。この低レベルのシグナルハンドリングは、C言語で書かれたGoランタイムのコード(src/pkg/runtimeディレクトリ以下)で行われ、OSのシステムコールを直接呼び出します。

6. Linux/ARMアーキテクチャの特性

Linuxは様々なCPUアーキテクチャをサポートしており、ARMもその一つです。一般的に、シグナルハンドリングの概念はPOSIX標準に準拠しているため、アーキテクチャ間で大きな違いはありません。しかし、シグナルの数値や、低レベルのカーネル実装の詳細、データ型のサイズ(特にsigset_tのようなシステム依存の型)は、アーキテクチャによって異なる場合があります。このコミットでは、Linux/ARMにおけるsigset_tのサイズが他のアーキテクチャ(386, amd64)と異なる可能性、またはその定義が不正確であったことが問題の根源となっています。

技術的詳細

このコミットの技術的な核心は、rt_sigactionシステムコールの第4引数であるsigsetsizeの正確な値の指定と、Sigaction構造体におけるsa_maskフィールドの適切なデータ型です。

  1. rt_sigactionsigsetsize引数: rt_sigactionシステムコールは、シグナルハンドラを設定する際に、シグナルマスクのサイズを明示的にカーネルに伝える必要があります。このサイズは、sigset_t型の実際のサイズと厳密に一致していなければなりません。もしこの値が誤っていると、カーネルはシグナルマスクを正しく解釈できず、シグナルハンドリングが予期せぬ動作をする可能性があります。例えば、シグナルが正しくブロックされなかったり、メモリ破壊が発生したりする恐れがあります。 元のコードでは、この引数に固定値8が渡されていました。これは、多くのシステムでsigset_tが8バイトであるという一般的な知識に基づいていたと考えられます。しかし、これはハードコーディングであり、将来的なアーキテクチャの変更や、特定の環境でのsigset_tの異なるサイズに対応できません。

  2. Sigaction構造体のsa_maskフィールドの型: Linux/ARMのsrc/pkg/runtime/defs_linux_arm.hファイルでは、Sigaction構造体のsa_maskフィールドがuint32(4バイト)として定義されていました。しかし、Linuxのsigset_tは通常64ビット(8バイト)です。この不一致が、rt_sigactionシステムコールに渡されるsa_maskのサイズが正しくないという問題を引き起こしていました。 このコミットでは、sa_maskの型をuint32からuint64に変更することで、Sigaction構造体内のsa_masksigset_tの実際のサイズと一致するように修正しています。

  3. sizeof(sa.sa_mask)の使用: このコミットの重要な改善点は、rt_sigactionシステムコールの第4引数に固定値8を渡す代わりに、sizeof(sa.sa_mask)を使用するように変更したことです。

    • sizeof演算子を使用することで、コンパイル時にsa.sa_maskの実際のサイズが計算され、その値がシステムコールに渡されます。
    • これにより、sa_maskの型定義が変更された場合でも、rt_sigactionに渡されるサイズが自動的に更新されるため、将来的な互換性が向上し、同様のバグが再発するのを防ぐことができます。
    • コミットメッセージにあるように、「もしsizeof(sa.sa_mask)を最後の引数として使用していれば、このバグは既に修正されていたはずだ」という記述は、この変更の重要性を示しています。
  4. rt_sigactionの戻り値のチェック: システムコールは失敗する可能性があります。rt_sigactionも例外ではありません。このコミットでは、rt_sigactionの呼び出しが成功したかどうかをチェックし、失敗した場合にはruntime·throw("rt_sigaction failure");を呼び出してランタイムパニックを発生させるように変更しています。これにより、シグナルハンドリングの設定に問題が発生した場合に、Goプログラムがより早期に異常を検知し、診断できるようになります。

これらの変更は、GoランタイムがLinuxシステムとより正確に連携し、シグナルハンドリングの堅牢性と信頼性を向上させる上で不可欠です。特に、異なるアーキテクチャ間での移植性を考慮する上で、データ型のサイズに関する正確な知識と、それに基づいたコードの実装が重要であることを示しています。

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

このコミットでは、以下の5つのファイルが変更されています。

  1. src/pkg/runtime/defs_linux_arm.h:

    • struct Sigaction内のsa_maskフィールドの型がuint32からuint64に変更されました。
  2. src/pkg/runtime/os_linux.h:

    • runtime·rt_sigaction関数の宣言が、戻り値の型をvoidからint32に変更されました。これは、rt_sigactionシステムコールが成功/失敗を示す整数値を返すためです。
  3. src/pkg/runtime/signal_linux_386.c:

    • runtime·setsig関数内でruntime·rt_sigactionを呼び出す際、最後の引数が固定値8からsizeof(sa.sa_mask)に変更されました。
    • runtime·rt_sigactionの戻り値がチェックされ、0でない場合はruntime·throw("rt_sigaction failure");が呼び出されるようになりました。
  4. src/pkg/runtime/signal_linux_amd64.c:

    • runtime·setsig関数内でruntime·rt_sigactionを呼び出す際、最後の引数が固定値8からsizeof(sa.sa_mask)に変更されました。
    • runtime·rt_sigactionの戻り値がチェックされ、0でない場合はruntime·throw("rt_sigaction failure");が呼び出されるようになりました。
  5. src/pkg/runtime/signal_linux_arm.c:

    • runtime·setsig関数内でruntime·rt_sigactionを呼び出す際、最後の引数が固定値8からsizeof(sa.sa_mask)に変更されました。
    • runtime·rt_sigactionの戻り値がチェックされ、0でない場合はruntime·throw("rt_sigaction failure");が呼び出されるようになりました。

コアとなるコードの解説

src/pkg/runtime/defs_linux_arm.h の変更

--- a/src/pkg/runtime/defs_linux_arm.h
+++ b/src/pkg/runtime/defs_linux_arm.h
@@ -143,6 +143,6 @@ struct Sigaction {
 	void *sa_handler;
 	uint32 sa_flags;
 	void *sa_restorer;
-	uint32 sa_mask;
+	uint64 sa_mask;
 };
 #pragma pack off

この変更は、Linux/ARMアーキテクチャにおけるSigaction構造体のsa_maskフィールドのデータ型をuint32(32ビット符号なし整数)からuint64(64ビット符号なし整数)に修正しています。これは、Linuxシステムコールが期待するsigset_tのサイズが通常64ビットであるため、Goランタイムの内部定義をこれに合わせることで、シグナルマスクが正しく扱われるようにするための重要な修正です。

src/pkg/runtime/os_linux.h の変更

--- a/src/pkg/runtime/os_linux.h
+++ b/src/pkg/runtime/os_linux.h
@@ -10,7 +10,7 @@ int32	runtime·futex(uint32*, int32, uint32, Timespec*, uint32*, uint32);
 int32	runtime·clone(int32, void*, M*, G*, void(*)(void));
 
 struct Sigaction;
-void	runtime·rt_sigaction(uintptr, struct Sigaction*, void*, uintptr);
+int32	runtime·rt_sigaction(uintptr, struct Sigaction*, void*, uintptr);
 void	runtime·setsig(int32, void(*)(int32, Siginfo*, void*, G*), bool);
 void	runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp);

runtime·rt_sigaction関数の宣言がvoidからint32に戻り値の型を変更しています。これは、rt_sigactionシステムコールが成功時には0を返し、失敗時にはエラーコードを返すため、その戻り値をGoランタイムが適切に処理できるようにするための変更です。

src/pkg/runtime/signal_linux_386.c, src/pkg/runtime/signal_linux_amd64.c, src/pkg/runtime/signal_linux_arm.c の変更

これら3つのファイルにおける変更は同様のパターンに従っています。以下にsignal_linux_386.cの例を示します。

--- a/src/pkg/runtime/signal_linux_386.c
+++ b/src/pkg/runtime/signal_linux_386.c
@@ -129,7 +129,8 @@ runtime·setsig(int32 i, void (*fn)(int32, Siginfo*, void*, G*), bool restart)
 	if(fn == runtime·sighandler)
 		fn = (void*)runtime·sigtramp;
 	sa.k_sa_handler = fn;
-	runtime·rt_sigaction(i, &sa, nil, 8);
+	if(runtime·rt_sigaction(i, &sa, nil, sizeof(sa.sa_mask)) != 0)
+		runtime·throw("rt_sigaction failure");
 }

このコードスニペットは、runtime·setsig関数内でruntime·rt_sigactionシステムコールを呼び出す部分の変更を示しています。

  1. sizeof(sa.sa_mask)への変更: 以前はruntime·rt_sigaction(i, &sa, nil, 8);のように、最後の引数に固定値8が渡されていました。これは、sigset_tのサイズが8バイトであることを前提としたマジックナンバーです。 変更後、この引数はsizeof(sa.sa_mask)となりました。これにより、sa.sa_maskフィールドの実際のサイズ(このコミットでuint64に変更されたため8バイト)が動的に計算され、システムコールに渡されます。これにより、コードの堅牢性が向上し、sa_maskの型定義が将来変更された場合でも、この部分のコードを修正する必要がなくなります。

  2. 戻り値のチェックとエラーハンドリング: if(runtime·rt_sigaction(i, &sa, nil, sizeof(sa.sa_mask)) != 0)という条件が追加されました。これは、rt_sigactionシステムコールの戻り値をチェックしています。システムコールが成功すると0を返すため、0以外の値が返された場合はエラーが発生したことを意味します。 エラーが発生した場合、runtime·throw("rt_sigaction failure");が呼び出され、Goランタイムがパニックを起こしてプログラムを終了させます。これにより、シグナルハンドリングの設定に問題がある場合に、Goプログラムが異常な状態のまま実行を続けることを防ぎ、問題の早期発見とデバッグを支援します。

これらの変更は、GoランタイムがLinuxシステムコールをより正確かつ安全に利用するための重要なステップであり、特にクロスアーキテクチャの互換性と堅牢性を高める上で不可欠です。

関連リンク

参考にした情報源リンク