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

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

このコミットは、Goリンカ(cmd/ld)におけるシンボルテーブルの型("symbol table letters")の割り当てに関するバグ修正です。具体的には、シンボルテーブルやPC-lineテーブルといった特定のシンボル、および内容を持つべきBSSセクションのシンボルが、リンカによって誤った型として扱われる問題を修正しています。これにより、生成されるバイナリのデバッグ情報やセクションの分類が正確になり、特にARMアーキテクチャでのデバッグ時の問題が解消されます。

コミット

commit c2cd0d09c2e784fb818aea47557269a3bac9d8b1
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 22 01:30:04 2012 -0500

    ld: get symbol table letters right
    
    Have to assign them before we create the symbol table.
    ARM debugging.
    
    TBR=r
    CC=golang-dev
    https://golang.org/cl/5689067

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

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

元コミット内容

ld: get symbol table letters right

Have to assign them before we create the symbol table.
ARM debugging.

変更の背景

この変更は、Goリンカが生成するバイナリのシンボルテーブルにおいて、シンボルの型(セクションの種類を示す「文字」)が正しく割り当てられていないという問題に対処するために行われました。コミットメッセージにある「ARM debugging」という記述から、この問題が特にARMアーキテクチャでのデバッグ時に顕在化したことが示唆されます。

Goのリンカは、コンパイルされたGoのコードとライブラリを結合して実行可能なバイナリを生成します。この過程で、関数、グローバル変数、データセクションなどの様々な要素がシンボルとして扱われ、それらの情報がシンボルテーブルに記録されます。シンボルテーブルは、デバッガがプログラムの実行状態を理解したり、スタックトレースを生成したりするために不可欠な情報源です。

問題は、SSYMTAB(シンボルテーブル自体)やSPCLNTAB(PC-lineテーブル)のような特定の内部シンボル、あるいは本来データを持つべきだが誤ってBSS(未初期化データ)セクションに分類されていたシンボルが、リンカによって不正確な型として扱われていたことにありました。これにより、デバッガがこれらのシンボルを正しく解釈できず、デバッグ体験に支障をきたす可能性がありました。このコミットは、シンボルテーブルが構築される前に、これらのシンボルの型を正確に設定することで、この問題を解決しようとしています。

前提知識の解説

このコミットを理解するためには、以下のGoリンカとバイナリ構造に関する基本的な知識が必要です。

  • Goリンカ (cmd/ld): Go言語のビルドツールチェーンの一部であり、コンパイルされたGoのオブジェクトファイル(.oファイル)やアーカイブファイル(.aファイル)を結合し、実行可能なバイナリを生成する役割を担います。Goは独自のリンカを使用しており、C/C++のリンカとは異なる内部構造を持っています。
  • シンボルテーブル (Symbol Table): 実行可能ファイルやオブジェクトファイル内に含まれるデータ構造で、プログラム内のシンボル(関数名、グローバル変数名など)とそのアドレス、サイズ、型などの情報が格納されています。デバッガやプロファイラは、このシンボルテーブルを利用して、ソースコードレベルでのデバッグやパフォーマンス分析を行います。
  • セクション (Sections): 実行可能ファイルは、異なる種類のデータやコードを格納するために複数のセクションに分割されています。主要なセクションには以下のようなものがあります。
    • .text (STEXT): 実行可能な機械語コードが格納されます。
    • .rodata (SRODATA): 読み取り専用のデータ(文字列リテラル、定数など)が格納されます。
    • .data (SDATA): 初期化されたグローバル変数や静的変数が格納されます。プログラムの開始時に初期値が設定されます。
    • .bss (SBSS): 初期化されていないグローバル変数や静的変数が格納されます。プログラムの開始時にゼロで初期化されることが保証されます。.dataセクションとは異なり、ファイルサイズを削減するために、実際のデータはバイナリファイルには含まれず、実行時にメモリが確保されます。
    • SNOPTRDATA / SNOPTRBSS: Go特有のセクションで、ポインタを含まないデータ(SNOPTRDATA)や未初期化データ(SNOPTRBSS)が格納されます。これらはガベージコレクションの効率化のために導入されています。ガベージコレクタはこれらのセクションをスキャンする必要がないため、パフォーマンスが向上します。
  • SSYMTAB: シンボルテーブル自体を表すシンボル型。
  • SPCLNTAB: PC-lineテーブル(Program Counter to Line number table)を表すシンボル型。これは、プログラムカウンタ(実行中の命令のアドレス)から対応するソースコードのファイル名と行番号をマッピングするために使用され、デバッグ情報やスタックトレースの生成に不可欠です。
  • s->np: シンボル構造体 Sym のメンバーで、シンボルが持つ実際のデータ量(バイト数)を示すフィールドであると推測されます。np > 0 は、そのシンボルが何らかの具体的な内容を持っていることを意味します。
  • xdefine: リンカ内部でシンボルを定義するために使用される関数。通常、セクションの開始アドレスや終了アドレスを示す特殊なシンボル(例: _etext, _edata, _end)を定義する際に用いられます。

技術的詳細

このコミットの技術的な核心は、シンボルが持つべき実際のデータと、リンカがそのシンボルに割り当てるセクションタイプとの間の不整合を解消することにあります。

以前のリンカの挙動では、SSYMTABSPCLNTABといった特定の内部シンボルが、その性質上データを持つにもかかわらず、シンボルテーブルの構築段階で適切なセクションタイプ(例えばSRODATASDATA)として認識されていなかった可能性があります。また、SBSSSNOPTRBSSとして分類されていたシンボルの中に、実際にはs->np > 0(つまり、内容を持つ)ものが存在し、これらが本来SDATASNOPTRDATAとして扱われるべきであったという問題がありました。

このコミットは、以下の主要な変更によってこの問題を解決しています。

  1. dosymtype() 関数の導入と早期実行:

    • src/cmd/ld/data.cdosymtype() という新しい関数が導入されました。
    • この関数は、リンカがシンボルテーブルを構築する前(symtab()関数の冒頭)に呼び出されます。
    • dosymtype() の内部では、すべてのシンボルを走査し、もしシンボルが SBSS または SNOPTRBSS の型を持ち、かつ s->np > 0(内容を持つ)である場合、そのシンボルの型をそれぞれ SDATA または SNOPTRDATA に変更します。これは、内容を持つBSSシンボルは実質的に初期化済みデータであるという論理に基づいています。
    • この変更により、シンボルテーブルが作成される前に、すべてのシンボルの型がその実際の性質に基づいて正確に決定されるようになります。
  2. asm.c ファイル群でのシンボル型認識の修正:

    • src/cmd/5l/asm.c (ARM), src/cmd/6l/asm.c (x86), src/cmd/8l/asm.c (x86-64) の各リンカの genasmsym 関数において、SSYMTABSPCLNTABSRODATA または SDATA セクションとして正しく扱われるように変更されました。これにより、これらの重要な内部シンボルがデバッグ情報に適切に反映されるようになります。
    • また、SNOPTRBSS 型のシンボルが s->np > 0 である場合に診断メッセージを出力するコードが追加されました。これは、本来内容を持たないはずのBSSセクションに内容があるという異常を検知するためのものです。
  3. xdefine 呼び出しにおけるセクション型の修正:

    • src/cmd/ld/data.csrc/cmd/ld/symtab.c の両方で、noptrdata, data, noptrbss といったセクションの開始/終了を示す特殊なシンボルを定義する xdefine 呼び出しにおいて、割り当てられるセクション型が SBSS からそれぞれの正しい型(SNOPTRDATA, SDATA, SNOPTRBSS)に変更されました。これは、これらのセクション自体がシンボルテーブル内で正しい型として登録されることを保証します。

これらの変更により、Goリンカはより正確なシンボルテーブルを生成し、特にデバッグ情報の一貫性と正確性が向上しました。ARMアーキテクチャのような特定の環境でデバッグが困難であった問題が、この修正によって解消されたと考えられます。

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

このコミットで変更された主要なファイルと、その変更の概要は以下の通りです。

  • src/cmd/5l/asm.c, src/cmd/6l/asm.c, src/cmd/8l/asm.c:

    • 各アーキテクチャ(ARM, x86, x86-64)のリンカのアセンブリ出力関連ファイル。
    • genasmsym 関数内で、SSYMTABSPCLNTAB シンボルがデータセクション('D')として正しく処理されるように追加されました。
    • SNOPTRBSS 型のシンボルが s->np > 0 の場合に診断メッセージを出力するチェックが追加されました。
  • src/cmd/ld/data.c:

    • リンカのデータセクション処理に関するファイル。
    • dosymtype() 関数が新しく追加され、シンボルが内容を持つ場合にBSSからデータセクションに型を変更するロジックが実装されました。
    • dodata() 関数内で、dosymtype() が呼び出されるようになりました。
    • address() 関数内で、xdefine を用いてセクションの開始/終了シンボルを定義する際に、noptrdata, data, noptrbss の型が SBSS からそれぞれの正しい型(SNOPTRDATA, SDATA, SNOPTRBSS)に変更されました。
  • src/cmd/ld/lib.h:

    • リンカの共通ヘッダファイル。
    • 新しく追加された dosymtype() 関数のプロトタイプ宣言が追加されました。
  • src/cmd/ld/symtab.c:

    • リンカのシンボルテーブル構築に関するファイル。
    • symtab() 関数の冒頭で dosymtype() が呼び出されるようになりました。これにより、シンボルテーブルが構築される前にシンボルの型が修正されます。
    • xdefine を用いてセクションの開始/終了シンボルを定義する際に、noptrdata, data, noptrbss の型が SBSS からそれぞれの正しい型(SNOPTRDATA, SDATA, SNOPTRBSS)に変更されました。

コアとなるコードの解説

このコミットの最も重要な変更は、src/cmd/ld/data.csrc/cmd/ld/symtab.c に導入された dosymtype() 関数とその呼び出し、そして xdefine の引数変更です。

dosymtype() 関数の導入

// src/cmd/ld/data.c
void
dosymtype(void)
{
	Sym *s;

	for(s = allsym; s != nil; s = s->allsym) {
		if(s->np > 0) {
			if(s->type == SBSS)
				s->type = SDATA;
			if(s->type == SNOPTRBSS)
				s->type = SNOPTRDATA;
		}
	}
}

この関数は、リンカが認識しているすべてのシンボル(allsym リストを走査)をループ処理します。各シンボル s について、s->np > 0(シンボルが何らかのデータ内容を持つ)という条件をチェックします。 もしこの条件が真であり、かつシンボルの現在の型が SBSS(通常の未初期化データ)であれば、その型を SDATA(初期化済みデータ)に変更します。 同様に、シンボルの型が SNOPTRBSS(ポインタを含まない未初期化データ)であれば、その型を SNOPTRDATA(ポインタを含まない初期化済みデータ)に変更します。 このロジックは、本来内容を持つべきシンボルが誤ってBSSセクションに分類されていた場合に、その型を正しいデータセクションの型に修正することを目的としています。

dosymtype() の呼び出し箇所

dosymtype() は、以下の2つの重要な場所で呼び出されます。

  1. src/cmd/ld/symtab.csymtab() 関数内:

    // src/cmd/ld/symtab.c
    void
    symtab(void)
    {
    	Sym *s;
    
    	dosymtype(); // ここで呼び出される
    
    	// Define these so that they'll get put into the symbol table.
    	// data.c:/^address will provide the actual values.
    	xdefine("text", STEXT, 0);
    	// ...
    }
    

    symtab() 関数は、最終的な実行可能ファイルに含めるシンボルテーブルを構築する主要な関数です。この関数の冒頭で dosymtype() を呼び出すことで、シンボルテーブルが実際に作成される前に、すべてのシンボルの型が正確に修正されることが保証されます。これにより、シンボルテーブルに記録される情報が最初から正しい状態になります。

  2. src/cmd/ld/data.cdodata() 関数内: 変更前の dodata() 関数内にも同様の型修正ロジックがありましたが、dosymtype() として独立させ、dodata() の冒頭で呼び出すように変更されました。これにより、データセクションの処理が始まる前にシンボルの型が確定します。

xdefine 呼び出しの修正

src/cmd/ld/data.caddress() 関数と src/cmd/ld/symtab.csymtab() 関数では、xdefine を用いて、noptrdata, data, noptrbss といったセクションの開始/終了を示す特殊なシンボルを定義しています。

変更前は、これらのシンボルがすべて SBSS 型として定義されていました。

// 変更前 (例: src/cmd/ld/data.c の address() 関数内)
xdefine("noptrdata", SBSS, noptr->vaddr);
xdefine("data", SBSS, data->vaddr);
xdefine("noptrbss", SBSS, noptrbss->vaddr);

変更後は、それぞれのセクションの実際の型に合わせて修正されました。

// 変更後 (例: src/cmd/ld/data.c の address() 関数内)
xdefine("noptrdata", SNOPTRDATA, noptr->vaddr);
xdefine("data", SDATA, data->vaddr);
xdefine("noptrbss", SNOPTRBSS, noptrbss->vaddr);

この修正は、これらのセクションマーカーシンボル自体が、シンボルテーブル内でそのセクションの正しい型として登録されることを保証します。これにより、デバッガや他のツールがバイナリのセクションレイアウトを正確に解釈できるようになります。

これらの変更は、Goリンカが生成するバイナリの内部構造、特にシンボルテーブルとセクション情報の正確性を大幅に向上させ、デバッグ体験の改善に貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/ld ディレクトリ)
  • リンカ、シンボルテーブル、セクションに関する一般的なコンピュータサイエンスの知識