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

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

このコミットは、Go言語のリンカである cmd/ld におけるシンボルテーブルの扱いに関するバグ修正です。具体的には、src/cmd/ld/symtab.c ファイル内の putsymb 関数が、静的オブジェクト(Go言語における非公開のパッケージレベル変数や関数に相当)のスコープを正しくシンボルテーブルに記録していなかった問題を修正しています。

コミット

Go言語のリンカ cmd/ld において、シンボルテーブルにおける静的オブジェクトのスコープの扱いを修正しました。以前のシンボルテーブル形式変更時に失われた、正しいシンボルスコープの挙動を回復させます。これにより、すべてのシンボルが誤ってグローバルスコープとしてマークされる問題が解消されます。

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

https://github.com/golang/go/commit/7ac20853fc3437647c4abfc3236e5c380bf5e68d

元コミット内容

commit 7ac20853fc3437647c4abfc3236e5c380bf5e68d
Author: Anthony Martin <ality@pbrane.org>
Date:   Wed Apr 10 18:47:58 2013 -0700

    cmd/ld: fix scope of static objects in symbol table
    
    All symbols are currently marked with global scope.
    The correct behavior was lost when the symbol table
    format was changed.
    
    R=golang-dev, iant, r
    CC=golang-dev
    https://golang.org/cl/8625043

変更の背景

このコミットが行われた背景には、Go言語のリンカ cmd/ld がシンボルテーブルのフォーマットを変更した際に、静的オブジェクト(Go言語では通常、小文字で始まるパッケージレベルの変数や関数など、パッケージ内部でのみ参照可能なエンティティを指します)のスコープ情報を正しく記録できなくなったという問題があります。

本来、静的オブジェクトは内部リンケージ(internal linkage)を持ち、その可視性は定義されたパッケージ内に限定されるべきです。しかし、シンボルテーブルのフォーマット変更により、すべてのシンボルが誤ってグローバルスコープとして扱われるようになっていました。これにより、リンカがプログラム内の参照を解決する際に、意図しない挙動や潜在的な衝突が発生する可能性がありました。このコミットは、この失われた正しい挙動を回復させ、シンボルテーブルが静的オブジェクトのスコープを正確に反映するように修正することを目的としています。

前提知識の解説

シンボルテーブル (Symbol Table)

シンボルテーブルは、コンパイラやリンカがプログラム内の識別子(変数名、関数名、クラス名など)に関する情報を格納するために使用するデータ構造です。各エントリはシンボル名と、そのシンボルに関する属性(型、スコープ、メモリ上のアドレスなど)を関連付けます。リンカは、このシンボルテーブルを参照して、異なるコンパイル単位(オブジェクトファイル)間で参照されるシンボルを解決し、最終的な実行可能ファイルを生成します。

静的オブジェクト (Static Objects)

プログラミング言語における「静的オブジェクト」という用語は、文脈によって意味合いが異なりますが、ここではGo言語の文脈とC言語のリンケージの概念を交えて説明します。

  • C言語における静的 (static):

    • 関数スコープ内の static 変数: その関数が呼び出されるたびに初期化されるのではなく、プログラムの実行期間を通じて値が保持される変数です。可視性は関数内のみです。
    • ファイルスコープ内の static 変数/関数: そのファイル(翻訳単位)内でのみ可視性を持つ変数や関数です。外部のファイルからは参照できません。これは「内部リンケージ」と呼ばれます。
  • Go言語における静的オブジェクト: Go言語にはC言語のような static キーワードはありませんが、同様の概念が存在します。

    • パッケージレベルの変数/関数: Goでは、識別子(変数名、関数名など)の最初の文字が大文字か小文字かでその可視性が決まります。
      • 大文字で始まる識別子: パッケージ外からも参照可能(エクスポートされる)。これは「外部リンケージ」に相当します。
      • 小文字で始まる識別子: パッケージ内でのみ参照可能(エクスポートされない)。これはC言語のファイルスコープの static と同様に「内部リンケージ」に相当し、このコミットでいう「静的オブジェクト」に該当します。

シンボルスコープ (Symbol Scope)

シンボルスコープは、プログラムのどの部分から特定のシンボルにアクセスできるかを定義します。主なスコープの種類は以下の通りです。

  • グローバルスコープ (Global Scope): プログラム全体からアクセス可能なシンボル。リンカはこれらのシンボルを異なるオブジェクトファイル間で解決します。
  • ローカルスコープ (Local Scope): 特定の関数やブロック内でのみアクセス可能なシンボル。
  • ファイルスコープ/内部リンケージ (File Scope / Internal Linkage): 特定のソースファイル内でのみアクセス可能なシンボル。このコミットで問題となっていたのは、本来内部リンケージを持つべきシンボルが、誤ってグローバルスコープとして扱われていた点です。

リンカは、これらのスコープ情報を利用して、シンボル参照が正しく解決されるようにします。誤ったスコープ情報がシンボルテーブルに記録されると、リンカは本来参照できないはずのシンボルを解決しようとしたり、逆に参照できるはずのシンボルを見つけられなかったりする可能性があります。

技術的詳細

このコミットは、Go言語のリンカ cmd/ldsrc/cmd/ld/symtab.c ファイル内の putsymb 関数に焦点を当てています。putsymb 関数は、シンボルをシンボルテーブルに書き込む役割を担っています。

シンボルテーブルのエントリには、シンボルの名前、型、値、サイズ、バージョン、そして重要な「型バイト (type byte)」が含まれます。この「型バイト」は、シンボルの種類(例えば、データ、テキスト、未初期化データなど)と、そのシンボルのリンケージ(グローバルかローカルか、あるいは静的か)を示すために使用されます。

問題は、シンボルテーブルのフォーマットが変更された際に、この「型バイト」の計算ロジックが静的オブジェクトのスコープを正しく反映しなくなったことにありました。具体的には、シンボルの型を示す文字 t が大文字('A'から'Z')の場合、以前は単に 'A' を引いて c を計算していました。しかし、これではシンボルのバージョン情報(ver)が考慮されず、すべてのシンボルがグローバルスコープとして扱われる可能性がありました。

リンカは、シンボルの可視性やリンケージを区別するために、シンボルタイプに加えてバージョン情報やその他のフラグを使用します。この修正は、ver パラメータ(おそらくシンボルのバージョンや特定の属性を示すもの)を考慮に入れることで、静的オブジェクトが正しく内部リンケージを持つように「型バイト」を調整します。

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

--- a/src/cmd/ld/symtab.c
+++ b/src/cmd/ld/symtab.c
@@ -347,7 +347,7 @@ putsymb(Sym *s, char *name, int t, vlong v, vlong size, int ver, Sym *typ)\n 	\n 	// type byte\n 	if('A' <= t && t <= 'Z')
-\t\tc = t - 'A';
+\t\tc = t - 'A' + (ver ? 26 : 0);\n 	else if('a' <= t && t && t <= 'z')\n 	\tc = t - 'a' + 26;\n 	else {

コアとなるコードの解説

変更は src/cmd/ld/symtab.c ファイル内の putsymb 関数にあります。

 	// type byte
 	if('A' <= t && t <= 'Z')
-\t\tc = t - 'A';
+\t\tc = t - 'A' + (ver ? 26 : 0);\n
 	else if('a' <= t && t <= 'z')
 	\tc = t - 'a' + 26;
 	else {

このコードスニペットは、シンボルの「型バイト」c を計算する部分です。

  • t はシンボルの型を示す文字です。
  • 'A' から 'Z' の範囲は、通常、グローバルな(外部リンケージを持つ)シンボルや特定の種類のシンボルを表すために使用されます。
  • 'a' から 'z' の範囲は、通常、ローカルな(内部リンケージを持つ)シンボルを表すために使用されます。

修正前の行 c = t - 'A'; は、大文字のシンボル型に対して、単にその文字のアルファベット順のオフセットを c に設定していました。これでは、シンボルのバージョン情報や、それが静的オブジェクトであるかどうかの区別ができませんでした。

修正後の行 c = t - 'A' + (ver ? 26 : 0); がこの問題を解決します。

  • ver は、おそらくシンボルのバージョンや、それが静的(内部リンケージ)であるかどうかのフラグを示す整数値です。
  • 三項演算子 (ver ? 26 : 0) は、ver が真(非ゼロ)の場合に 26 を返し、偽(ゼロ)の場合に 0 を返します。
  • 26 という値は、アルファベットの文字数(A-Z)に対応しており、シンボルタイプを「シフト」させることで、静的オブジェクトのシンボルタイプとグローバルオブジェクトのシンボルタイプを区別するためのオフセットとして機能します。

この変更により、ver が示すようにシンボルが静的オブジェクトである場合、その「型バイト」c は異なる値を持つようになります。これにより、リンカはシンボルテーブルを読み込む際に、そのシンボルがグローバルスコープを持つのか、それともパッケージ内部でのみ有効な静的スコープを持つのかを正しく識別できるようになります。結果として、リンカは静的オブジェクトの参照を正しく解決し、誤ってグローバルとして扱われる問題を解消します。

関連リンク

参考にした情報源リンク