[インデックス 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アーキテクチャのような特定のプラットフォームでは、レジスタ(特に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の場合(例えば、インライン化された関数や、フレームを持たない特殊な関数)に問題が生じました。
修正後のコードでは、以下の点が改善されています。
- 一時変数
x
の導入: スタックから読み出した値を直接pc
に代入するのではなく、一時変数x
に保存します。 runtime·findfunc
による関数情報の確認:x
の値をruntime·findfunc
に渡し、それが有効な関数のエントリポイントであるかどうかを確認します。runtime·findfunc
は、与えられたアドレスに対応する関数情報(Func
構造体)を検索するランタイム関数です。
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
関数内の変更点です。
--- 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はリターンアドレスとして機能すべき)
}
x = *(uintptr *)sp;
: まず、スタックから読み出した値を一時変数x
に格納します。これにより、元のpc
の値をすぐに上書きすることなく、x
の値を評価できるようになります。sp += 4;
: スタックポインタを進めるのは変更前と同じです。f = runtime·findfunc(pc);
: ここで、現在のpc
の値(これはwaspanic
ブロックに入る前のpc
、または直前のスタックフレームから計算されたpc
)に対応する関数情報をruntime·findfunc
を使って検索します。if (f == nil)
:runtime·findfunc
がnil
を返した場合、これは現在のpc
が有効な関数のエントリポイントを指していないことを意味します。この場合、スタックから読み出したx
の値が実際にはリターンアドレス(lr
)であり、それをpc
として設定することで、正しい呼び出し元に戻るためのアドレスをpc
に設定します。else if (f->frame == 0)
:runtime·findfunc
が関数情報を見つけたものの、その関数のframe
サイズが0である場合(例えば、インライン化された関数や、スタックフレームを持たない特殊なランタイム関数など)、x
の値をlr
に設定します。これは、x
がリターンアドレスとして機能すべきであり、現在のpc
はそのまま維持されるべきであることを示唆しています。
この修正により、sigpanic
発生時にスタックから読み出された値が、プログラムカウンタ(pc
)として扱うべきか、それともリンクレジスタ(lr
)として扱うべきかを、より文脈に沿って判断できるようになりました。これにより、ARM環境でのスタックトレースバックの正確性が向上し、ビルドや実行時の問題が解決されたと考えられます。
関連リンク
- Go CL 7299055: https://golang.org/cl/7299055
参考にした情報源リンク
- 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はリターンアドレスとして機能すべき)
}
x = *(uintptr *)sp;
: まず、スタックから読み出した値を一時変数x
に格納します。これにより、元のpc
の値をすぐに上書きすることなく、x
の値を評価できるようになります。sp += 4;
: スタックポインタを進めるのは変更前と同じです。f = runtime·findfunc(pc);
: ここで、現在のpc
の値(これはwaspanic
ブロックに入る前のpc
、または直前のスタックフレームから計算されたpc
)に対応する関数情報をruntime·findfunc
を使って検索します。if (f == nil)
:runtime·findfunc
がnil
を返した場合、これは現在のpc
が有効な関数のエントリポイントを指していないことを意味します。この場合、スタックから読み出したx
の値が実際にはリターンアドレス(lr
)であり、それをpc
として設定することで、正しい呼び出し元に戻るためのアドレスをpc
に設定します。else if (f->frame == 0)
:runtime·findfunc
が関数情報を見つけたものの、その関数のframe
サイズが0である場合(例えば、インライン化された関数や、スタックフレームを持たない特殊なランタイム関数など)、x
の値をlr
に設定します。これは、x
がリターンアドレスとして機能すべきであり、現在のpc
はそのまま維持されるべきであることを示唆しています。
この修正により、sigpanic
発生時にスタックから読み出された値が、プログラムカウンタ(pc
)として扱うべきか、それともリンクレジスタ(lr
)として扱うべきかを、より文脈に沿って判断できるようになりました。これにより、ARM環境でのスタックトレースバックの正確性が向上し、ビルドや実行時の問題が解決されたと考えられます。
関連リンク
- Go CL 7299055: https://golang.org/cl/7299055
参考にした情報源リンク
- Go言語のソースコード(
src/pkg/runtime/traceback_arm.c
) - Go言語のランタイムに関する一般的なドキュメントや記事
- ARMアーキテクチャのレジスタとスタックフレームに関する情報
- Go言語のパニックとシグナル処理に関する情報