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

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

このコミットは、Goランタイムがx86アーキテクチャ上でGDBのブレークポイントを適切に処理するように修正するものです。これにより、GDBでデバッグ中にコールスタック上のどこかにブレークポイントが設定されている場合でも、Goのスタックスプリット機能が正しく動作するようになります。

コミット

commit 89c5d178785bd7884dbb14d73f85f600196d6cb6
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Jan 8 12:36:31 2014 -0800

    runtime: handle gdb breakpoint in x86 traceback
    
    This lets stack splits work correctly when running under gdb
    when gdb has inserted a breakpoint somewhere on the call
    stack.
    
    Fixes #6834.
    
    R=golang-codereviews, minux.ma
    CC=golang-codereviews
    https://golang.org/cl/48650043

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

https://github.com/golang/go/commit/89c5d178785bd7884dbb14d73f85f600196d6cb6

元コミット内容

runtime: handle gdb breakpoint in x86 traceback

This lets stack splits work correctly when running under gdb
when gdb has inserted a breakpoint somewhere on the call
stack.

Fixes #6834.

変更の背景

Go言語のランタイムは、ゴルーチンのスタックを動的に拡張・縮小する「スタックスプリット(Stack Split)」というメカニズムを持っています。これは、スタックの使用量を効率的に管理し、多数のゴルーチンを軽量に実行するために非常に重要な機能です。

しかし、GDB(GNU Debugger)のようなデバッガがプログラムの実行中にブレークポイントを設定すると、問題が発生することがありました。GDBは通常、ブレークポイントを設定するために、指定された命令アドレスの元の命令をint3(x86アーキテクチャでは0xCCバイトコード)というソフトウェアブレークポイント命令に置き換えます。プログラムがこのint3命令に到達すると、CPUは割り込みを発生させ、GDBに制御が渡されます。

Goランタイムのスタックスプリット処理、特にスタックの巻き戻し(unwinding)やトレースバックの際に、このGDBが挿入した0xCC命令が予期せぬ動作を引き起こす可能性がありました。具体的には、runtime·rewindmorestack関数は、スタックスプリットの際にスタックポインタやプログラムカウンタを調整する役割を担っていますが、GDBのブレークポイントによってプログラムカウンタが0xCCを指している場合、この関数が正しい命令を認識できず、スタックの巻き戻しに失敗し、結果としてプログラムがクラッシュしたり、デバッグが困難になったりする問題が発生していました(Go Issue #6834)。

このコミットは、この問題を解決し、GDBでGoプログラムをデバッグする際の安定性と信頼性を向上させることを目的としています。

前提知識の解説

Goランタイム

Goランタイムは、Goプログラムの実行を管理する非常に重要なコンポーネントです。C言語やC++のランタイムライブラリに似ていますが、Goのランタイムはより多くの責任を負っています。主な機能は以下の通りです。

  • ゴルーチン(Goroutines): Goの軽量な並行処理単位。ランタイムがゴルーチンのスケジューリング、作成、破棄を管理します。
  • スケジューラ(Scheduler): OSのスレッド上でゴルーチンを効率的に実行するためのスケジューリングを行います。M:Nスケジューリングモデル(M個のゴルーチンをN個のOSスレッドで実行)を採用しています。
  • メモリ管理(Memory Management): ヒープメモリのアロケーションと解放、そしてガベージコレクション(GC)を担当します。Goはトレース型ガベージコレクタを使用しています。
  • スタック管理(Stack Management): ゴルーチンのスタックを動的に管理します。これが今回のコミットの核心部分です。
  • チャネル(Channels): ゴルーチン間の安全な通信メカニズムを提供します。
  • システムコールインターフェース: OSの機能へのアクセスを提供します。

スタックスプリット(Stack Split)

Goのゴルーチンは非常に軽量であり、初期スタックサイズは非常に小さい(数KB程度)です。これは、数百万ものゴルーチンを同時に実行できるようにするためです。しかし、関数呼び出しが深くネストしたり、大きなローカル変数が使用されたりすると、スタックが不足する可能性があります。

ここで「スタックスプリット」が登場します。Goランタイムは、関数呼び出しの冒頭で、現在のスタックが残りの処理に十分なサイズがあるかをチェックします。もし不足していると判断した場合、ランタイムはより大きな新しいスタックを割り当て、現在のスタックの内容を新しいスタックにコピーし、実行を新しいスタックに切り替えます。このプロセスがスタックスプリットです。関数からのリターン時には、元の小さなスタックに戻ることもあります。

この動的なスタック管理により、Goはメモリを効率的に使用し、スタックオーバーフローを自動的に回避できます。

GDB(GNU Debugger)とブレークポイント

GDBは、C、C++、Goなど様々なプログラミング言語のプログラムをデバッグするための強力なツールです。GDBの主要な機能の一つが「ブレークポイント」です。ブレークポイントは、プログラムの特定の場所で実行を一時停止させるために設定されます。

GDBがソフトウェアブレークポイントを設定する一般的な方法は、対象の命令をCPUが認識する特別な命令に置き換えることです。x86アーキテクチャの場合、この特別な命令はint3(Interrupt 3)命令であり、そのオペコードは0xCCです。

  1. ブレークポイント設定: GDBは、ユーザーが指定したアドレスの元の命令を読み取り、その場所に0xCCバイトを書き込みます。元の命令はGDBの内部で保存されます。
  2. 実行の停止: プログラムが0xCC命令に到達すると、CPUは割り込み(Interrupt 3)を生成します。
  3. GDBへの制御移行: OSはこの割り込みを捕捉し、GDBに制御を渡します。GDBは実行を一時停止し、ユーザーにプロンプトを表示します。
  4. 元の命令の復元: GDBは、一時停止した場所の0xCCを元の命令に一時的に戻し、ユーザーがステップ実行などのデバッグ操作を行えるようにします。
  5. 再開: ユーザーが実行を再開すると、GDBは再び0xCCを書き込み、プログラムの実行を続行させます。

runtime·rewindmorestack関数

runtime·rewindmorestackはGoランタイム内部の関数で、スタックスプリットの際にスタックを巻き戻す(rewind)処理を担当します。具体的には、スタックの拡張が必要と判断された際に、morestackというランタイム関数が呼び出され、新しいスタックへの切り替えが行われます。morestackから元の関数に戻る際に、rewindmorestackが呼び出され、プログラムカウンタ(PC)やスタックポインタ(SP)を適切に調整し、元の関数の実行を再開できるようにします。

この関数は、PCが特定のジャンプ命令(jmp)を指していることを期待して処理を進めます。しかし、GDBが挿入した0xCC命令はこれらのジャンプ命令とは異なるため、rewindmorestackがPCの指す内容を正しく解釈できず、問題が発生していました。

技術的詳細

このコミットの技術的詳細を理解するためには、runtime·rewindmorestack関数の役割と、GDBがブレークポイントを挿入する方法の相互作用を深く掘り下げる必要があります。

runtime·rewindmorestack関数は、スタックスプリットが発生し、新しいスタックフレームが割り当てられた後、元の関数に戻る際に呼び出されます。この関数の目的は、gobuf(ゴルーチンのコンテキストを保存する構造体)内のプログラムカウンタ(gobuf->pc)を、スタックスプリットの呼び出し元(morestack関数)から戻るべき正しい位置に調整することです。

通常のGoの関数呼び出し規約では、スタックスプリットが必要な場合、コンパイラは関数のプロローグにスタックチェックコードを挿入します。このコードは、スタックが不足している場合にruntime·morestack関数を呼び出すためのジャンプ命令を含んでいます。runtime·rewindmorestackは、このmorestackからの戻り時に、gobuf->pcが指す命令を解析し、元の関数の正しい実行開始点にPCを巻き戻します。

コミット前のruntime·rewindmorestackは、gobuf->pcが指す命令の最初のバイトをチェックし、それが0xe9jmp命令のオペコード)または0xeb(短いjmp命令のオペコード)であると仮定していました。これらのジャンプ命令は、スタックチェックが失敗した場合にruntime·morestackへ分岐するためにコンパイラによって挿入されるものです。rewindmorestackは、これらのジャンプ命令の長さを考慮してgobuf->pcを調整し、元の関数の実際の開始点にPCを戻していました。

しかし、GDBがブレークポイントを挿入すると、gobuf->pcが指すアドレスの命令が0xCCint3命令のオペコード)に置き換えられます。この0xCC0xe90xebとは異なるため、runtime·rewindmorestackは予期しない命令コードに遭遇し、正しいPCの巻き戻しができなくなっていました。これにより、スタックスプリットが完了した後、プログラムが誤ったアドレスから実行を再開しようとし、クラッシュや不正な動作を引き起こしていました。

このコミットでは、runtime·rewindmorestack関数に新しいチェックが追加されました。具体的には、gobuf->pcが指す命令の最初のバイトが0xCCであるかどうかを確認します。もし0xCCであれば、それはGDBによって挿入されたブレークポイントであると判断します。

ブレークポイントであると判断した場合、rewindmorestackruntime·findfunc(gobuf->pc)を呼び出して、現在のPCが属する関数の情報を取得します。そして、その関数のエントリポイント(f->entry)をgobuf->pcに設定します。これにより、GDBのブレークポイントによってPCが一時的に0xCCを指していても、rewindmorestackは元の関数の正しい開始点にPCを戻すことができ、スタックスプリット後の実行が正常に継続されるようになります。

この修正は、Goランタイムがデバッガの介入をより堅牢に処理できるようにし、デバッグ体験を大幅に改善します。

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

変更はsrc/pkg/runtime/sys_x86.cファイル内のruntime·rewindmorestack関数に集中しています。

--- a/src/pkg/runtime/sys_x86.c
+++ b/src/pkg/runtime/sys_x86.c
@@ -27,7 +27,8 @@ void
 runtime·rewindmorestack(Gobuf *gobuf)
 {
 	byte *pc;
-	
+	Func *f;
+
 	pc = (byte*)gobuf->pc;
 	if(pc[0] == 0xe9) { // jmp 4-byte offset
 		gobuf->pc = gobuf->pc + 5 + *(int32*)(pc+1);
@@ -37,6 +38,13 @@ runtime·rewindmorestack(Gobuf *gobuf)
 		gobuf->pc = gobuf->pc + 2 + *(int8*)(pc+1);
 		return;
 	}\n+\tif(pc[0] == 0xcc) { // breakpoint inserted by gdb
+\t\tf = runtime·findfunc(gobuf->pc);\n+\t\tif(f != nil) {\n+\t\t\tgobuf->pc = f->entry;\n+\t\t\treturn;\n+\t\t}\n+\t}\
 \truntime·printf("runtime: pc=%p %x %x %x %x %x\n", pc, pc[0], pc[1], pc[2], pc[3], pc[4]);
 \truntime·throw("runtime: misuse of rewindmorestack");
 }\n```

## コアとなるコードの解説

追加されたコードブロックは以下の部分です。

```c
	if(pc[0] == 0xcc) { // breakpoint inserted by gdb
		f = runtime·findfunc(gobuf->pc);
		if(f != nil) {
			gobuf->pc = f->entry;
			return;
		}
	}
  1. if(pc[0] == 0xcc):

    • pcgobuf->pc(現在のゴルーチンのプログラムカウンタ)をバイトポインタにキャストしたものです。
    • pc[0]は、そのアドレスにある命令の最初のバイトを指します。
    • この条件は、現在のプログラムカウンタが指す命令の最初のバイトが0xCC(x86アーキテクチャにおけるint3命令、すなわちGDBが挿入するソフトウェアブレークポイント命令のオペコード)であるかどうかをチェックしています。
    • もし0xCCであれば、GDBによってブレークポイントが設定されていると判断します。
  2. f = runtime·findfunc(gobuf->pc);:

    • runtime·findfuncは、与えられたアドレス(ここではgobuf->pc)がどのGo関数に属するかを検索し、その関数のメタデータ(Func構造体)を返します。
    • このステップは、ブレークポイントが設定されているにもかかわらず、それがどのGo関数内にあるのかを特定するために必要です。
  3. if(f != nil):

    • runtime·findfuncが有効な関数を見つけられた場合(fnilでない場合)に、以下の処理に進みます。
  4. gobuf->pc = f->entry;:

    • f->entryは、Func構造体に含まれる、その関数の実際のエントリポイント(開始アドレス)です。
    • gobuf->pcをこのf->entryに設定することで、スタックスプリットからの戻り時に、GDBのブレークポイントによって一時的に変更されたPCを、元の関数の正しい開始点に巻き戻します。
    • これにより、スタックスプリット処理が完了した後、プログラムはブレークポイントの影響を受けずに、元の関数の冒頭から正常に実行を再開できるようになります。
  5. return;:

    • PCの調整が完了したため、runtime·rewindmorestack関数を終了します。

この変更により、runtime·rewindmorestackは、通常のジャンプ命令だけでなく、GDBが挿入したブレークポイント命令も適切に処理できるようになり、GDBでのデバッグ中のスタックスプリットの信頼性が向上しました。

関連リンク

  • Go Issue #6834: https://github.com/golang/go/issues/6834
  • Go Code Review 48650043: https://golang.org/cl/48650043 (これはコミットメッセージに記載されている古いGo Code ReviewのURLですが、現在のGoのコードレビューシステムでは直接アクセスできない可能性があります。しかし、コミットハッシュからGitHubで確認できます。)

参考にした情報源リンク

  • Go言語の公式ドキュメント (Goランタイム、ゴルーチン、スタック管理に関する一般的な情報)
  • GDBの公式ドキュメント (ブレークポイントの仕組みに関する一般的な情報)
  • x86命令セットリファレンス (int3命令のオペコード0xCCに関する情報)
  • Go言語のソースコード (src/runtime/ ディレクトリ内の関連ファイル)
  • Go Issue #6834の議論スレッド (問題の背景と解決策に関する詳細)
  • Go Code Reviewシステム (過去の変更履歴と議論)
  • Goのスタック管理について (Stack Split) (一般的な情報源)
  • GDBのブレークポイントの仕組み (一般的な情報源)# [インデックス 18193] ファイルの概要

このコミットは、Goランタイムがx86アーキテクチャ上でGDBのブレークポイントを適切に処理するように修正するものです。これにより、GDBでデバッグ中にコールスタック上のどこかにブレークポイントが設定されている場合でも、Goのスタックスプリット機能が正しく動作するようになります。

コミット

commit 89c5d178785bd7884dbb14d73f85f600196d6cb6
Author: Ian Lance Taylor <iant@golang.org>
Date:   Wed Jan 8 12:36:31 2014 -0800

    runtime: handle gdb breakpoint in x86 traceback
    
    This lets stack splits work correctly when running under gdb
    when gdb has inserted a breakpoint somewhere on the call
    stack.
    
    Fixes #6834.
    
    R=golang-codereviews, minux.ma
    CC=golang-codereviews
    https://golang.org/cl/48650043

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

https://github.com/golang/go/commit/89c5d178785bd7884dbb14d73f85f600196d6cb6

元コミット内容

runtime: handle gdb breakpoint in x86 traceback

This lets stack splits work correctly when running under gdb
when gdb has inserted a breakpoint somewhere on the call
stack.

Fixes #6834.

変更の背景

Go言語のランタイムは、ゴルーチンのスタックを動的に拡張・縮小する「スタックスプリット(Stack Split)」というメカニズムを持っています。これは、スタックの使用量を効率的に管理し、多数のゴルーチンを軽量に実行するために非常に重要な機能です。

しかし、GDB(GNU Debugger)のようなデバッガがプログラムの実行中にブレークポイントを設定すると、問題が発生することがありました。GDBは通常、ブレークポイントを設定するために、指定された命令アドレスの元の命令をint3(x86アーキテクチャでは0xCCバイトコード)というソフトウェアブレークポイント命令に置き換えます。プログラムがこのint3命令に到達すると、CPUは割り込みを発生させ、GDBに制御が渡されます。

Goランタイムのスタックスプリット処理、特にスタックの巻き戻し(unwinding)やトレースバックの際に、このGDBが挿入した0xCC命令が予期せぬ動作を引き起こす可能性がありました。具体的には、runtime·rewindmorestack関数は、スタックスプリットの際にスタックポインタやプログラムカウンタを調整する役割を担っていますが、GDBのブレークポイントによってプログラムカウンタが0xCCを指している場合、この関数が正しい命令を認識できず、スタックの巻き戻しに失敗し、結果としてプログラムがクラッシュしたり、デバッグが困難になったりする問題が発生していました(Go Issue #6834)。

このコミットは、この問題を解決し、GDBでGoプログラムをデバッグする際の安定性と信頼性を向上させることを目的としています。

前提知識の解説

Goランタイム

Goランタイムは、Goプログラムの実行を管理する非常に重要なコンポーネントです。C言語やC++のランタイムライブラリに似ていますが、Goのランタイムはより多くの責任を負っています。主な機能は以下の通りです。

  • ゴルーチン(Goroutines): Goの軽量な並行処理単位。ランタイムがゴルーチンのスケジューリング、作成、破棄を管理します。
  • スケジューラ(Scheduler): OSのスレッド上でゴルーチンを効率的に実行するためのスケジューリングを行います。M:Nスケジューリングモデル(M個のゴルーチンをN個のOSスレッドで実行)を採用しています。
  • メモリ管理(Memory Management): ヒープメモリのアロケーションと解放、そしてガベージコレクション(GC)を担当します。Goはトレース型ガベージコレクタを使用しています。
  • スタック管理(Stack Management): ゴルーチンのスタックを動的に管理します。これが今回のコミットの核心部分です。
  • チャネル(Channels): ゴルーチン間の安全な通信メカニズムを提供します。
  • システムコールインターフェース: OSの機能へのアクセスを提供します。

スタックスプリット(Stack Split)

Goのゴルーチンは非常に軽量であり、初期スタックサイズは非常に小さい(数KB程度)です。これは、数百万ものゴルーチンを同時に実行できるようにするためです。しかし、関数呼び出しが深くネストしたり、大きなローカル変数が使用されたりすると、スタックが不足する可能性があります。

ここで「スタックスプリット」が登場します。Goランタイムは、関数呼び出しの冒頭で、現在のスタックが残りの処理に十分なサイズがあるかをチェックします。もし不足していると判断した場合、ランタイムはより大きな新しいスタックを割り当て、現在のスタックの内容を新しいスタックにコピーし、実行を新しいスタックに切り替えます。このプロセスがスタックスプリットです。関数からのリターン時には、元の小さなスタックに戻ることもあります。

この動的なスタック管理により、Goはメモリを効率的に使用し、スタックオーバーフローを自動的に回避できます。

GDB(GNU Debugger)とブレークポイント

GDBは、C、C++、Goなど様々なプログラミング言語のプログラムをデバッグするための強力なツールです。GDBの主要な機能の一つが「ブレークポイント」です。ブレークポイントは、プログラムの特定の場所で実行を一時停止させるために設定されます。

GDBがソフトウェアブレークポイントを設定する一般的な方法は、対象の命令をCPUが認識する特別な命令に置き換えることです。x86アーキテクチャの場合、この特別な命令はint3(Interrupt 3)命令であり、そのオペコードは0xCCです。

  1. ブレークポイント設定: GDBは、ユーザーが指定したアドレスの元の命令を読み取り、その場所に0xCCバイトを書き込みます。元の命令はGDBの内部で保存されます。
  2. 実行の停止: プログラムが0xCC命令に到達すると、CPUは割り込み(Interrupt 3)を生成します。
  3. GDBへの制御移行: OSはこの割り込みを捕捉し、GDBに制御を渡します。GDBは実行を一時停止し、ユーザーにプロンプトを表示します。
  4. 元の命令の復元: GDBは、一時停止した場所の0xCCを元の命令に一時的に戻し、ユーザーがステップ実行などのデバッグ操作を行えるようにします。
  5. 再開: ユーザーが実行を再開すると、GDBは再び0xCCを書き込み、プログラムの実行を続行させます。

runtime·rewindmorestack関数

runtime·rewindmorestackはGoランタイム内部の関数で、スタックを巻き戻す(rewind)処理を担当します。具体的には、スタックの拡張が必要と判断された際に、morestackというランタイム関数が呼び出され、新しいスタックへの切り替えが行われます。morestackから元の関数に戻る際に、rewindmorestackが呼び出され、プログラムカウンタ(PC)やスタックポインタ(SP)を適切に調整し、元の関数の実行を再開できるようにします。

この関数は、PCが特定のジャンプ命令(jmp)を指していることを期待して処理を進めます。しかし、GDBが挿入した0xCC命令はこれらのジャンプ命令とは異なるため、rewindmorestackがPCの指す内容を正しく解釈できず、問題が発生していました。

技術的詳細

このコミットの技術的詳細を理解するためには、runtime·rewindmorestack関数の役割と、GDBがブレークポイントを挿入する方法の相互作用を深く掘り下げる必要があります。

runtime·rewindmorestack関数は、スタックスプリットが発生し、新しいスタックフレームが割り当てられた後、元の関数に戻る際に呼び出されます。この関数の目的は、gobuf(ゴルーチンのコンテキストを保存する構造体)内のプログラムカウンタ(gobuf->pc)を、スタックスプリットの呼び出し元(morestack関数)から戻るべき正しい位置に調整することです。

通常のGoの関数呼び出し規約では、スタックスプリットが必要な場合、コンパイラは関数のプロローグにスタックチェックコードを挿入します。このコードは、スタックが不足している場合にruntime·morestack関数を呼び出すためのジャンプ命令を含んでいます。runtime·rewindmorestackは、このmorestackからの戻り時に、gobuf->pcが指す命令を解析し、元の関数の正しい実行開始点にPCを巻き戻します。

コミット前のruntime·rewindmorestackは、gobuf->pcが指す命令の最初のバイトをチェックし、それが0xe9jmp命令のオペコード)または0xeb(短いjmp命令のオペコード)であると仮定していました。これらのジャンプ命令は、スタックチェックが失敗した場合にruntime·morestackへ分岐するためにコンパイラによって挿入されるものです。rewindmorestackは、これらのジャンプ命令の長さを考慮してgobuf->pcを調整し、元の関数の実際の開始点にPCを戻していました。

しかし、GDBがブレークポイントを挿入すると、gobuf->pcが指すアドレスの命令が0xCCint3命令のオペコード)に置き換えられます。この0xCC0xe90xebとは異なるため、runtime·rewindmorestackは予期しない命令コードに遭遇し、正しいPCの巻き戻しができなくなっていました。これにより、スタックスプリットが完了した後、プログラムが誤ったアドレスから実行を再開しようとし、クラッシュや不正な動作を引き起こしていました。

このコミットでは、runtime·rewindmorestack関数に新しいチェックが追加されました。具体的には、gobuf->pcが指す命令の最初のバイトが0xCCであるかどうかを確認します。もし0xCCであれば、それはGDBによって挿入されたブレークポイントであると判断します。

ブレークポイントであると判断した場合、rewindmorestackruntime·findfunc(gobuf->pc)を呼び出して、現在のPCが属する関数の情報を取得します。そして、その関数のエントリポイント(f->entry)をgobuf->pcに設定します。これにより、GDBのブレークポイントによってPCが一時的に0xCCを指していても、rewindmorestackは元の関数の正しい開始点にPCを戻すことができ、スタックスプリット後の実行が正常に継続されるようになります。

この修正は、Goランタイムがデバッガの介入をより堅牢に処理できるようにし、デバッグ体験を大幅に改善します。

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

変更はsrc/pkg/runtime/sys_x86.cファイル内のruntime·rewindmorestack関数に集中しています。

--- a/src/pkg/runtime/sys_x86.c
+++ b/src/pkg/runtime/sys_x86.c
@@ -27,7 +27,8 @@ void
 runtime·rewindmorestack(Gobuf *gobuf)
 {
 	byte *pc;
-	
+	Func *f;
+
 	pc = (byte*)gobuf->pc;
 	if(pc[0] == 0xe9) { // jmp 4-byte offset
 		gobuf->pc = gobuf->pc + 5 + *(int32*)(pc+1);
@@ -37,6 +38,13 @@ runtime·rewindmorestack(Gobuf *gobuf)
 		gobuf->pc = gobuf->pc + 2 + *(int8*)(pc+1);
 		return;
 	}\n+\tif(pc[0] == 0xcc) { // breakpoint inserted by gdb
+\t\tf = runtime·findfunc(gobuf->pc);\n+\t\tif(f != nil) {\n+\t\t\tgobuf->pc = f->entry;\n+\t\t\treturn;\n+\t\t}\n+\t}\
 \truntime·printf("runtime: pc=%p %x %x %x %x %x\n", pc, pc[0], pc[1], pc[2], pc[3], pc[4]);
 \truntime·throw("runtime: misuse of rewindmorestack");
 }\n```

## コアとなるコードの解説

追加されたコードブロックは以下の部分です。

```c
	if(pc[0] == 0xcc) { // breakpoint inserted by gdb
		f = runtime·findfunc(gobuf->pc);
		if(f != nil) {
			gobuf->pc = f->entry;
			return;
		}
	}
  1. if(pc[0] == 0xcc):

    • pcgobuf->pc(現在のゴルーチンのプログラムカウンタ)をバイトポインタにキャストしたものです。
    • pc[0]は、そのアドレスにある命令の最初のバイトを指します。
    • この条件は、現在のプログラムカウンタが指す命令の最初のバイトが0xCC(x86アーキテクチャにおけるint3命令、すなわちGDBが挿入するソフトウェアブレークポイント命令のオペコード)であるかどうかをチェックしています。
    • もし0xCCであれば、GDBによってブレークポイントが設定されていると判断します。
  2. f = runtime·findfunc(gobuf->pc);:

    • runtime·findfuncは、与えられたアドレス(ここではgobuf->pc)がどのGo関数に属するかを検索し、その関数のメタデータ(Func構造体)を返します。
    • このステップは、ブレークポイントが設定されているにもかかわらず、それがどのGo関数内にあるのかを特定するために必要です。
  3. if(f != nil):

    • runtime·findfuncが有効な関数を見つけられた場合(fnilでない場合)に、以下の処理に進みます。
  4. gobuf->pc = f->entry;:

    • f->entryは、Func構造体に含まれる、その関数の実際のエントリポイント(開始アドレス)です。
    • gobuf->pcをこのf->entryに設定することで、スタックスプリットからの戻り時に、GDBのブレークポイントによって一時的に変更されたPCを、元の関数の正しい開始点に巻き戻します。
    • これにより、スタックスプリット処理が完了した後、プログラムはブレークポイントの影響を受けずに、元の関数の冒頭から正常に実行を再開できるようになります。
  5. return;:

    • PCの調整が完了したため、runtime·rewindmorestack関数を終了します。

この変更により、runtime·rewindmorestackは、通常のジャンプ命令だけでなく、GDBが挿入したブレークポイント命令も適切に処理できるようになり、GDBでのデバッグ中のスタックスプリットの信頼性が向上しました。

関連リンク

  • Go Issue #6834: https://github.com/golang/go/issues/6834
  • Go Code Review 48650043: https://golang.org/cl/48650043 (これはコミットメッセージに記載されている古いGo Code ReviewのURLですが、現在のGoのコードレビューシステムでは直接アクセスできない可能性があります。しかし、コミットハッシュからGitHubで確認できます。)

参考にした情報源リンク

  • Go言語の公式ドキュメント (Goランタイム、ゴルーチン、スタック管理に関する一般的な情報)
  • GDBの公式ドキュメント (ブレークポイントの仕組みに関する一般的な情報)
  • x86命令セットリファレンス (int3命令のオペコード0xCCに関する情報)
  • Go言語のソースコード (src/runtime/ ディレクトリ内の関連ファイル)
  • Go Issue #6834の議論スレッド (問題の背景と解決策に関する詳細)
  • Go Code Reviewシステム (過去の変更履歴と議論)
  • Goのスタック管理について (Stack Split) (一般的な情報源)
  • GDBのブレークポイントの仕組み (一般的な情報源)