[インデックス 17998] ファイルの概要
このコミットは、Go言語のツールチェインにおいて、新しいGoオブジェクトファイル形式を読み込むためのdebug/goobj
パッケージを追加するものです。具体的には、src/pkg/debug/goobj/read.go
という単一のファイルが新規に作成され、Goのコンパイラやリンカが生成するオブジェクトファイル(.o
ファイル)や、それらをまとめたアーカイブファイル(.a
ファイル)の内部構造を解析し、シンボル情報、リロケーション情報、関数データなどを抽出するための機能を提供します。このパッケージは、デバッグツールやプロファイリングツール、あるいはリンカのような低レベルのツールがGoのバイナリを理解するために不可欠な基盤となります。
コミット
commit 08b846b1293f3aa5e7fab55d6455a36330104c5c
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 16 12:52:21 2013 -0500
debug/goobj: add package for reading new Go object files
R=golang-dev, r, iant
CC=golang-dev
https://golang.org/cl/40610043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/08b846b1293f3aa5e7fab55d6455a36330104c5c
元コミット内容
debug/goobj: add package for reading new Go object files
変更の背景
このコミットが行われた2013年12月頃は、Go言語のコンパイラとリンカが大きく進化していた時期にあたります。特に、Go 1.2のリリースに向けて、オブジェクトファイルのフォーマットが変更された可能性があります。新しいフォーマットに対応するためには、それを読み解くための新しいパーサーが必要となります。debug/goobj
パッケージの追加は、この新しいオブジェクトファイル形式をプログラム的に解析し、Goのデバッグツールやその他の開発ツールが最新のGoバイナリと連携できるようにするための基盤を構築することを目的としています。
Goのオブジェクトファイルは、コンパイルされたGoのソースコードから生成される中間ファイルであり、最終的な実行可能ファイルを生成するリンカによって結合されます。これらのファイルには、関数、変数、型情報などのシンボル情報、そしてそれらのシンボル間の参照を解決するためのリロケーション情報が含まれています。フォーマットの変更は、パフォーマンスの向上、新しい言語機能のサポート、またはクロスコンパイルの改善など、様々な理由で行われることがあります。このコミットは、そのような内部的なフォーマット変更に対応するための重要なインフラストラクチャの更新と言えます。
前提知識の解説
Goオブジェクトファイル (.o)
Goオブジェクトファイル(通常は.o
拡張子を持つ)は、GoコンパイラがGoのソースコードをコンパイルした結果生成されるバイナリファイルです。これらは、まだ完全にリンクされていない、機械語のコードとデータを含んでいます。オブジェクトファイルには以下の主要な情報が含まれます。
- 機械語コード: コンパイルされたGo関数の命令セット。
- データ: グローバル変数、定数、文字列リテラルなどのデータ。
- シンボルテーブル: 関数名、変数名、型名などの識別子と、それらがオブジェクトファイル内のどこに位置するか(アドレス)をマッピングする情報。シンボルには、その種類(コード、データなど)や可視性(エクスポートされているか、内部のみか)などの属性が付与されます。
- リロケーションエントリ: オブジェクトファイル内のコードやデータが、まだ最終的なアドレスが決定していない他のシンボルを参照している場合、その参照をリンカが解決できるようにするための情報。例えば、ある関数が別のパッケージの関数を呼び出す場合、その呼び出し命令はリンカによって最終的なアドレスに修正される必要があります。
- デバッグ情報: オプションで、ソースコードの行番号と機械語命令のマッピング、変数名、型情報など、デバッガがソースレベルでプログラムを理解するために必要な情報が含まれることがあります。
アーカイブファイル (.a)
アーカイブファイル(通常は.a
拡張子を持つ)は、複数のオブジェクトファイルを一つにまとめたものです。Goのパッケージ(ライブラリ)は、通常、コンパイルされると.a
ファイルとして生成されます。これにより、複数のオブジェクトファイルを個別に扱う手間を省き、リンカが一度に多くのコードを処理できるようになります。Unix系のシステムでは、ar
コマンドで作成・管理されることが一般的です。アーカイブファイルは、内部に個々のオブジェクトファイルと、それらのオブジェクトファイルに含まれるシンボルのインデックス(目次)を持つことがあります。
シンボル (Symbol)
プログラムにおけるシンボルとは、関数、変数、型、ラベルなど、プログラムの構成要素を識別するための名前です。オブジェクトファイルや実行可能ファイルでは、これらのシンボルが特定のアドレスやメモリ領域に対応付けられます。
- グローバルシンボル: 複数のファイルやモジュールから参照可能なシンボル。
- ローカルシンボル: 特定のファイルやモジュール内でのみ参照可能なシンボル。
- 未定義シンボル: 参照されているが、その定義が現在のオブジェクトファイルにはないシンボル。リンカが他のオブジェクトファイルやライブラリから定義を見つけて解決する必要があります。
リロケーション (Relocation)
リロケーションとは、コンパイル時にアドレスが確定できないシンボル参照を、リンカが最終的な実行可能ファイルを生成する際に解決するプロセスです。例えば、ある関数が別の関数を呼び出す場合、呼び出し元の命令には呼び出し先の関数のアドレスが必要ですが、コンパイル時にはそのアドレスはまだ不明です。リロケーションエントリは、リンカに対して「この場所のこのバイト列を、指定されたシンボルの最終アドレスに基づいて修正しなさい」という指示を与えます。
Varint (Variable-length integer)
Varintは、可変長整数エンコーディングの一種で、小さな数値を少ないバイト数で表現し、大きな数値をより多くのバイト数で表現することで、全体的なデータサイズを削減する目的で使用されます。Goのオブジェクトファイルでは、数値データを効率的に格納するために使用されている可能性があります。通常、各バイトの最上位ビット(MSB)が、そのバイトが数値の最後のバイトであるかどうかを示し、残りのビットが数値のデータを含みます。
Zigzagエンコーディング
Zigzagエンコーディングは、符号付き整数を符号なし整数にマッピングする手法です。これにより、負の数もVarintで効率的にエンコードできるようになります。例えば、0は0、-1は1、1は2、-2は3、2は4といった具合にマッピングされます。これにより、絶対値が小さい負の数も、Varintで少ないバイト数で表現できるようになります。
技術的詳細
debug/goobj
パッケージのread.go
ファイルは、Goオブジェクトファイルおよびアーカイブファイルを解析するための主要なロジックを含んでいます。
データ構造
このパッケージは、Goオブジェクトファイル内の様々な要素を表現するためのGoの構造体を定義しています。
SymKind
: シンボルの種類を表す整数型。STEXT
(実行可能コード)、SRODATA
(読み取り専用データ)、SDATA
(書き込み可能データ)、SBSS
(初期化されていないデータ)など、多くの定数が定義されています。これらはinclude/link.h
から取られたもので、Goのリンカが内部的に使用するシンボル種別に対応しています。Sym
: オブジェクトファイル内の名前付きシンボルを表す構造体。SymID
: シンボル名とバージョン(可視性)の組み合わせで、パッケージ内でシンボルを一意に識別します。Kind
:SymKind
で定義されたシンボルの種類。DupOK
: 重複定義が許容されるか。Size
: 対応するデータのサイズ。Type
: Goの型情報を持つシンボルへの参照。Data
: シンボルのメモリイメージへの参照(オフセットとサイズ)。Reloc
: シンボルに適用されるリロケーションのリスト。Func
: 関数固有の追加データ(STEXT
シンボルの場合のみ)。
SymID
: シンボル名(Name
)とバージョン(Version
)の組み合わせ。バージョンは、グローバルシンボルでは0、ファイルスコープの静的シンボルでは非ゼロの値を取ります。Data
: オブジェクトファイル内のデータへの参照。オフセットとサイズで指定されます。Reloc
: リロケーション情報を記述する構造体。Offset
: メモリイメージ内の更新対象バイトのオフセット。Size
: 更新対象バイトのサイズ。Sym
: 参照先のシンボル。Add
: 参照先のシンボルアドレスに加算されるオフセット。Type
: リロケーションの種類(絶対アドレス、PC相対など)。
Var
: 関数スタックフレーム内の変数(ローカル変数、引数、戻り値)を記述する構造体。Func
: 関数固有の追加情報。Args
: 引数フレームのサイズ(バイト単位)。Frame
: ローカル変数フレームのサイズ(バイト単位)。Var
: ローカル変数の詳細。PCSP
,PCFile
,PCLine
,PCData
: プログラムカウンタ(PC)とスタックポインタ(SP)オフセット、ファイル番号、行番号、ランタイムサポートデータなどのマッピング情報への参照。FuncData
: PCに依存しないランタイムサポートデータ。File
:PCFile
でインデックス付けされるファイルパスのリスト。
FuncData
: 単一の関数固有のデータ値。Package
: 解析されたGoオブジェクトファイルまたはアーカイブを表す構造体。ImportPath
: このパッケージのインポートパス。Imports
: このパッケージがインポートする他のパッケージのリスト。Syms
: このパッケージで定義されるシンボルのリスト。MaxVersion
:Syms
内の任意のSymID
における最大バージョン。
リーダーの実装 (objReader
)
objReader
構造体は、オブジェクトファイルからの読み取り操作を管理します。
init
:objReader
を初期化し、読み取り対象のio.ReadSeeker
とPackage
を設定します。error
: 読み取り中に発生した最初のエラーを記録します。readByte
: 1バイトを読み取ります。readFull
: 指定されたバイト数のデータを正確に読み取ります。readInt
: Zigzag Varintを読み取ります。これは、Goオブジェクトファイル内の数値データ(サイズ、オフセット、カウントなど)を効率的にデコードするために使用されます。readString
: 長さで区切られた文字列を読み取ります。文字列の長さはreadInt
で読み取られます。readSymID
:SymID
(シンボル名とバージョン)を読み取ります。ファイルスコープの静的シンボル(バージョン1)は、現在のファイル番号に対応する一意のバージョンに変換されます。readData
:Data
構造体(オフセットとサイズ)を読み取ります。skip
: 指定されたバイト数をスキップします。バッファリングされたデータがある場合はそれを利用し、大量のスキップが必要な場合はSeek
を使用します。
解析ロジック
-
Parse(r io.ReadSeeker, pkgpath string) (*Package, error)
:- Goオブジェクトファイルまたはアーカイブを解析するためのエントリポイント。
- ファイルの先頭8バイトを読み取り、
archiveHeader
(!<arch>\n
)またはgoobjHeader
(go objec
)と比較して、ファイルの種類(アーカイブか単一のオブジェクトファイルか)を判別します。 - 適切なパーサー(
parseArchive
またはparseObject
)を呼び出します。
-
parseArchive() error
:- Unixアーカイブファイル(
.a
)を解析します。 - アーカイブヘッダ(各メンバーファイルのメタデータ)を読み取り、ファイル名、サイズ、マジックバイトなどを抽出します。
__.SYMDEF
,__.GOSYMDEF
,__.PKGDEF
のような特殊なアーカイブメンバーはスキップします。- 各オブジェクトファイルメンバーに対して
parseObject
を呼び出し、その内容を解析します。 - アーカイブメンバーのサイズが奇数の場合、パディングバイトをスキップします。
- Unixアーカイブファイル(
-
parseObject(prefix []byte) error
:- 単一のGoオブジェクトファイルを解析します。
- オブジェクトファイルのヘッダ(テキスト形式で、
\n!\n
で終わる)を読み飛ばします。このヘッダには、アーキテクチャやバージョン情報が含まれる可能性がありますが、このパーサーでは利用されません。 \x00\x00go13ld
というマジックバイトを検証し、Go 1.3リンカのオブジェクトファイルであることを確認します。- 直接のパッケージ依存関係: 空文字列が現れるまで文字列を読み取り、インポートパスのリストを構築します。
- シンボル:
0xfe
で始まるシンボルエントリを繰り返し読み取ります。- シンボルの種類、
SymID
、DupOK
、サイズ、型情報、データ参照、リロケーションの数を読み取ります。 - リロケーションのリストを読み取り、各リロケーションのオフセット、サイズ、種類、加算値、参照シンボルを抽出します。
- シンボルが
STEXT
(関数)の場合、Func
構造体を割り当て、引数サイズ、フレームサイズ、ローカル変数、PC-SP/ファイル/行/データマップ、関数データ、ファイルパスなどの関数固有の情報を読み取ります。
- シンボルの種類、
- ファイルの終わりを示す
\xffgo13ld
というフッターを検証します。
エラーハンドリング
objReader
は、読み取り中に発生した最初のエラーを記録し、それ以降の読み取り操作ではそのエラーを返します。これにより、エラーが連鎖的に発生した場合でも、根本原因のエラー情報が失われるのを防ぎます。また、ファイルが破損している場合や、予期せぬEOFに達した場合に適切なエラーを返します。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、src/pkg/debug/goobj/read.go
という新しいファイルが追加されたことです。このファイルは555行に及び、Goオブジェクトファイルとアーカイブファイルを解析するためのすべてのロジックを含んでいます。
コアとなるコードの解説
read.go
の主要な機能は、GoオブジェクトファイルのバイナリフォーマットをGoのデータ構造にマッピングすることです。
- フォーマットの識別:
Parse
関数は、入力ストリームの最初の数バイトを調べて、それがGoオブジェクトファイルであるか、Goオブジェクトファイルを含むアーカイブであるかを判断します。これは、マジックバイト(!<arch>\n
やgo objec
)によって行われます。 - アーカイブの解析:
parseArchive
関数は、Unixar
形式のアーカイブファイルを処理します。アーカイブ内の各メンバーのヘッダを読み取り、そのサイズと名前を解析します。Goオブジェクトファイルであるメンバーについては、parseObject
を呼び出して個別に解析します。 - オブジェクトファイルの解析:
parseObject
関数は、単一のGoオブジェクトファイルの解析の中核を担います。- まず、テキスト形式のヘッダを読み飛ばし、バイナリデータ部分の開始位置を見つけます。
- 次に、
\x00\x00go13ld
というマジックバイトをチェックして、ファイルが期待されるGoオブジェクトファイル形式であることを確認します。 - インポートパスの読み取り: オブジェクトファイルが依存する他のパッケージのインポートパスを読み取ります。
- シンボルの読み取り: オブジェクトファイル内で定義されているすべてのシンボルを繰り返し読み取ります。各シンボルについて、その種類(
SymKind
)、名前とバージョン(SymID
)、サイズ、型情報、実際のデータへの参照(Data
)、そしてリロケーションのリスト(Reloc
)を抽出します。 - 関数データの読み取り: シンボルが関数(
STEXT
)である場合、その関数に特有の追加情報(引数とフレームのサイズ、ローカル変数、PC-SP/ファイル/行/データマップなど)をFunc
構造体に読み込みます。 - フッターの検証: ファイルの終わりに
\xffgo13ld
というフッターをチェックし、ファイルの整合性を確認します。
このコードは、Goのオブジェクトファイルがどのように構造化されているか、そしてリンカやデバッグツールがどのようにその情報を利用するかを理解するための低レベルなインターフェースを提供します。特に、readInt
やreadString
のようなヘルパー関数は、GoオブジェクトファイルがVarintや長さで区切られた文字列などの効率的なバイナリエンコーディングを使用していることを示しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/08b846b1293f3aa5e7fab55d6455a36330104c5c
- Go Code Review (CL 40610043): https://golang.org/cl/40610043
参考にした情報源リンク
- Go Code Review (CL 40610043): https://golang.org/cl/40610043 (コミットの背景と議論の詳細を提供)
- Go言語のオブジェクトファイルフォーマットに関する一般的な情報 (このコミットの時点での具体的なドキュメントは公開されていない可能性が高いが、Goのリンカのソースコードや関連する議論から推測される)
- Unix
ar
アーカイブフォーマットに関する一般的な情報 - VarintおよびZigzagエンコーディングに関する一般的な情報
- Goのリンカのソースコード(特に
src/cmd/link
やsrc/liblink
)は、オブジェクトファイルのフォーマットを理解する上で最も信頼できる情報源となる。