[インデックス 15006] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) におけるシンボルテーブルのダンプ処理中に発生するセグメンテーション違反(セグフォールト)を回避するための修正です。具体的には、シンボル名を取得する際に、Sym
構造体へのポインタが nil
である可能性を考慮していなかったバグを修正しています。
コミット
commit 1c6b6b125eea1de587734ea7b16c05831e91ecf9
Author: Carl Shapiro <cshapiro@google.com>
Date: Mon Jan 28 15:47:25 2013 -0800
cmd/ld: avoid a segfault when dumping the symbol table
The dumping routine incorrectly assumed that all incoming
symbols would be non-nil and load through it to retrieve the
symbol name. Instead of using the symbol to retrieve a name,
use the name provided by the caller.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7224043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1c6b6b125eea1de587734ea7b16c05831e91ecf9
元コミット内容
Go言語のリンカ (cmd/ld
) のシンボルテーブルダンプルーチンにおいて、シンボル名を取得する際にセグメンテーション違反が発生する可能性があった。これは、ダンプルーチンが入力されるすべてのシンボルが nil
ではないと誤って仮定し、Sym
構造体を通じてシンボル名を取得しようとしていたためである。このコミットでは、シンボルから名前を取得する代わりに、呼び出し元から提供された名前を使用するように修正している。
変更の背景
Go言語のリンカは、コンパイルされたオブジェクトファイルを結合し、実行可能ファイルを生成する役割を担っています。この過程で、プログラム内の関数、変数、型などの情報を管理するために「シンボルテーブル」が使用されます。シンボルテーブルをダンプ(出力)する機能は、デバッグやリンカの動作解析において非常に重要です。
このコミットが修正しようとしている問題は、シンボルテーブルをダンプする際に、特定の条件下でリンカがクラッシュ(セグメンテーション違反)するというものでした。これは、ダンプ処理を行うコードが、常に有効な Sym
(シンボル)オブジェクトへのポインタを受け取ると仮定していたことに起因します。しかし、実際には nil
ポインタが渡される可能性があり、その nil
ポインタを介してシンボル名 (s->name
) にアクセスしようとすると、未定義のメモリ領域にアクセスすることになり、結果としてセグメンテーション違反が発生していました。
この種のバグは、プログラムの安定性を著しく損なうため、早期の修正が求められます。特にリンカのような低レベルのツールでは、予期せぬクラッシュは開発プロセス全体に影響を与える可能性があります。
前提知識の解説
1. リンカ (cmd/ld
)
Go言語のツールチェインの一部であり、コンパイルされたGoのソースコード(オブジェクトファイル)を結合して、実行可能なバイナリを生成するプログラムです。リンカは、異なるオブジェクトファイル間で参照されるシンボル(関数名、変数名など)を解決し、最終的な実行可能ファイルにそれらのアドレスを割り当てます。
2. シンボルテーブル
プログラム内のシンボル(関数、変数、型など)とその関連情報(アドレス、サイズ、型など)をマッピングするデータ構造です。コンパイラはソースコードをコンパイルする際にシンボルテーブルを生成し、リンカはこのテーブルを利用して異なるモジュール間の参照を解決します。
3. セグメンテーション違反 (Segmentation Fault / Segfault)
プログラムがアクセスを許可されていないメモリ領域にアクセスしようとしたときに発生するエラーです。これは通常、無効なポインタ(nil
ポインタや解放済みのメモリへのポインタなど)を逆参照しようとした場合に起こります。オペレーティングシステムはこのような不正なメモリアクセスを検出し、プログラムを強制終了させます。
4. Sym
構造体
Goリンカの内部でシンボル情報を表現するために使用されるC言語の構造体です。この構造体には、シンボルの名前 (name
)、アドレス、サイズ、型などの情報が含まれています。
5. Bprint
関数
Goのリンカ内部で使用されるバッファリングされた出力関数です。標準Cライブラリの fprintf
や printf
に似ていますが、より効率的なI/Oのためにバッファリングを行います。
技術的詳細
このコミットの核心は、src/cmd/ld/symtab.c
ファイル内の putsymb
関数におけるシンボル名の取得方法の変更です。
putsymb
関数は、シンボルテーブルのエントリを整形して出力する役割を担っています。この関数は、Sym *s
(シンボルへのポインタ)と char *name
(シンボル名文字列)という2つの引数を受け取ります。
修正前のコードでは、Bprint
関数を呼び出す際に、シンボル名として s->name
を直接使用していました。
// 修正前
Bprint(&bso, "%c %.8llux %s<%d> %s\n", t, v, s->name, ver, typ ? typ->name : "");
Bprint(&bso, "%c %.8llux %s %s\n", t, v, s->name, typ ? typ->name : "");
ここで問題となるのは、s
が nil
ポインタである可能性があるにもかかわらず、s->name
のように s
を逆参照しようとしていた点です。もし s
が nil
であった場合、s->name
は nil
ポインタ逆参照となり、セグメンテーション違反を引き起こします。
このコミットでは、この問題を解決するために、s->name
の代わりに、putsymb
関数の引数として既に渡されている name
を使用するように変更しました。
// 修正後
Bprint(&bso, "%c %.8llux %s<%d> %s\n", t, v, name, ver, typ ? typ->name : "");
Bprint(&bso, "%c %.8llux %s %s\n", t, v, name, typ ? typ->name : "");
これにより、putsymb
関数がシンボル名を出力する際に、Sym
ポインタ s
の有効性に依存することなく、常に有効な name
文字列ポインタを使用できるようになります。これは、関数が受け取る引数の意味を正しく解釈し、より堅牢なコードにするための重要な変更です。
この修正は、リンカの安定性を向上させ、シンボルテーブルのダンプ処理中に発生する可能性のあるクラッシュを防ぎます。
コアとなるコードの変更箇所
変更は src/cmd/ld/symtab.c
ファイルの putsymb
関数内で行われています。
--- a/src/cmd/ld/symtab.c
+++ b/src/cmd/ld/symtab.c
@@ -367,9 +367,9 @@ putsymb(Sym *s, char *name, int t, vlong v, vlong size, int ver, Sym *typ)
return;
}
if(ver)
- Bprint(&bso, "%c %.8llux %s<%d> %s\n", t, v, s->name, ver, typ ? typ->name : "");
+ Bprint(&bso, "%c %.8llux %s<%d> %s\n", t, v, name, ver, typ ? typ->name : "");
else
- Bprint(&bso, "%c %.8llux %s %s\n", t, v, s->name, typ ? typ->name : "");
+ Bprint(&bso, "%c %.8llux %s %s\n", t, v, name, ver, typ ? typ->name : "");
}
コアとなるコードの解説
putsymb
関数は、シンボル s
、その名前 name
、タイプ t
、値 v
、サイズ size
、バージョン ver
、そして型シンボル typ
を引数として受け取ります。
変更前のコードでは、Bprint
関数への引数として、シンボルの名前を s->name
から取得していました。これは、Sym *s
が常に有効なシンボルオブジェクトを指しているという前提に基づいています。しかし、何らかの理由で s
が NULL
ポインタである場合、s->name
にアクセスしようとすると、プログラムはクラッシュします。
このコミットでは、この脆弱性を修正するために、s->name
の代わりに、putsymb
関数に既に引数として渡されている name
変数を使用するように変更しました。name
は char *
型であり、呼び出し元によって有効な文字列ポインタが渡されることが期待されます。これにより、putsymb
関数は s
が NULL
であっても安全にシンボル名を出力できるようになり、セグメンテーション違反が回避されます。
この変更は、関数の引数の役割を明確にし、より堅牢なプログラミングプラクティスに従うものです。
関連リンク
- Go言語のリンカに関する公式ドキュメントやソースコード:
- Go Source Code - cmd/link (Go 1.0時代のリンカは
cmd/ld
にありましたが、現在はcmd/link
に統合されています。しかし、基本的な概念は共通しています。)
- Go Source Code - cmd/link (Go 1.0時代のリンカは
- Go言語のコードレビューシステム (Gerrit):
- https://golang.org/cl/7224043 (このコミットに対応する変更リスト)
参考にした情報源リンク
- Go言語のリンカの内部構造に関する一般的な情報源 (Goのバージョンによって詳細は異なる可能性がありますが、基本的な概念は共通しています):
- "Go's Linker" by Russ Cox: https://research.swtch.com/go-linker (Goリンカの動作原理に関する詳細な解説)
- セグメンテーション違反に関する一般的なプログラミング知識:
- Wikipedia: セグメンテーション違反
- C言語におけるポインタとNULLポインタの概念。
- Go言語の初期のツールチェインの構造に関する情報。