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

[インデックス 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はファイル操作を行う関数(例: BreadBwrite)を抽象化するための引数です。これにより、同じマクロ定義で読み込みと書き込みの両方に対応できます。
  • 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)を使って読み込みます。これにより、構造体全体のメモリレイアウトに依存することなく、各フィールドのデータが確実に読み込まれるようになります。

このアプローチは、以下の点で優れています。

  1. フィールドごとの読み込み: 構造体全体を一括で読み込むのではなく、各フィールドを個別に読み込むことで、コンパイラによる構造体のアライメントやパディングの影響を排除します。
  2. 明示的なサイズ指定: sizeof(h.field)を使用することで、各フィールドの正確なバイト数を指定して読み込みます。これにより、異なるプラットフォーム間でのフィールドサイズの解釈の差異をなくします。
  3. エラーチェック: 各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ファイルに以下の重要な変更が加えられています。

  1. 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))が読み込まれたかどうかをチェックします。いずれかの読み込みが失敗した場合(読み込まれたバイト数が期待値と異なる場合)、マクロ全体が真(エラー)を返します。これにより、アーカイブヘッダの各フィールドがプラットフォームに依存しない方法で確実に読み込まれるようになります。

  2. 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言語における構造体のアライメントとパディングに関する一般的な知識
  • バイトオーダー(エンディアン)に関する一般的な知識