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

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

このコミットは、Go言語のリンカ(cmd/link)とリンカライブラリ(liblink)において、オブジェクトファイルにバージョン番号を追加する変更を導入しています。これにより、将来のGoバージョン(特にGo 1.3以降)でオブジェクトファイルのフォーマットに変更を加える際の互換性管理が容易になります。

コミット

liblink, cmd/link: add version number to object file

There are changes we know we want to make, but not before Go 1.3 Add a version number so that we can make them more easily later.

LGTM=iant R=iant CC=golang-codereviews https://golang.org/cl/87670043

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

https://github.com/golang/go/commit/0e8de61d7311d6bc74fc244de44df9be452a112b

元コミット内容

liblink, cmd/link: add version number to object file

There are changes we know we want to make, but not before Go 1.3
Add a version number so that we can make them more easily later.

LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/87670043

変更の背景

このコミットの主な背景は、Go言語の将来のバージョン、特にGo 1.3リリースに向けて、オブジェクトファイルフォーマットに互換性のない変更を導入する計画があったことです。Go言語の開発では、パフォーマンスの向上、機能の追加、内部構造の最適化のために、コンパイラやリンカが生成するバイナリフォーマットに変更を加えることがしばしばあります。しかし、これらの変更は、古いバージョンのツールで生成されたオブジェクトファイルが新しいバージョンのツールで正しく処理されない、あるいはその逆の互換性問題を引き起こす可能性があります。

このような互換性問題を管理し、将来のフォーマット変更をより容易にするために、オブジェクトファイル自体にバージョン番号を埋め込むことが決定されました。これにより、リンカやデバッグツールは、オブジェクトファイルを読み込む際にそのバージョン番号をチェックし、適切な処理ロジックを適用したり、互換性のないバージョンであればエラーを報告したりできるようになります。コミットメッセージにある「There are changes we know we want to make, but not before Go 1.3」という記述は、Go 1.3で導入される予定の重要な変更に備えるための準備であることを示唆しています。

前提知識の解説

オブジェクトファイルとは

オブジェクトファイル(.oファイルやGo言語における.6ファイルなど)は、コンパイラによってソースコードから生成される中間ファイルです。これには、機械語コード、データ、シンボルテーブル(関数名や変数名とそのアドレスのマッピング)、そして他のオブジェクトファイルやライブラリへの参照(未解決のシンボル)などが含まれます。リンカはこれらのオブジェクトファイルを結合し、必要なライブラリをリンクして、実行可能なバイナリファイルを生成します。

Go言語のビルドプロセスでは、go tool compileがGoソースコードをコンパイルしてオブジェクトファイルを生成し、go tool linkがこれらのオブジェクトファイルをリンクして最終的な実行ファイルを生成します。

  • liblink: Go言語のリンカのコアロジックを提供するライブラリです。オブジェクトファイルの読み書き、シンボルの解決、セクションの配置など、リンカの主要な機能がここに実装されています。
  • cmd/link: liblinkを利用してGoの実行ファイルを生成するコマンドラインツールです。go tool linkとしてユーザーが間接的に利用します。

オブジェクトファイルフォーマットのバージョン管理の重要性

ソフトウェア開発において、ファイルフォーマットのバージョン管理は非常に重要です。特に、コンパイラやリンカのようなツールが生成するバイナリフォーマットは、ツールのバージョンアップに伴って変更される可能性があります。バージョン管理を適切に行わないと、以下のような問題が発生します。

  1. 互換性の破綻: 新しいバージョンのツールが古いフォーマットのファイルを読み込めなくなったり、その逆が発生したりします。これにより、異なるバージョンのツールが混在する環境でのビルドが困難になります。
  2. デバッグの困難さ: デバッグツールが古いフォーマットのバイナリを正しく解析できない場合、デバッグ作業が著しく困難になります。
  3. 移行の複雑さ: フォーマット変更時に、ユーザーが手動で全てのオブジェクトファイルを再コンパイルする必要が生じるなど、移行プロセスが複雑になります。

オブジェクトファイルにバージョン番号を埋め込むことで、ツールはファイルのバージョンを識別し、適切なパーシングロジックを適用したり、互換性のないファイルに対しては明確なエラーメッセージを出すことができるようになります。これは、Go言語のような急速に進化する言語において、将来の変更を円滑に進めるための重要な基盤となります。

Go 1.3について

Go 1.3は、2014年6月にリリースされたGo言語のメジャーバージョンアップです。このリリースでは、ガベージコレクタの改善、スタック管理の変更(連続スタックから非連続スタックへの移行)、新しいアセンブラの導入など、ランタイムとコンパイラに多くの重要な変更が加えられました。これらの変更の中には、オブジェクトファイルのフォーマットに影響を与える可能性のあるものも含まれており、本コミットはそのような変更に備えるためのものでした。

技術的詳細

このコミットは、Goのオブジェクトファイルフォーマットに1バイトのバージョン番号フィールドを追加します。このバージョン番号は、マジックヘッダ(\x00\x00go13ld)の直後に配置されます。

書き込み側 (src/liblink/objfile.c)

リンカがオブジェクトファイルを書き出す際、linkwriteobj関数内でマジックヘッダの直後にバージョン番号を書き込みます。

  • Bprint(b, "go13ld"); の直後に Bputc(b, 1); が追加されています。
  • これにより、オブジェクトファイルのヘッダは \x00\x00go13ld の後に 1 というバイトが続く形式になります。この 1 が現在のオブジェクトファイルフォーマットのバージョン番号を示します。

読み込み側 (src/liblink/objfile.c および src/pkg/debug/goobj/read.go)

リンカやデバッグツールがオブジェクトファイルを読み込む際、マジックヘッダを読み込んだ直後にバージョン番号をチェックします。

  • src/liblink/objfile.cldobjfile 関数:
    • マジックヘッダのチェック後、Bgetc(f) で次の1バイト(バージョン番号)を読み込みます。
    • 読み込んだバージョン番号が 1 でない場合、sysfatal を呼び出して致命的なエラーを発生させます。これは、リンカが予期しないバージョンのオブジェクトファイルを検出した場合に、それ以上処理を続行せずに終了するための安全機構です。
  • src/pkg/debug/goobj/read.goparseObject メソッド:
    • このパッケージは、Goのオブジェクトファイルを解析するためのGo言語側の実装です。
    • 同様に、マジックヘッダの読み込み後、r.readByte() でバージョン番号を読み込みます。
    • 読み込んだバージョン番号が 1 でない場合、r.error(errCorruptObject) を呼び出して、オブジェクトファイルが破損しているというエラーを返します。

テストデータの変更

src/cmd/link/testdata/Makefile が変更され、.6 ファイル(Goのオブジェクトファイル)を生成するためのルールが汎用化されました。これは、新しいバージョン番号の追加によって既存のテストデータが更新されるため、それらを再生成しやすくするためです。また、既存のバイナリテストデータファイル(autosection.6, autoweak.6, dead.6, hello.6, layout.6, pclntab.6)も、新しいバージョン番号が埋め込まれた形式に更新されています。link.hello.darwin.amd64のようなリンカの出力テストデータも、パスの変更に伴い更新されています。

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

src/liblink/objfile.c

--- a/src/liblink/objfile.c
+++ b/src/liblink/objfile.c
@@ -16,6 +16,7 @@
 // The file format is:
 //
 //	- magic header: "\x00\x00go13ld"
+//	- byte 1 - version number
 //	- sequence of strings giving dependencies (imported packages)
 //	- empty string (marks end of sequence)
 //	- sequence of defined symbols
@@ -248,7 +249,8 @@ linkwriteobj(Link *ctxt, Biobuf *b)
 	Bputc(b, 0);
 	Bputc(b, 0);
 	Bprint(b, "go13ld");
-	
+	Bputc(b, 1); // version
+
 	// Emit autolib.
 	for(h = ctxt->hist; h != nil; h = h->link)
 		if(h->offset < 0)
@@ -453,6 +455,8 @@ ldobjfile(Link *ctxt, Biobuf *f, char *pkg, int64 len, char *pn)
 	Bread(f, buf, sizeof buf);
 	if(memcmp(buf, startmagic, sizeof buf) != 0)
 		sysfatal("%s: invalid file start %x %x %x %x %x %x %x %x", pn, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
+	if((c = Bgetc(f)) != 1)
+		sysfatal("%s: invalid file version number %d", pn, c);
 
 	for(;;) {
 		lib = rdstring(f);

src/pkg/debug/goobj/read.go

--- a/src/pkg/debug/goobj/read.go
+++ b/src/pkg/debug/goobj/read.go
@@ -573,6 +573,11 @@ func (r *objReader) parseObject(prefix []byte) error {
 		return r.error(errCorruptObject)
 	}
 
+	b := r.readByte()
+	if b != 1 {
+		return r.error(errCorruptObject)
+	}
+
 	// Direct package dependencies.
 	for {
 		s := r.readString()

コアとなるコードの解説

src/liblink/objfile.c

  • linkwriteobj 関数:
    • Bputc(b, 1); // version の行が追加されました。これは、オブジェクトファイルを書き出す際に、マジックヘッダ go13ld の直後にバージョン番号 1 を1バイトとして書き込むことを意味します。この 1 は、この時点でのオブジェクトファイルフォーマットのバージョンを示します。
  • ldobjfile 関数:
    • if((c = Bgetc(f)) != 1) の行が追加されました。これは、オブジェクトファイルを読み込む際に、マジックヘッダの読み込みが成功した後、次の1バイトを読み込み(これがバージョン番号)、それが期待されるバージョン番号 1 と異なる場合に sysfatal を呼び出してエラー終了することを意味します。これにより、リンカは互換性のない古い、あるいは未来のバージョンのオブジェクトファイルを検出した場合に、安全に処理を中断できます。

src/pkg/debug/goobj/read.go

  • parseObject メソッド:
    • b := r.readByte() の行が追加されました。これは、Goのオブジェクトファイルを解析する際に、マジックヘッダの読み込み後、次の1バイト(バージョン番号)を読み込むことを意味します。
    • if b != 1 { return r.error(errCorruptObject) } の行が追加されました。これは、読み込んだバージョン番号が 1 でない場合に、オブジェクトファイルが破損しているというエラー(errCorruptObject)を返すことを意味します。このパッケージはデバッグツールなどで利用されるため、リンカのように致命的なエラーで終了するのではなく、エラーを返すことで呼び出し元が適切に処理できるようにしています。

これらの変更により、Goのオブジェクトファイルは、そのフォーマットのバージョン情報を内部に持つようになり、将来のフォーマット変更に対する柔軟性と互換性管理が向上しました。

関連リンク

参考にした情報源リンク

  • Go 1.3 Release Notes: https://go.dev/doc/go1.3 (Go 1.3の変更点に関する公式ドキュメント)
  • Goのオブジェクトファイルフォーマットに関する一般的な情報 (Goのドキュメントやソースコード内のコメント、関連する設計ドキュメントなど)
  • Goのリンカのソースコード (src/cmd/link, src/liblink)
  • Goのデバッグオブジェクトファイル読み込みパッケージのソースコード (src/pkg/debug/goobj)
  • Go言語のビルドシステムに関する一般的な知識