[インデックス 10271] ファイルの概要
このコミットは、Goコンパイラ(gc
)のlex.c
ファイルにおけるアーカイブヘッダの読み込み方法を改善するものです。具体的には、gopack
ツールで使用されるHEADER_IO
マクロを導入することで、異なるシステム(特にPlan 9 6cで生成されたアーカイブ)間でのアーカイブヘッダの読み込みにおける移植性の問題を解決しています。これにより、アーカイブファイルの処理がより堅牢になります。
コミット
commit 986ad31b2dad78d2238a426904fcf9fbf120d2c8
Author: Ron Minnich <rminnich@gmail.com>
Date: Mon Nov 7 11:42:13 2011 -0500
gc: use HEADER_IO macro from gopack
Use HEADER_IO macro from gopack to read archive header
The HEADER_IO macro portably reads archive headers. The
current arsize code fails in the case of archive headers produced
on plan 9 6c and read on other systems (it's not portable).
Modify lex.c to use the portable macro
Build tested (including tests) on OSX.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5323072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/986ad31b2dad78d2238a426904fcf9fbf120d2c8
元コミット内容
このコミットは、Goコンパイラ(gc
)がアーカイブヘッダを読み込む際に、gopack
で定義されているHEADER_IO
マクロを使用するように変更することを目的としています。既存のarsize
関数におけるアーカイブヘッダの処理コードは、Plan 9 6cシステムで生成されたアーカイブヘッダを他のシステムで読み込む際に問題が発生し、移植性が低いという課題がありました。この変更により、lex.c
ファイルがHEADER_IO
マクロを利用することで、アーカイブヘッダの読み込みがより移植性の高い方法で行われるようになります。この変更はOSX上でビルドおよびテストが実施され、問題がないことが確認されています。
変更の背景
この変更の主な背景は、Goコンパイラがアーカイブファイルを処理する際の移植性の問題にありました。特に、Plan 9 6cという特定の環境で生成されたアーカイブファイルのヘッダが、他のオペレーティングシステム(例えばOSXなど)で正しく読み込めないという問題が報告されていました。
アーカイブファイル(特にUnix系のar
形式アーカイブ)のヘッダは、ファイル名、サイズ、タイムスタンプ、パーミッションなどのメタデータを含んでいます。これらの情報は、通常、固定長フィールドとして格納されますが、異なるシステムやコンパイラの実装によっては、バイトオーダー(エンディアン)や構造体のアライメント、あるいは文字列の終端処理などの細かな差異が生じることがあります。
既存のarsize
関数は、これらの差異を吸収できず、Plan 9 6cで生成されたアーカイブヘッダを非移植的な方法で読み込んでいたため、互換性の問題を引き起こしていました。この問題を解決し、Goコンパイラがより広範な環境で生成されたアーカイブファイルを確実に処理できるようにするために、gopack
で既に実績のある移植性の高いHEADER_IO
マクロを導入する必要がありました。このマクロは、アーカイブヘッダの各フィールドをバイト単位で確実に読み込むことで、プラットフォーム間の差異を吸収し、移植性を確保します。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
- Goコンパイラ (
gc
): Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。src/cmd/gc
ディレクトリにそのコードが存在します。 gopack
: Go言語のパッケージ管理ツールの一つで、Goのライブラリや実行可能ファイルをアーカイブ(.a
ファイル)としてまとめる際に使用されます。このツールは、アーカイブヘッダの読み書きに関する移植性の高いメカニズムを内部的に持っています。- アーカイブヘッダ (
ar_hdr
): Unix系のシステムで広く使われるar
(archiver)形式のアーカイブファイルは、複数のファイルを一つにまとめるための形式です。各ファイルは、その内容の前に「アーカイブヘッダ」と呼ばれるメタデータブロックを持ちます。このヘッダには、ファイル名、最終更新日時、所有者ID、グループID、パーミッション、ファイルサイズなどの情報が含まれます。struct ar_hdr
は、このヘッダの構造を定義したものです。 - 移植性 (Portability): ソフトウェアが異なるハードウェアアーキテクチャ、オペレーティングシステム、またはコンパイラ環境で、変更なしに、または最小限の変更で正しく動作する能力を指します。このコミットでは、特にバイトオーダーやデータ構造のレイアウトの違いが問題となっていました。
- Plan 9: ベル研究所で開発された分散オペレーティングシステムです。Go言語の開発者の一部はPlan 9の設計思想に影響を受けており、GoのツールチェインにはPlan 9の概念が取り入れられている部分があります。
6c
はPlan 9のCコンパイラの一つです。 Biobuf
: Goコンパイラの内部で使用されるバッファリングされたI/O構造体です。ファイルからの読み込みを効率的に行うために使用されます。Brdline
/Blinelen
:Biobuf
に関連する関数で、それぞれバッファから一行を読み込む、読み込んだ行の長さを取得する、といった機能を提供します。これらの関数は、このコミットでHEADER_IO
マクロに置き換えられます。cmd
(マクロ引数):HEADER_IO
マクロの定義において、cmd
はファイル操作を行う関数(例:Bread
、Bwrite
)を抽象化するための引数です。これにより、同じマクロ定義で読み込みと書き込みの両方に対応できます。Bread
:Biobuf
から指定されたバイト数を読み込む関数です。
技術的詳細
このコミットの核心は、アーカイブヘッダの読み込みにおける「移植性」の確保です。従来のarsize
関数は、Brdline
を使ってアーカイブヘッダ全体を一度に読み込み、その長さをsizeof(struct ar_hdr)
と比較していました。しかし、struct ar_hdr
の具体的なメモリレイアウトは、コンパイラやプラットフォームによって微妙に異なる可能性があります(例えば、パディングの有無やバイトオーダー)。Plan 9 6cで生成されたアーカイブヘッダが他のシステムで問題を起こしたのは、まさにこのレイアウトの差異が原因でした。
HEADER_IO
マクロは、この問題を解決するために導入されました。このマクロは、アーカイブヘッダの各フィールド(name
, date
, uid
, gid
, mode
, size
, fmag
)を個別に、かつそのフィールドの正確なサイズ分だけ読み込むように設計されています。例えば、cmd(f, h.name, sizeof(h.name))
という形式で、h.name
フィールドをsizeof(h.name)
バイト分だけファイルf
からcmd
関数(この場合はBread
)を使って読み込みます。これにより、構造体全体のメモリレイアウトに依存することなく、各フィールドのデータが確実に読み込まれるようになります。
このアプローチは、以下の点で優れています。
- フィールドごとの読み込み: 構造体全体を一括で読み込むのではなく、各フィールドを個別に読み込むことで、コンパイラによる構造体のアライメントやパディングの影響を排除します。
- 明示的なサイズ指定:
sizeof(h.field)
を使用することで、各フィールドの正確なバイト数を指定して読み込みます。これにより、異なるプラットフォーム間でのフィールドサイズの解釈の差異をなくします。 - エラーチェック: 各
cmd
呼び出しの結果が期待されるバイト数と一致するかどうかをチェックしています。これにより、部分的な読み込みや読み込みエラーを早期に検出できます。
lex.c
はGoコンパイラの字句解析器(lexer)の一部であり、コンパイルプロセスにおいてソースコードやライブラリファイルの内容を読み込み、トークンに分解する役割を担っています。この文脈で、アーカイブファイルからシンボル情報などを読み込む際にarsize
関数が使用され、その中でアーカイブヘッダの処理が必要となります。HEADER_IO
マクロの導入は、この低レベルなファイルI/Oの堅牢性を高めることで、コンパイラ全体の安定性と移植性に貢献します。
Web検索の結果が示すように、HEADER_IO
マクロはGo言語の標準ライブラリや一般的なAPIとして公開されているものではなく、Goツールチェインの内部実装、特に低レベルなアセンブリコードやコンパイラ関連のコードで使用される可能性のある、内部的な詳細であると考えられます。これは、GoのコンパイラがC言語で書かれていた時代の名残であり、Go言語自体が提供する高レベルなI/O機能とは異なる文脈で利用されています。
コアとなるコードの変更箇所
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -380,18 +380,30 @@ saveerrors(void)\n \tnerrors = 0;\n }\n \n+/*\n+ *\tmacro to portably read/write archive header.\n+ *\t'cmd' is read/write/Bread/Bwrite, etc.\n+ */\n+#define\tHEADER_IO(cmd, f, h)\tcmd(f, h.name, sizeof(h.name)) != sizeof(h.name)\\\n+\t\t\t\t|| cmd(f, h.date, sizeof(h.date)) != sizeof(h.date)\\\n+\t\t\t\t|| cmd(f, h.uid, sizeof(h.uid)) != sizeof(h.uid)\\\n+\t\t\t\t|| cmd(f, h.gid, sizeof(h.gid)) != sizeof(h.gid)\\\n+\t\t\t\t|| cmd(f, h.mode, sizeof(h.mode)) != sizeof(h.mode)\\\n+\t\t\t\t|| cmd(f, h.size, sizeof(h.size)) != sizeof(h.size)\\\n+\t\t\t\t|| cmd(f, h.fmag, sizeof(h.fmag)) != sizeof(h.fmag)\n+\n static int\n arsize(Biobuf *b, char *name)\n {\n-\tstruct ar_hdr *a;\n+\tstruct ar_hdr a;\n \n-\tif((a = Brdline(b, '\\n')) == nil)\n+\tif (HEADER_IO(Bread, b, a))\n \t\treturn -1;\n-\tif(Blinelen(b) != sizeof(struct ar_hdr))\n-\t\treturn -1;\n-\tif(strncmp(a->name, name, strlen(name)) != 0)\n+\n+\tif(strncmp(a.name, name, strlen(name)) != 0)\n \t\treturn -1;\n-\treturn atoi(a->size);\n+\n+\treturn atoi(a.size);\n }\n \n static int
コアとなるコードの解説
このコミットでは、src/cmd/gc/lex.c
ファイルに以下の重要な変更が加えられています。
-
HEADER_IO
マクロの定義の追加:#define HEADER_IO(cmd, f, h) cmd(f, h.name, sizeof(h.name)) != sizeof(h.name)\ || cmd(f, h.date, sizeof(h.date)) != sizeof(h.date)\ || cmd(f, h.uid, sizeof(h.uid)) != sizeof(h.uid)\ || cmd(f, h.gid, sizeof(h.gid)) != sizeof(h.gid)\ || cmd(f, h.mode, sizeof(h.mode)) != sizeof(h.mode)\ || cmd(f, h.size, sizeof(h.size)) != sizeof(h.size)\ || cmd(f, h.fmag, sizeof(h.fmag)) != sizeof(h.fmag)
このマクロは、アーカイブヘッダ
h
の各フィールド(name
,date
,uid
,gid
,mode
,size
,fmag
)を、指定されたコマンドcmd
(例:Bread
)を使ってファイルf
から個別に読み込むためのものです。各cmd
呼び出しは、読み込みが成功し、期待されるバイト数(sizeof(h.field)
)が読み込まれたかどうかをチェックします。いずれかの読み込みが失敗した場合(読み込まれたバイト数が期待値と異なる場合)、マクロ全体が真(エラー)を返します。これにより、アーカイブヘッダの各フィールドがプラットフォームに依存しない方法で確実に読み込まれるようになります。 -
arsize
関数の変更:-
struct ar_hdr
の宣言変更:- struct ar_hdr *a; + struct ar_hdr a;
以前は
struct ar_hdr
へのポインタa
を宣言していましたが、変更後はスタック上にstruct ar_hdr
型の変数a
を直接宣言しています。これにより、Brdline
で読み込んだメモリ領域に依存するのではなく、HEADER_IO
マクロが直接この構造体のフィールドに書き込むことができるようになります。 -
アーカイブヘッダ読み込みロジックの変更:
- if((a = Brdline(b, '\n')) == nil) + if (HEADER_IO(Bread, b, a)) return -1; - if(Blinelen(b) != sizeof(struct ar_hdr)) - return -1;
従来のコードでは、
Brdline
を使ってバッファb
から改行文字までを一行として読み込み、その結果をa
に代入していました。その後、Blinelen
で読み込んだ行の長さがsizeof(struct ar_hdr)
と一致するかをチェックしていました。この方法は、前述の通り、プラットフォーム間の構造体レイアウトの差異に脆弱でした。 新しいコードでは、HEADER_IO(Bread, b, a)
を呼び出しています。これは、定義されたHEADER_IO
マクロがBread
関数を使ってb
からa
の各フィールドを移植性のある方法で読み込むことを意味します。もしHEADER_IO
が真を返した場合(つまり、いずれかのフィールドの読み込みに失敗した場合)、関数は-1
を返してエラーを示します。これにより、より堅牢で移植性の高いヘッダ読み込みが実現されます。 -
フィールドアクセス方法の変更:
- if(strncmp(a->name, name, strlen(name)) != 0) + if(strncmp(a.name, name, strlen(name)) != 0) return -1; // ... - return atoi(a->size); + return atoi(a.size);
a
がポインタから直接の構造体変数になったため、フィールドへのアクセスもa->name
からa.name
へと変更されています。
-
これらの変更により、arsize
関数は、アーカイブヘッダの読み込みにおいて、特定のプラットフォームの構造体レイアウトに依存することなく、各フィールドを確実に読み込むことができるようになりました。これは、Goコンパイラが様々な環境で生成されたアーカイブファイルを正しく処理するために不可欠な改善です。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/5323072
参考にした情報源リンク
- Google Web Search: "golang gopack HEADER_IO macro" (この検索結果は、
HEADER_IO
マクロがGoの一般的なAPIではなく、Goツールチェインの内部実装の詳細である可能性が高いことを示唆しています。) - Go言語のソースコード(特に
src/cmd/gc
およびsrc/cmd/pack
ディレクトリ内の関連ファイル) - Unix
ar
アーカイブ形式に関する一般的なドキュメント - C言語における構造体のアライメントとパディングに関する一般的な知識
- バイトオーダー(エンディアン)に関する一般的な知識