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

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

このコミットは、GoランタイムにおけるNetBSD/ARMアーキテクチャ向けのシグナルハンドリングに関するバグ修正です。具体的には、runtime.sigreturn_tramp 関数がm->tls[0]を使用してucontextポインタを保存する方法が再入可能(re-entry safe)ではなかった問題と、非Goスレッドでシグナルが受信された際にucontextが適切に設定されない問題に対処しています。これにより、misc/cgo/testが特定の条件下でハングアップする問題(Go issue 5337に関連)が解決されました。

コミット

commit e55517259217b29383d1a77e3c70e5cd19fa778b
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Jul 3 00:33:38 2013 +0800

    runtime: fix runtime.sigreturn_tramp for NetBSD/ARM
    using m->tls[0] to save ucontext pointer is not re-entry safe, and
    the old code didn't set it before the early return when signal is
    received on non-Go threads.
    
    so misc/cgo/test used to hang when testing issue 5337.
    
    R=golang-dev, bradfitz, rsc
    CC=golang-dev
    https://golang.org/cl/10076045

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

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

元コミット内容

runtime: fix runtime.sigreturn_tramp for NetBSD/ARM
using m->tls[0] to save ucontext pointer is not re-entry safe, and
the old code didn't set it before the early return when signal is
received on non-Go threads.

so misc/cgo/test used to hang when testing issue 5337.

変更の背景

この変更は、GoランタイムがNetBSD/ARM環境でシグナルを処理する際の既存のバグを修正するために行われました。特に、runtime.sigreturn_tramp関数がucontextポインタを保存するためにm->tls[0](スレッドローカルストレージ)を使用していた方法に問題がありました。

問題の核心は以下の2点です。

  1. 再入可能性(Re-entry Safety)の欠如: m->tls[0]ucontextポインタを保存する方法は、シグナルハンドラが再入された場合に安全ではありませんでした。シグナルハンドラが実行中に別のシグナルが到着し、同じハンドラが再度呼び出される(再入する)と、m->tls[0]に保存されたucontextポインタが上書きされ、元のコンテキストへの復元が正しく行えなくなる可能性がありました。
  2. 非Goスレッドでのucontext設定の不備: Goランタイムが管理していないCGOなどによって作成された非Goスレッドでシグナルが受信された場合、古いコードでは早期リターン(early return)の前にucontextm->tls[0]に適切に設定されていませんでした。これにより、シグナルハンドラからの復帰時に不正な状態になる可能性がありました。

これらの問題が複合的に作用し、misc/cgo/test内のテストがハングアップするという具体的な症状として現れていました。このハングアップは、Go issue 5337に関連するテストケースで特に顕著でした。Go issue 5337は、Goランタイムが外部スレッド(CGOなど)で受信したSIGPROFシグナルを適切に処理できるかどうかに焦点を当てた問題であり、このコミットはその問題の解決に貢献しています。

前提知識の解説

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

  • シグナル (Signal): オペレーティングシステムがプロセスに送信する非同期通知です。プログラムの異常終了、ユーザーからの割り込み、タイマーの満了など、様々なイベントをプロセスに伝えるために使用されます。シグナルを受信したプロセスは、デフォルトの動作を実行するか、登録されたシグナルハンドラを実行します。
  • ucontext (User Context): POSIXシステムにおけるユーザーコンテキストを表すデータ構造です。これには、CPUレジスタの状態、シグナルマスク、スタックポインタなど、プログラムの実行状態を完全に復元するために必要な情報が含まれています。シグナルハンドラから元の実行フローに戻る際や、コルーチン、スレッドの切り替えなどで使用されます。
  • sigreturn_tramp (Signal Return Trampoline): シグナルハンドラから元の実行コンテキストに復帰するためのアセンブリコードの小さな断片(トランポリン)です。シグナルハンドラが終了すると、通常はこのトランポリンが呼び出され、ucontext構造体を使用してプロセスの状態をシグナル受信前の状態に復元し、元の実行を再開します。
  • sigtramp (Signal Trampoline): シグナルが到着した際に、カーネルからシグナルハンドラに制御を移す前に実行されるアセンブリコードの小さな断片です。このコードは、ucontextなどの情報をスタックに保存したり、シグナルハンドラに渡す引数を設定したりする役割を担います。
  • m->tls[0] (Thread Local Storage): Goランタイムにおけるスレッドローカルストレージ(TLS)の一種です。mはGoランタイムのM(Machine)構造体を表し、これはOSスレッドに対応します。tlsはスレッドごとに固有のデータを保存するための領域であり、tls[0]はその配列の最初の要素を指します。Goランタイムは、特定のOSスレッドに関連する情報をここに保存することがあります。
  • NetBSD/ARM: NetBSDはオープンソースのUnix系オペレーティングシステムであり、ARMはAdvanced RISC Machinesの略で、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。このコミットは、NetBSD上でARMプロセッサを使用する環境に特化した修正です。
  • CGO: Go言語の機能の一つで、C言語のコードをGoプログラムから呼び出すことを可能にします。CGOを使用すると、Goランタイムが直接管理しないOSスレッドが生成されることがあり、これらのスレッドでのシグナルハンドリングはGoランタイムにとって特別な考慮が必要になります。
  • Go issue 5337: GoランタイムがCGOによって生成された外部スレッドでSIGPROFシグナル(プロファイリングなどで使用されるタイマーシグナル)を受信した際に、適切に処理できない問題に関するGoのバグトラッカー上の課題です。この問題は、Goランタイムが非Goスレッドのコンテキストを正しく保存・復元できないことに起因していました。

技術的詳細

このコミットの技術的な詳細は、NetBSD/ARMにおけるシグナルハンドリングのメカニズムと、Goランタイムがそれをどのように利用していたか、そしてその問題点を修正する方法にあります。

旧来の実装の問題点:

  1. m->tls[0]の不適切な使用: 以前のruntime.sigtramp関数では、シグナルハンドラが呼び出される前に、受信したucontextポインタをm->tls[0]に保存していました。そして、runtime.sigreturn_trampでは、このm->tls[0]からucontextポインタをロードしてsys_setcontextシステムコールを呼び出し、元のコンテキストに復帰しようとしていました。
    • 再入可能性の問題: シグナルハンドラが実行中に別のシグナルが到着し、同じシグナルハンドラが再入された場合、m->tls[0]に保存された最初のucontextポインタが、2番目のシグナルハンドラによって上書きされてしまいます。これにより、最初のシグナルハンドラが終了してsigreturn_trampが呼び出された際に、誤ったucontextポインタをロードしてしまい、プログラムがクラッシュしたり、予期せぬ動作をしたりする可能性がありました。
    • 非Goスレッドの問題: CGOなどによって生成された非Goスレッドでシグナルが受信された場合、GoランタイムのM(Machine)構造体やTLSが適切に初期化されていない、またはGoランタイムの管理下にない場合があります。このような状況でm->tls[0]ucontextポインタを保存しようとすると、不正なメモリアクセスが発生したり、そもそもmが有効な状態でないためにucontextが保存されなかったりする問題がありました。特に、シグナルが非Goスレッドで受信され、かつ早期リターンパスが実行される場合、m->tls[0]が設定されないままsigreturn_trampが呼び出される可能性があり、結果としてハングアップやクラッシュを引き起こしていました。

新しい実装の解決策:

このコミットでは、ucontextポインタをm->tls[0]に保存するという危険なアプローチを完全に廃止しました。代わりに、sigreturn_trampが呼び出された時点で、ucontext構造体がスタック上に配置されているというNetBSDのシグナルハンドリング規約を利用します。

  1. sigreturn_trampの変更:
    • 以前はMOVW m_tls(m), R0m->tls[0]からucontextポインタをロードしていました。
    • 新しいコードでは、ADD $0x80, R13, R0という命令を使用しています。
      • R13はARMアーキテクチャにおけるスタックポインタ(SP)レジスタです。
      • シグナルが到着すると、カーネルはシグナル情報(siginfo_t)とucontext_t構造体を現在のスタックフレームにプッシュします。
      • sigreturn_trampが呼び出された時点では、SPR13)はsiginfo構造体の先頭を指しています。
      • NetBSD/ARMでは、ucontext_t構造体はsiginfo_t構造体の直後に配置され、そのサイズは0x80バイト(128バイト)です。
      • したがって、SP0x80を加算することで、スタック上のucontext構造体の先頭アドレスを正確に計算し、それをR0レジスタ(システムコール呼び出し規約で最初の引数に使用される)にロードします。
    • これにより、ucontextポインタをスレッドローカルストレージに依存することなく、直接スタックから取得できるようになり、再入可能性の問題と非Goスレッドでの初期化不足の問題が同時に解決されます。
  2. sigtrampの変更:
    • 以前はMOVW R2, m_tls(m)という命令でucontextポインタ(R2に格納されている)をm->tls[0]に保存していました。
    • このコミットでは、この行を完全に削除しています。これにより、ucontextポインタをTLSに保存するという問題のある動作がなくなります。

この修正により、GoランタイムはNetBSD/ARM環境でより堅牢なシグナルハンドリングを実現し、特にCGOを使用するアプリケーションにおける安定性が向上しました。

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

--- a/src/pkg/runtime/sys_netbsd_arm.s
+++ b/src/pkg/runtime/sys_netbsd_arm.s
@@ -174,9 +174,9 @@ TEXT runtime·sigprocmask(SB),7,$0
 	RET
 
 TEXT runtime·sigreturn_tramp(SB),7,$-4
-\t// in runtime·sigtramp, we saved ucontext into m->tls[0],
-\t// here we just load it and call sys_setcontext
-\tMOVW m_tls(m), R0
+\t// on entry, SP points to siginfo, we add sizeof(ucontext)
+\t// to SP to get a pointer to ucontext.
+\tADD $0x80, R13, R0 // 0x80 == sizeof(UcontextT)
 	SWI $0xa00134	// sys_setcontext
 	// something failed, we have to exit
 	MOVW $0x4242, R0 // magic return number
@@ -223,9 +223,6 @@ TEXT runtime·sigtramp(SB),7,$24
 	MOVW R1, 8(R13) // info
 	MOVW R2, 12(R13) // context
 	MOVW R4, 16(R13) // gp
-\t// we also save the ucontext into m->tls[0] for easy
-\t// signal return
-\tMOVW R2, m_tls(m)
 
 	BL runtime·sighandler(SB)
 

コアとなるコードの解説

このコミットは、src/pkg/runtime/sys_netbsd_arm.sファイル内の2つのアセンブリ関数、runtime·sigreturn_trampruntime·sigtrampに対する変更を含んでいます。

TEXT runtime·sigreturn_tramp(SB),7,$-4 の変更

  • 変更前:

    // in runtime·sigtramp, we saved ucontext into m->tls[0],
    // here we just load it and call sys_setcontext
    MOVW m_tls(m), R0
    

    この部分では、以前にruntime·sigtrampm->tls[0]に保存されたucontextポインタをR0レジスタにロードしていました。m_tls(m)は、現在のM(Machine)構造体に関連付けられたスレッドローカルストレージのベースアドレスを指し、そこからucontextポインタを取得していました。

  • 変更後:

    // on entry, SP points to siginfo, we add sizeof(ucontext)
    // to SP to get a pointer to ucontext.
    ADD $0x80, R13, R0 // 0x80 == sizeof(UcontextT)
    

    この変更がこのコミットの核心です。

    • R13はARMアーキテクチャにおけるスタックポインタ(SP)レジスタです。
    • NetBSDのシグナルハンドリング規約では、シグナルハンドラが呼び出される際、カーネルはスタックにシグナル情報(siginfo_t)とユーザーコンテキスト(ucontext_t)をプッシュします。sigreturn_trampが実行される時点では、R13(SP)はsiginfo_t構造体の先頭を指しています。
    • ucontext_t構造体はsiginfo_tの直後に配置されており、そのサイズは0x80バイト(128バイト)です。
    • したがって、ADD $0x80, R13, R0という命令は、現在のスタックポインタR130x80を加算することで、スタック上のucontext_t構造体の先頭アドレスを計算し、その結果をR0レジスタに格納します。
    • R0は通常、システムコールや関数呼び出しの最初の引数を渡すために使用されるレジスタであるため、このucontextポインタは後続のSWI $0xa00134sys_setcontextシステムコール)に渡されます。
    • この変更により、ucontextポインタをm->tls[0]に依存することなく、直接スタックから取得できるようになり、再入可能性の問題と非Goスレッドでの初期化不足の問題が解決されます。

TEXT runtime·sigtramp(SB),7,$24 の変更

  • 変更前:

    // we also save the ucontext into m->tls[0] for easy
    // signal return
    MOVW R2, m_tls(m)
    

    この行は、runtime·sigtrampucontextポインタ(R2レジスタに格納されている)をm->tls[0]に保存していた部分です。この保存は、sigreturn_trampucontextを簡単に取得できるようにするためのものでしたが、前述の通り再入可能性と非Goスレッドの問題を引き起こしていました。

  • 変更後: この行は完全に削除されています。これにより、ucontextポインタをm->tls[0]に保存するという問題のある動作がなくなります。sigreturn_trampがスタックから直接ucontextを取得するようになったため、この保存は不要になりました。

これらの変更により、NetBSD/ARM環境におけるGoランタイムのシグナルハンドリングがより堅牢になり、特にCGOを使用する際の安定性が向上しました。

関連リンク

参考にした情報源リンク