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

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

このコミットは、Go言語のlibmachライブラリにおけるPlan 9ビルドに関する修正を目的としています。具体的には、printf系のフォーマット指定子の誤用や、構造体ポインタの扱いに関するバグを修正し、Plan 9環境でのGoツールの正確な動作を保証します。

コミット

commit 7f417d8d6679fa0d6e8fdfee232a043706a414a0
Author: Lucio De Re <lucio.dere@gmail.com>
Date:   Fri Nov 18 23:11:50 2011 -0500

    libmach: fix for Plan 9 build
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5316059

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

https://github.com/golang/go/commit/7f417d8d6679fa0d6e8fdfee232a043706a414a0

元コミット内容

libmach: fix for Plan 9 build

R=rsc
CC=golang-dev
https://golang.org/cl/5316059

変更の背景

このコミットの背景には、Go言語が初期からサポートしていたPlan 9オペレーティングシステムにおけるビルドと実行環境の整合性の問題があります。libmachは、Goのデバッガやプロファイラ、シンボル情報解析ツールなど、実行ファイルの低レベルな情報を扱うためのライブラリです。Plan 9は、Unixとは異なる独自のシステムコールやデータ型、ABI(Application Binary Interface)を持つため、一般的なUnix系システム向けに書かれたCコードがそのままでは動作しない場合があります。

特に、C言語のprintfファミリー関数におけるフォーマット指定子は、引数のデータ型と厳密に一致している必要があります。異なるプラットフォームでは、long型やint型のサイズが異なることがあり、これが原因で予期せぬ値の切り捨てや、メモリ上の不正な読み込みが発生し、プログラムのクラッシュや誤動作につながることがあります。

このコミットは、Plan 9環境でlibmachが正しく機能しない、あるいは警告を出すといった問題に対処するために行われました。具体的には、数値の出力フォーマットが、実際の変数の型とPlan 9のCコンパイラの解釈との間で不一致を起こしていた可能性が高いです。また、構造体へのポインタの渡し方にも潜在的な問題があったようです。

前提知識の解説

Plan 9

Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。Unixの設計思想をさらに推し進め、すべてのリソースをファイルとして表現し、ネットワーク透過性を重視しています。Go言語の開発者の一部(Rob Pike, Ken Thompsonなど)はPlan 9の開発にも深く関わっており、Go言語の設計思想にもPlan 9の影響が見られます。Plan 9は、Unixとは異なる独自のCコンパイラ(8c, 6c, 5cなど)やライブラリセットを使用しており、これが移植性の課題となることがあります。

libmach

libmachは、Go言語のツールチェインの一部として、実行ファイル(バイナリ)の機械語レベルの情報を解析するためのC言語ライブラリです。主に以下の機能を提供します。

  • シンボルテーブルの解析: 実行ファイル内の関数名、変数名、アドレスなどのシンボル情報を読み取ります。
  • 逆アセンブル: 機械語コードをアセンブリ言語に変換します。
  • デバッグ情報の処理: スタックトレースやレジスタ情報などを扱います。
  • 異なるアーキテクチャのサポート: 複数のCPUアーキテクチャ(x86, ARMなど)のバイナリを解析できます。

このライブラリは、Goのデバッガ(gdbとの連携など)、プロファイラ、そしてgo tool objdumpのようなコマンドの基盤となっています。

C言語のprintfフォーマット指定子

C言語のprintf関数は、可変個引数を取り、フォーマット文字列に基づいて引数を整形して出力します。この際、フォーマット指定子と引数の型が一致していることが非常に重要です。

  • %d: int型の符号付き10進数。
  • %u: unsigned int型の符号なし10進数。
  • %x / %X: unsigned int型の符号なし16進数。
  • %ld: long int型の符号付き10進数。
  • %lu: unsigned long int型の符号なし10進数。
  • %lx / %lX: unsigned long int型の符号なし16進数。
  • %lld: long long int型の符号付き10進数。
  • %llu: unsigned long long int型の符号なし10進数。
  • %llx / %llX: unsigned long long int型の符号なし16進数。
  • %p: ポインタのアドレス(通常は16進数)。

異なるシステムでは、int, long, long longのサイズが異なる場合があります(例: 32ビットシステムではintlongが32ビット、64ビットシステムではlongが64ビットの場合など)。Plan 9のCコンパイラが、特定の型を期待するフォーマット指定子に対して、異なるサイズの引数が渡された場合に警告を発したり、未定義の動作を引き起こしたりすることが、このコミットの修正対象となっています。

snprint関数

snprintは、Plan 9のCライブラリで提供される関数で、Unix系のsnprintfに相当します。指定されたバッファに、フォーマットされた文字列を書き込みます。バッファオーバーフローを防ぐために、書き込む最大バイト数を指定できる点が特徴です。

mallocwerrstr

  • malloc: C言語で動的にメモリを確保するための標準ライブラリ関数です。
  • werrstr: Plan 9のシステムコールで、エラーメッセージを設定するために使用されます。Unix系のerrnostrerrorに似ていますが、よりPlan 9のファイルシステム中心の設計に統合されています。

技術的詳細

このコミットの主要な変更点は、libmach内のCソースコードにおけるprintf系関数のフォーマット指定子の修正と、readn関数の引数に関する修正です。

フォーマット指定子の修正

多くの箇所で、%ld%lux%.8luxがそれぞれ%d%ux%.8uxに変更されています。

  • %ld -> %d: これは、long型として扱われていた変数が、実際にはint型であるか、あるいはPlan 9の環境ではlongintのサイズが同じ(32ビット)であり、%dで十分かつより適切であると判断されたことを示唆します。例えば、mallocの引数として渡されるサイズや、行番号、シンボル数などがこれに該当します。これらの値が64ビットを超えることは稀であり、32ビットのintで表現可能であれば、より一般的な%dを使用することで移植性や正確性が向上します。
  • %lux -> %ux: 同様に、unsigned long型として扱われていた変数が、実際にはunsigned int型であるか、または32ビットのunsigned intで表現可能である場合に修正されています。アドレスのオフセットや、浮動小数点数の内部表現の一部などがこれに該当します。特に、%#luxから%#uxへの変更は、16進数表現におけるlongからintへの変更を示します。
  • %lluxの維持: src/libmach/5db.cgsymoff関数では、v-s.valueの出力に%lluxが維持されています。これは、vs.valueuvlong(unsigned long long)型であり、その差が64ビットの範囲に収まる可能性があるため、long long unsigned intとして正しくフォーマットする必要があることを意味します。

これらの変更は、Plan 9のCコンパイラが、long型とint型のサイズに関して、他のシステムとは異なる厳密な解釈をしていたか、あるいは、コードが意図していた変数の実際の型とフォーマット指定子が一致していなかったというバグを修正するものです。これにより、数値の切り捨てや、不正なメモリ読み込みを防ぎ、デバッグ情報の正確な表示を保証します。

src/libmach/executable.cにおけるreadnの引数修正

pedotout関数内で、readn(fd, &sym, sizeof(sym))readn(fd, sym, sizeof(sym))に変更されています。

  • readn関数: Plan 9のシステムコールで、ファイルディスクリプタfdからnバイトを読み込み、バッファbufに格納します。そのシグネチャは通常 long readn(int fd, void *buf, long n) のようになります。
  • sym変数: この文脈では、symIMAGE_SYMBOLのような構造体型(またはそのポインタ)であると推測されます。
  • &sym vs sym:
    • もしsymが構造体変数(例: IMAGE_SYMBOL sym;)であれば、そのアドレスを渡すために&symとするのが正しいです。
    • もしsymが構造体へのポインタ変数(例: IMAGE_SYMBOL *sym;)であれば、そのポインタが指すメモリ領域に読み込むため、symを直接渡すのが正しいです。

元のコードが&symであったことから、symは構造体変数として宣言されていた可能性が高いです。しかし、変更後のreadn(fd, sym, sizeof(sym))が正しいとされていることから、symが実際には構造体へのポインタとして宣言されていたか、あるいはreadn関数がポインタを直接期待するような特殊なケースであった可能性が考えられます。

一般的なCの慣習では、構造体変数に読み込む場合は&symが正しいです。この変更が正しいとされるならば、symの宣言がポインタ型であったか、あるいはreadnの内部実装がポインタのポインタを期待しない(つまり、void *として受け取る際に、すでにポインタであるものを渡す)という特殊なケースだったのかもしれません。これは、Plan 9のCコンパイラやライブラリの特定の挙動に合わせた修正である可能性が高いです。

その他の変更

  • src/libmach/executable.cで、Phdr64 *phPhdr64 *ph, *pph;に変更され、setdataの呼び出しでph[id]の代わりにpphが使用されています。これは、コードの可読性向上や、特定の最適化、あるいはポインタ演算の明確化を目的としたマイナーなリファクタリングと考えられます。
  • hdrsize = 0;の削除や、textsize, datasize, bsssize, fp->txtaddr, fp->dataddrの明示的なゼロ初期化の追加は、コードの堅牢性を高め、未初期化変数の使用による潜在的なバグを防ぐためのものです。

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

src/libmach/8db.c (フォーマット指定子の変更例)

--- a/src/libmach/8db.c
+++ b/src/libmach/8db.c
@@ -125,7 +125,7 @@ i386excep(Map *map, Rgetter rget)\n 		if (memcmp(buf, machdata->bpinst, machdata->bpsize) == 0)\n 			return "breakpoint";\n 		}\n-		snprint(buf, sizeof(buf), "exception %ld", c);\n+		snprint(buf, sizeof(buf), "exception %d", c);\
 		return buf;\n 	} else\n 		return excname[c];
@@ -1971,7 +1971,7 @@ plocal(Instr *ip)\n 
 	offset = ip->disp;\n 	if (!findsym(ip->addr, CTEXT, &s) || !findlocal(&s, FRAMENAME, &s)) {\n-		bprint(ip, "%lux(SP)", offset);\n+		bprint(ip, "%ux(SP)", offset);\
 		return;\n 	}

src/libmach/executable.c (readn引数の変更例)

--- a/src/libmach/executable.c
+++ b/src/libmach/executable.c
@@ -1398,7 +1401,7 @@ pedotout(int fd, Fhdr *fp, ExecHdr *hp)\n 	seek(fd, leswal(fh.PointerToSymbolTable), 0);\n 	symtab = esymtab = 0;\n 	for (i=0; i<leswal(fh.NumberOfSymbols); i++) {\n-		if (readn(fd, &sym, sizeof(sym)) != sizeof(sym)) {\n+		if (readn(fd, sym, sizeof(sym)) != sizeof(sym)) {\
 			werrstr("crippled COFF symbol %d", i);\n 			return 0;\n 		}\

src/libmach/sym.c (mallocエラーメッセージの変更例)

--- a/src/libmach/sym.c
+++ b/src/libmach/sym.c
@@ -124,7 +124,7 @@ syminit(int fd, Fhdr *fp)\n 		/* minimum symbol record size = 4+1+2 bytes */\n 	symbols = malloc((fp->symsz/(4+1+2)+1)*sizeof(Sym));\n 	if(symbols == 0) {\n-		werrstr("can't malloc %ld bytes", fp->symsz);\n+		werrstr("can't malloc %d bytes", fp->symsz);\
 		return -1;\n 	}
 	Binit(&b, fd, OREAD);

コアとなるコードの解説

上記の変更箇所は、このコミットの意図を明確に示しています。

  1. src/libmach/8db.cのフォーマット指定子変更:

    • snprint(buf, sizeof(buf), "exception %ld", c); から snprint(buf, sizeof(buf), "exception %d", c); への変更は、変数cint型であり、long型としてフォーマットする必要がないことを示しています。Plan 9のCコンパイラが%ldに対して32ビットのintを渡された場合に警告を出すか、あるいは誤った解釈をする可能性があったため、これを修正しています。
    • bprint(ip, "%lux(SP)", offset); から bprint(ip, "%ux(SP)", offset); への変更も同様に、offset変数がunsigned int型であり、unsigned longとしてフォーマットする必要がないことを示しています。これは、アドレスオフセットが通常32ビットで十分な範囲に収まることを反映していると考えられます。
  2. src/libmach/executable.creadn引数変更:

    • if (readn(fd, &sym, sizeof(sym)) != sizeof(sym)) から if (readn(fd, sym, sizeof(sym)) != sizeof(sym)) への変更は、symが既にポインタ型として宣言されていることを示唆しています。もしsymstruct IMAGE_SYMBOL sym_var;のように宣言された構造体変数であれば、そのアドレスを渡すために&sym_varとするのがCの標準的な作法です。しかし、もしsymstruct IMAGE_SYMBOL *sym_ptr;のように宣言されたポインタ変数であれば、そのポインタが指すメモリ領域に直接データを読み込むため、sym_ptrを渡すのが正しいです。この変更は、symがポインタ型であるという前提に基づいた修正であり、Plan 9の特定のコンパイラやライブラリの挙動に合わせたバグ修正である可能性が高いです。
  3. src/libmach/sym.cmallocエラーメッセージ変更:

    • werrstr("can't malloc %ld bytes", fp->symsz); から werrstr("can't malloc %d bytes", fp->symsz); への変更は、fp->symszint型であるか、またはその値がintの範囲に収まるため、long型としてフォーマットする必要がないことを示しています。これは、エラーメッセージの正確性を保ちつつ、潜在的なフォーマット不一致の警告を回避するための修正です。

これらの変更は、GoのlibmachがPlan 9環境でより堅牢に、かつ正確に動作するための重要なステップであり、C言語における型とフォーマット指定子の厳密な一致の重要性を示しています。

関連リンク

参考にした情報源リンク

  • C言語 printf フォーマット指定子に関するドキュメント
  • Plan 9のC言語プログラミングに関する資料
  • Go言語のソースコードリポジトリとコミット履歴
  • readnシステムコールに関するPlan 9のドキュメント (例: man 2 readn on a Plan 9 system)
  • mallocおよびwerrstrに関するPlan 9のドキュメント