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

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

このコミットは、Go言語のリンカ(cmd/ld)がMach-O形式のバイナリを生成する際に、完全なシンボルテーブルを含めるように変更するものです。これにより、macOSのnmコマンドがGoバイナリを正しく解析できるようになります。

コミット

commit df6072b41c76f2ac839d248b7c706fa554f483ed
Author: Russ Cox <rsc@golang.org>
Date:   Sun Mar 10 16:24:01 2013 -0400

    cmd/ld: include full symbol table in Mach-O output

    This makes binaries work with OS X nm.

    R=ken2
    CC=golang-dev
    https://golang.org/cl/7558044

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

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

元コミット内容

cmd/ld: include full symbol table in Mach-O output

このコミットは、GoリンカがMach-O形式の実行ファイルを生成する際に、完全なシンボルテーブルを含めるように修正します。これにより、macOSのnmコマンドがGoによって生成されたバイナリを適切に処理できるようになります。

変更の背景

Go言語のリンカは、macOS向けの実行ファイルをMach-O形式で生成します。しかし、以前のリンカの挙動では、生成されるMach-Oバイナリのシンボルテーブルが不完全であったため、macOSに標準で搭載されているnm(name list)コマンドがこれらのバイナリのシンボル情報を正確に表示できませんでした。nmコマンドは、デバッグやバイナリ解析において非常に重要なツールであり、シンボル情報が欠落していると開発者がGoバイナリを分析する上で大きな障壁となります。

特に、Mach-OファイルにはLC_SYMTAB(シンボルテーブル)とLC_DYSYMTAB(動的シンボルテーブル)という2つの重要なロードコマンドが存在します。nmコマンドは主にLC_SYMTABに依存してシンボル情報を取得します。Goリンカが生成するバイナリがnmで正しく機能しないということは、LC_SYMTABの構造や内容に問題があったことを示唆しています。

この問題は、GoのIssue 4029(コミットメッセージ内のコメントで参照されているが、現在の公開リポジトリでは直接見つからない可能性あり)で報告されていた可能性があります。このコミットは、この互換性の問題を解決し、GoバイナリがmacOSの標準ツールとよりシームレスに連携できるようにすることを目的としています。

前提知識の解説

Mach-O (Mach object) ファイル形式

Mach-Oは、macOS、iOS、watchOS、tvOSなどのApple製オペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリのファイル形式です。Mach-Oファイルは以下の主要な部分で構成されます。

  1. ヘッダ (Header): ファイルの基本的な情報(CPUアーキテクチャ、ファイルタイプなど)を含みます。
  2. ロードコマンド (Load Commands): ファイルの残りの部分のレイアウトを記述します。これには、セグメントの定義、シンボルテーブルの場所、動的リンカの情報などが含まれます。
  3. セグメント (Segments): 実行可能コードやデータを含むメモリ領域を定義します。各セグメントは1つ以上のセクションに分割されます。
  4. セクション (Sections): セグメント内の具体的なデータ(コード、初期化済みデータ、未初期化データなど)を格納します。

シンボルテーブル (LC_SYMTABLC_DYSYMTAB)

Mach-Oファイルには、プログラム内のシンボル(関数名、変数名など)に関する情報が格納されています。

  • LC_SYMTAB (Symbol Table Load Command):

    • バイナリ内のすべてのシンボルを格納する主要なシンボルテーブルです。
    • シンボル名が格納される「文字列テーブル」と、各シンボルのアドレスや型などの情報が格納される「シンボルテーブル」の2つの主要なコンポーネントから構成されます。
    • デバッグツールやnmコマンドがシンボル情報を取得する際に利用されます。
  • LC_DYSYMTAB (Dynamic Symbol Table Load Command):

    • 動的にリンクされるシンボルに関する追加情報を提供します。
    • 動的リンカ(macOSではdyld)が実行時にシンボルを解決するために使用します。
    • 特に、遅延バインディングや共有ライブラリからのシンボル解決に関連する情報(間接シンボルテーブルなど)を含みます。
    • LC_DYSYMTABが存在するためには、LC_SYMTABもファイル内に存在する必要があります。

nm コマンド

nmは、Unix系システムでオブジェクトファイル、アーカイブ、実行可能ファイルのシンボルテーブルを表示するためのコマンドラインユーティリティです。macOSでは、Mach-O形式のバイナリのLC_SYMTABを解析し、その中のシンボル(関数、グローバル変数など)の名前、型、アドレスなどを一覧表示します。開発者がバイナリの内容を理解し、デバッグする上で不可欠なツールです。

Goリンカ (cmd/ld)

Go言語のビルドプロセスにおいて、リンカ(cmd/ld)はコンパイルされたGoパッケージを結合し、単一の実行可能バイナリを生成する役割を担います。ターゲットOSがmacOSの場合、GoリンカはMach-O形式の実行ファイルを生成し、その際にMach-Oヘッダ、ロードコマンド、セグメント、セクションなどを適切に構築します。

技術的詳細

このコミットの主要な変更点は、GoリンカがMach-Oバイナリのシンボルテーブルを生成する方法を根本的に見直したことです。

  1. シンボルテーブルの再構築:

    • 以前は、動的シンボル(SDYNIMPORT)のみをadddynsym関数で処理し、LC_DYSYMTABに関連する情報のみを生成していました。しかし、nmコマンドが期待するのはLC_SYMTABに格納される完全なシンボル情報です。
    • このコミットでは、macho.cmachosymorder関数とmachosymtab関数を導入し、すべてのシンボル(ローカル、外部定義、未定義)を収集し、ソートして、LC_SYMTABに書き込むようにしました。
    • シンボルは、SymKindLocal (ローカルシンボル), SymKindExtdef (外部定義シンボル), SymKindUndef (未定義シンボル) の3種類に分類され、この順序でソートされます。これは、Mach-Oのシンボルテーブルの慣例に合わせたものです。
  2. LC_SYMTABLC_DYSYMTABの分離と正確な生成:

    • 以前は.dynsym.dynstrというシンボルと文字列テーブルを使用していましたが、これらをMach-Oの慣例に合わせて.machosymtab.machosymstrに名称変更し、SMACHOSYMTABSMACHOSYMSTRという新しいシンボルタイプを導入しました。
    • LC_SYMTABasmbmacho関数内で、nsortsym(ソートされたシンボル数)とs4->size(文字列テーブルサイズ)を正確に設定して生成されます。
    • LC_DYSYMTABmachodysymtab関数で生成されるようになり、nkind配列を使用して各シンボル種類の数を正確に反映するようになりました。これにより、ilocalsym, nlocalsym, iextdefsym, nextdefsym, iundefsym, nundefsymといったフィールドが正しく設定されます。
  3. シンボル名のプレフィックス処理:

    • macOSではC言語のシンボルに_プレフィックスが付く慣例があります。このコミットでは、xsymname関数を導入し、シンボル名を取得する際に必要に応じて_プレフィックスを付加するようにしました。また、machosymtab関数内でシンボル文字列を書き込む際に_を明示的に追加しています。
  4. セクション番号の割り当て:

    • dodata関数内で、Mach-Oのセクションにextnum(外部セクション番号)を割り当てる処理が追加されました。これは、シンボルテーブルがシンボルが属するセクションを参照するために必要です。
  5. 不要なコードの削除:

    • src/cmd/6l/asm.csrc/cmd/8l/asm.cから、Mach-Oの動的シンボルテーブルを直接構築していた古いコードが削除されました。これは、新しいmacho.c内のロジックに置き換えられたためです。
    • sortdynexp関数(Issue 4029で言及されていた、エクスポートされたシンボルのソートに関するもの)がsrc/cmd/ld/go.csrc/cmd/ld/lib.cから削除されました。これは、新しいmachosymorder関数がより包括的なシンボルソートとdynid割り当てを行うため、不要になったためです。

これらの変更により、GoリンカはMach-Oの仕様に準拠した完全なシンボルテーブルを生成できるようになり、nmコマンドを含むmacOSの標準ツールとの互換性が向上しました。

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

このコミットのコアとなる変更は、主にsrc/cmd/ld/macho.csrc/cmd/ld/lib.h、そしてsrc/cmd/ld/data.cに集中しています。

  1. src/cmd/ld/macho.c:

    • SymKindLocal, SymKindExtdef, SymKindUndefというシンボル種別の列挙型と、それらをカウントするためのnkind配列、ソートされたシンボルを保持するsortsym配列が追加されました。
    • machosymorder(): すべてのシンボルを収集し、種類別に分類し、ソートしてdynidを割り当てる新しい関数。
    • machosymtab(): ソートされたシンボルリストに基づいて、Mach-OのLC_SYMTAB(シンボルテーブルと文字列テーブル)を実際に構築する新しい関数。
    • machodysymtab(): LC_DYSYMTABを構築する新しい関数。nkind配列のカウントを利用して、ローカル、外部定義、未定義シンボルの数を正確に設定します。
    • domacho(): .dynsym.dynstrの代わりに.machosymtab.machosymstrを使用するように変更。
    • asmbmacho(): LC_SYMTABLC_DYSYMTABの生成ロジックが新しい関数呼び出しに置き換えられました。
    • domacholink(): .dynsym.dynstrの代わりに.machosymtab.machosymstrを使用するように変更。
  2. src/cmd/ld/lib.h:

    • 新しいシンボルタイプ SMACHOSYMSTRSMACHOSYMTAB が追加されました。
    • Section構造体にextnumフィールドが追加されました。
    • machosymorder()関数のプロトタイプが追加されました。
  3. src/cmd/ld/data.c:

    • dodata()関数内で、machosymorder()が呼び出されるようになりました。これは、動的再配置を処理する前にシンボルテーブルの順序付けが必要なためです。
    • dodata()関数内で、各セクションにextnum(外部セクション番号)を割り当てるループが追加されました。
  4. src/cmd/ld/go.c:

    • addexport()関数がHEADTYPE == Hdarwinの場合に早期リターンするようになりました。これは、Mach-Oのシンボル処理がmacho.cに集約されたためです。
    • scmp関数とsortdynexp関数が削除されました。
  5. src/cmd/ld/lib.c:

    • sortdynexp()の呼び出しがloadlib()から削除されました。
  6. src/cmd/6l/asm.c および src/cmd/8l/asm.c:

    • adddynsym関数内のMach-O固有のシンボルテーブル構築ロジックが削除されました。

コアとなるコードの解説

src/cmd/ld/macho.c の変更

このコミットの心臓部はmacho.cにあります。

  • シンボル種別の定義とカウント:

    enum
    {
    	SymKindLocal = 0,
    	SymKindExtdef,
    	SymKindUndef,
    	NumSymKind
    };
    
    static int nkind[NumSymKind];
    static Sym** sortsym;
    static int nsortsym;
    

    これは、シンボルをローカル、外部定義、未定義の3種類に分類し、それぞれの数をnkindでカウントし、ソートされたシンボルをsortsymに格納するための準備です。

  • machosymorder() 関数:

    void
    machosymorder(void)
    {
    	int i;
    
    	// ... (既存のコメントとdynexpのreachable設定)
    	machogenasmsym(addsym); // 最初の呼び出しでnsortsymをカウント
    	sortsym = mal(nsortsym * sizeof sortsym[0]);
    	nsortsym = 0; // カウントをリセット
    	machogenasmsym(addsym); // 2回目の呼び出しでsortsymにシンボルを格納
    	qsort(sortsym, nsortsym, sizeof sortsym[0], scmp); // シンボルをソート
    	for(i=0; i<nsortsym; i++)
    		sortsym[i]->dynid = i; // ソート順にdynidを割り当て
    }
    

    この関数は、Goリンカが認識するすべてのシンボルを収集し、addsymヘルパー関数を通じてnkindを更新し、sortsym配列に格納します。その後、scmp関数(シンボル種別と名前でソート)を使用してqsortでシンボルをソートし、ソートされた順序でdynid(シンボルID)を割り当てます。このdynidは、Mach-Oのシンボルテーブル内のインデックスとして機能します。

  • machosymtab() 関数:

    static void
    machosymtab(void)
    {
    	int i;
    	Sym *symtab, *symstr, *s, *o;
    
    	symtab = lookup(".machosymtab", 0); // 新しいシンボルテーブルシンボル
    	symstr = lookup(".machosymstr", 0); // 新しい文字列テーブルシンボル
    
    	for(i=0; i<nsortsym; i++) {
    		s = sortsym[i];
    		adduint32(symtab, symstr->size); // シンボル名へのオフセット
    		adduint8(symstr, '_'); // macOSの慣例で_プレフィックスを追加
    		addstring(symstr, xsymname(s)); // シンボル名を文字列テーブルに追加
    
    		if(s->type == SDYNIMPORT) {
    			// 外部シンボルのnlistエントリを構築
    			adduint8(symtab, 0x01); // N_EXT (外部シンボル)
    			adduint8(symtab, 0); // セクションなし
    			adduint16(symtab, 0); // desc
    			adduintxx(symtab, 0, PtrSize); // 値なし
    		} else {
    			// 内部シンボルのnlistエントリを構築
    			adduint8(symtab, 0x0f); // N_EXT | N_TYPE (外部かつ型情報あり)
    			// シンボルが属するセクションのextnumを設定
    			o = s;
    			while(o->outer != nil)
    				o = o->outer;
    			if(o->sect == nil) {
    				diag("missing section for %s", s->name);
    				adduint8(symtab, 0);
    			} else
    				adduint8(symtab, o->sect->extnum);
    			adduint16(symtab, 0); // desc
    			adduintxx(symtab, symaddr(s), PtrSize); // シンボルのアドレス
    		}
    	}
    }
    

    この関数は、machosymorderでソートされたsortsym配列をイテレートし、各シンボルに対してMach-Oのnlistエントリ(シンボルテーブルのエントリ)を構築します。シンボル名へのオフセット、シンボルタイプ、セクション番号、値(アドレス)などを設定し、.machosymtab.machosymstrシンボルにデータを書き込んでいきます。特に、macOSの慣例である_プレフィックスの追加や、シンボルが属するセクションのextnumの参照が重要です。

  • machodysymtab() 関数:

    static void
    machodysymtab(void)
    {
    	int n;
    	MachoLoad *ml;
    	Sym *s1, *s2, *s3;
    
    	ml = newMachoLoad(11, 18);	/* LC_DYSYMTAB */
    
    	n = 0;
    	ml->data[0] = n;	/* ilocalsym */
    	ml->data[1] = nkind[SymKindLocal];	/* nlocalsym */
    	n += nkind[SymKindLocal];
    
    	ml->data[2] = n;	/* iextdefsym */
    	ml->data[3] = nkind[SymKindExtdef];	/* nextdefsym */
    	n += nkind[SymKindExtdef];
    
    	ml->data[4] = n;	/* iundefsym */
    	ml->data[5] = nkind[SymKindUndef];	/* nundefsym */
    
    	// ... (その他のLC_DYSYMTABフィールドの設定)
    }
    

    この関数は、LC_DYSYMTABロードコマンドを構築します。nkind配列でカウントされた各シンボル種別(ローカル、外部定義、未定義)の数に基づいて、ilocalsym(ローカルシンボルの開始インデックス)、nlocalsym(ローカルシンボルの数)などのフィールドを正確に設定します。これにより、動的リンカがシンボルを効率的に解決できるようになります。

src/cmd/ld/data.c の変更

  • dodata() 関数内の machosymorder() 呼び出しとセクション番号割り当て:
    void
    dodata(void)
    {
    	// ...
    	if(HEADTYPE == Hdarwin)
    		machosymorder(); // Mach-Oの場合、シンボルを順序付け
    	dynreloc();
    	// ...
    	/* number the sections */
    	n = 1;
    	for(sect = segtext.sect; sect != nil; sect = sect->next)
    		sect->extnum = n++;
    	for(sect = segdata.sect; sect != nil; sect = sect->next)
    		sect->extnum = n++;
    }
    
    dodata関数は、データセクションの処理を行うリンカの重要なフェーズです。ここでmachosymorder()を呼び出すことで、動的再配置の処理前にシンボルテーブルが適切に準備されることが保証されます。また、各セクションに一意のextnumを割り当てることで、シンボルテーブルのエントリが正しいセクションを参照できるようになります。

これらの変更により、GoリンカはMach-Oのシンボルテーブルの仕様に完全に準拠し、macOSのnmコマンドがGoバイナリを正しく解析できるようになりました。

関連リンク

参考にした情報源リンク