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

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

このコミットは、Goランタイムにおけるconcatstring関数の定義に関するバグ修正です。特に、386アーキテクチャでのビルド問題と、ガベージコレクタがスタックフレームを正しく解釈できない問題に対処しています。

コミット

commit 249f807c39e96a30707f5005881b6c1b8e08077e
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jul 18 12:19:38 2013 -0400

    runtime: mark concatstring as variadic (fixes 386 build)
    
    Windows was the only one seeing this bug reliably in the builder,
    but it was easy to reproduce using 'GOGC=1 go test strconv'.
    concatstring looked like it took only one string, but in fact it
    takes a long list of strings. Add an explicit ... so that the traceback
    will not use the "fixed" frame size and instead look at the
    frame size metadata recorded by the caller.
    
    R=golang-dev
    TBR=golang-dev
    CC=golang-dev
    https://golang.org/cl/11531043

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

https://github.com/golang/go/commit/249f807c39e96a30707f5005881b6c1b8e08077e

元コミット内容

runtime: mark concatstring as variadic (fixes 386 build)

このコミットは、runtimeパッケージ内のconcatstring関数を可変引数関数として明示的にマークすることで、386アーキテクチャでのビルド問題を修正します。

Windowsのビルダでこのバグが頻繁に発生していましたが、GOGC=1 go test strconvを実行することで容易に再現可能でした。concatstring関数は、一見すると1つの文字列しか受け取らないように見えますが、実際には多数の文字列を受け取ります。明示的に...を追加することで、トレースバックが「固定された」フレームサイズを使用せず、呼び出し元によって記録されたフレームサイズメタデータを使用するようになります。

変更の背景

Goのランタイム、特にガベージコレクタ(GC)は、スタックをスキャンして到達可能なオブジェクトを特定します。このスキャンプロセスでは、各関数のスタックフレームのサイズと、そのフレーム内にポインタが含まれているかどうかを正確に知る必要があります。

concatstring関数は、Goの内部で文字列の連結を行うために使用されるランタイム関数です。コミットメッセージによると、この関数は実際には複数の文字列引数を受け取るにもかかわらず、そのシグネチャが可変引数(variadic)として明示されていませんでした。

この不一致が、特に386アーキテクチャのような特定の環境や、GCが積極的に動作する状況(GOGC=1)で問題を引き起こしました。ガベージコレクタは、関数のスタックフレームサイズが固定であると誤解し、実際には可変長の引数がスタックに積まれているにもかかわらず、その部分を正しくスキャンできませんでした。これにより、ガベージコレクタがポインタを誤って解釈したり、到達可能なオブジェクトを解放してしまったりする可能性があり、結果としてクラッシュやデータ破損につながる可能性がありました。

このコミットは、concatstring関数が可変引数であることを明示的に示すことで、ガベージコレクタがスタックフレームを正しく解釈し、ポインタを正確に追跡できるようにすることを目的としています。

前提知識の解説

Goランタイムとガベージコレクタ (GC)

Go言語は、独自のランタイムとガベージコレクタを内蔵しています。ガベージコレクタは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能になった(参照されなくなった)メモリ領域を自動的に解放する役割を担います。GoのGCは、並行マーク&スイープ方式を採用しており、プログラムの実行と並行して動作することで、アプリケーションの一時停止(ストップ・ザ・ワールド)時間を最小限に抑えるように設計されています。

GCが正しく動作するためには、スタック上に存在するポインタを正確に識別し、それらが指すヒープ上のオブジェクトをマークする必要があります。このプロセスでは、各関数のスタックフレームの構造、特にポインタがどこに配置されているかを知る必要があります。

スタックフレームとポインタ情報

関数が呼び出されると、その関数専用のスタックフレームがスタック上に作成されます。このフレームには、関数のローカル変数、引数、戻りアドレスなどが格納されます。Goのランタイムは、コンパイル時に各関数のスタックフレームに関するメタデータ(ポインタマップ)を生成します。このメタデータは、GCがスタックをスキャンする際に、どのメモリ位置がポインタであり、どのメモリ位置がポインタではないかを識別するために使用されます。

可変引数関数 (Variadic Functions)

Go言語では、func foo(args ...int)のように、引数の数が可変である関数を定義できます。このような関数が呼び出されると、可変引数はスライスとして関数内で扱われます。スタック上では、これらの可変引数は通常、固定引数の後に連続して配置されます。

386アーキテクチャ

386は、Intel 80386プロセッサに由来する32ビットアーキテクチャの総称です。古いシステムや特定の組み込み環境でまだ使用されている場合があります。アーキテクチャによっては、スタックフレームのレイアウトや、コンパイラが生成するポインタマップの解釈に微妙な違いが生じることがあります。このコミットで386ビルドが特に言及されているのは、そのアーキテクチャでのスタックフレームの扱いが他のアーキテクチャと異なり、このバグが顕在化しやすかったためと考えられます。

GOGC環境変数

GOGCはGoのガベージコレクタの動作を制御する環境変数です。デフォルト値は100で、ヒープサイズが前回のGC後から100%増加するとGCが実行されます。GOGC=1のように非常に小さい値を設定すると、GCが頻繁に実行されるようになり、GC関連のバグを再現しやすくなります。

技術的詳細

このコミットの核心は、GoランタイムがC言語で記述された部分(src/pkg/runtime/string.goc)におけるconcatstring関数のシグネチャの修正です。

元のconcatstring関数は、C言語の構文で以下のように定義されていました。

func concatstring(n int, s1 String) {
    // ...
}

この定義は、n(文字列の数)とs1(最初の文字列)の2つの固定引数を持つ関数として解釈されます。しかし、実際にはconcatstrings1の後に続くn-1個の文字列もスタックから読み取って処理します。これは、Goの内部的な呼び出し規約において、可変引数が固定引数の後に連続してスタックに積まれることを利用したものです。

ガベージコレクタは、このC言語の関数定義を見て、concatstringのスタックフレームが固定サイズであると判断し、そのポインタマップも固定引数のみを考慮して生成していました。しかし、実際にはs1の後に可変長の文字列ポインタがスタックに存在するため、GCがスタックをスキャンする際に、これらの追加の文字列ポインタを認識できず、誤って非ポインタデータとして扱ったり、あるいはポインタではないデータをポインタとして解釈して不正なメモリアクセスを引き起こしたりする可能性がありました。

この問題を解決するため、コミットではconcatstringの定義を以下のように変更しました。

void
runtime·concatstring(int32 n, String s1, ...)
{
    // ...
}

ここで重要なのは、C言語の関数シグネチャに...(エリプシス)が追加されたことです。これは、C言語において可変引数関数を示す標準的な方法です。Goのランタイムコンパイラ(gc)は、C言語のソースコードをGoのランタイムにコンパイルする際に、この...を認識し、この関数が可変引数関数であることをガベージコレクタに伝えるための特別なメタデータ(ポインタマップ)を生成します。

具体的には、可変引数関数としてマークされることで、GCは「固定されたフレームサイズ」に依存するのではなく、「呼び出し元によって記録されたフレームサイズメタデータ」を参照するようになります。これにより、concatstringが実際に受け取る文字列の数に応じて、スタックフレームの可変部分も正しくスキャンできるようになり、ガベージコレクタがすべての文字列ポインタを正確に追跡できるようになります。

また、コミットには以下のコメントが追加されています。

// NOTE: Cannot use func syntax, because we need the ...
// to signal to the garbage collector that this function does
// not have a fixed size argument count.

これは、Goのfuncキーワードを使った通常の関数定義ではなく、C言語の構文でruntime·concatstringと定義している理由を説明しています。Goのfunc構文では、ランタイム内部の特定の低レベル関数に対して、GCに可変引数であることを正確に伝えるための...を直接指定する方法がなかったため、C言語の構文で定義する必要があったことを示唆しています。

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

変更はsrc/pkg/runtime/string.gocファイルに集中しています。

--- a/src/pkg/runtime/string.goc
+++ b/src/pkg/runtime/string.goc
@@ -170,10 +170,13 @@ concatstring(intgo n, String *s)
 	return out;
 }
 
+// NOTE: Cannot use func syntax, because we need the ...,\
+// to signal to the garbage collector that this function does\
+// not have a fixed size argument count.\
 #pragma textflag 7
-// s1 is the first of n strings.\
-// the output string follows.\
-func concatstring(n int, s1 String) {
+void
+runtime·concatstring(int32 n, String s1, ...)
+{
 	(&s1)[n] = concatstring(n, &s1);\
 }
 

コアとなるコードの解説

この変更の核心は、concatstring関数のシグネチャの変更と、その変更の意図を説明するコメントの追加です。

  1. 関数のシグネチャ変更:

    • 変更前: func concatstring(n int, s1 String)
    • 変更後: void runtime·concatstring(int32 n, String s1, ...)

    この変更により、concatstring関数はC言語の可変引数関数として定義されました。voidは戻り値がないことを示し、runtime·プレフィックスはGoランタイム内部の関数であることを示します。最も重要なのは、引数リストの最後に...が追加されたことです。これにより、コンパイラとガベージコレクタは、この関数が固定数の引数だけでなく、可変数の引数も受け取ることを認識します。

  2. コメントの追加:

    // NOTE: Cannot use func syntax, because we need the ...,\
    // to signal to the garbage collector that this function does\
    // not have a fixed size argument count.\
    

    このコメントは、なぜGoの通常のfuncキーワードではなく、C言語の構文で関数を定義する必要があったのかを明確に説明しています。Goのfunc構文では、ランタイムの低レベルな部分でGCに可変引数であることを正確に伝えるための...を直接指定するメカニズムが当時なかったため、C言語の可変引数構文を利用する必要があったことを示しています。これは、Goランタイムの非常に低レベルな部分での実装上の制約と、GCの正確な動作を保証するための工夫を示しています。

この修正により、ガベージコレクタはconcatstringのスタックフレームをスキャンする際に、可変引数部分も正しくポインタとして認識し、メモリの安全性を確保できるようになりました。

関連リンク

参考にした情報源リンク