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

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

このコミットは、Goランタイムにおけるスタック分割(stack split)時のゴルーチン状態の記録方法を改善し、特にスタックトレースの正確性を向上させることを目的としています。これまでの実装では、newstackoldstackといったスタック操作ルーチン実行中にゴルーチンの状態が散逸し、システムがゴルーチンの正確な状態を把握できない問題がありました。これにより、スタックオーバーフローなどの致命的なエラー発生時に、正確なスタックトレースが出力されないという課題がありました。

コミット

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ランタイムの概念とアセンブリ言語の知識が不可欠です。

  1. ゴルーチン (Goroutine): Goにおける軽量な実行スレッドです。OSのスレッドとは異なり、Goランタイムがスケジューリングを行います。
  2. スタック分割 (Split Stack): Goのゴルーチンは、最初は小さなスタックで開始し、必要に応じてスタックを動的に拡張します。これにより、メモリ使用量を効率化します。スタックが不足すると、runtime.morestackが呼び出され、新しいスタックセグメントが割り当てられます。
  3. 関数プロローグ (Function Prologue): 関数が呼び出された直後に実行されるアセンブリコードのシーケンスです。スタックフレームのセットアップ、レジスタの保存、スタックガードチェックなどが含まれます。
  4. runtime.morestack: スタックが不足した際にGoランタイムによって呼び出される関数です。新しいスタックセグメントを割り当て、現在のゴルーチンの実行を新しいスタックに移行させます。
  5. runtime.newstack: morestackから呼び出される関数で、実際に新しいスタックの割り当てやスタック情報の更新を行います。
  6. runtime.oldstack: スタックが縮小される際に呼び出される関数です。
  7. PC (Program Counter): 次に実行される命令のアドレスを指すレジスタです。
  8. SP (Stack Pointer): 現在のスタックの最上位(または最下位、アーキテクチャによる)のアドレスを指すレジスタです。
  9. Gobuf: ゴルーチンの実行コンテキスト(PC、SP、Gレジスタなど)を保存するための構造体です。ゴルーチンの切り替え時に使用されます。
  10. m (M-struct): OSスレッドを表すランタイムの構造体です。各Mは1つ以上のゴルーチンを実行できます。
  11. g (G-struct): ゴルーチンを表すランタイムの構造体です。
  12. jmp 命令: アセンブリ言語における無条件ジャンプ命令です。指定されたアドレスにプログラムの実行フローを移します。
  13. call 命令: サブルーチン呼び出し命令です。呼び出し元のリターンアドレスをスタックにプッシュし、指定されたアドレスにジャンプします。
  14. TEXT ディレクティブ: Goのアセンブリ言語(Plan 9アセンブラ)で関数を定義するために使用されます。
  15. StackGuard: スタックオーバーフローを検出するための境界値です。SPがこの値を超えるとスタックオーバーフローと判断されます。
  16. プリエンプション (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命令のオペコード(0xe90xeb)をチェックし、そのオフセットを計算して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関数は、スタックトレースを生成する主要な関数です。このコミットにより、gentracebacknewstacklessstackといったランタイム内部の特殊なフレームを特別扱いする必要がなくなりました。ゴルーチンの状態が常に正確に記録されるため、一般的なトレースバックロジックで全てのフレームを正しく辿れるようになりました。

また、runtime.showframe関数のシグネチャが変更され、現在のゴルーチンかどうかを示すブール値の代わりにG* gp(ゴルーチンポインタ)を受け取るようになりました。これにより、トレースバック中にどのゴルーチンのフレームを表示するかをより柔軟に判断できるようになります。

5. プリエンプションへの影響: newstackがスタック分割ではなくプリエンプションのために呼び出された場合、この変更によりゴルーチンの状態が正しく一時停止されます。スケジューリングが必要な場合、ゴルーチンは元のスタック上のstartラベルに戻り、スタックチェックを再実行できます。これにより、プリエンプションの信頼性と正確性が向上します。

6. スタックオーバーフローの二重チェック: プロローグが繰り返されることで、morestackが誤って小さすぎるスタックに戻った場合でも、スタック分割チェックが再度行われるため、データ破損が発生する前に問題を検出できるようになります。

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

このコミットにおける主要なコード変更は、以下のファイル群に集中しています。

  1. リンカ (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(関数の先頭)に設定されています。
  2. ランタイムアセンブリコード (src/pkg/runtime/asm_{386,amd64,arm}.s):

    • runtime.morestack関数のアセンブリ実装が変更され、ゴルーチンの状態(PC, SP, ctxt)をg->schedに直接保存するようになりました。
    • m->morepcm->cretといった古いフィールドへの参照が削除されました。
  3. ランタイムスタック管理 (src/pkg/runtime/stack.c):

    • runtime.newstack関数内で、runtime.rewindmorestackが呼び出されるようになりました。これにより、スタック分割をトリガーした関数のPCが巻き戻されます。
    • runtime.newstackおよびruntime.oldstack内で、ゴルーチンのステータス(gp->status)と待機理由(gp->waitreason)が適切に設定されるようになりました。
    • デバッグ用のStackDebugフラグが追加され、詳細なスタック情報が出力できるようになりました。
  4. ランタイムシステムコール・シグナルハンドリング (src/pkg/runtime/sys_arm.c, src/pkg/runtime/sys_x86.c, src/pkg/runtime/signal_*.c):

    • runtime.rewindmorestack関数のアーキテクチャ固有の実装が追加されました。この関数は、リンカによって挿入されたjmp命令を解析し、関数の先頭アドレスを計算します。
    • シグナルハンドラ内で、致命的なシグナル発生時にm->throwingm->caughtsigが設定されるようになりました。
  5. ランタイムヘッダ (src/pkg/runtime/runtime.h):

    • runtime.rewindmorestack関数のプロトタイプが追加されました。
    • M構造体からmorepcフィールドが削除され、caughtsigフィールドが追加されました。
    • runtime.gentracebackおよびruntime.showframe関数のシグネチャが変更されました。
  6. トレースバック (src/pkg/runtime/traceback_arm.c, src/pkg/runtime/traceback_x86.c):

    • runtime.gentraceback関数から、newstacklessstackに関する特殊な処理が削除されました。これは、ゴルーチン状態が常に正確に記録されるようになったため、これらの特殊なケースを考慮する必要がなくなったためです。
    • gentracebackprintallという新しいブール引数が追加され、ランタイムフレームを含む全てのフレームを強制的に出力するかどうかを制御できるようになりました。
    • 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命令がACALLmorestack呼び出し)の直後に挿入されています。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のソースコード (特にsrc/cmd/, src/pkg/runtime/ ディレクトリ)
  • Goの分割スタックに関するドキュメントやブログ記事 (例: "Go's Stack Management")
  • アセンブリ言語(x86, ARM)の命令セットリファレンス
  • Goのランタイムスケジューラに関する資料