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

[インデックス 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ライブラリの fprintfprintf に似ていますが、より効率的な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 : "");

ここで問題となるのは、snil ポインタである可能性があるにもかかわらず、s->name のように s を逆参照しようとしていた点です。もし snil であった場合、s->namenil ポインタ逆参照となり、セグメンテーション違反を引き起こします。

このコミットでは、この問題を解決するために、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 が常に有効なシンボルオブジェクトを指しているという前提に基づいています。しかし、何らかの理由で sNULL ポインタである場合、s->name にアクセスしようとすると、プログラムはクラッシュします。

このコミットでは、この脆弱性を修正するために、s->name の代わりに、putsymb 関数に既に引数として渡されている name 変数を使用するように変更しました。namechar * 型であり、呼び出し元によって有効な文字列ポインタが渡されることが期待されます。これにより、putsymb 関数は sNULL であっても安全にシンボル名を出力できるようになり、セグメンテーション違反が回避されます。

この変更は、関数の引数の役割を明確にし、より堅牢なプログラミングプラクティスに従うものです。

関連リンク

  • Go言語のリンカに関する公式ドキュメントやソースコード:
    • Go Source Code - cmd/link (Go 1.0時代のリンカは cmd/ld にありましたが、現在は cmd/link に統合されています。しかし、基本的な概念は共通しています。)
  • Go言語のコードレビューシステム (Gerrit):

参考にした情報源リンク

  • Go言語のリンカの内部構造に関する一般的な情報源 (Goのバージョンによって詳細は異なる可能性がありますが、基本的な概念は共通しています):
  • セグメンテーション違反に関する一般的なプログラミング知識:
  • C言語におけるポインタとNULLポインタの概念。
  • Go言語の初期のツールチェインの構造に関する情報。