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

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

このコミットは、Go言語のARMアーキテクチャ向けコンパイラ(5c, 5g)およびリンカ(5l)におけるバグ修正を目的としています。具体的には、命令のフラグとして使用されるregフィールドが、デフォルト値のNREG(16)を持つことで、誤ってNOPTRビット(これも値が16)と解釈されてしまう問題を解決します。これにより、ポインタ情報が正しく扱われず、ビルドや実行時に問題が発生する可能性がありました。このコミットでは、regフィールドを明示的に0にクリアすることで、この誤解釈を防ぎ、ARMビルドの安定性を向上させています。

コミット

  • コミットハッシュ: a5bc16d619657a243ea55c2ebefc9a2f672ab2de
  • Author: Russ Cox rsc@golang.org
  • Date: Wed Feb 22 16:29:14 2012 -0500
  • コミットメッセージ:
    5c, 5g, 5l: fix arm bug
    
    Using reg as the flag word was unfortunate, since the
    default value is not 0 but NREG (==16), which happens
    to be the bit NOPTR now.  Clear it.
    
    If I say this will fix the build, it won't.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5690072
    

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

https://github.com/golang/go/commit/a5bc16d619657a243ea55c2ebefc9a2f672ab2de

元コミット内容

commit a5bc16d619657a243ea55c2ebefc9a2f672ab2de
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 22 16:29:14 2012 -0500

    5c, 5g, 5l: fix arm bug
    
    Using reg as the flag word was unfortunate, since the
    default value is not 0 but NREG (==16), which happens
    to be the bit NOPTR now.  Clear it.
    
    If I say this will fix the build, it won't.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5690072
---
 src/cmd/5c/txt.c   | 3 ++-
 src/cmd/5g/gsubr.c | 4 +++-\
 src/cmd/5g/list.c  | 2 +-\
 src/cmd/5l/list.c  | 2 +-\
 4 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/cmd/5c/txt.c b/src/cmd/5c/txt.c
index dea406eb92..1a189e3afe 100644
--- a/src/cmd/5c/txt.c
+++ b/src/cmd/5c/txt.c
@@ -1185,7 +1185,8 @@ gpseudo(int a, Sym *s, Node *n)\
  	if(a == ATEXT) {\
  		p->reg = textflag;\
  		textflag = 0;\
-\t}\
+\t} else if(a == AGLOBL)\
+\t\tp->reg = 0;\
  	if(s->class == CSTATIC)\
  		p->from.name = D_STATIC;\
  	naddr(n, &p->to);\
diff --git a/src/cmd/5g/gsubr.c b/src/cmd/5g/gsubr.c
index 86f05fb32c..9acf93670f 100644
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -75,6 +75,7 @@ prog(int as)\
  	\tp = dpc;\
  	\tdpc = mal(sizeof(*dpc));\
  	\tp->link = dpc;\
+\t\tp->reg = 0;  // used for flags\
  	} else {\
  	\tp = pc;\
  	\tpc = mal(sizeof(*pc));\
@@ -1116,7 +1117,8 @@ gins(int as, Node *f, Node *t)\
  	if(f != N)\
  	\tnaddr(f, &af, 1);\
  	if(t != N)\
-\t\tnaddr(t, &at, 1);\tp = prog(as);\
+\t\tnaddr(t, &at, 1);\
+\tp = prog(as);\
  	if(f != N)\
  	\tp->from = af;\
  	if(t != N)\
diff --git a/src/cmd/5g/list.c b/src/cmd/5g/list.c
index fef9c85435..9bc3a9a9a6 100644
--- a/src/cmd/5g/list.c
+++ b/src/cmd/5g/list.c
@@ -59,7 +59,7 @@ Pconv(Fmt *fp)\
  	switch(p->as) {\
  	default:\
  	\tsnprint(str1, sizeof(str1), "%A%C", p->as, p->scond);\
-\t\tif(p->reg == NREG)\
+\t\tif(p->reg == NREG && p->as != AGLOBL)\
  	\t\tsnprint(str, sizeof(str), "%.4d (%L) %-7s\t%D,%D", \
  	\t\t\tp->loc, p->lineno, str1, &p->from, &p->to);\
  	\telse\
diff --git a/src/cmd/5l/list.c b/src/cmd/5l/list.c
index fa838215b1..7b623d78a5 100644
--- a/src/cmd/5l/list.c
+++ b/src/cmd/5l/list.c
@@ -65,7 +65,7 @@ Pconv(Fmt *fp)\
  	switch(a) {\
  	default:\
  	\tfmtprint(fp, "(%d)", p->line);\
-\t\tif(p->reg == NREG)\
+\t\tif(p->reg == NREG && p->as != AGLOBL)\
  	\t\tfmtprint(fp, "\t%A%C\t%D,%D",\
  	\t\t\ta, p->scond, &p->from, &p->to);\
  	\telse\

変更の背景

この変更は、Go言語のARMアーキテクチャ向けコンパイラおよびリンカにおける、regフィールドの誤用によるバグを修正するために行われました。

Goコンパイラの内部では、命令やデータ構造を表現するために様々なフィールドが使用されます。その中には、レジスタ番号を示すregフィールドがありますが、このフィールドがレジスタ番号だけでなく、命令に関する特定のフラグ(例えば、ポインタ情報)を格納するためにも流用されていました。

問題は、regフィールドのデフォルト値が0ではなく、NREGという定数(値は16)であったことです。偶然にも、ポインタ情報を示すNOPTRというビットフラグも値が16でした。このため、regフィールドがデフォルト値のNREGを持つ場合、それが意図せずNOPTRフラグが設定されていると解釈されてしまい、ポインタの追跡やガベージコレクションに誤った情報が渡される可能性がありました。

この誤解釈は、特にARMアーキテクチャのビルドにおいて、正しくないコード生成やランタイムエラーを引き起こす原因となっていました。コミットメッセージにある「If I say this will fix the build, it won't.」という一文は、このバグがビルドプロセス全体に与える影響の複雑さを示唆している可能性があります。

前提知識の解説

Go言語のコンパイラツールチェイン (5c, 5g, 5l)

Go言語の初期のコンパイラツールチェインは、Plan 9オペレーティングシステムのツールチェインに由来しています。これらのツールは、特定のアーキテクチャをターゲットとする際に、数字と文字の組み合わせで命名されていました。

  • 5c: ARMアーキテクチャ向けのCコンパイラ。Go言語のランタイムや一部のライブラリはC言語で書かれていたため、これらをコンパイルするために使用されました。
  • 5g: ARMアーキテクチャ向けのGoコンパイラ。Go言語のソースコードをコンパイルするために使用されました。
  • 5l: ARMアーキテクチャ向けのリンカ。コンパイルされたオブジェクトファイルを結合して実行可能ファイルを生成するために使用されました。

数字の「5」はARMアーキテクチャを指し、同様に「6」はamd64、「8」は386を指していました。現代のGo開発では、go buildコマンドがこれらの個別のコンパイラやリンカの呼び出しを抽象化しているため、開発者が直接これらの名前を意識することは少なくなっています。

NREGNOPTR

Goコンパイラの内部では、命令やデータ構造の属性を表現するために様々な定数やフラグが定義されています。

  • NREG: "No Register" (レジスタなし) を意味する定数で、特定の命令がレジスタを使用しない場合や、レジスタが割り当てられていない状態を示すために使用されます。このコミットの時点では、NREGの値は16でした。
  • NOPTR: "No Pointer" (ポインタなし) を意味するビットフラグです。これは、特定のメモリ領域やデータ構造がポインタを含まないことを示すために使用されます。Goのガベージコレクタは、この情報を使用して、どのメモリ領域をスキャンする必要があるかを判断します。このコミットの時点では、NOPTRビットの値も16でした。

この二つの定数が偶然にも同じ値を持っていたことが、今回のバグの根本原因となりました。regフィールドがデフォルト値のNREGを持つと、それがNOPTRフラグが設定されていると誤解釈され、ガベージコレクタがポインタをスキャンすべきでない場所をスキップしてしまう可能性がありました。

技術的詳細

このバグは、Goコンパイラの内部表現におけるProg構造体(またはそれに類する命令表現)のregフィールドのセマンティクスに起因していました。

通常、regフィールドは命令が使用するレジスタ番号を格納するために設計されています。しかし、Goコンパイラの設計では、このregフィールドがレジスタ番号だけでなく、命令に関する追加のメタデータやフラグを格納するためにも再利用されていました。これは、メモリ効率やコードの簡潔さを追求する上で一般的な最適化手法です。

問題は、このregフィールドが初期化されない場合、または特定の条件下でデフォルト値を持つ場合に発生しました。コミットメッセージによると、regフィールドのデフォルト値は0ではなく、NREG(値は16)でした。

同時に、Goのランタイムとガベージコレクタは、メモリ内のポインタを正確に追跡するために、命令やデータ構造に付随するポインタ情報に依存しています。このポインタ情報は、NOPTRのようなビットフラグとして表現されることがあり、このNOPTRフラグも値が16でした。

したがって、regフィールドがデフォルト値のNREG(16)を持つと、コンパイラやリンカの他の部分が、そのregフィールドを「ポインタを含まない」ことを示すNOPTRフラグが設定されていると誤解釈してしまいました。これは、実際にはポインタを含む可能性のあるデータや命令に対して、ガベージコレクタが誤った動作をする原因となり、メモリリークやクラッシュなどの深刻な問題を引き起こす可能性がありました。

このコミットの解決策は、regフィールドがフラグとして使用される場合に、その値を明示的に0にクリアすることです。これにより、NREGNOPTRの値が偶然一致することによる誤解釈を防ぎ、ポインタ情報が正しく伝達されるようになります。

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

このコミットでは、以下の4つのファイルが変更されています。

  1. src/cmd/5c/txt.c
  2. src/cmd/5g/gsubr.c
  3. src/cmd/5g/list.c
  4. src/cmd/5l/list.c

これらのファイルは、それぞれARMアーキテクチャ向けのCコンパイラ、Goコンパイラのサブモジュール、およびリンカの一部を構成しています。変更は主に、命令を生成またはリスト表示する際に、regフィールドの扱いを修正することに焦点を当てています。

コアとなるコードの解説

src/cmd/5c/txt.c

このファイルは、C言語のソースコードからARMアセンブリコードを生成する部分に関連しています。

--- a/src/cmd/5c/txt.c
+++ b/src/cmd/5c/txt.c
@@ -1185,7 +1185,8 @@ gpseudo(int a, Sym *s, Node *n)\
  	if(a == ATEXT) {\
  		p->reg = textflag;\
  		textflag = 0;\
-\t}\
+\t} else if(a == AGLOBL)\
+\t\tp->reg = 0;\
  	if(s->class == CSTATIC)\
  		p->from.name = D_STATIC;\
  	naddr(n, &p->to);\

gpseudo関数は、擬似命令(ATEXT, AGLOBLなど)を処理します。 変更前はATEXTの場合のみp->regtextflagで設定していましたが、AGLOBL(グローバル変数宣言)の場合にp->regが初期化されないままでした。この変更により、AGLOBLの場合にp->regを明示的に0に設定することで、NREGが誤ってNOPTRと解釈されるのを防ぎます。

src/cmd/5g/gsubr.c

このファイルは、Goコンパイラの汎用サブルーチンを含んでいます。

--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -75,6 +75,7 @@ prog(int as)\
  	\tp = dpc;\
  	\tdpc = mal(sizeof(*dpc));\
  	\tp->link = dpc;\
+\t\tp->reg = 0;  // used for flags\
  	} else {\
  	\tp = pc;\
  	\tpc = mal(sizeof(*pc));\
@@ -1116,7 +1117,8 @@ gins(int as, Node *f, Node *t)\
  	if(f != N)\
  	\tnaddr(f, &af, 1);\
  	if(t != N)\
-\t\tnaddr(t, &at, 1);\tp = prog(as);\
+\t\tnaddr(t, &at, 1);\
+\tp = prog(as);\
  	if(f != N)\
  	\tp->from = af;\
  	if(t != N)\

prog関数は新しい命令(Prog構造体)を割り当てて初期化します。変更前はp->regが初期化されていませんでしたが、p->reg = 0; // used for flagsという行が追加され、命令が生成される際にregフィールドが明示的に0にクリアされるようになりました。これにより、regがフラグとして使用される場合に、意図しないNREGの値が残ることを防ぎます。

gins関数は命令を生成する関数ですが、変更は主にフォーマットの修正であり、機能的な変更はprog関数への依存によるものです。

src/cmd/5g/list.c

このファイルは、Goコンパイラが生成するアセンブリコードをリスト表示する部分に関連しています。

--- a/src/cmd/5g/list.c
+++ b/src/cmd/5g/list.c
@@ -59,7 +59,7 @@ Pconv(Fmt *fp)\
  	switch(p->as) {\
  	default:\
  	\tsnprint(str1, sizeof(str1), "%A%C", p->as, p->scond);\
-\t\tif(p->reg == NREG)\
+\t\tif(p->reg == NREG && p->as != AGLOBL)\
  	\t\tsnprint(str, sizeof(str), "%.4d (%L) %-7s\t%D,%D", \
  	\t\t\tp->loc, p->lineno, str1, &p->from, &p->to);\
  	\telse\

Pconv関数は、Prog構造体を文字列に変換して表示します。変更前はp->reg == NREGの場合に特定のフォーマットで表示していましたが、AGLOBL命令の場合にp->regNREGであっても、それがレジスタとしてのNREGではなく、フラグとしてのNREG(つまりNOPTR)である可能性がありました。&& p->as != AGLOBLという条件が追加されたことで、AGLOBL命令の場合はp->regNREGであっても、レジスタとして扱わないように表示ロジックが修正されました。これは、AGLOBLがレジスタを使用しない命令であるため、regフィールドがフラグとしてのみ意味を持つことを反映しています。

src/cmd/5l/list.c

このファイルは、Goリンカが生成するアセンブリコードをリスト表示する部分に関連しています。

--- a/src/cmd/5l/list.c
+++ b/src/cmd/5l/list.c
@@ -65,7 +65,7 @@ Pconv(Fmt *fp)\
  	switch(a) {\
  	default:\
  	\tfmtprint(fp, "(%d)", p->line);\
-\t\tif(p->reg == NREG)\
+\t\tif(p->reg == NREG && p->as != AGLOBL)\
  	\t\tfmtprint(fp, "\t%A%C\t%D,%D",\
  	\t\t\ta, p->scond, &p->from, &p->to);\
  	\telse\

src/cmd/5g/list.cと同様に、リンカのリスト表示部分でもPconv関数が修正されています。p->reg == NREGの条件に&& p->as != AGLOBLが追加され、AGLOBL命令の表示が正しく行われるようになりました。これにより、リンカが命令を解釈する際にも、regフィールドの誤解釈を防ぎます。

これらの変更は、regフィールドがレジスタ番号とフラグの両方の意味を持つという設計上の課題に対し、フラグとしての使用時に明示的な初期化を行うことで、NREGNOPTRの偶然の一致によるバグを根本的に解決しています。

関連リンク

参考にした情報源リンク