[インデックス 16658] ファイルの概要
このコミットは、Goランタイムにおけるスタック分割(stack split)時のゴルーチン状態の記録方法を改善し、特にスタックトレースの正確性を向上させることを目的としています。これまでの実装では、newstack
やoldstack
といったスタック操作ルーチン実行中にゴルーチンの状態が散逸し、システムがゴルーチンの正確な状態を把握できない問題がありました。これにより、スタックオーバーフローなどの致命的なエラー発生時に、正確なスタックトレースが出力されないという課題がありました。
コミット
commit 6fa3c89b77c2b616be41ef9dd250b9f0e3e851f5
Author: Russ Cox <rsc@golang.org>
Date: Thu Jun 27 11:32:01 2013 -0400
runtime: record proper goroutine state during stack split
Until now, the goroutine state has been scattered during the
execution of newstack and oldstack. It's all there, and those routines
know how to get back to a working goroutine, but other pieces of
the system, like stack traces, do not. If something does interrupt
the newstack or oldstack execution, the rest of the system can't
understand the goroutine. For example, if newstack decides there
is an overflow and calls throw, the stack tracer wouldn't dump the
goroutine correctly.
For newstack to save a useful state snapshot, it needs to be able
to rewind the PC in the function that triggered the split back to
the beginning of the function. (The PC is a few instructions in, just
after the call to morestack.) To make that possible, we change the
prologues to insert a jmp back to the beginning of the function
after the call to morestack. That is, the prologue used to be roughly:
TEXT myfunc
check for split
jmpcond nosplit
call morestack
nosplit:
sub $xxx, sp
Now an extra instruction is inserted after the call:
TEXT myfunc
start:
check for split
jmpcond nosplit
call morestack
jmp start
nosplit:
sub $xxx, sp
The jmp is not executed directly. It is decoded and simulated by
runtime.rewindmorestack to discover the beginning of the function,
and then the call to morestack returns directly to the start label
instead of to the jump instruction. So logically the jmp is still
executed, just not by the cpu.
The prologue thus repeats in the case of a function that needs a
stack split, but against the cost of the split itself, the extra few
instructions are noise. The repeated prologue has the nice effect of
making a stack split double-check that the new stack is big enough:
if morestack happens to return on a too-small stack, we'll now notice
before corruption happens.
The ability for newstack to rewind to the beginning of the function
should help preemption too. If newstack decides that it was called
for preemption instead of a stack split, it now has the goroutine state
correctly paused if rescheduling is needed, and when the goroutine
can run again, it can return to the start label on its original stack
and re-execute the split check.
Here is an example of a split stack overflow showing the full
trace, without any special cases in the stack printer.
(This one was triggered by making the split check incorrect.)
runtime: newstack framesize=0x0 argsize=0x18 sp=0x6aebd0 stack=[0x6b0000, 0x6b0fa0]
morebuf={pc:0x69f5b sp:0x6aebd8 lr:0x0}
sched={pc:0x68880 sp:0x6aebd0 lr:0x0 ctxt:0x34e700}
runtime: split stack overflow: 0x6aebd0 < 0x6b0000
fatal error: runtime: split stack overflow
goroutine 1 [stack split]:
runtime.mallocgc(0x290, 0x100000000, 0x1)
/Users/rsc/g/go/src/pkg/runtime/zmalloc_darwin_amd64.c:21 fp=0x6aebd8
runtime.new()
/Users/rsc/g/go/src/pkg/runtime/zmalloc_darwin_amd64.c:682 +0x5b fp=0x6aec08
go/build.(*Context).Import(0x5ae340, 0xc210030c71, 0xa, 0xc2100b4380, 0x1b, ...)
/Users/rsc/g/go/src/pkg/go/build/build.go:424 +0x3a fp=0x6b00a0
main.loadImport(0xc210030c71, 0xa, 0xc2100b4380, 0x1b, 0xc2100b42c0, ...)
/Users/rsc/g/go/src/cmd/go/pkg.go:249 +0x371 fp=0x6b01a8
main.(*Package).load(0xc21017c800, 0xc2100b42c0, 0xc2101828c0, 0x0, 0x0, ...)
/Users/rsc/g/go/src/cmd/go/pkg.go:431 +0x2801 fp=0x6b0c98
main.loadPackage(0x369040, 0x7, 0xc2100b42c0, 0x0)
/Users/rsc/g/go/src/cmd/go/pkg.go:709 +0x857 fp=0x6b0f80
----- stack segment boundary -----
main.(*builder).action(0xc2100902a0, 0x0, 0x0, 0xc2100e6c00, 0xc2100e5750, ...)
/Users/rsc/g/go/src/cmd/go/build.go:539 +0x437 fp=0x6b14a0
main.(*builder).action(0xc2100902a0, 0x0, 0x0, 0xc21015b400, 0x2, ...)
/Users/rsc/g/go/src/cmd/go/build.go:528 +0x1d2 fp=0x6b1658
main.(*builder).test(0xc2100902a0, 0xc210092000, 0x0, 0x0, 0xc21008ff60, ...)
/Users/rsc/g/go/src/cmd/go/test.go:622 +0x1b53 fp=0x6b1f68
----- stack segment boundary -----
main.runTest(0x5a6b20, 0xc21000a020, 0x2, 0x2)
/Users/rsc/g/go/src/cmd/go/test.go:366 +0xd09 fp=0x6a5cf0
main.main()
/Users/rsc/g/go/src/cmd/go/main.go:161 +0x4f9 fp=0x6a5f78
runtime.main()
/Users/rsc/g/go/src/pkg/runtime/proc.c:183 +0x92 fp=0x6a5fa0
runtime.goexit()
/Users/rsc/g/go/src/pkg/runtime/proc.c:1266 fp=0x6a5fa8
And here is a seg fault during oldstack:
SIGSEGV: segmentation violation
PC=0x1b2a6
runtime.oldstack()
/Users/rsc/g/go/src/pkg/runtime/stack.c:159 +0x76
runtime.lessstack()
/Users/rsc/g/go/src/pkg/runtime/asm_amd64.s:270 +0x22
goroutine 1 [stack unsplit]:
fmt.(*pp).printArg(0x2102e64e0, 0xe5c80, 0x2102c9220, 0x73, 0x0, ...)
/Users/rsc/g/go/src/pkg/fmt/print.go:818 +0x3d3 fp=0x221031e6f8
fmt.(*pp).doPrintf(0x2102e64e0, 0x12fb20, 0x2, 0x221031eb98, 0x1, ...)
/Users/rsc/g/go/src/pkg/fmt/print.go:1183 +0x15cb fp=0x221031eaf0
fmt.Sprintf(0x12fb20, 0x2, 0x221031eb98, 0x1, 0x1, ...)
/Users/rsc/g/go/src/pkg/fmt/print.go:234 +0x67 fp=0x221031eb40
flag.(*stringValue).String(0x2102c9210, 0x1, 0x0)
/Users/rsc/g/go/src/pkg/flag/flag.go:180 +0xb3 fp=0x221031ebb0
flag.(*FlagSet).Var(0x2102f6000, 0x293d38, 0x2102c9210, 0x143490, 0xa, ...)
/Users/rsc/g/go/src/pkg/flag/flag.go:633 +0x40 fp=0x221031eca0
flag.(*FlagSet).StringVar(0x2102f6000, 0x2102c9210, 0x143490, 0xa, 0x12fa60, ...)
/Users/rsc/g/go/src/pkg/flag/flag.go:550 +0x91 fp=0x221031ece8
flag.(*FlagSet).String(0x2102f6000, 0x143490, 0xa, 0x12fa60, 0x0, ...)
/Users/rsc/g/go/src/pkg/flag/flag.go:563 +0x87 fp=0x221031ed38
flag.String(0x143490, 0xa, 0x12fa60, 0x0, 0x161950, ...)
/Users/rsc/g/go/src/pkg/flag/flag.go:570 +0x6b fp=0x221031ed80
testing.init()
/Users/rsc/g/go/src/pkg/testing/testing.go:-531 +0xbb fp=0x221031edc0
strings_test.init()
/Users/rsc/g/go/src/pkg/strings/strings_test.go:1115 +0x62 fp=0x221031ef70
main.init()
strings/_test/_testmain.go:90 +0x3d fp=0x221031ef78
runtime.main()
/Users/rsc/g/go/src/pkg/runtime/proc.c:180 +0x8a fp=0x221031efa0
runtime.goexit()
/Users/rsc/g/go/src/pkg/runtime/proc.c:1269 fp=0x221031efa8
goroutine 2 [runnable]:
runtime.MHeap_Scavenger()
/Users/rsc/g/go/src/pkg/runtime/mheap.c:438
runtime.goexit()
/Users/rsc/g/go/src/pkg/runtime/proc.c:1269
created by runtime.main
/Users/rsc/g/go/src/pkg/runtime/proc.c:166
rax 0x23ccc0
rbx 0x23ccc0
rcx 0x0
rdx 0x38
rdi 0x2102c0170
rsi 0x221032cfe0
rbp 0x221032cfa0
rsp 0x7fff5fbff5b0
r8 0x2102c0120
r9 0x221032cfa0
r10 0x221032c000
r11 0x104ce8
r12 0xe5c80
r13 0x1be82baac718
r14 0x13091135f7d69200
r15 0x0
rip 0x1b2a6
rflags 0x10246
cs 0x2b
fs 0x0
gs 0x0
Fixes #5723.
R=r, dvyukov, go.peter.90, dave, iant
CC=golang-dev
https://golang.org/cl/10360048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6fa3c89b77c2b616be41ef9dd250b9f0e3e851f5
元コミット内容
このコミットの目的は、Goランタイムがスタック分割(stack split)処理中、特にnewstack
およびoldstack
関数が実行されている間に、ゴルーチンの状態をより正確に記録することです。これまでの実装では、これらのルーチンが実行されている間、ゴルーチンの状態が散逸しており、スタックトレースなどのシステム内の他の部分がゴルーチンの正確な状態を理解できないという問題がありました。
具体的には、newstack
がスタックオーバーフローを検出し、throw
を呼び出すような状況で、スタックトレーサーがゴルーチンを正しくダンプできないことがありました。これは、newstack
がスタック分割をトリガーした関数のPC(プログラムカウンタ)を関数の先頭まで巻き戻し、有用な状態スナップショットを保存する必要があるためです。しかし、これまでのプロローグ(関数の冒頭部分)の構造では、これが困難でした。
変更の背景
Goのランタイムは、ゴルーチンのスタックを動的に拡張・縮小する「分割スタック(split stack)」というメカニズムを採用しています。これにより、小さなスタックでゴルーチンを開始し、必要に応じてスタックを拡張することでメモリ使用量を効率化しています。スタックの拡張が必要になった場合、Goコンパイラは各関数のプロローグにスタックガードチェックを挿入します。スタックが不足していると判断されると、runtime.morestack
関数が呼び出され、新しいスタックセグメントの割り当てや既存スタックの拡張が行われます。
しかし、このスタック分割処理中に、ゴルーチンのPC(プログラムカウンタ)やSP(スタックポインタ)などの重要なレジスタ情報が一時的に不整合な状態になることがありました。特に、morestack
が呼び出された直後、元の関数に戻る前の状態では、スタックトレースが正しく生成できない、あるいはゴルーチンの状態が不明瞭になるという問題が顕在化していました。
この問題は、デバッグ時やクラッシュ時のスタックトレースの信頼性を著しく低下させ、問題の特定を困難にしていました。また、Goのランタイムがゴルーチンをプリエンプション(強制的に実行を中断し、別のゴルーチンに切り替える)する際にも、正確なゴルーチン状態の把握が不可欠であり、この不整合はプリエンプションの正確性にも影響を与える可能性がありました。
このコミットは、これらの問題を解決し、スタック分割処理中であってもゴルーチンの状態を常に正確に保つことで、スタックトレースの信頼性向上とプリエンプションの正確性確保を目指しています。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念とアセンブリ言語の知識が不可欠です。
- ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。OSのスレッドとは異なり、Goランタイムがスケジューリングを行います。
- スタック分割 (Split Stack): Goのゴルーチンは、最初は小さなスタックで開始し、必要に応じてスタックを動的に拡張します。これにより、メモリ使用量を効率化します。スタックが不足すると、
runtime.morestack
が呼び出され、新しいスタックセグメントが割り当てられます。 - 関数プロローグ (Function Prologue): 関数が呼び出された直後に実行されるアセンブリコードのシーケンスです。スタックフレームのセットアップ、レジスタの保存、スタックガードチェックなどが含まれます。
runtime.morestack
: スタックが不足した際にGoランタイムによって呼び出される関数です。新しいスタックセグメントを割り当て、現在のゴルーチンの実行を新しいスタックに移行させます。runtime.newstack
:morestack
から呼び出される関数で、実際に新しいスタックの割り当てやスタック情報の更新を行います。runtime.oldstack
: スタックが縮小される際に呼び出される関数です。- PC (Program Counter): 次に実行される命令のアドレスを指すレジスタです。
- SP (Stack Pointer): 現在のスタックの最上位(または最下位、アーキテクチャによる)のアドレスを指すレジスタです。
Gobuf
: ゴルーチンの実行コンテキスト(PC、SP、Gレジスタなど)を保存するための構造体です。ゴルーチンの切り替え時に使用されます。m
(M-struct): OSスレッドを表すランタイムの構造体です。各Mは1つ以上のゴルーチンを実行できます。g
(G-struct): ゴルーチンを表すランタイムの構造体です。jmp
命令: アセンブリ言語における無条件ジャンプ命令です。指定されたアドレスにプログラムの実行フローを移します。call
命令: サブルーチン呼び出し命令です。呼び出し元のリターンアドレスをスタックにプッシュし、指定されたアドレスにジャンプします。TEXT
ディレクティブ: Goのアセンブリ言語(Plan 9アセンブラ)で関数を定義するために使用されます。StackGuard
: スタックオーバーフローを検出するための境界値です。SPがこの値を超えるとスタックオーバーフローと判断されます。- プリエンプション (Preemption): Goランタイムが、実行中のゴルーチンを強制的に中断し、別のゴルーチンにCPUを割り当てるメカニズムです。これにより、協調的スケジューリングではなく、より公平なスケジューリングが可能になります。
技術的詳細
このコミットの核心は、Goコンパイラが生成する関数のプロローグと、ランタイムのスタック管理ルーチン(morestack
, newstack
, oldstack
)の連携を改善することにあります。
1. 関数プロローグの変更:
Goの関数は、スタックが不足しているかどうかをチェックするために、プロローグでruntime.morestack
を呼び出します。これまでのプロローグは、morestack
の呼び出し後に直接関数の本体に進んでいました。
変更後のプロローグは、call morestack
の直後にjmp start
(関数の先頭へのジャンプ命令)を挿入します。
TEXT myfunc
start:
check for split
jmpcond nosplit
call morestack
jmp start // <-- 新しく挿入されたジャンプ命令
nosplit:
sub $xxx, sp
このjmp start
命令は、CPUによって直接実行されることを意図していません。morestack
がスタックを拡張し、元の関数に戻る際に、このjmp
命令の存在が重要になります。
2. runtime.rewindmorestack
の導入:
runtime.newstack
関数は、スタック分割をトリガーした関数のPCを、その関数の先頭まで巻き戻す必要があります。これは、スタックトレースを正確に生成するため、およびプリエンプション時にゴルーチンの状態を正しく保存するために不可欠です。
新しく導入されたruntime.rewindmorestack
関数は、このjmp start
命令をデコードし、シミュレートすることで、関数の先頭アドレスを特定します。morestack
からの戻り値は、このjmp
命令の直後ではなく、関数の先頭(start
ラベル)に直接設定されます。これにより、論理的にはjmp
命令が実行されたかのように振る舞いますが、CPUによる実際のジャンプは発生しません。
runtime.rewindmorestack
の実装は、アーキテクチャ(x86, ARMなど)に依存します。例えば、x86ではjmp
命令のオペコード(0xe9
や0xeb
)をチェックし、そのオフセットを計算してPCを巻き戻します。ARMでは、特定の条件付き分岐命令(BLS
など)のエンコーディングを解析します。
3. ゴルーチン状態の正確な記録:
runtime.morestack
およびruntime.newstack
の内部で、ゴルーチンのGobuf
構造体(g->sched
)にPC、SP、コンテキスト(ctxt
)などの情報が正確に保存されるようになりました。これにより、スタック分割処理中にゴルーチンの状態が常に一貫して保たれます。
m->morepc
などの古いフィールドは削除され、g->sched
に直接情報が格納されるようになりました。runtime.newstack
では、ゴルーチンのステータスがGwaiting
に設定され、waitreason
が"stack split"と記録されます。runtime.oldstack
では、ゴルーチンのステータスがGwaiting
に設定され、waitreason
が"stack unsplit"と記録され、その後Grunning
に戻されます。
4. スタックトレースの改善:
runtime.gentraceback
関数は、スタックトレースを生成する主要な関数です。このコミットにより、gentraceback
はnewstack
やlessstack
といったランタイム内部の特殊なフレームを特別扱いする必要がなくなりました。ゴルーチンの状態が常に正確に記録されるため、一般的なトレースバックロジックで全てのフレームを正しく辿れるようになりました。
また、runtime.showframe
関数のシグネチャが変更され、現在のゴルーチンかどうかを示すブール値の代わりにG* gp
(ゴルーチンポインタ)を受け取るようになりました。これにより、トレースバック中にどのゴルーチンのフレームを表示するかをより柔軟に判断できるようになります。
5. プリエンプションへの影響:
newstack
がスタック分割ではなくプリエンプションのために呼び出された場合、この変更によりゴルーチンの状態が正しく一時停止されます。スケジューリングが必要な場合、ゴルーチンは元のスタック上のstart
ラベルに戻り、スタックチェックを再実行できます。これにより、プリエンプションの信頼性と正確性が向上します。
6. スタックオーバーフローの二重チェック:
プロローグが繰り返されることで、morestack
が誤って小さすぎるスタックに戻った場合でも、スタック分割チェックが再度行われるため、データ破損が発生する前に問題を検出できるようになります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイル群に集中しています。
-
リンカ (
src/cmd/{5,6,8}l/pass.c
,src/cmd/{5,6,8}g/gsubr.c
):- Goコンパイラが生成するアセンブリコードにおいて、関数のプロローグに
call morestack
の直後にjmp start
命令(またはそれに相当するアーキテクチャ固有のジャンプ命令)を挿入するロジックが追加されました。 - 例えば、
src/cmd/6l/pass.c
では、dostkoff
関数内でAJMP
命令が追加され、そのジャンプ先がcursym->text->link
(関数の先頭)に設定されています。
- Goコンパイラが生成するアセンブリコードにおいて、関数のプロローグに
-
ランタイムアセンブリコード (
src/pkg/runtime/asm_{386,amd64,arm}.s
):runtime.morestack
関数のアセンブリ実装が変更され、ゴルーチンの状態(PC, SP, ctxt)をg->sched
に直接保存するようになりました。m->morepc
やm->cret
といった古いフィールドへの参照が削除されました。
-
ランタイムスタック管理 (
src/pkg/runtime/stack.c
):runtime.newstack
関数内で、runtime.rewindmorestack
が呼び出されるようになりました。これにより、スタック分割をトリガーした関数のPCが巻き戻されます。runtime.newstack
およびruntime.oldstack
内で、ゴルーチンのステータス(gp->status
)と待機理由(gp->waitreason
)が適切に設定されるようになりました。- デバッグ用の
StackDebug
フラグが追加され、詳細なスタック情報が出力できるようになりました。
-
ランタイムシステムコール・シグナルハンドリング (
src/pkg/runtime/sys_arm.c
,src/pkg/runtime/sys_x86.c
,src/pkg/runtime/signal_*.c
):runtime.rewindmorestack
関数のアーキテクチャ固有の実装が追加されました。この関数は、リンカによって挿入されたjmp
命令を解析し、関数の先頭アドレスを計算します。- シグナルハンドラ内で、致命的なシグナル発生時に
m->throwing
とm->caughtsig
が設定されるようになりました。
-
ランタイムヘッダ (
src/pkg/runtime/runtime.h
):runtime.rewindmorestack
関数のプロトタイプが追加されました。M
構造体からmorepc
フィールドが削除され、caughtsig
フィールドが追加されました。runtime.gentraceback
およびruntime.showframe
関数のシグネチャが変更されました。
-
トレースバック (
src/pkg/runtime/traceback_arm.c
,src/pkg/runtime/traceback_x86.c
):runtime.gentraceback
関数から、newstack
やlessstack
に関する特殊な処理が削除されました。これは、ゴルーチン状態が常に正確に記録されるようになったため、これらの特殊なケースを考慮する必要がなくなったためです。gentraceback
にprintall
という新しいブール引数が追加され、ランタイムフレームを含む全てのフレームを強制的に出力するかどうかを制御できるようになりました。printcreatedby
というヘルパー関数が追加され、ゴルーチンがどこで作成されたかを出力するようになりました。
コアとなるコードの解説
このコミットの最も重要な変更は、リンカが生成する関数プロローグと、それを解釈するランタイムのruntime.rewindmorestack
関数にあります。
リンカによるプロローグの変更例 (src/cmd/6l/pass.c):
// src/cmd/6l/pass.c (抜粋)
// ...
p = appendp(p);
p->as = ACALL; // morestackを呼び出す命令
p->to.type = D_BRANCH;
p->to.sym = symmorestack[3]; // runtime.morestackへのシンボル
p->pcond = pmorestack[3];
// 新しく追加されたジャンプ命令
p = appendp(p);
p->as = AJMP; // 無条件ジャンプ
p->to.type = D_BRANCH;
p->pcond = cursym->text->link; // 関数の先頭へのジャンプ
// ...
ここでAJMP
命令がACALL
(morestack
呼び出し)の直後に挿入されています。p->pcond = cursym->text->link;
は、このジャンプ命令のターゲットが現在の関数のテキストセグメントの先頭(つまり、関数のエントリポイント)であることを示しています。
runtime.rewindmorestack
の実装例 (src/pkg/runtime/sys_x86.c):
// src/pkg/runtime/sys_x86.c (抜粋)
// Called to rewind context saved during morestack back to beginning of function.
// To help us, the linker emits a jmp back to the beginning right after the
// call to morestack. We just have to decode and apply that jump.
void
runtime·rewindmorestack(Gobuf *gobuf)
{
byte *pc;
pc = (byte*)gobuf->pc;
if(pc[0] == 0xe9) { // jmp 4-byte offset (near jump)
gobuf->pc = gobuf->pc + 5 + *(int32*)(pc+1);
return;
}
if(pc[0] == 0xeb) { // jmp 1-byte offset (short jump)
gobuf->pc = gobuf->pc + 2 + *(int8*)(pc+1);
return;
}
runtime·printf("runtime: pc=%p %x %x %x %x %x\n", pc, pc[0], pc[1], pc[1], pc[2], pc[3], pc[4]);
runtime·throw("runtime: misuse of rewindmorestack");
}
この関数は、Gobuf
構造体に含まれるPC(gobuf->pc
)を読み取り、そのアドレスにある命令を調べます。もしその命令がjmp
命令(x86では0xe9
または0xeb
)であれば、その命令のオペランド(ジャンプオフセット)を解析し、PCを関数の先頭まで巻き戻します。これにより、morestack
から戻った際に、あたかも関数が最初から実行されたかのように振る舞い、スタックチェックが再度行われることになります。
runtime.newstack
の変更 (src/pkg/runtime/stack.c):
// src/pkg/runtime/stack.c (抜粋)
// ...
gp = m->curg;
gp->status = Gwaiting; // ゴルーチン状態をGwaitingに設定
gp->waitreason = "stack split"; // 待機理由を記録
reflectcall = framesize==1;
if(!reflectcall)
runtime·rewindmorestack(&gp->sched); // ここでPCを巻き戻す
// ...
// ... (新しいスタックの割り当てとデータコピー) ...
// ...
label.pc = (uintptr)runtime·lessstack;
label.g = m->curg;
if(reflectcall)
runtime·gostartcallfn(&label, (FuncVal*)m->cret);
else {
runtime·gostartcall(&label, (void(*)(void))gp->sched.pc, gp->sched.ctxt); // 巻き戻されたPCを使用
gp->sched.ctxt = nil;
}
gp->status = Grunning; // 実行状態に戻す
runtime·gogo(&label);
// ...
runtime.newstack
は、スタック分割処理の開始時にゴルーチンの状態をGwaiting
に設定し、runtime.rewindmorestack
を呼び出してPCを巻き戻します。その後、新しいスタックで実行を再開する際に、巻き戻されたPCを使用してruntime.gostartcall
を呼び出します。これにより、スタック分割処理中であっても、ゴルーチンの状態が正確に保たれ、スタックトレースやプリエンプションが正しく機能するようになります。
関連リンク
- Go Issue #5723: runtime: stack traces during stack split are bad
- Go CL 10360048: https://golang.org/cl/10360048
参考にした情報源リンク
- Goのソースコード (特に
src/cmd/
,src/pkg/runtime/
ディレクトリ) - Goの分割スタックに関するドキュメントやブログ記事 (例: "Go's Stack Management")
- アセンブリ言語(x86, ARM)の命令セットリファレンス
- Goのランタイムスケジューラに関する資料