[インデックス 16552] ファイルの概要
コミット
commit e58f798c0c97c252e0b800a4223d20790ef6b166
Author: Russ Cox <rsc@golang.org>
Date: Wed Jun 12 08:49:38 2013 -0400
runtime: adjust traceback / garbage collector boundary
The garbage collection routine addframeroots is duplicating
logic in the traceback routine that calls it, sometimes correctly,
sometimes incorrectly, sometimes incompletely.
Pass necessary information to addframeroots instead of
deriving it anew.
Should make addframeroots significantly more robust.
It's certainly smaller.
Also try to standardize on uintptr for saved pc, sp values.
Will make CL 10036044 trivial.
R=golang-dev, dave, dvyukov
CC=golang-dev
https://golang.org/cl/10169045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e58f798c0c97c252e0b800a4223d20790ef6b166
元コミット内容
このコミットは、Goランタイムにおけるトレースバック(スタックトレース生成)とガベージコレクタ(GC)の境界を調整することを目的としています。具体的には、GCルーチンであるaddframeroots
が、それを呼び出すトレースバックルーチン内のロジックを重複して持っている問題を解決します。この重複は、時に正しく、時に不正確に、時に不完全に実装されており、バグの原因となる可能性がありました。
変更の主な内容は以下の通りです。
addframeroots
関数に、必要な情報を新たに導出するのではなく、呼び出し元から直接渡すように変更します。これにより、addframeroots
の堅牢性が向上し、コードサイズも削減されます。- 保存される
pc
(プログラムカウンタ)とsp
(スタックポインタ)の値にuintptr
型を標準的に使用するように変更します。
この変更は、関連する変更セット(CL 10036044)をより単純化する効果も期待されています。
変更の背景
Goランタイムでは、ガベージコレクション(GC)の際に、スタック上のライブオブジェクト(参照されているオブジェクト)を正確に識別し、マークする必要があります。このプロセスには、各スタックフレーム内のローカル変数や引数をスキャンする機能が必要です。同時に、デバッグやエラー報告のためにスタックトレースを生成する機能も存在します。
これまでの実装では、addframeroots
(GCの一部)とトレースバックルーチン(gentraceback
など)が、スタックフレームの構造を解析し、変数や引数の位置を特定するロジックをそれぞれ独自に持っていました。この重複は、以下のような問題を引き起こしていました。
- コードの重複と保守性の低下: 同じようなロジックが複数箇所に存在するため、一方の変更がもう一方に反映されず、不整合が生じるリスクがありました。
- バグの温床: スタックフレームの構造は複雑であり、アーキテクチャやコンパイラの最適化によって変化する可能性があります。重複したロジックは、異なる実装間で微妙な差異が生じやすく、それがバグにつながる可能性がありました。特に、GCは正確性が極めて重要であるため、このような不正確さは深刻な問題となります。
- 複雑性の増加:
addframeroots
が自身でスタックフレームの情報を再構築しようとすることで、その関数自体の複雑性が増していました。
このコミットは、これらの問題を解決し、GCとトレースバックの間の境界をより明確にすることで、ランタイムの堅牢性と保守性を向上させることを目的としています。具体的には、トレースバックルーチンがスタックフレームの情報を正確に解析し、その結果をaddframeroots
に渡すことで、addframeroots
が重複したロジックを持つ必要がなくなります。
また、pc
やsp
といったポインタ値をuintptr
型で統一することは、ポインタ演算の安全性を高め、異なるアーキテクチャ間での互換性を向上させるための一般的なプラクティスです。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムの概念に関する知識が必要です。
1. ガベージコレクション (GC) とスタックスキャン
Goはトレース型ガベージコレクタを採用しています。GCは、プログラムが使用しなくなったメモリを自動的に解放する役割を担います。GoのGCは、主に以下のフェーズで動作します。
- マークフェーズ: ルート(グローバル変数、アクティブなゴルーチンのスタック、レジスタなど)から到達可能なすべてのオブジェクトをマークします。
- スキャンフェーズ: マークされたオブジェクトをスキャンし、そこから参照されている他のオブジェクトをマークします。このプロセスを再帰的に繰り返します。
- スイープフェーズ: マークされなかったオブジェクト(到達不能なオブジェクト)を解放します。
このコミットで関連するのは「スタックスキャン」です。各ゴルーチンは独自のスタックを持っており、そのスタック上にはローカル変数や関数呼び出しの引数、戻り値などが格納されています。GCは、これらのスタックフレームをスキャンして、ヒープ上のオブジェクトへのポインタを見つけ出し、それらをルートとしてマークする必要があります。このスタックフレームのスキャンを担当するルーチンの一つがaddframeroots
です。
2. スタックトレースとトレースバック
スタックトレース(またはバックトレース)は、プログラムの実行中に特定の時点での関数呼び出しのシーケンス(コールスタック)を示すものです。Goでは、パニック発生時やデバッグ時にスタックトレースが生成されます。スタックトレースは、どの関数がどの関数を呼び出し、現在どの関数が実行されているかを示すことで、問題の診断に役立ちます。
Goランタイムには、このスタックトレースを生成するためのgentraceback
という関数が存在します。この関数は、現在のプログラムカウンタ(PC)とスタックポインタ(SP)から始まり、スタックフレームを逆順に辿って、各関数の情報(関数名、ファイル名、行番号など)を収集します。
3. スタックフレームの構造
Goのスタックフレームは、関数呼び出しごとにスタック上に割り当てられるメモリ領域です。一般的なスタックフレームには、以下のような情報が含まれます。
- 戻りアドレス (Return Address): 呼び出し元に戻るための命令ポインタ。
- フレームポインタ (Frame Pointer - FP): 現在のスタックフレームの基準点を示すポインタ。
- スタックポインタ (Stack Pointer - SP): 現在のスタックの最上位(または最下位、アーキテクチャによる)を示すポインタ。
- 引数 (Arguments): 呼び出された関数に渡された引数。
- ローカル変数 (Local Variables): 関数内で宣言されたローカル変数。
Goのスタックフレームは、動的にサイズが変更される可能性があり(スタックの拡張/縮小)、また、コンパイラの最適化によってフレームポインタが省略されることもあります。これらの複雑さが、スタックフレームの正確な解析を困難にしています。
4. uintptr
型
uintptr
は、Goの組み込み型の一つで、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、ポインタを整数として扱う必要がある場合(例えば、ポインタ演算を行ったり、ポインタをハッシュテーブルのキーとして使用したりする場合)に便利です。しかし、uintptr
はガベージコレクタによって追跡されないため、GCが管理するオブジェクトへのポインタをuintptr
として保持すると、そのオブジェクトが誤って解放される可能性があります。このコミットでは、pc
やsp
といったメモリ上のアドレスを表現するためにuintptr
を使用しており、これらはGCの対象となるデータへのポインタではないため、問題ありません。
5. Stkframe
構造体
このコミットで導入されるStkframe
構造体は、単一のスタックフレームに関する情報をカプセル化するためのものです。これには、関数ポインタ、PC、LR(リンクレジスタ)、SP、FP、引数ポインタ、引数長、ローカル変数ポインタ、ローカル変数長などが含まれます。この構造体を使用することで、スタックフレームの情報を一貫した形式でgentraceback
からaddframeroots
へ渡すことが可能になります。
技術的詳細
このコミットの技術的な核心は、Goランタイムのスタックフレーム解析ロジックの集約と、データ型の標準化にあります。
1. addframeroots
とgentraceback
の役割分担の明確化
以前のバージョンでは、addframeroots
(GCの一部)とgentraceback
(スタックトレース生成)の両方が、スタックフレームの構造を解析し、引数やローカル変数の位置を特定するロジックを持っていました。これは、コードの重複と不整合の原因となっていました。
このコミットでは、この役割分担を明確にします。
gentraceback
: スタックフレームを逆順に辿り、各フレームのPC、SP、FP、LR、引数、ローカル変数などの詳細な情報を正確に解析する責任を負います。この解析結果は、新しく導入されたStkframe
構造体に格納されます。addframeroots
:gentraceback
によって解析されたStkframe
構造体を受け取り、その情報に基づいてスタックフレーム内のライブオブジェクト(ポインタ)をGCに登録する責任を負います。これにより、addframeroots
はスタックフレームの複雑な解析ロジックを持つ必要がなくなり、そのコードは大幅に簡素化され、堅牢性が向上します。
この変更により、スタックフレームの解析ロジックはgentraceback
に一元化され、GCの正確性とトレースバックの信頼性が同時に向上します。
2. Stkframe
構造体の導入
src/pkg/runtime/runtime.h
に新しい構造体Stkframe
が定義されました。
typedef struct Stkframe Stkframe;
struct Stkframe
{
Func* fn; // function being run
uintptr pc; // program counter within fn
uintptr lr; // program counter at caller aka link register
uintptr sp; // stack pointer at pc
uintptr fp; // stack pointer at caller aka frame pointer
byte* argp; // pointer to function arguments
uintptr arglen; // number of bytes at argp
byte* varp; // pointer to local variables
uintptr varlen; // number of bytes at varp
};
この構造体は、単一のスタックフレームに関する包括的な情報を提供します。gentraceback
は、スタックを辿る際にこのStkframe
構造体を構築し、それをaddframeroots
のようなコールバック関数に渡します。これにより、addframeroots
は必要なすべての情報を一貫した形式で受け取ることができます。
3. uintptr
型への統一
コミットメッセージにもあるように、pc
(プログラムカウンタ)やsp
(スタックポインタ)といったメモリ上のアドレスを表現する際に、byte*
型からuintptr
型への統一が進められています。
src/pkg/runtime/runtime.h
のGobuf
構造体内のpc
フィールドがbyte*
からuintptr
に変更されました。G
構造体内のgcpc
フィールドも同様にbyte*
からuintptr
に変更されました。Stktop
構造体内のstackguard
とstackbase
フィールドもuint8*
からuintptr
に変更されました。Panic
構造体内のstackbase
フィールドもbyte*
からuintptr
に変更されました。
この変更は、ポインタを整数として扱うことで、ポインタ演算の柔軟性を高めるとともに、異なるアーキテクチャ間でのポインタ表現の差異を吸収し、コードの移植性を向上させる効果があります。また、uintptr
はGCの対象とならないため、GCの正確性にも影響を与えません。
4. gentraceback
のシグネチャ変更とコールバックメカニズム
gentraceback
関数のシグネチャが変更され、新しいStkframe
構造体とコールバック関数を受け取るようになりました。
変更前:
int32 runtime·gentraceback(byte *pc0, byte *sp, byte *lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*fn)(Func*, byte*, byte*, void*), void *arg)
変更後:
int32 runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*callback)(Stkframe*, void*), void *v)
pc0
,sp0
,lr0
がbyte*
からuintptr
に変更されました。- コールバック関数のシグネチャが
void (*fn)(Func*, byte*, byte*, void*)
からvoid (*callback)(Stkframe*, void*)
に変更され、Stkframe
構造体を直接受け取るようになりました。
これにより、gentraceback
はスタックフレームを解析し、その結果をStkframe
としてコールバック関数(例: addframeroots
)に渡すことができるようになりました。
5. スタックフレーム情報の導出ロジックの集約
traceback_arm.c
とtraceback_x86.c
内のgentraceback
関数において、スタックフレームの引数領域(argp
, arglen
)とローカル変数領域(varp
, varlen
)を導出するロジックが追加・修正されました。
例えば、引数サイズの導出には、関数のf->args
フィールド(既知の場合)、runtime·goexit
やruntime·mcall
などの特殊な関数のエントリポイント、runtime·lessstack
からの呼び出し、deferproc
やnewproc
の引数構造などが考慮されています。
ローカル変数についても、フレームポインタとスタックポインタの関係、関数のf->locals
フィールドなどに基づいて、その位置とサイズが計算されます。
これらの複雑なロジックがgentraceback
内に集約されたことで、他のルーチン(特にaddframeroots
)はこれらの詳細を知る必要がなくなり、よりシンプルでエラーの少ない実装が可能になります。
コアとなるコードの変更箇所
このコミットは、主にGoランタイムの以下のファイルに影響を与えています。
src/pkg/runtime/mgc0.c
: ガベージコレクタのスタックスキャンロジックが含まれるファイル。addframeroots
関数の変更が中心。src/pkg/runtime/mprof.goc
: メモリプロファイリング関連のファイル。uintptr
型への変更が含まれる。src/pkg/runtime/panic.c
: パニック処理関連のファイル。スタックベースやPC/SPの型変更が含まれる。src/pkg/runtime/proc.c
: プロセス(ゴルーチン、Mなど)管理関連のファイル。PC/SPの型変更が含まれる。src/pkg/runtime/runtime.h
: ランタイムの主要なデータ構造と関数宣言が含まれるヘッダファイル。Stkframe
構造体の追加、既存構造体のフィールドの型変更、関数シグネチャの変更が中心。src/pkg/runtime/signal_386.c
,src/pkg/runtime/signal_amd64.c
,src/pkg/runtime/signal_arm.c
: シグナルハンドリング関連のファイル。トレースバック呼び出し時のPC/SPの型キャスト変更が含まれる。src/pkg/runtime/stack.c
: スタック管理関連のファイル。スタックベースやPC/SPの型変更が含まれる。src/pkg/runtime/traceback_arm.c
,src/pkg/runtime/traceback_x86.c
: ARMおよびx86アーキテクチャ向けのトレースバック実装ファイル。gentraceback
関数の大幅な変更(Stkframe
の利用、スタックフレーム解析ロジックの集約)が中心。
src/pkg/runtime/mgc0.c
の変更点
addframeroots
関数のシグネチャと実装が大幅に変更されました。
変更前:
static void
addframeroots(Func *f, byte*, byte *sp, void *doframe)
この関数は、Func
ポインタ、スタックポインタsp
、そしてdoframe
というフラグを受け取っていました。内部でスタックフレームの構造を解析し、引数領域やローカル変数領域をスキャンしていました。特に、doframe
フラグは、引数領域のサイズが不明な場合にスキャンを遅延させるための複雑なロジックを伴っていました。
変更後:
static void
addframeroots(Stkframe *frame, void*)
新しいシグネチャでは、Stkframe
構造体へのポインタを直接受け取ります。これにより、addframeroots
はスタックフレームの解析ロジックを内部に持つ必要がなくなり、Stkframe
から提供されるframe->varp
, frame->varlen
, frame->argp
, frame->arglen
といった情報を使って、シンプルにルートを追加するだけになりました。
src/pkg/runtime/runtime.h
の変更点
Stkframe
構造体の新規追加。Gobuf
、G
、Stktop
、Panic
構造体内のポインタ型フィールドがbyte*
やuint8*
からuintptr
に変更。runtime·gentraceback
、runtime·traceback
、runtime·getcallersp
などの関数シグネチャが、新しいuintptr
型やStkframe
構造体を使用するように変更。
src/pkg/runtime/traceback_arm.c
および src/pkg/runtime/traceback_x86.c
の変更点
gentraceback
関数の実装が大幅に書き換えられました。
- 関数シグネチャが
uintptr
型とStkframe
コールバックを使用するように変更。 - 内部で
Stkframe
構造体を初期化し、スタックを辿る各ステップでこの構造体に現在のフレームのPC、SP、LR、FP、引数、ローカル変数の情報を格納するロジックが追加されました。 - 特に、引数とローカル変数の位置とサイズを正確に導出するための複雑なロジックがこの関数内に集約されました。これには、関数の種類(例:
runtime·goexit
,runtime·deferproc
)、スタックの拡張/縮小(runtime·lessstack
)、パニック処理(runtime·sigpanic
)などが考慮されています。 addframeroots
のようなコールバック関数は、この完全に構築されたStkframe
構造体を受け取るようになりました。
コアとなるコードの解説
ここでは、主要な変更点であるaddframeroots
とgentraceback
の変更に焦点を当てて解説します。
addframeroots
(src/pkg/runtime/mgc0.c)
// Scan a stack frame: local variables and function arguments/results.
static void
addframeroots(Stkframe *frame, void*)
{
Func *f;
byte *ap;
int32 i, j, nuintptr;
uint32 w, b;
// Scan local variables if stack frame has been allocated.
if(frame->varlen > 0)
addroot((Obj){frame->varp, frame->varlen, 0});
// Scan arguments.
// Use pointer information if known.
f = frame->fn;
if(f->args > 0 && f->ptrs.array != nil) {
ap = frame->argp;
nuintptr = f->args / sizeof(uintptr);
for(i = 0; i < f->ptrs.len; i++) {
w = ((uint32*)f->ptrs.array)[i];
b = 1;
j = nuintptr;
if(j > 32)
j = 32;
for(; j > 0; j--) {
if(w & b)
addroot((Obj){ap, sizeof(uintptr), 0});
b <<= 1;
ap += sizeof(uintptr);
}
nuintptr -= 32;
}
} else
addroot((Obj){frame->argp, frame->arglen, 0});
}
この新しいaddframeroots
関数は、以前のバージョンと比較して劇的に簡素化されています。
- 入力の変更: 以前は
Func *f, byte *sp, void *doframe
といった引数を受け取り、自身でスタックフレームのオフセットなどを計算する必要がありました。しかし、変更後はStkframe *frame
という単一の引数を受け取ります。このStkframe
構造体には、既にgentraceback
によって解析された、引数領域(argp
,arglen
)とローカル変数領域(varp
,varlen
)の正確なポインタとサイズが含まれています。 - ローカル変数のスキャン:
if(frame->varlen > 0)
の条件で、ローカル変数領域が存在する場合、frame->varp
とframe->varlen
を使用してaddroot
を呼び出し、GCルートとして登録します。 - 引数のスキャン:
- 関数
f
にポインタ情報(f->ptrs.array
)がある場合、そのビットマップ情報に基づいて、引数領域内のどのワードがポインタであるかを正確に識別し、addroot
で登録します。これは、引数にポインタと非ポインタが混在している場合に、非ポインタをスキャンしないようにするための最適化です。 - ポインタ情報がない場合(
else
ブロック)、frame->argp
とframe->arglen
を使用して、引数領域全体をGCルートとして登録します。
- 関数
この変更により、addframeroots
はスタックフレームの複雑なレイアウトを理解する必要がなくなり、単にgentraceback
が提供する情報に基づいてGCルートを追加するだけの、よりシンプルで堅牢な関数になりました。
gentraceback
(src/pkg/runtime/traceback_arm.c および src/pkg/runtime/traceback_x86.c)
gentraceback
関数は、スタックトレースの生成と、GCのためのスタックフレーム情報の提供という二つの主要な役割を担うようになりました。その実装は、スタックフレームの複雑な構造を正確に解析するための中心的なロジックを含んでいます。
int32
runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip, uintptr *pcbuf, int32 max, void (*callback)(Stkframe*, void*), void *v)
{
// ... (初期化とループのセットアップ) ...
// メインのスタックフレーム走査ループ
for(;;) {
// ... (スタックセグメント境界の処理) ...
// 関数情報の取得
if(frame.pc <= 0x1000 || (frame.fn = f = runtime·findfunc(frame.pc)) == nil) {
// ... (エラー処理) ...
break;
}
// フレームポインタとリンクレジスタの導出
if(frame.fp == 0) {
frame.fp = frame.sp;
if(frame.pc > f->entry && f->frame >= sizeof(uintptr))
frame.fp += f->frame;
else // x86の場合、fpはsp+sizeof(uintptr)
frame.fp += sizeof(uintptr);
}
if(frame.lr == 0)
frame.lr = ((uintptr*)frame.fp)[-1]; // x86の場合、lrはfp-sizeof(uintptr)の位置にある
// 引数サイズの導出
frame.argp = (byte*)frame.fp; // x86の場合、引数はfpから始まる
frame.arglen = 0;
if(f->args != ArgsSizeUnknown)
frame.arglen = f->args;
else if(frame.pc == (uintptr)runtime·goexit || f->entry == (uintptr)runtime·mcall || f->entry == (uintptr)runtime·mstart || f->entry == (uintptr)_rt0_go)
frame.arglen = 0;
else if(frame.lr == (uintptr)runtime·lessstack)
frame.arglen = stk->argsize;
else if(f->entry == (uintptr)runtime·deferproc || f->entry == (uintptr)runtime·newproc)
frame.arglen = 2*sizeof(uintptr) + ((uintptr*)frame.argp)[1];
else if((f2 = runtime·findfunc(frame.lr)) != nil && f2->frame >= sizeof(uintptr))
frame.arglen = f2->frame; // 控えめな過大評価
else {
runtime·printf("runtime: unknown argument frame size for %S\\n", f->name);
runtime·throw("invalid stack");
}
// ローカル変数領域の位置とサイズの導出
if(frame.fp == frame.sp) {
// 関数がまだ自身のフレームを作成していない場合
frame.varp = nil;
frame.varlen = 0;
} else if(f->locals == 0) {
// 情報がない場合、フレーム全体を使用(TODO: local==0とlocal==unknownを区別する)
frame.varp = (byte*)frame.sp;
frame.varlen = frame.fp - sizeof(uintptr) - frame.sp; // x86の場合
} else {
// 既知のローカル変数サイズを使用
if(f->locals > frame.fp - sizeof(uintptr) - frame.sp) { // x86の場合
runtime·printf("runtime: inconsistent locals=%p frame=%p fp=%p sp=%p for %S\\n", (uintptr)f->locals, (uintptr)f->frame, frame.fp, frame.sp, f->name);
runtime·throw("invalid stack");
}
frame.varp = (byte*)frame.fp - sizeof(uintptr) - f->locals; // x86の場合
frame.varlen = f->locals;
}
// ... (スキップ処理、pcbufへの格納、コールバック呼び出し、デバッグ出力) ...
// 次のフレームへアンワインド
frame.pc = frame.lr;
frame.lr = 0;
frame.sp = frame.fp;
frame.fp = 0;
// ... (特殊な関数のスタック調整) ...
}
// ... (ゴルーチン作成元の表示) ...
return n;
}
このコードは、スタックフレームを一つずつ逆順に辿りながら、各フレームの情報をStkframe
構造体に正確に格納するプロセスを示しています。
- 初期化:
Stkframe
構造体をゼロクリアし、初期のPC、SP、LRを設定します。 - ループ: スタックの最下部または特定の終了条件に達するまでループします。
- スタックセグメント境界:
runtime·lessstack
に遭遇した場合、それはスタックセグメントの境界に達したことを意味します。この場合、次のスタックセグメントの情報を取得し、現在のフレーム情報を更新して続行します。 - 関数情報の取得: 現在のPCに対応する
Func
構造体(関数情報)をruntime·findfunc
で検索します。 - フレームポインタとリンクレジスタの導出:
- フレームポインタ(
frame.fp
)がまだ設定されていない場合、現在のSPと関数のフレームサイズ(f->frame
)に基づいて計算します。これはアーキテクチャ(ARMとx86)によって計算方法が異なります。 - リンクレジスタ(
frame.lr
、呼び出し元のPC)がまだ設定されていない場合、フレームポインタの特定の位置から取得します。
- フレームポインタ(
- 引数サイズの導出:
frame.argp
とframe.arglen
を計算します。これは最も複雑な部分の一つで、関数のf->args
フィールド(コンパイラが既知の場合)、runtime·goexit
のような特殊な関数のエントリポイント、runtime·lessstack
からの呼び出し、deferproc
やnewproc
のようなランタイム関数の引数構造など、様々なケースを考慮して正確な引数サイズを決定します。 - ローカル変数領域の位置とサイズの導出:
frame.varp
とframe.varlen
を計算します。関数がまだ自身のスタックフレームを作成していない場合や、ローカル変数情報が不明な場合、あるいは既知のローカル変数サイズがある場合など、様々なシナリオに対応します。 - コールバックの呼び出し:
skip
カウントがゼロになり、pcbuf
がnil
でなく、かつcallback
がnil
でない場合、構築されたStkframe
構造体をcallback
関数(例:addframeroots
)に渡します。これにより、GCは必要なスタックフレーム情報を取得できます。 - 次のフレームへのアンワインド: 現在のフレームのPC、SP、LR、FPを更新し、次の(呼び出し元の)スタックフレームへと移動します。特殊なランタイム関数(
_div
,_modu
,deferproc
,newproc
など)については、スタックポインタの調整が行われます。 - パニック処理:
waspanic
フラグが設定されている場合、シグナルハンドラがスタックに保存したLRを復元するロジックが含まれます。
このgentraceback
の変更により、スタックフレームの解析ロジックがGoランタイムの中心的な場所(gentraceback
)に集約され、その結果がStkframe
という統一された形式で提供されるようになりました。これにより、GCやデバッグツールなど、スタックフレーム情報を必要とする他のコンポーネントは、この複雑な解析ロジックを再実装する必要がなくなり、ランタイム全体の堅牢性と保守性が大幅に向上しました。
関連リンク
- Goのガベージコレクションに関する公式ドキュメントやブログ記事
- Goのスタックトレースに関するドキュメント
- Goのランタイムソースコード(特に
src/runtime
ディレクトリ)
参考にした情報源リンク
- Go CL 10169045 (このコミットのChange List)
- Go CL 10036044 (関連するChange List、このコミットによって簡素化されると記載されている)
- Goのガベージコレクションについて (Go公式ドキュメント)
- Goのスタック管理について (Goランタイムソースコード -
stack.go
はGoで書かれた部分だが、Cコードの理解にも役立つ) - Goのランタイムソースコード (GoランタイムのCコード)
- Goの
uintptr
型について (Go言語仕様) - Goのスタックトレースの仕組み (Goランタイムソースコード -
traceback.go
はGoで書かれた部分だが、Cコードの理解にも役立つ) - Goのコンパイラとランタイムの内部構造に関する記事やプレゼンテーション (Go公式のトーク集)