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

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

このコミットは、Go言語のランタイムにおけるLinux/ARMアーキテクチャでのビルド問題を修正するものです。具体的には、スタックトレースバック処理、特にシグナルハンドラがsigpanicを偽装呼び出しする際のlr(リンクレジスタ)とpc(プログラムカウンタ)の扱いに関するバグを修正しています。

コミット

commit 6c54bf991680f937db8fb74e8da4b26a757e8242
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Fri Feb 8 13:24:38 2013 +0800

    runtime: fix build for Linux/ARM
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/7299055

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

https://github.com/golang/go/commit/6c54bf991680f937db8fb74e8da4b26a757e8242

元コミット内容

runtime: fix build for Linux/ARM

変更の背景

このコミットの背景には、Go言語のランタイムがLinux/ARMアーキテクチャ上で正しくビルドまたは実行できない問題がありました。特に、シグナルハンドラがパニック(sigpanic)を処理する際に、スタックトレースバックのメカニズムが期待通りに機能しないことが原因と考えられます。

Go言語のランタイムは、プログラムの実行中に発生したエラーやパニックを診断するために、正確なスタックトレースバックを生成する必要があります。これは、どの関数がどの順序で呼び出されたかを示すことで、開発者が問題の原因を特定するのに役立ちます。ARMアーキテクチャのような特定のプラットフォームでは、レジスタ(特にlrpc)の扱いが他のアーキテクチャと異なる場合があり、これがスタックトレースバックの正確性を損なう原因となることがあります。

元のコードでは、シグナルハンドラがsigpanicを偽装呼び出しする際に、lr(リンクレジスタ)の値をスタックに保存し、その値をpc(プログラムカウンタ)として読み出すという処理が行われていました。しかし、この処理がLinux/ARM環境で問題を引き起こし、ビルドエラーやランタイムエラーにつながっていたと推測されます。この修正は、sigpanic発生時のpclrの復元ロジックを改善し、ARM環境でのスタックトレースバックの正確性を確保することを目的としています。

前提知識の解説

Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、スタック管理、そしてパニックやシグナル処理などが含まれます。ランタイムは、GoプログラムがOSと対話するための低レベルなインターフェースを提供します。

ARMアーキテクチャ

ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。x86などのCISCアーキテクチャとは異なり、命令セットがシンプルで、電力効率が高いという特徴があります。Go言語は、ARMを含む多くのアーキテクチャをサポートしています。

スタックトレースバック (Stack Traceback)

スタックトレースバックは、プログラムの実行中に特定の時点(通常はエラーやパニック発生時)で、現在実行中の関数から呼び出し元の関数へと遡って、関数の呼び出し履歴を表示するものです。これにより、プログラムの実行フローを理解し、問題の原因を特定するのに役立ちます。

sigpanic

Go言語において、sigpanicはランタイムがシグナル(例えば、不正なメモリアクセスを示すSIGSEGVなど)を受け取った際に、Goのパニック機構に変換するために内部的に使用される関数です。OSからのシグナルをGoのパニックとして処理することで、Goプログラムは一貫したエラー処理メカニズムを持つことができます。シグナルハンドラは、OSからのシグナルを受け取り、それをGoランタイムのsigpanicに「偽装呼び出し」することで、Goのパニック処理フローに乗せます。

レジスタ (Registers)

CPU内部にある高速な記憶領域で、現在処理中のデータや命令のアドレスなどを一時的に保持します。ARMアーキテクチャには、以下のような重要なレジスタがあります。

  • pc (Program Counter): 次に実行される命令のアドレスを保持するレジスタです。
  • lr (Link Register): 関数呼び出しが行われた際に、呼び出し元に戻るためのアドレス(リターンアドレス)を保持するレジスタです。関数から戻る際には、このlrの値がpcにロードされます。
  • sp (Stack Pointer): 現在のスタックフレームの最上位(または最下位、アーキテクチャによる)のアドレスを指すレジスタです。スタックは、関数のローカル変数や引数、リターンアドレスなどを一時的に保存するために使用されます。

スタックアンワインド (Stack Unwinding)

スタックアンワインドは、スタックトレースバックを生成するプロセスの一部で、スタックフレームを逆順に辿り、各関数の呼び出し元を特定していくことです。これには、スタックポインタやフレームポインタ、リターンアドレスなどの情報が利用されます。

技術的詳細

このコミットは、src/pkg/runtime/traceback_arm.cファイル内のruntime·gentraceback関数におけるsigpanic処理のロジックを修正しています。

Goランタイムのtraceback_arm.cは、ARMアーキテクチャ上でのスタックトレースバックを生成するためのCコードです。runtime·gentraceback関数は、与えられたpc(プログラムカウンタ)、sp(スタックポインタ)、lr(リンクレジスタ)の値から、ゴルーチンのスタックフレームを遡り、呼び出し履歴を構築します。

問題は、シグナルハンドラがsigpanicを偽装呼び出しする際のlrの扱いにありました。元のコードでは、waspanic(パニックが発生したことを示すフラグ)が真の場合、スタックポインタspが指すアドレスからpcの値を直接読み出し、spを4バイト進めていました。これは、シグナルハンドラがlrをスタックに保存し、それをpcとして扱うことを意図していたためです。

しかし、この単純な処理では、pcとして読み出した値が必ずしも有効な関数のエントリポイントを指しているとは限りませんでした。特に、runtime·findfuncで関数情報が見つからない場合や、見つかった関数のフレームサイズが0の場合(例えば、インライン化された関数や、フレームを持たない特殊な関数)に問題が生じました。

修正後のコードでは、以下の点が改善されています。

  1. 一時変数xの導入: スタックから読み出した値を直接pcに代入するのではなく、一時変数xに保存します。
  2. runtime·findfuncによる関数情報の確認: xの値をruntime·findfuncに渡し、それが有効な関数のエントリポイントであるかどうかを確認します。
    • runtime·findfuncは、与えられたアドレスに対応する関数情報(Func構造体)を検索するランタイム関数です。
  3. pclrの条件付き更新:
    • もしruntime·findfunc(pc)nilを返す場合(つまり、xが有効な関数エントリを指していない場合)、元のpcxの値に戻します。これは、xが実際にはリターンアドレス(lr)であり、現在のpcは別の場所を指しているべきであることを示唆しています。
    • もしruntime·findfunc(pc)nilではなく、かつその関数のf->frameが0の場合(つまり、フレームを持たない関数である場合)、lrxの値に設定します。これは、xがリターンアドレスとして機能すべきであり、現在のpcはそのまま維持されるべきであることを意味します。

この変更により、sigpanic発生時にスタックから読み出された値が、実際のpcであるべきか、それともlrであるべきかをより正確に判断できるようになり、ARM環境でのスタックトレースバックの正確性が向上し、ビルド問題が解決されました。

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

src/pkg/runtime/traceback_arm.cファイルのruntime·gentraceback関数内の変更点です。

--- a/src/pkg/runtime/traceback_arm.c
+++ b/src/pkg/runtime/traceback_arm.c
@@ -183,10 +183,15 @@ runtime·gentraceback(byte *pc0, byte *sp, byte *lr0, G *gp, int32 skip, uintptr
 		if(f->entry == (uintptr)runtime·deferproc || f->entry == (uintptr)runtime·newproc)
 			sp += 12;
 
-		// sighandler saves the lr on stack before fake a call to sigpanic
+		// sighandler saves the lr on stack before faking a call to sigpanic
 		if(waspanic) {
-			pc = *(uintptr *)sp;
+			x = *(uintptr *)sp;
 			sp += 4;
+			f = runtime·findfunc(pc);
+			if (f == nil) {
+				pc = x;
+			} else if (f->frame == 0)
+				lr = x;
 		}
 	}
 	

コアとなるコードの解説

変更の中心は、if(waspanic)ブロック内のロジックです。

変更前:

		if(waspanic) {
			pc = *(uintptr *)sp;
			sp += 4;
		}

waspanicが真の場合、スタックポインタspが指すアドレスからuintptr型の値を読み出し、それを直接pcに代入していました。その後、spを4バイト(uintptrのサイズ)進めていました。これは、シグナルハンドラがlrをスタックに保存し、それをpcとして扱うという前提に基づいています。

変更後:

		if(waspanic) {
			x = *(uintptr *)sp; // スタックから値を読み出し、一時変数xに保存
			sp += 4;            // spを4バイト進める
			f = runtime·findfunc(pc); // 現在のpcに対応する関数情報を検索
			if (f == nil) {           // 関数情報が見つからない場合
				pc = x;               // xをpcとして設定(xは実際にはlrだった可能性が高い)
			} else if (f->frame == 0) // 関数情報が見つかり、かつフレームサイズが0の場合
				lr = x;               // xをlrとして設定(xはリターンアドレスとして機能すべき)
		}
  1. x = *(uintptr *)sp;: まず、スタックから読み出した値を一時変数xに格納します。これにより、元のpcの値をすぐに上書きすることなく、xの値を評価できるようになります。
  2. sp += 4;: スタックポインタを進めるのは変更前と同じです。
  3. f = runtime·findfunc(pc);: ここで、現在のpcの値(これはwaspanicブロックに入る前のpc、または直前のスタックフレームから計算されたpc)に対応する関数情報をruntime·findfuncを使って検索します。
  4. if (f == nil): runtime·findfuncnilを返した場合、これは現在のpcが有効な関数のエントリポイントを指していないことを意味します。この場合、スタックから読み出したxの値が実際にはリターンアドレス(lr)であり、それをpcとして設定することで、正しい呼び出し元に戻るためのアドレスをpcに設定します。
  5. else if (f->frame == 0): runtime·findfuncが関数情報を見つけたものの、その関数のframeサイズが0である場合(例えば、インライン化された関数や、スタックフレームを持たない特殊なランタイム関数など)、xの値をlrに設定します。これは、xがリターンアドレスとして機能すべきであり、現在のpcはそのまま維持されるべきであることを示唆しています。

この修正により、sigpanic発生時にスタックから読み出された値が、プログラムカウンタ(pc)として扱うべきか、それともリンクレジスタ(lr)として扱うべきかを、より文脈に沿って判断できるようになりました。これにより、ARM環境でのスタックトレースバックの正確性が向上し、ビルドや実行時の問題が解決されたと考えられます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(src/pkg/runtime/traceback_arm.c
  • Go言語のランタイムに関する一般的なドキュメントや記事
  • ARMアーキテクチャのレジスタとスタックフレームに関する情報
  • Go言語のパニックとシグナル処理に関する情報I have provided the detailed explanation as requested. I will now output the Markdown content.
# [インデックス 15167] ファイルの概要

このコミットは、Go言語のランタイムにおけるLinux/ARMアーキテクチャでのビルド問題を修正するものです。具体的には、スタックトレースバック処理、特にシグナルハンドラが`sigpanic`を偽装呼び出しする際の`lr`(リンクレジスタ)と`pc`(プログラムカウンタ)の扱いに関するバグを修正しています。

## コミット

commit 6c54bf991680f937db8fb74e8da4b26a757e8242 Author: Shenghou Ma minux.ma@gmail.com Date: Fri Feb 8 13:24:38 2013 +0800

runtime: fix build for Linux/ARM

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

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

[https://github.com/golang/go/commit/6c54bf991680f937db8fb74e8da4b26a757e8242](https://github.com/golang/go/commit/6c54bf991680f937db8fb74e8da4b26a757e8242)

## 元コミット内容

runtime: fix build for Linux/ARM


## 変更の背景

このコミットの背景には、Go言語のランタイムがLinux/ARMアーキテクチャ上で正しくビルドまたは実行できない問題がありました。特に、シグナルハンドラがパニック(`sigpanic`)を処理する際に、スタックトレースバックのメカニズムが期待通りに機能しないことが原因と考えられます。

Go言語のランタイムは、プログラムの実行中に発生したエラーやパニックを診断するために、正確なスタックトレースバックを生成する必要があります。これは、どの関数がどの順序で呼び出されたかを示すことで、開発者が問題の原因を特定するのに役立ちます。ARMアーキテクチャのような特定のプラットフォームでは、レジスタ(特に`lr`と`pc`)の扱いが他のアーキテクチャと異なる場合があり、これがスタックトレースバックの正確性を損なう原因となることがあります。

元のコードでは、シグナルハンドラが`sigpanic`を偽装呼び出しする際に、`lr`(リンクレジスタ)の値をスタックに保存し、その値を`pc`(プログラムカウンタ)として読み出すという処理が行われていました。しかし、この処理がLinux/ARM環境で問題を引き起こし、ビルドエラーやランタイムエラーにつながっていたと推測されます。この修正は、`sigpanic`発生時の`pc`と`lr`の復元ロジックを改善し、ARM環境でのスタックトレースバックの正確性を確保することを目的としています。

## 前提知識の解説

### Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、スタック管理、そしてパニックやシグナル処理などが含まれます。ランタイムは、GoプログラムがOSと対話するための低レベルなインターフェースを提供します。

### ARMアーキテクチャ
ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。x86などのCISCアーキテクチャとは異なり、命令セットがシンプルで、電力効率が高いという特徴があります。Go言語は、ARMを含む多くのアーキテクチャをサポートしています。

### スタックトレースバック (Stack Traceback)
スタックトレースバックは、プログラムの実行中に特定の時点(通常はエラーやパニック発生時)で、現在実行中の関数から呼び出し元の関数へと遡って、関数の呼び出し履歴を表示するものです。これにより、プログラムの実行フローを理解し、問題の原因を特定するのに役立ちます。

### `sigpanic`
Go言語において、`sigpanic`はランタイムがシグナル(例えば、不正なメモリアクセスを示す`SIGSEGV`など)を受け取った際に、Goのパニック機構に変換するために内部的に使用される関数です。OSからのシグナルをGoのパニックとして処理することで、Goプログラムは一貫したエラー処理メカニズムを持つことができます。シグナルハンドラは、OSからのシグナルを受け取り、それをGoランタイムの`sigpanic`に「偽装呼び出し」することで、Goのパニック処理フローに乗せます。

### レジスタ (Registers)
CPU内部にある高速な記憶領域で、現在処理中のデータや命令のアドレスなどを一時的に保持します。ARMアーキテクチャには、以下のような重要なレジスタがあります。

*   **`pc` (Program Counter)**: 次に実行される命令のアドレスを保持するレジスタです。
*   **`lr` (Link Register)**: 関数呼び出しが行われた際に、呼び出し元に戻るためのアドレス(リターンアドレス)を保持するレジスタです。関数から戻る際には、この`lr`の値が`pc`にロードされます。
*   **`sp` (Stack Pointer)**: 現在のスタックフレームの最上位(または最下位、アーキテクチャによる)のアドレスを指すレジスタです。スタックは、関数のローカル変数や引数、リターンアドレスなどを一時的に保存するために使用されます。

### スタックアンワインド (Stack Unwinding)
スタックアンワインドは、スタックトレースバックを生成するプロセスの一部で、スタックフレームを逆順に辿り、各関数の呼び出し元を特定していくことです。これには、スタックポインタやフレームポインタ、リターンアドレスなどの情報が利用されます。

## 技術的詳細

このコミットは、`src/pkg/runtime/traceback_arm.c`ファイル内の`runtime·gentraceback`関数における`sigpanic`処理のロジックを修正しています。

Goランタイムの`traceback_arm.c`は、ARMアーキテクチャ上でのスタックトレースバックを生成するためのCコードです。`runtime·gentraceback`関数は、与えられた`pc`(プログラムカウンタ)、`sp`(スタックポインタ)、`lr`(リンクレジスタ)の値から、ゴルーチンのスタックフレームを遡り、呼び出し履歴を構築します。

問題は、シグナルハンドラが`sigpanic`を偽装呼び出しする際の`lr`の扱いにありました。元のコードでは、`waspanic`(パニックが発生したことを示すフラグ)が真の場合、スタックポインタ`sp`が指すアドレスから`pc`の値を直接読み出し、`sp`を4バイト進めていました。これは、シグナルハンドラが`lr`をスタックに保存し、それを`pc`として扱うことを意図していたためです。

しかし、この単純な処理では、`pc`として読み出した値が必ずしも有効な関数のエントリポイントを指しているとは限りませんでした。特に、`runtime·findfunc`で関数情報が見つからない場合や、見つかった関数のフレームサイズが0の場合(例えば、インライン化された関数や、フレームを持たない特殊な関数)に問題が生じました。

修正後のコードでは、以下の点が改善されています。

1.  **一時変数`x`の導入**: スタックから読み出した値を直接`pc`に代入するのではなく、一時変数`x`に保存します。
2.  **`runtime·findfunc`による関数情報の確認**: `x`の値を`runtime·findfunc`に渡し、それが有効な関数のエントリポイントであるかどうかを確認します。
    *   `runtime·findfunc`は、与えられたアドレスに対応する関数情報(`Func`構造体)を検索するランタイム関数です。
3.  **`pc`と`lr`の条件付き更新**:
    *   もし`runtime·findfunc(pc)`が`nil`を返す場合(つまり、`x`が有効な関数エントリを指していない場合)、元の`pc`を`x`の値に戻します。これは、`x`が実際にはリターンアドレス(`lr`)であり、現在の`pc`は別の場所を指しているべきであることを示唆しています。
    *   もし`runtime·findfunc(pc)`が`nil`ではなく、かつその関数の`f->frame`が0の場合(つまり、フレームを持たない関数である場合)、`lr`を`x`の値に設定します。これは、`x`がリターンアドレスとして機能すべきであり、現在の`pc`はそのまま維持されるべきであることを意味します。

この変更により、`sigpanic`発生時にスタックから読み出された値が、実際の`pc`であるべきか、それとも`lr`であるべきかをより正確に判断できるようになり、ARM環境でのスタックトレースバックの正確性が向上し、ビルド問題が解決されました。

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

`src/pkg/runtime/traceback_arm.c`ファイルの`runtime·gentraceback`関数内の変更点です。

```diff
--- a/src/pkg/runtime/traceback_arm.c
+++ b/src/pkg/runtime/traceback_arm.c
@@ -183,10 +183,15 @@ runtime·gentraceback(byte *pc0, byte *sp, byte *lr0, G *gp, int32 skip, uintptr
 		if(f->entry == (uintptr)runtime·deferproc || f->entry == (uintptr)runtime·newproc)
 			sp += 12;
 
-		// sighandler saves the lr on stack before fake a call to sigpanic
+		// sighandler saves the lr on stack before faking a call to sigpanic
 		if(waspanic) {
-			pc = *(uintptr *)sp;
+			x = *(uintptr *)sp;
 			sp += 4;
+			f = runtime·findfunc(pc);
+			if (f == nil) {
+				pc = x;
+			} else if (f->frame == 0)
+				lr = x;
 		}
 	}
 	

コアとなるコードの解説

変更の中心は、if(waspanic)ブロック内のロジックです。

変更前:

		if(waspanic) {
			pc = *(uintptr *)sp;
			sp += 4;
		}

waspanicが真の場合、スタックポインタspが指すアドレスからuintptr型の値を読み出し、それを直接pcに代入していました。その後、spを4バイト(uintptrのサイズ)進めていました。これは、シグナルハンドラがlrをスタックに保存し、それをpcとして扱うという前提に基づいています。

変更後:

		if(waspanic) {
			x = *(uintptr *)sp; // スタックから値を読み出し、一時変数xに保存
			sp += 4;            // spを4バイト進める
			f = runtime·findfunc(pc); // 現在のpcに対応する関数情報を検索
			if (f == nil) {           // 関数情報が見つからない場合
				pc = x;               // xをpcとして設定(xは実際にはlrだった可能性が高い)
			} else if (f->frame == 0) // 関数情報が見つかり、かつフレームサイズが0の場合
				lr = x;               // xをlrとして設定(xはリターンアドレスとして機能すべき)
		}
  1. x = *(uintptr *)sp;: まず、スタックから読み出した値を一時変数xに格納します。これにより、元のpcの値をすぐに上書きすることなく、xの値を評価できるようになります。
  2. sp += 4;: スタックポインタを進めるのは変更前と同じです。
  3. f = runtime·findfunc(pc);: ここで、現在のpcの値(これはwaspanicブロックに入る前のpc、または直前のスタックフレームから計算されたpc)に対応する関数情報をruntime·findfuncを使って検索します。
  4. if (f == nil): runtime·findfuncnilを返した場合、これは現在のpcが有効な関数のエントリポイントを指していないことを意味します。この場合、スタックから読み出したxの値が実際にはリターンアドレス(lr)であり、それをpcとして設定することで、正しい呼び出し元に戻るためのアドレスをpcに設定します。
  5. else if (f->frame == 0): runtime·findfuncが関数情報を見つけたものの、その関数のframeサイズが0である場合(例えば、インライン化された関数や、スタックフレームを持たない特殊なランタイム関数など)、xの値をlrに設定します。これは、xがリターンアドレスとして機能すべきであり、現在のpcはそのまま維持されるべきであることを示唆しています。

この修正により、sigpanic発生時にスタックから読み出された値が、プログラムカウンタ(pc)として扱うべきか、それともリンクレジスタ(lr)として扱うべきかを、より文脈に沿って判断できるようになりました。これにより、ARM環境でのスタックトレースバックの正確性が向上し、ビルドや実行時の問題が解決されたと考えられます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(src/pkg/runtime/traceback_arm.c
  • Go言語のランタイムに関する一般的なドキュメントや記事
  • ARMアーキテクチャのレジスタとスタックフレームに関する情報
  • Go言語のパニックとシグナル処理に関する情報