[インデックス 15964] ファイルの概要
このコミットは、libmach
ライブラリにおけるシンボルテーブルの境界処理に関するバグ修正です。具体的には、新しいシンボルテーブル形式において、fp->symsz
がヘッダサイズを含んでいたために、シンボルテーブルの末尾を超えてデータを読み込んでしまい、PC/ラインテーブル内のゴミデータを誤ってシンボルとしてデコードしてしまう問題を解決します。
コミット
commit d1eb9c8e0d4a9903c3b94e41aacc73117cc400e6
Author: Anthony Martin <ality@pbrane.org>
Date: Wed Mar 27 05:59:06 2013 -0700
libmach: respect symbol table boundaries
Since fp->symsz includes the size of the header
in the new symbol table format, we were reading
past the end and decoding a few garbage symbols
from data in the pc/line table.
R=rsc, r
CC=golang-dev
https://golang.org/cl/7993043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d1eb9c8e0d4a9903c3b94e41aacc73117cc400e6
元コミット内容
libmach: respect symbol table boundaries
Since fp->symsz includes the size of the header
in the new symbol table format, we were reading
past the end and decoding a few garbage symbols
from data in the pc/line table.
変更の背景
この変更は、Go言語のツールチェインの一部であるlibmach
ライブラリが、新しいシンボルテーブル形式を処理する際に発生していたバグを修正するために行われました。問題は、シンボルテーブルのサイズを示すfp->symsz
というフィールドが、シンボルデータだけでなく、シンボルテーブル自体のヘッダのサイズも誤って含んでいたことにありました。
この誤ったサイズ情報が原因で、libmach
はシンボルテーブルの実際の終端を超えてデータを読み込んでいました。その結果、プログラムの実行アドレスとソースコードの行番号をマッピングするPC/ラインテーブル(pc/line table
)に含まれる無関係なデータが、あたかも有効なシンボルであるかのように誤って解釈され、デコードされてしまうという不具合が発生していました。これは、デバッグ情報やプロファイリング情報の正確性に影響を与える可能性のある重要な問題でした。
前提知識の解説
- シンボルテーブル (Symbol Table): プログラムのコンパイル時に生成されるデータ構造で、変数名、関数名、ラベルなどのシンボルと、それらがメモリ上のどこに配置されているか(アドレス)などの情報をマッピングします。デバッガやプロファイラが、実行中のプログラムの内部状態を人間が理解できる形で表示するために不可欠です。
- PC/ラインテーブル (PC/Line Table): プログラムカウンタ(PC、現在の実行命令のアドレス)とソースコードの行番号との対応付けを記録するテーブルです。これにより、デバッガは特定の実行アドレスがソースコードのどの行に対応するかを特定できます。
libmach
: Go言語のツールチェインの一部として、実行可能ファイルやオブジェクトファイルの機械語レベルの情報を解析するためのライブラリであると推測されます。シンボルテーブルやPC/ラインテーブルの読み込み・解析を担当していると考えられます。fp->symsz
:Fhdr
構造体(ファイルヘッダを表すと思われる)のメンバーで、シンボルテーブルのサイズを示すフィールドです。このコミットの文脈では、この値が実際のシンボルデータサイズに加えてヘッダサイズを含んでしまっていたことが問題でした。Biobuf
: Go言語の標準ライブラリには直接見当たらないため、このプロジェクト内で定義されたバッファリングI/Oのための構造体または型である可能性が高いです。ファイルからの効率的な読み込みを目的としていると考えられます。newformat
: 新しいシンボルテーブル形式が導入されたことを示すフラグです。Go言語の進化に伴い、内部データ構造が変更されることがあります。このコミットは、Go 1.1の新しいシンボルテーブル形式に対応するものです。
技術的詳細
このコミットの技術的な核心は、シンボルテーブルの読み込みループの境界条件を正しく設定することにあります。
元のコードでは、シンボルテーブルの読み込みループがfor(p = symbols; size < fp->symsz; p++, nsym++)
となっていました。ここでfp->symsz
は、新しいシンボルテーブル形式において、シンボルデータだけでなく、シンボルテーブルのヘッダサイズ(8バイト)も含まれていました。そのため、ループは実際のシンボルデータの終端を超えて、余分な8バイトを読み込もうとしていました。この余分な8バイトが、PC/ラインテーブルのデータと重なり、誤ってシンボルとして解釈されていました。
修正では、以下の変更が加えられました。
syminit
関数内に新しい変数symsz
が導入されました。この変数は、fp->symsz
の値を初期値として受け取ります。- 新しいシンボルテーブル形式(
newformat
が真の場合)の処理において、ヘッダサイズをsymsz
から減算するロジックが追加されました。if(memcmp(buf, "\\xfd\\xff\\xff\\xff\\x00\\x00\\x00", 7) == 0)
の条件分岐内で、Bseek(&b, fp->symoff+6, 0);
の後にsymsz -= 6;
が追加されています。これは、特定のヘッダ形式で6バイトのオフセットがある場合に対応しているようです。- さらに、
if(newformat)
ブロック内で、svalsz
(シンボル値のサイズ)が8バイトの場合にsymsz -= 8;
が追加されています。これは、新しい形式のシンボルテーブルヘッダが8バイトであることを示唆しています。
- シンボル読み込みループの条件が
size < fp->symsz
からsize < symsz
に変更されました。これにより、ループはヘッダサイズを差し引いた、純粋なシンボルデータのサイズに基づいて実行されるようになります。
この修正により、libmach
はシンボルテーブルの正しい境界内でデータを読み込むようになり、PC/ラインテーブルからのゴミデータの誤ったデコードが防止されます。
コアとなるコードの変更箇所
--- a/src/libmach/sym.c
+++ b/src/libmach/sym.c
@@ -109,7 +109,7 @@ int
syminit(int fd, Fhdr *fp)\n {\n \tSym *p;\n-\tint32 i, l, size;\n+\tint32 i, l, size, symsz;\n \tvlong vl;\n \tBiobuf b;\n \tint svalsz, newformat, shift;\n@@ -138,6 +138,7 @@ syminit(int fd, Fhdr *fp)\n \tmemset(buf, 0, sizeof buf);\n \tBread(&b, buf, sizeof buf);\n \tnewformat = 0;\n+\tsymsz = fp->symsz;\n \tif(memcmp(buf, \"\\xfd\\xff\\xff\\xff\\x00\\x00\\x00\", 7) == 0) {\n \t\tswav = leswav;\n \t\tswal = leswal;\n@@ -151,6 +152,7 @@ syminit(int fd, Fhdr *fp)\n \t\tswav = leswav;\n \t\tswal = leswal;\n \t\tBseek(&b, fp->symoff+6, 0);\n+\t\tsymsz -= 6;\n \t} else {\n \t\tBseek(&b, fp->symoff, 0);\n \t}\n@@ -161,11 +163,12 @@ syminit(int fd, Fhdr *fp)\n \t\t\twerrstr(\"invalid word size %d bytes\", svalsz);\n \t\t\treturn -1;\n \t\t}\n+\t\tsymsz -= 8;\n \t}\n \n \tnsym = 0;\n \tsize = 0;\n-\tfor(p = symbols; size < fp->symsz; p++, nsym++) {\n+\tfor(p = symbols; size < symsz; p++, nsym++) {\n \t\tif(newformat) {\n \t\t\t// Go 1.1 format. See comment at top of ../pkg/runtime/symtab.c.\n \t\t\tif(Bread(&b, &c, 1) != 1)\n```
## コアとなるコードの解説
変更は`src/libmach/sym.c`ファイル内の`syminit`関数に集中しています。この関数は、シンボルテーブルの初期化を担当しています。
1. **`symsz`変数の導入**:
```c
- int32 i, l, size;
+ int32 i, l, size, symsz;
```
`symsz`という新しい`int32`型の変数が追加されました。これは、シンボルテーブルの実際のサイズを保持するために使用されます。
2. **`symsz`の初期化**:
```c
newformat = 0;
+ symsz = fp->symsz;
```
`fp->symsz`の値が`symsz`にコピーされます。`fp->symsz`はファイルヘッダから読み取られたシンボルテーブルの全体サイズ(ヘッダ含む)です。
3. **ヘッダサイズの減算(特定の形式の場合)**:
```c
if(memcmp(buf, "\\xfd\\xff\\xff\\xff\\x00\\x00\\x00", 7) == 0) {
// ...
Bseek(&b, fp->symoff+6, 0);
+ symsz -= 6;
} else {
Bseek(&b, fp->symoff, 0);
}
```
特定のヘッダパターン(`\xfd\xff\xff\xff\x00\x00\x00`)に一致する場合、`symsz`から6バイトが減算されます。これは、この特定の形式のシンボルテーブルが6バイトのヘッダを持つことを示唆しています。
4. **ヘッダサイズの減算(新しい形式の場合)**:
```c
if(svalsz != 1 && svalsz != 2 && svalsz != 4 && svalsz != 8) {
werrstr("invalid word size %d bytes", svalsz);
return -1;
}
+ symsz -= 8;
}
```
`newformat`が真の場合(新しいシンボルテーブル形式)、`symsz`から8バイトが減算されます。これは、Go 1.1の新しいシンボルテーブル形式のヘッダサイズが8バイトであることを示しています。
5. **シンボル読み込みループの境界条件の修正**:
```c
nsym = 0;
size = 0;
- for(p = symbols; size < fp->symsz; p++, nsym++) {
+ for(p = symbols; size < symsz; p++, nsym++) {
```
最も重要な変更点です。シンボルを読み込むループの条件が`size < fp->symsz`から`size < symsz`に変更されました。これにより、ループはヘッダサイズが差し引かれた正しいシンボルデータの範囲内で実行されるようになり、シンボルテーブルの境界を超えて読み込むことがなくなります。
これらの変更により、`libmach`は新しいシンボルテーブル形式のヘッダサイズを正しく考慮し、シンボルテーブルの実際のデータ部分のみを処理するようになります。
## 関連リンク
* Go CL (Code Review) 7993043: [https://golang.org/cl/7993043](https://golang.org/cl/7993043)
## 参考にした情報源リンク
* GitHubコミットページ: [https://github.com/golang/go/commit/d1eb9c8e0d4a9903c3b94e41aacc73117cc400e6](https://github.com/golang/go/commit/d1eb9c8e0d4a9903c3b94e41aacc73117cc400e6)
* コミットメッセージと差分 (`commit_data/15964.txt`)
* 一般的なシンボルテーブルとPC/ラインテーブルの概念に関する知識
* Go言語の内部構造に関する一般的な知識(Go 1.1のシンボルテーブル形式に関する言及から)