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

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

このコミットは、Go言語のツールチェインの一部である cmd/pack における変更です。具体的には、オブジェクトファイルを解析して __.SYMDEF を生成する処理を停止します。これは、__.SYMDEF がもはや参照されなくなり、Goのオブジェクトファイル形式が変更されつつあるという背景に基づいています。

コミット

commit 4c01a23cf1a18a0fb7a35dd389a8ef058ffe03d4
Author: Russ Cox <rsc@golang.org>
Date:   Mon Dec 9 19:35:17 2013 -0500

    cmd/pack: stop parsing object files to generate __.SYMDEF
    
    Nothing looks at __.SYMDEF, and the object file format is changing.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/39580044
---
 src/cmd/pack/ar.c | 67 ++++++++++++-------------------------------------------
 1 file changed, 14 insertions(+), 53 deletions(-)

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

https://github.com/golang/go/commit/4c01a23cf1a18a0fb7a35dd389a8ef058ffe03d4

元コミット内容

cmd/pack: stop parsing object files to generate __.SYMDEF

Nothing looks at __.SYMDEF, and the object file format is changing.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/39580044

変更の背景

このコミットの主な背景は二つあります。

  1. __.SYMDEF の非使用化: コミットメッセージに明記されている通り、Goツールチェインの内部で __.SYMDEF ファイルがもはや参照されなくなりました。__.SYMDEF は、通常、静的ライブラリ(.a ファイル)内に含まれるシンボルディレクトリであり、リンカがライブラリ内のシンボル定義を効率的に検索するために使用されます。Goのビルドプロセスにおいて、このシンボル情報の管理方法が変更されたため、__.SYMDEF を生成する必要がなくなったと考えられます。
  2. オブジェクトファイル形式の変更: 2013年頃、Goのオブジェクトファイル形式は大きな変更が加えられつつありました。特に、Go 1.3のリンカのオーバーホールの一環として、新しいオブジェクトファイル形式が設計されました。この新しい形式は、従来の擬似命令ストリームではなく、実行可能なコードとデータブロック、および再配置情報を直接含むように設計されています。このようなフォーマットの変更に伴い、古い __.SYMDEF の生成ロジックが不要になったか、あるいは新しいフォーマットに対応させるのが非効率になった可能性があります。

これらの理由から、cmd/pack がオブジェクトファイルを解析して __.SYMDEF を生成する処理は冗長となり、削除されることになりました。これにより、ビルドプロセスの複雑性が軽減され、将来のオブジェクトファイル形式の変更への対応が容易になります。

前提知識の解説

cmd/pack

cmd/pack は、Goツールチェインの内部ツールであり、従来のUnixの ar ユーティリティの簡易版として機能します。主な目的は、Goのビルドプロセスで使用されるアーカイブファイル(通常は .a 拡張子を持つ静的ライブラリ)を作成および操作することです。これは一般的なアーカイブ目的ではなく、Goコンパイラとリンカがコンパイルおよびリンクフェーズ中にオブジェクトファイルのコレクションやその他のアセットを管理するために不可欠なコンポーネントです。go tool pack コマンドを通じてアクセスでき、ファイルの追加、内容の表示、アーカイブからの抽出などの操作をサポートします。

__.SYMDEF

__.SYMDEF は、アーカイブファイル(特に静的ライブラリ .a)内に存在する特殊なメンバー(ファイル)の名前です。これはシンボルディレクトリとして機能し、アーカイブ内の他のオブジェクトファイルによって定義された外部シンボルをリストします。リンカは、静的ライブラリにリンクする際に、アーカイブ内のすべてのオブジェクトファイルをスキャンすることなく、このディレクトリを利用してシンボル定義を迅速に見つけることができます。Unix系のシステムでは、ar ユーティリティがこの __.SYMDEF メンバーの作成と維持を担当し、ranlib などのツールがアーカイブ内の __.SYMDEF テーブルを追加または更新するために使用されます。Goのビルドプロセスにおいても、かつては静的ライブラリのビルド時にこの慣習が利用されていました。

Goのオブジェクトファイル形式

Go言語は、標準的なELFやPEのようなオブジェクトファイル形式ではなく、独自のカスタムオブジェクトファイル形式を使用しています。これは、Goのランタイムデータ構造を構築するためにGo固有のリンカが必要であり、標準的なリンカでは対応できないためです。2013年頃には、Go 1.3のリンカのオーバーホールの一環として、このカスタムオブジェクトファイル形式に大きな変更が加えられました。この変更は、オブジェクトファイルが擬似命令ストリームではなく、直接実行可能なコードとデータブロック、および再配置情報を含むようにすることを目的としていました。

技術的詳細

このコミットは、cmd/pack ツールがオブジェクトファイルを処理する scanobj 関数から、__.SYMDEF の生成に関連するロジックを削除することに焦点を当てています。

変更前は、scanobj 関数内で、入力されたファイルがGoのオブジェクトファイルであるかどうかを判断し、そのシンボル情報を解析して __.SYMDEF に含めるための処理が行われていました。これには、オブジェクトファイルのタイプを識別する objtype 関数の呼び出しや、シンボル参照の検証、そして objtraverse を用いたシンボル情報のトラバースなどが含まれていました。

しかし、Goツールチェインの進化に伴い、__.SYMDEF がもはやリンカや他のツールによって参照されなくなったため、この処理は不要になりました。また、Goのオブジェクトファイル形式自体が変更されつつあったため、古い形式の解析ロジックを維持する必要がなくなったことも、この変更を後押ししました。

具体的には、以下の点が技術的な変更のポイントです。

  • objtype 関数の削除: オブジェクトファイルのタイプを識別する objtype 関数の呼び出しが削除されました。これにより、Goのオブジェクトファイルであるかどうかの厳密なチェックが簡素化され、外部のオブジェクトファイル(ELF, Windows PE, Mach-Oなど)の識別のみに限定されました。
  • シンボル解析ロジックの削除: readarobjtraverse といった、オブジェクトファイル内のシンボルを解析し、__.SYMDEF に関連する処理を行うコードが削除されました。これにより、cmd/pack はシンボル情報の詳細な解析を行わなくなりました。
  • エラーハンドリングの簡素化: オブジェクトファイルの形式が不正である場合や、シンボル参照に問題がある場合のエラーハンドリングが大幅に簡素化されました。これは、詳細なシンボル解析が不要になったため、それに関連するエラーチェックも不要になったためです。
  • lastobj 変数の削除: 複数のオブジェクトファイル間で一貫性をチェックするために使用されていた lastobj 変数とその関連ロジックが削除されました。これは、シンボル解析が不要になったため、オブジェクトファイルの一貫性チェックも不要になったためです。

この変更により、cmd/pack はより軽量になり、Goのビルドプロセスにおけるオブジェクトファイルの処理が効率化されました。また、将来のオブジェクトファイル形式の変更に対して、より柔軟に対応できるようになりました。

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

src/cmd/pack/ar.c ファイルの scanobj 関数が変更されています。

--- a/src/cmd/pack/ar.c
+++ b/src/cmd/pack/ar.c
@@ -649,49 +649,28 @@ matchhdr(char *p, char **lastp)
 void
 scanobj(Biobuf *b, Arfile *ap, long size)
 {
-	int obj, goobject;
-	vlong offset, offset1;
-	Dir *d;
-	static int lastobj = -1;
+	int goobject;
+	vlong offset;
 	uchar buf[4];
 	char *p;
 
 	if (!allobj)			/* non-object file encountered */
 		return;
 	offset = Boffset(b);
-	obj = objtype(b, 0);
-	if (obj < 0) {			/* not an object file */
-		/* maybe a foreign object file */
-		Bseek(b, offset, 0);
-		memset(buf, 0, sizeof buf);
-		Bread(b, buf, 4);
-		
-		/* maybe a foreign object file?  that's okay */
-		if((buf[0] == 0x7F && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F') ||   // ELF
-		   (buf[0] == 0x4c && buf[1] == 0x01 || buf[0] == 0x64 && buf[1] == 0x86) || // Windows PE
-		   (buf[0] == 0xFE && buf[1] == 0xED && buf[2] == 0xFA && (buf[3]&~1) == 0xCE) ||  // Mach-O big-endian
-		   (buf[3] == 0xFE && buf[2] == 0xED && buf[1] == 0xFA && (buf[0]&~1) == 0xCE)) {  // Mach-O little-endian
-			Bseek(b, offset, 0);
-			return;
-		}
-		
-		if (!gflag || strcmp(file, pkgdef) != 0) {  /* don't clear allobj if it's pkg defs */
-			fprint(2, "pack: non-object file %s\n", file);
-			errors++;
-			allobj = 0;
-		}
-		d = dirfstat(Bfildes(b));
-		if (d != nil && d->length == 0) {
-			fprint(2, "pack: zero length file %s\n", file);
-			errors++;
-		}
-		free(d);
-		Bseek(b, offset, 0);
-		return;
-	}
-
+
+	memset(buf, 0, sizeof buf);
+	Bread(b, buf, 4);
+	
+	/* maybe a foreign object file?  that's okay */
+	if((buf[0] == 0x7F && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F') ||   // ELF
+	   (buf[0] == 0x4c && buf[1] == 0x01 || buf[0] == 0x64 && buf[1] == 0x86) || // Windows PE
+	   (buf[0] == 0xFE && buf[1] == 0xED && buf[2] == 0xFA && (buf[3]&~1) == 0xCE) ||  // Mach-O big-endian
+	   (buf[3] == 0xFE && buf[2] == 0xED && buf[1] == 0xFA && (buf[0]&~1) == 0xCE)) {  // Mach-O little-endian
+		Bseek(b, offset, 0);
+		return;
+	}
+	
 	goobject = 1;
-	offset1 = Boffset(b);
 	Bseek(b, offset, 0);
 	p = Brdstr(b, '\n', 1);
 	
@@ -702,12 +681,11 @@ scanobj(Biobuf *b, Arfile *ap, long size)
 	// Go metadata is present.
 	if(BGETC(b) == '!')
 		goobject = 0;
+	Bseek(b, offset, 0);
 
-	Bseek(b, offset1, 0);
 	if(p == nil || strncmp(p, "go object ", 10) != 0) {
 		fprint(2, "pack: malformed object file %s\n", file);
 		errors++;
-		Bseek(b, offset, 0);
 		free(p);
 		return;
 	}
@@ -721,24 +699,7 @@ scanobj(Biobuf *b, Arfile *ap, long size)
 	}
 	free(p);
 
-	// Old check.  Should be impossible since objhdrs match, but keep the check anyway.
-	if (lastobj >= 0 && obj != lastobj) {
-		fprint(2, "pack: inconsistent object file %s\n", file);
-		errors++;
-		allobj = 0;
-		return;
-	}
-	lastobj = obj;
-		
-	if (!readar(b, obj, offset+size, 0)) {
-		fprint(2, "pack: invalid symbol reference in file %s\n", file);
-		errors++;
-		allobj = 0;
-		Bseek(b, offset, 0);
-		return;
-	}
-	Bseek(b, offset, 0);
-	objtraverse(objsym, ap);
+	USED(ap);
 	if (gflag && goobject) {
 		scanpkg(b, size);
 		Bseek(b, offset, 0);

コアとなるコードの解説

scanobj 関数は、アーカイブ内の個々のファイルをスキャンし、それがGoのオブジェクトファイルであるかどうかを判断し、必要に応じて処理を行う役割を担っていました。このコミットでは、__.SYMDEF の生成が不要になったことに伴い、この関数の内部ロジックが大幅に簡素化されています。

変更のポイントは以下の通りです。

  1. 不要な変数の削除:

    • obj: オブジェクトファイルのタイプを格納していた変数。
    • offset1: ファイル内のオフセットを一時的に保存していた変数。
    • d: ファイルの統計情報を格納していた Dir 構造体へのポインタ。
    • lastobj: 複数のオブジェクトファイル間の一貫性をチェックするために使用されていた静的変数。 これらの変数は、__.SYMDEF の生成や詳細なシンボル解析に関連するロジックが削除されたため、不要になりました。
  2. オブジェクトファイルタイプの判定ロジックの簡素化:

    • 変更前は、objtype(b, 0) を呼び出してGoのオブジェクトファイルタイプを判定し、それ以外の場合は外部のオブジェクトファイル(ELF, Windows PE, Mach-O)であるかをチェックしていました。
    • 変更後は、objtype の呼び出しが削除され、直接ファイルの先頭4バイトを読み込んで、それが外部のオブジェクトファイル形式のマジックナンバーと一致するかどうかのみをチェックするようになりました。これにより、Go固有のオブジェクトファイル形式の詳細な判定が不要になりました。
  3. シンボル解析および関連ロジックの削除:

    • if (obj < 0) { ... } ブロック全体が削除されました。このブロックには、Goのオブジェクトファイルではない場合の詳細なエラーハンドリングや、ゼロバイトファイルのチェックなどが含まれていました。
    • offset1 = Boffset(b); の行が削除されました。これは、Goオブジェクトファイルのメタデータを読み込んだ後に元のオフセットに戻るために使用されていましたが、その後のシンボル解析ロジックが削除されたため不要になりました。
    • if (lastobj >= 0 && obj != lastobj) { ... } ブロックが削除されました。これは、複数のオブジェクトファイル間でオブジェクトタイプの一貫性をチェックするためのものでしたが、シンボル解析が不要になったため、このチェックも不要になりました。
    • if (!readar(b, obj, offset+size, 0)) { ... } ブロックが削除されました。このブロックは、オブジェクトファイル内のシンボル参照を読み込み、検証するためのものでした。
    • objtraverse(objsym, ap); の呼び出しが削除されました。この関数は、オブジェクトファイル内のシンボルをトラバースし、__.SYMDEF に関連する処理を行うためのものでした。
  4. USED(ap); の追加:

    • ap (Arfile ポインタ) は、変更前のコードでは objtraverse 関数に渡されていましたが、この関数が削除されたため、ap が未使用の引数となりました。C言語では未使用の引数があるとコンパイラの警告が出る可能性があるため、USED(ap); というマクロ(または同様の仕組み)を追加して、この引数が意図的に使用されていないことを明示しています。

これらの変更により、scanobj 関数は、Goのオブジェクトファイルから __.SYMDEF を生成するための複雑なシンボル解析ロジックから解放され、よりシンプルで効率的な処理を行うようになりました。これは、Goツールチェインの内部的な進化と、__.SYMDEF の役割の終焉を反映したものです。

関連リンク

参考にした情報源リンク