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

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

このコミットは、Go言語のランタイムにおけるNetBSD/386アーキテクチャでのスタックポインタの取り扱いに関するバグ修正です。具体的には、スタックポインタを操作する際に、ESPレジスタの代わりにUESPレジスタを使用するように変更することで、NetBSD/386上でのテストの失敗を修正しています。

コミット

commit 1b6557a0cf54a901db38b13b78f919b7959286ee
Author: Joel Sing <jsing@google.com>
Date:   Fri Aug 17 21:53:02 2012 +1000

    runtime: fix netbsd/386 stack pointer handling
    
    When manipulating the stack pointer use the UESP register instead
    of the ESP register, since the UESP register is the one that gets
    restored from the machine context. Fixes broken tests on netbsd/386.
    
    R=golang-dev, minux.ma, r, bsiegert
    CC=golang-dev
    https://golang.org/cl/6465054

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

https://github.com/golang/go/commit/1b6557a0cf54a901db38b13b78f919b7959286ee

元コミット内容

Goランタイムにおいて、NetBSD/386環境でのスタックポインタの処理を修正します。スタックポインタを操作する際には、ESPレジスタではなくUESPレジスタを使用します。これは、マシンコンテキストから復元されるのがUESPレジスタであるためです。この修正により、NetBSD/386上での壊れたテストが修正されます。

変更の背景

この変更の背景には、GoランタイムがNetBSD/386環境で正しく動作しないという問題がありました。特に、シグナルハンドリングやコンテキストスイッチの際に、スタックポインタの扱いが適切でなかったことが原因と考えられます。x86アーキテクチャでは、スタックポインタとしてESP(Extended Stack Pointer)レジスタが一般的に使用されますが、特定のOS(この場合はNetBSD)や特定の状況下では、ユーザーモードのスタックポインタを指す別のレジスタ(UESP)が使用されることがあります。

Goランタイムは、ガベージコレクション、ゴルーチンのスケジューリング、シグナルハンドリングなど、低レベルのシステム操作を頻繁に行います。これらの操作では、現在の実行コンテキスト(レジスタの状態、スタックポインタなど)を保存し、必要に応じて復元する必要があります。NetBSD/386環境において、Goランタイムがスタックポインタの保存・復元にESPレジスタを使用していたため、マシンコンテキストから復元されるUESPレジスタとの間に不整合が生じ、結果としてテストが失敗するなどの問題が発生していました。

このコミットは、この不整合を解消し、NetBSD/386環境でのGoランタイムの安定性と正確性を確保することを目的としています。

前提知識の解説

x86アーキテクチャにおけるレジスタとスタック

x86アーキテクチャでは、CPUの内部に多数のレジスタが存在し、プログラムの実行状態を保持します。その中でも、スタック操作に密接に関わるのが以下のレジスタです。

  • ESP (Extended Stack Pointer): 32ビットモードにおけるスタックポインタレジスタです。通常、スタックの最上位(現在のスタックフレームの先頭)を指します。PUSH命令やPOP命令によって自動的に値が変更されます。
  • EBP (Extended Base Pointer): 32ビットモードにおけるベースポインタレジスタです。通常、現在のスタックフレームの基底アドレスを指し、ローカル変数や関数引数へのアクセスに利用されます。

スタックは、プログラムが関数呼び出しやローカル変数の保存に使用するメモリ領域です。通常、スタックはメモリの高いアドレスから低いアドレスに向かって成長します。

シグナルハンドリングとコンテキストスイッチ

オペレーティングシステム(OS)は、プログラムの実行中に発生する様々なイベント(例えば、不正なメモリアクセス、タイマー割り込み、ユーザーからのCtrl+Cなど)を「シグナル」として扱います。シグナルが発生すると、OSは通常のプログラム実行を一時中断し、事前に登録された「シグナルハンドラ」と呼ばれる特別な関数を実行します。

シグナルハンドラが実行される際には、元のプログラムの実行コンテキスト(レジスタの値、スタックポインタ、プログラムカウンタなど)を保存し、シグナルハンドラが終了した後に元のコンテキストを復元する必要があります。このコンテキストの保存と復元は、OSのカーネルによって行われます。

NetBSD/386におけるUESPレジスタ

一般的なx86システムではESPがスタックポインタとして機能しますが、NetBSDのような一部のUNIX系OSでは、ユーザーモードのスタックポインタを明示的に区別するためにUESP(User Extended Stack Pointer)という概念を使用することがあります。これは、カーネルモードとユーザーモードの切り替えや、シグナルハンドリングの際に、ユーザー空間のスタックポインタを正確に管理するために導入されることがあります。

特に、シグナルハンドラが呼び出される際、OSは現在のプロセスのレジスタ状態をucontext_t構造体などのコンテキスト構造体に保存し、シグナルハンドラに渡します。このコンテキスト構造体には、ユーザーモードのスタックポインタとしてUESPの値が格納されており、シグナルハンドラからの復帰時にはこのUESPの値が使用されて元のコンテキストが復元されます。

Goランタイムのような低レベルのコードが、OSのシグナルハンドリング機構と連携する際には、OSが期待するレジスタ(この場合はUESP)を正しく操作することが不可欠となります。

技術的詳細

このコミットは、src/pkg/runtime/signal_netbsd_386.cファイルに対する変更です。このファイルは、NetBSD/386アーキテクチャにおけるGoランタイムのシグナルハンドリングに関連するコードを含んでいます。

変更の核心は、スタックポインタを参照または操作する際に、mc->__gregs[REG_ESP]の代わりにmc->__gregs[REG_UESP]を使用するように修正した点です。ここで、mcMcontextT型のポインタであり、シグナルハンドラに渡されるマシンコンテキスト(レジスタの状態など)を保持しています。__gregsは汎用レジスタの配列であり、REG_ESPREG_UESPはそれぞれのレジスタに対応するインデックスです。

具体的には、以下の箇所が変更されています。

  1. runtime·dumpregs関数: デバッグ目的でレジスタの値をダンプする関数です。ここでespレジスタの値を表示する際に、REG_ESPではなくREG_UESPから値を取得するように変更されました。

    -	runtime·printf("esp     %x\\n", mc->__gregs[REG_ESP]);
    +	runtime·printf("esp     %x\\n", mc->__gregs[REG_UESP]);
    

    この変更は、表示されるスタックポインタの値が、実際にOSが管理しているユーザーモードのスタックポインタと一致するようにするためのものです。

  2. runtime·sighandler関数: シグナルハンドラの本体です。

    • SIGPROFシグナル(プロファイリングシグナル)の処理において、スタックポインタをruntime·sigprof関数に渡す際にREG_ESPからREG_UESPに変更されました。
      -		\t\t\t(uint8*)mc->__gregs[REG_ESP], nil, gp);\
      +		\t\t\t(uint8*)mc->__gregs[REG_UESP], nil, gp);\
      
    • シグナルによるパニック(sigpanic)が発生した場合のスタックポインタの操作において、sp変数の初期化とmc->__gregs[REG_ESP]への代入がmc->__gregs[REG_UESP]に変更されました。
      -		\t\t\tsp = (uintptr*)mc->__gregs[REG_ESP];
      +		\t\t\tsp = (uintptr*)mc->__gregs[REG_UESP];
      -		\t\t\tmc->__gregs[REG_ESP] = (uintptr)sp;
      +		\t\t\tmc->__gregs[REG_UESP] = (uintptr)sp;
      
      この部分は、シグナルハンドラ内でスタックを操作し、パニック発生時のスタックトレースを正しく生成するために、OSが復元するスタックポインタ(UESP)を正確に扱うことが重要であることを示しています。
  3. Throwラベル内の処理: エラー発生時などにトレースバックを行う部分です。ここでも、トレースバックの開始点としてスタックポインタを渡す際にREG_ESPからREG_UESPに変更されました。

    -		\t\t\t(void*)mc->__gregs[REG_ESP], 0, gp);\
    +		\t\t\t(void*)mc->__gregs[REG_UESP], 0, gp);\
    

    これにより、トレースバックが正しいスタックフレームから開始されるようになります。

これらの変更は、NetBSD/386環境において、Goランタイムがシグナルハンドリング中にスタックポインタを操作する際に、OSが期待するUESPレジスタを正しく使用するようにすることで、コンテキストの不整合を防ぎ、プログラムの安定した動作を保証します。

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

src/pkg/runtime/signal_netbsd_386.cファイルにおいて、mc->__gregs[REG_ESP]の参照および代入がmc->__gregs[REG_UESP]に変更されています。

--- a/src/pkg/runtime/signal_netbsd_386.c
+++ b/src/pkg/runtime/signal_netbsd_386.c
@@ -29,7 +29,7 @@ runtime·dumpregs(McontextT *mc)
 	runtime·printf("edi     %x\\n", mc->__gregs[REG_EDI]);
 	runtime·printf("esi     %x\\n", mc->__gregs[REG_ESI]);
 	runtime·printf("ebp     %x\\n", mc->__gregs[REG_EBP]);
-	runtime·printf("esp     %x\\n", mc->__gregs[REG_ESP]);
+	runtime·printf("esp     %x\\n", mc->__gregs[REG_UESP]);
 	runtime·printf("eip     %x\\n", mc->__gregs[REG_EIP]);
 	runtime·printf("eflags  %x\\n", mc->__gregs[REG_EFL]);
 	runtime·printf("cs      %x\\n", mc->__gregs[REG_CS]);
@@ -47,7 +47,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp)
 
 	if(sig == SIGPROF) {
 		runtime·sigprof((uint8*)mc->__gregs[REG_EIP],
-			(uint8*)mc->__gregs[REG_ESP], nil, gp);
+			(uint8*)mc->__gregs[REG_UESP], nil, gp);
 		return;
 	}
 
@@ -71,9 +71,9 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp)
 		// (Otherwise the trace will end at runtime·sigpanic
 		// and we won't get to see who faulted.)
 		if(mc->__gregs[REG_EIP] != 0) {
-			sp = (uintptr*)mc->__gregs[REG_ESP];
+			sp = (uintptr*)mc->__gregs[REG_UESP];
 			*--sp = mc->__gregs[REG_EIP];
-			mc->__gregs[REG_ESP] = (uintptr)sp;
+			mc->__gregs[REG_UESP] = (uintptr)sp;
 		}
 		mc->__gregs[REG_EIP] = (uintptr)runtime·sigpanic;
 		return;
@@ -100,7 +100,7 @@ Throw:
 
 	if(runtime·gotraceback()){
 		runtime·traceback((void*)mc->__gregs[REG_EIP],
-			(void*)mc->__gregs[REG_ESP], 0, gp);
+			(void*)mc->__gregs[REG_UESP], 0, gp);
 		runtime·tracebackothers(gp);
 		runtime·dumpregs(mc);
 	}

コアとなるコードの解説

このコミットのコード変更は、GoランタイムがNetBSD/386環境でシグナルハンドリングを行う際の、スタックポインタの参照と操作に関する根本的な修正です。

Goランタイムは、独自のゴルーチンスタック管理とOSのシグナルハンドリング機構との連携を必要とします。シグナルが発生すると、OSは現在のCPUの状態(レジスタの値など)を保存し、シグナルハンドラを呼び出します。この際、OSはユーザーモードのスタックポインタとしてUESPレジスタの値をコンテキスト情報に格納します。

Goランタイムのシグナルハンドラ(runtime·sighandler)は、このOSから渡されたコンテキスト情報(McontextT *mc)を使用して、現在のスタックの状態を把握し、必要に応じてスタックを操作します。

  • runtime·dumpregs: デバッグ時にレジスタの値を表示する関数です。以前はESPレジスタの値を表示していましたが、NetBSD/386ではUESPが実際のユーザーモードスタックポインタであるため、UESPの値を表示するように変更されました。これにより、デバッグ情報がより正確になります。

  • runtime·sighandler内のSIGPROF処理: プロファイリングシグナルが受信された際に、現在のプログラムカウンタ(REG_EIP)とスタックポインタ(REG_UESP)をruntime·sigprof関数に渡します。これにより、プロファイリングが正しいスタックの状態を基に行われるようになります。

  • runtime·sighandler内のパニック処理: シグナルによってパニックが発生した場合、Goランタイムはスタックを操作して、パニック発生時のスタックトレースを生成するための準備を行います。この操作において、スタックポインタの取得と設定の両方でUESPレジスタを使用するように変更されました。これにより、パニック時のスタックトレースが、OSが認識している正しいスタックの状態を反映するようになります。もしESPを使用し続けていた場合、OSが復元するUESPとGoランタイムが操作するESPとの間に不整合が生じ、スタックトレースが壊れたり、さらなるクラッシュを引き起こす可能性がありました。

  • Throwラベル内のトレースバック処理: エラー発生時などに呼び出されるトレースバック関数(runtime·traceback)にスタックポインタを渡す際も、UESPレジスタを使用するように変更されました。これにより、トレースバックが正しいスタックフレームから開始され、正確なコールスタック情報が提供されます。

これらの変更は、NetBSD/386のOSカーネルがスタックポインタをUESPレジスタとして管理・復元するという特性にGoランタイムを適合させるものです。これにより、GoプログラムがNetBSD/386上でシグナルを正しく処理し、安定して動作することが保証されます。

関連リンク

  • Go言語のランタイムに関するドキュメントやソースコード
  • NetBSDのシグナルハンドリングに関するドキュメント
  • x86アーキテクチャのレジスタに関する情報

参考にした情報源リンク

  • https://golang.org/cl/6465054 (Go Gerrit Code Review)
  • x86アーキテクチャのレジスタに関する一般的な情報源 (例: Intel Software Developer's Manual)
  • NetBSDのシステムコールやカーネルに関するドキュメント (例: NetBSD man pages)
  • Go言語のソースコード (特にsrc/runtimeディレクトリ)

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

このコミットは、Go言語のランタイムにおけるNetBSD/386アーキテクチャでのスタックポインタの取り扱いに関するバグ修正です。具体的には、スタックポインタを操作する際に、ESPレジスタの代わりにUESPレジスタを使用するように変更することで、NetBSD/386上でのテストの失敗を修正しています。

コミット

commit 1b6557a0cf54a901db38b13b78f919b7959286ee
Author: Joel Sing <jsing@google.com>
Date:   Fri Aug 17 21:53:02 2012 +1000

    runtime: fix netbsd/386 stack pointer handling
    
    When manipulating the stack pointer use the UESP register instead
    of the ESP register, since the UESP register is the one that gets
    restored from the machine context. Fixes broken tests on netbsd/386.
    
    R=golang-dev, minux.ma, r, bsiegert
    CC=golang-dev
    https://golang.org/cl/6465054

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

https://github.com/golang/go/commit/1b6557a0cf54a901db38b13b78f919b7959286ee

元コミット内容

Goランタイムにおいて、NetBSD/386環境でのスタックポインタの処理を修正します。スタックポインタを操作する際には、ESPレジスタではなくUESPレジスタを使用します。これは、マシンコンテキストから復元されるのがUESPレジスタであるためです。この修正により、NetBSD/386上での壊れたテストが修正されます。

変更の背景

この変更の背景には、GoランタイムがNetBSD/386環境で正しく動作しないという問題がありました。特に、シグナルハンドリングやコンテキストスイッチの際に、スタックポインタの扱いが適切でなかったことが原因と考えられます。x86アーキテクチャでは、スタックポインタとしてESP(Extended Stack Pointer)レジスタが一般的に使用されますが、特定のOS(この場合はNetBSD)や特定の状況下では、ユーザーモードのスタックポインタを指す別の概念であるUESPが使用されることがあります。

Goランタイムは、ガベージコレクション、ゴルーチンのスケジューリング、シグナルハンドリングなど、低レベルのシステム操作を頻繁に行います。これらの操作では、現在の実行コンテキスト(レジスタの状態、スタックポインタなど)を保存し、必要に応じて復元する必要があります。NetBSD/386環境において、Goランタイムがスタックポインタの保存・復元にESPレジスタの値を使用していたため、OSがマシンコンテキストから復元する際に使用するUESPの値との間に不整合が生じ、結果としてテストが失敗するなどの問題が発生していました。

このコミットは、この不整合を解消し、NetBSD/386環境でのGoランタイムの安定性と正確性を確保することを目的としています。

前提知識の解説

x86アーキテクチャにおけるレジスタとスタック

x86アーキテクチャでは、CPUの内部に多数のレジスタが存在し、プログラムの実行状態を保持します。その中でも、スタック操作に密接に関わるのが以下のレジスタです。

  • ESP (Extended Stack Pointer): 32ビットモードにおけるスタックポインタレジスタです。通常、スタックの最上位(現在のスタックフレームの先頭)を指します。PUSH命令やPOP命令によって自動的に値が変更されます。
  • EBP (Extended Base Pointer): 32ビットモードにおけるベースポインタレジスタです。通常、現在のスタックフレームの基底アドレスを指し、ローカル変数や関数引数へのアクセスに利用されます。

スタックは、プログラムが関数呼び出しやローカル変数の保存に使用するメモリ領域です。通常、スタックはメモリの高いアドレスから低いアドレスに向かって成長します。

シグナルハンドリングとコンテキストスイッチ

オペレーティングシステム(OS)は、プログラムの実行中に発生する様々なイベント(例えば、不正なメモリアクセス、タイマー割り込み、ユーザーからのCtrl+Cなど)を「シグナル」として扱います。シグナルが発生すると、OSは通常のプログラム実行を一時中断し、事前に登録された「シグナルハンドラ」と呼ばれる特別な関数を実行します。

シグナルハンドラが実行される際には、元のプログラムの実行コンテキスト(レジスタの値、スタックポインタ、プログラムカウンタなど)を保存し、シグナルハンドラが終了した後に元のコンテキストを復元する必要があります。このコンテキストの保存と復元は、OSのカーネルによって行われます。

NetBSD/386におけるUESPレジスタ

一般的なx86システムではESPがスタックポインタとして機能しますが、NetBSDのような一部のUNIX系OSでは、ユーザーモードのスタックポインタを明示的に区別するためにUESP(User Extended Stack Pointer)という概念を使用することがあります。これは、i386プロセッサ自体に存在する独立したハードウェアレジスタではなく、NetBSDのカーネルデータ構造、特にucontextまたはmcontext構造体内で使用されるシンボリックな名前またはインデックスです。

シグナルハンドラが呼び出される際、OSは現在のプロセスのレジスタ状態をmcontext構造体などのコンテキスト構造体に保存し、シグナルハンドラに渡します。このコンテキスト構造体には、ユーザーモードのスタックポインタとしてUESPの値が格納されており、シグナルハンドラからの復帰時にはこのUESPの値が使用されて元のコンテキストが復元されます。

Goランタイムのような低レベルのコードが、OSのシグナルハンドリング機構と連携する際には、OSが期待するレジスタ(この場合はUESP)を正しく操作することが不可欠となります。

技術的詳細

このコミットは、src/pkg/runtime/signal_netbsd_386.cファイルに対する変更です。このファイルは、NetBSD/386アーキテクチャにおけるGoランタイムのシグナルハンドリングに関連するコードを含んでいます。

変更の核心は、スタックポインタを参照または操作する際に、mc->__gregs[REG_ESP]の代わりにmc->__gregs[REG_UESP]を使用するように修正した点です。ここで、mcMcontextT型のポインタであり、シグナルハンドラに渡されるマシンコンテキスト(レジスタの状態など)を保持しています。__gregsは汎用レジスタの配列であり、REG_ESPREG_UESPはそれぞれのレジスタに対応するインデックスです。

具体的には、以下の箇所が変更されています。

  1. runtime·dumpregs関数: デバッグ目的でレジスタの値をダンプする関数です。ここでespレジスタの値を表示する際に、REG_ESPではなくREG_UESPから値を取得するように変更されました。

    -	runtime·printf("esp     %x\\n", mc->__gregs[REG_ESP]);
    +	runtime·printf("esp     %x\\n", mc->__gregs[REG_UESP]);
    

    この変更は、表示されるスタックポインタの値が、実際にOSが管理しているユーザーモードのスタックポインタと一致するようにするためのものです。

  2. runtime·sighandler関数: シグナルハンドラの本体です。

    • SIGPROFシグナル(プロファイリングシグナル)の処理において、スタックポインタをruntime·sigprof関数に渡す際にREG_ESPからREG_UESPに変更されました。
      -		\t\t\t(uint8*)mc->__gregs[REG_ESP], nil, gp);\
      +		\t\t\t(uint8*)mc->__gregs[REG_UESP], nil, gp);\
      
    • シグナルによるパニック(sigpanic)が発生した場合のスタックポインタの操作において、sp変数の初期化とmc->__gregs[REG_ESP]への代入がmc->__gregs[REG_UESP]に変更されました。
      -		\t\t\tsp = (uintptr*)mc->__gregs[REG_ESP];
      +		\t\t\tsp = (uintptr*)mc->__gregs[REG_UESP];
      -		\t\t\tmc->__gregs[REG_ESP] = (uintptr)sp;
      +		\t\t\tmc->__gregs[REG_UESP] = (uintptr)sp;
      
      この部分は、シグナルハンドラ内でスタックを操作し、パニック発生時のスタックトレースを正しく生成するために、OSが復元するスタックポインタ(UESP)を正確に扱うことが重要であることを示しています。
  3. Throwラベル内の処理: エラー発生時などにトレースバックを行う部分です。ここでも、トレースバックの開始点としてスタックポインタを渡す際にREG_ESPからREG_UESPに変更されました。

    -		\t\t\t(void*)mc->__gregs[REG_ESP], 0, gp);\
    +		\t\t\t(void*)mc->__gregs[REG_UESP], 0, gp);\
    

    これにより、トレースバックが正しいスタックフレームから開始されるようになります。

これらの変更は、NetBSD/386環境において、Goランタイムがシグナルハンドリング中にスタックポインタを操作する際に、OSが期待するUESPレジスタを正しく使用するようにすることで、コンテキストの不整合を防ぎ、プログラムの安定した動作を保証します。

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

src/pkg/runtime/signal_netbsd_386.cファイルにおいて、mc->__gregs[REG_ESP]の参照および代入がmc->__gregs[REG_UESP]に変更されています。

--- a/src/pkg/runtime/signal_netbsd_386.c
+++ b/src/pkg/runtime/signal_netbsd_386.c
@@ -29,7 +29,7 @@ runtime·dumpregs(McontextT *mc)
 	runtime·printf("edi     %x\\n", mc->__gregs[REG_EDI]);
 	runtime·printf("esi     %x\\n", mc->__gregs[REG_ESI]);
 	runtime·printf("ebp     %x\\n", mc->__gregs[REG_EBP]);
-	runtime·printf("esp     %x\\n", mc->__gregs[REG_ESP]);
+	runtime·printf("esp     %x\\n", mc->__gregs[REG_UESP]);
 	runtime·printf("eip     %x\\n", mc->__gregs[REG_EIP]);
 	runtime·printf("eflags  %x\\n", mc->__gregs[REG_EFL]);
 	runtime·printf("cs      %x\\n", mc->__gregs[REG_CS]);
@@ -47,7 +47,7 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp)
 
 	if(sig == SIGPROF) {
 		runtime·sigprof((uint8*)mc->__gregs[REG_EIP],
-			(uint8*)mc->__gregs[REG_ESP], nil, gp);
+			(uint8*)mc->__gregs[REG_UESP], nil, gp);
 		return;
 	}
 
@@ -71,9 +71,9 @@ runtime·sighandler(int32 sig, Siginfo *info, void *context, G *gp)
 		// (Otherwise the trace will end at runtime·sigpanic
 		// and we won't get to see who faulted.)
 		if(mc->__gregs[REG_EIP] != 0) {
-			sp = (uintptr*)mc->__gregs[REG_ESP];
+			sp = (uintptr*)mc->__gregs[REG_UESP];
 			*--sp = mc->__gregs[REG_EIP];
-			mc->__gregs[REG_ESP] = (uintptr)sp;
+			mc->__gregs[REG_UESP] = (uintptr)sp;
 		}
 		mc->__gregs[REG_EIP] = (uintptr)runtime·sigpanic;
 		return;
@@ -100,7 +100,7 @@ Throw:
 
 	if(runtime·gotraceback()){
 		runtime·traceback((void*)mc->__gregs[REG_EIP],
-			(void*)mc->__gregs[REG_ESP], 0, gp);
+			(void*)mc->__gregs[REG_UESP], 0, gp);
 		runtime·tracebackothers(gp);
 		runtime·dumpregs(mc);
 	}

コアとなるコードの解説

このコミットのコード変更は、GoランタイムがNetBSD/386環境でシグナルハンドリングを行う際の、スタックポインタの参照と操作に関する根本的な修正です。

Goランタイムは、独自のゴルーチンスタック管理とOSのシグナルハンドリング機構との連携を必要とします。シグナルが発生すると、OSは現在のCPUの状態(レジスタの値など)を保存し、シグナルハンドラを呼び出します。この際、OSはユーザーモードのスタックポインタとしてUESPレジスタの値をコンテキスト情報に格納します。

Goランタイムのシグナルハンドラ(runtime·sighandler)は、このOSから渡されたコンテキスト情報(McontextT *mc)を使用して、現在のスタックの状態を把握し、必要に応じてスタックを操作します。

  • runtime·dumpregs: デバッグ時にレジスタの値を表示する関数です。以前はESPレジスタの値を表示していましたが、NetBSD/386ではUESPが実際のユーザーモードスタックポインタであるため、UESPの値を表示するように変更されました。これにより、デバッグ情報がより正確になります。

  • runtime·sighandler内のSIGPROF処理: プロファイリングシグナルが受信された際に、現在のプログラムカウンタ(REG_EIP)とスタックポインタ(REG_UESP)をruntime·sigprof関数に渡します。これにより、プロファイリングが正しいスタックの状態を基に行われるようになります。

  • runtime·sighandler内のパニック処理: シグナルによってパニックが発生した場合、Goランタイムはスタックを操作して、パニック発生時のスタックトレースを生成するための準備を行います。この操作において、スタックポインタの取得と設定の両方でUESPレジスタを使用するように変更されました。これにより、パニック時のスタックトレースが、OSが認識している正しいスタックの状態を反映するようになります。もしESPを使用し続けていた場合、OSが復元するUESPとGoランタイムが操作するESPとの間に不整合が生じ、スタックトレースが壊れたり、さらなるクラッシュを引き起こす可能性がありました。

  • Throwラベル内のトレースバック処理: エラー発生時などに呼び出されるトレースバック関数(runtime·traceback)にスタックポインタを渡す際も、UESPレジスタを使用するように変更されました。これにより、トレースバックが正しいスタックフレームから開始され、正確なコールスタック情報が提供されます。

これらの変更は、NetBSD/386のOSカーネルがスタックポインタをUESPレジスタとして管理・復元するという特性にGoランタイムを適合させるものです。これにより、GoプログラムがNetBSD/386上でシグナルを正しく処理し、安定して動作することが保証されます。

関連リンク

  • Go言語のランタイムに関するドキュメントやソースコード
  • NetBSDのシグナルハンドリングに関するドキュメント
  • x86アーキテクチャのレジスタに関する情報

参考にした情報源リンク