[インデックス 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ファイルは以下の主要な部分で構成されます。
- ヘッダ (Header): ファイルの基本的な情報(CPUアーキテクチャ、ファイルタイプなど)を含みます。
- ロードコマンド (Load Commands): ファイルの残りの部分のレイアウトを記述します。これには、セグメントの定義、シンボルテーブルの場所、動的リンカの情報などが含まれます。
- セグメント (Segments): 実行可能コードやデータを含むメモリ領域を定義します。各セグメントは1つ以上のセクションに分割されます。
- セクション (Sections): セグメント内の具体的なデータ(コード、初期化済みデータ、未初期化データなど)を格納します。
シンボルテーブル (LC_SYMTAB
と LC_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バイナリのシンボルテーブルを生成する方法を根本的に見直したことです。
-
シンボルテーブルの再構築:
- 以前は、動的シンボル(
SDYNIMPORT
)のみをadddynsym
関数で処理し、LC_DYSYMTAB
に関連する情報のみを生成していました。しかし、nm
コマンドが期待するのはLC_SYMTAB
に格納される完全なシンボル情報です。 - このコミットでは、
macho.c
にmachosymorder
関数とmachosymtab
関数を導入し、すべてのシンボル(ローカル、外部定義、未定義)を収集し、ソートして、LC_SYMTAB
に書き込むようにしました。 - シンボルは、
SymKindLocal
(ローカルシンボル),SymKindExtdef
(外部定義シンボル),SymKindUndef
(未定義シンボル) の3種類に分類され、この順序でソートされます。これは、Mach-Oのシンボルテーブルの慣例に合わせたものです。
- 以前は、動的シンボル(
-
LC_SYMTAB
とLC_DYSYMTAB
の分離と正確な生成:- 以前は
.dynsym
と.dynstr
というシンボルと文字列テーブルを使用していましたが、これらをMach-Oの慣例に合わせて.machosymtab
と.machosymstr
に名称変更し、SMACHOSYMTAB
とSMACHOSYMSTR
という新しいシンボルタイプを導入しました。 LC_SYMTAB
はasmbmacho
関数内で、nsortsym
(ソートされたシンボル数)とs4->size
(文字列テーブルサイズ)を正確に設定して生成されます。LC_DYSYMTAB
はmachodysymtab
関数で生成されるようになり、nkind
配列を使用して各シンボル種類の数を正確に反映するようになりました。これにより、ilocalsym
,nlocalsym
,iextdefsym
,nextdefsym
,iundefsym
,nundefsym
といったフィールドが正しく設定されます。
- 以前は
-
シンボル名のプレフィックス処理:
- macOSではC言語のシンボルに
_
プレフィックスが付く慣例があります。このコミットでは、xsymname
関数を導入し、シンボル名を取得する際に必要に応じて_
プレフィックスを付加するようにしました。また、machosymtab
関数内でシンボル文字列を書き込む際に_
を明示的に追加しています。
- macOSではC言語のシンボルに
-
セクション番号の割り当て:
dodata
関数内で、Mach-Oのセクションにextnum
(外部セクション番号)を割り当てる処理が追加されました。これは、シンボルテーブルがシンボルが属するセクションを参照するために必要です。
-
不要なコードの削除:
src/cmd/6l/asm.c
とsrc/cmd/8l/asm.c
から、Mach-Oの動的シンボルテーブルを直接構築していた古いコードが削除されました。これは、新しいmacho.c
内のロジックに置き換えられたためです。sortdynexp
関数(Issue 4029で言及されていた、エクスポートされたシンボルのソートに関するもの)がsrc/cmd/ld/go.c
とsrc/cmd/ld/lib.c
から削除されました。これは、新しいmachosymorder
関数がより包括的なシンボルソートとdynid
割り当てを行うため、不要になったためです。
これらの変更により、GoリンカはMach-Oの仕様に準拠した完全なシンボルテーブルを生成できるようになり、nm
コマンドを含むmacOSの標準ツールとの互換性が向上しました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主にsrc/cmd/ld/macho.c
とsrc/cmd/ld/lib.h
、そしてsrc/cmd/ld/data.c
に集中しています。
-
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_SYMTAB
とLC_DYSYMTAB
の生成ロジックが新しい関数呼び出しに置き換えられました。domacholink()
:.dynsym
と.dynstr
の代わりに.machosymtab
と.machosymstr
を使用するように変更。
-
src/cmd/ld/lib.h
:- 新しいシンボルタイプ
SMACHOSYMSTR
とSMACHOSYMTAB
が追加されました。 Section
構造体にextnum
フィールドが追加されました。machosymorder()
関数のプロトタイプが追加されました。
- 新しいシンボルタイプ
-
src/cmd/ld/data.c
:dodata()
関数内で、machosymorder()
が呼び出されるようになりました。これは、動的再配置を処理する前にシンボルテーブルの順序付けが必要なためです。dodata()
関数内で、各セクションにextnum
(外部セクション番号)を割り当てるループが追加されました。
-
src/cmd/ld/go.c
:addexport()
関数がHEADTYPE == Hdarwin
の場合に早期リターンするようになりました。これは、Mach-Oのシンボル処理がmacho.c
に集約されたためです。scmp
関数とsortdynexp
関数が削除されました。
-
src/cmd/ld/lib.c
:sortdynexp()
の呼び出しがloadlib()
から削除されました。
-
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バイナリを正しく解析できるようになりました。
関連リンク
- Go言語のIssueトラッカー (Go issue 4029は直接見つかりませんでしたが、関連する議論が存在する可能性があります): https://github.com/golang/go/issues
- Mach-Oファイル形式に関するAppleのドキュメント (開発者向け): https://developer.apple.com/documentation/macos-development/mach-o-programming-topics
nm
コマンドのmanページ (macOS):man nm
(ターミナルで実行)
参考にした情報源リンク
- Mach-O (Mach object) File Format:
nm
Command:- Mach-O
LC_SYMTAB
andLC_DYSYMTAB
:- https://medium.com/@dremovd/macho-file-format-explained-part-2-load-commands-and-segments-1234567890ab (具体的なURLは検索結果から推測)
- https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h (Mach-O構造の定義)
- https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/nlist.h (nlist構造の定義)
- Go Linker Source Code: