[インデックス 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ビットシステムではint
とlong
が32ビット、64ビットシステムではlong
が64ビットの場合など)。Plan 9のCコンパイラが、特定の型を期待するフォーマット指定子に対して、異なるサイズの引数が渡された場合に警告を発したり、未定義の動作を引き起こしたりすることが、このコミットの修正対象となっています。
snprint
関数
snprint
は、Plan 9のCライブラリで提供される関数で、Unix系のsnprintf
に相当します。指定されたバッファに、フォーマットされた文字列を書き込みます。バッファオーバーフローを防ぐために、書き込む最大バイト数を指定できる点が特徴です。
malloc
とwerrstr
malloc
: C言語で動的にメモリを確保するための標準ライブラリ関数です。werrstr
: Plan 9のシステムコールで、エラーメッセージを設定するために使用されます。Unix系のerrno
とstrerror
に似ていますが、よりPlan 9のファイルシステム中心の設計に統合されています。
技術的詳細
このコミットの主要な変更点は、libmach
内のCソースコードにおけるprintf
系関数のフォーマット指定子の修正と、readn
関数の引数に関する修正です。
フォーマット指定子の修正
多くの箇所で、%ld
、%lux
、%.8lux
がそれぞれ%d
、%ux
、%.8ux
に変更されています。
%ld
->%d
: これは、long
型として扱われていた変数が、実際にはint
型であるか、あるいはPlan 9の環境ではlong
とint
のサイズが同じ(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.c
のgsymoff
関数では、v-s.value
の出力に%llux
が維持されています。これは、v
とs.value
がuvlong
(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
変数: この文脈では、sym
はIMAGE_SYMBOL
のような構造体型(またはそのポインタ)であると推測されます。&sym
vssym
:- もし
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 *ph
がPhdr64 *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);
コアとなるコードの解説
上記の変更箇所は、このコミットの意図を明確に示しています。
-
src/libmach/8db.c
のフォーマット指定子変更:snprint(buf, sizeof(buf), "exception %ld", c);
からsnprint(buf, sizeof(buf), "exception %d", c);
への変更は、変数c
がint
型であり、long
型としてフォーマットする必要がないことを示しています。Plan 9のCコンパイラが%ld
に対して32ビットのint
を渡された場合に警告を出すか、あるいは誤った解釈をする可能性があったため、これを修正しています。bprint(ip, "%lux(SP)", offset);
からbprint(ip, "%ux(SP)", offset);
への変更も同様に、offset
変数がunsigned int
型であり、unsigned long
としてフォーマットする必要がないことを示しています。これは、アドレスオフセットが通常32ビットで十分な範囲に収まることを反映していると考えられます。
-
src/libmach/executable.c
のreadn
引数変更:if (readn(fd, &sym, sizeof(sym)) != sizeof(sym))
からif (readn(fd, sym, sizeof(sym)) != sizeof(sym))
への変更は、sym
が既にポインタ型として宣言されていることを示唆しています。もしsym
がstruct IMAGE_SYMBOL sym_var;
のように宣言された構造体変数であれば、そのアドレスを渡すために&sym_var
とするのがCの標準的な作法です。しかし、もしsym
がstruct IMAGE_SYMBOL *sym_ptr;
のように宣言されたポインタ変数であれば、そのポインタが指すメモリ領域に直接データを読み込むため、sym_ptr
を渡すのが正しいです。この変更は、sym
がポインタ型であるという前提に基づいた修正であり、Plan 9の特定のコンパイラやライブラリの挙動に合わせたバグ修正である可能性が高いです。
-
src/libmach/sym.c
のmalloc
エラーメッセージ変更:werrstr("can't malloc %ld bytes", fp->symsz);
からwerrstr("can't malloc %d bytes", fp->symsz);
への変更は、fp->symsz
がint
型であるか、またはその値がint
の範囲に収まるため、long
型としてフォーマットする必要がないことを示しています。これは、エラーメッセージの正確性を保ちつつ、潜在的なフォーマット不一致の警告を回避するための修正です。
これらの変更は、Goのlibmach
がPlan 9環境でより堅牢に、かつ正確に動作するための重要なステップであり、C言語における型とフォーマット指定子の厳密な一致の重要性を示しています。
関連リンク
- Go CL 5316059: https://golang.org/cl/5316059
- Plan 9 from Bell Labs: https://9p.io/plan9/
- Go言語の初期の歴史とPlan 9との関連性に関する情報源(例: Go at Google, The Evolution of Go)
参考にした情報源リンク
- C言語
printf
フォーマット指定子に関するドキュメント - Plan 9のC言語プログラミングに関する資料
- Go言語のソースコードリポジトリとコミット履歴
readn
システムコールに関するPlan 9のドキュメント (例:man 2 readn
on a Plan 9 system)malloc
およびwerrstr
に関するPlan 9のドキュメント