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

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

このコミットは、Go言語のデバッグツールキットの一部である debug/elf パッケージにおける、GNUバージョンシンボルのオフセット計算のバグ修正に関するものです。具体的には、シンボルテーブルを読み込む際に最初の要素をスキップしなくなった変更に伴い、GNUバージョンシンボルの処理におけるオフセットのずれを修正し、Linux上でのビルド問題を解決することを目的としています。

コミット

commit 8c96e6d7404f27f473f97dcef8b9fc1f7f7161da
Author: Joel Sing <jsing@google.com>
Date:   Thu Nov 15 03:36:19 2012 +1100

    debug/elf: fix offset for GNU version symbols
    
    Since we no longer skip the first entry when reading a symbol table,
    we no longer need to allow for the offset difference when processing
    the GNU version symbols.
    
    Unbreaks builds on Linux.
    
    R=golang-dev, agl, iant
    CC=golang-dev
    https://golang.org/cl/6843057
---\n src/pkg/debug/elf/file.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)\n
diff --git a/src/pkg/debug/elf/file.go b/src/pkg/debug/elf/file.go
index 25b04d7959..b030b043df 100644
--- a/src/pkg/debug/elf/file.go
+++ b/src/pkg/debug/elf/file.go
@@ -700,8 +700,8 @@ func (f *File) gnuVersionInit(str []byte) {\n // gnuVersion adds Library and Version information to sym,\n // which came from offset i of the symbol table.\n func (f *File) gnuVersion(i int, sym *ImportedSymbol) {\n-\t// Each entry is two bytes; skip undef entry at beginning.\n-\ti = (i + 1) * 2\n+\t// Each entry is two bytes.\n+\ti = i * 2\n \tif i >= len(f.gnuVersym) {\n \t\treturn\n \t}\n```

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

[https://github.com/golang/go/commit/8c96e6d7404f27f473f97dcef8b9fc1f7f7161da](https://github.com/golang/go/commit/8c96e6d7404f27f473f97dcef8b9fc1f7f7161da)

## 元コミット内容

このコミットは、`debug/elf` パッケージにおけるGNUバージョンシンボルのオフセット計算の修正を目的としています。以前はシンボルテーブルを読み込む際に最初の要素をスキップしていたため、GNUバージョンシンボルを処理する際にオフセットの調整が必要でした。しかし、そのスキップが不要になったため、オフセットの調整も不要となり、その結果としてLinux上でのビルドが正常に行われるようになりました。

## 変更の背景

この変更の背景には、`debug/elf` パッケージがシンボルテーブルを読み込む内部ロジックの変更があります。コミットメッセージにある「Since we no longer skip the first entry when reading a symbol table」という記述から、以前はシンボルテーブルの最初の要素(通常は未定義シンボルなど、特殊な意味を持つエントリ)を意図的にスキップして処理していたことが伺えます。

このスキップ処理が何らかの理由で不要になった、あるいは別の方法で処理されるようになった結果、GNUバージョンシンボルを扱う `gnuVersion` 関数内で使用されていたオフセット計算 `(i + 1) * 2` が誤った値を指すようになりました。この誤ったオフセット計算が原因で、Linux環境でのGoのビルドが失敗するという問題が発生していました。このコミットは、このビルド問題を解決するために、オフセット計算を `i * 2` に修正するものです。

## 前提知識の解説

### ELF (Executable and Linkable Format)

ELFは、Unix系オペレーティングシステム(Linux、Solaris、FreeBSDなど)で広く使用されている、実行可能ファイル、オブジェクトコード、共有ライブラリ、コアダンプの標準ファイルフォーマットです。その柔軟性と拡張性から、様々なアーキテクチャやオペレーティングシステムに対応しています。

ELFファイルは、プログラムのコンパイル、リンク、実行において中心的な役割を果たします。主要な構成要素は以下の通りです。

*   **セクション (Sections)**: ELFファイルは複数のセクションに分割されます。これには、実行可能なコードを含む`.text`セクション、初期化されたデータを含む`.data`セクション、初期化されていないデータを含む`.bss`セクション、そしてシンボル情報を含む`.symtab`(シンボルテーブル)セクションなどがあります。
*   **セグメント (Segments)**: 実行時には、セクションはセグメントにグループ化されます。セグメントは、ファイルのどの部分がメモリにどのようにマッピングされるかを記述します。
*   **プログラムヘッダテーブル (Program Header Table)**: ファイルのセグメントがメモリにどのようにロードされるかを記述します。
*   **セクションヘッダテーブル (Section Header Table)**: ファイル内の様々なセクションを記述します。

### シンボルテーブル (Symbol Table)

シンボルテーブルは、ELFファイル内に含まれる重要なデータ構造の一つで、プログラム内のシンボル(変数名、関数名など)とそのアドレスや型などの情報が格納されています。リンカやデバッガは、このシンボルテーブルを利用して、プログラム内の特定の場所を特定したり、デバッグ情報を提供したりします。

### GNUバージョンシンボル (GNU Version Symbols)

GNUバージョンシンボルは、主にGNUツールチェイン(GCC、Binutilsなど)によって生成されるELFファイルで使用される拡張機能です。これは、共有ライブラリのシンボルにバージョン情報(例: `GLIBC_2.2.5`)を付加するためのメカニニズムです。これにより、同じ名前のシンボルが異なるバージョンのライブラリで異なる実装を持つ場合に、互換性を維持しながら複数のバージョンを共存させることができます。

GNUバージョンシンボルは、通常、`.gnu.version`セクションや`.gnu.version_r`セクションに格納され、シンボルテーブルのエントリと関連付けられます。`debug/elf`パッケージは、これらのバージョン情報を解析し、デバッグや分析のために利用します。

## 技術的詳細

このコミットが修正している問題は、`debug/elf`パッケージがELFファイルのシンボルテーブルからGNUバージョンシンボルを読み取る際のオフセット計算の誤りです。

`gnuVersion`関数は、シンボルテーブル内の特定のインデックス `i` に対応するシンボルに対して、ライブラリとバージョン情報を追加する役割を担っています。GNUバージョンシンボルは、通常、シンボルテーブルのエントリごとに2バイトのデータとして格納されています。

以前のコードでは、`i = (i + 1) * 2` という計算が行われていました。この `+ 1` は、シンボルテーブルの最初の要素(通常は未定義シンボル)をスキップするという前提に基づいていました。つまり、実際のバージョンシンボルデータは、シンボルテーブルの論理的なインデックス `i` よりも1つ後の位置から始まると仮定されていたのです。

しかし、Goの `debug/elf` パッケージの内部的な変更により、「シンボルテーブルを読み込む際に最初の要素をスキップしなくなった」ため、この `+ 1` のオフセット調整が不要になりました。シンボルテーブルの読み込みロジックが変更され、最初の要素も通常のシンボルとして扱われるようになったため、`gnuVersion`関数が受け取るインデックス `i` は、バージョンシンボルデータが格納されている実際のオフセットと直接対応するようになりました。

この変更により、`+ 1` のオフセット調整が残っていると、常に2バイト分(`1 * 2`)ずれた位置のデータを参照してしまうことになります。これがLinux上でのビルドエラーの原因となっていました。

修正は、この不要になった `+ 1` を削除し、単純に `i * 2` とすることで、正しいオフセットを計算するように変更しています。これにより、`gnuVersym`スライスから正しいバージョンシンボルデータを取得できるようになり、ビルド問題が解消されました。

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

```diff
--- a/src/pkg/debug/elf/file.go
+++ b/src/pkg/debug/elf/file.go
@@ -700,8 +700,8 @@ func (f *File) gnuVersionInit(str []byte) {\n // gnuVersion adds Library and Version information to sym,\n // which came from offset i of the symbol table.\n func (f *File) gnuVersion(i int, sym *ImportedSymbol) {\n-\t// Each entry is two bytes; skip undef entry at beginning.\n-\ti = (i + 1) * 2\n+\t// Each entry is two bytes.\n+\ti = i * 2\n \tif i >= len(f.gnuVersym) {\n \t\treturn\n \t}\n```

## コアとなるコードの解説

変更されたのは `src/pkg/debug/elf/file.go` ファイル内の `gnuVersion` 関数です。

元のコード:
```go
// Each entry is two bytes; skip undef entry at beginning.
i = (i + 1) * 2

この行は、gnuVersion関数に渡されたシンボルテーブルのインデックス i を、実際のGNUバージョンシンボルデータが格納されている f.gnuVersym スライス内のオフセットに変換していました。コメントにあるように、「各エントリは2バイト」であり、「先頭の未定義エントリをスキップする」という前提がありました。そのため、論理的なインデックス i1 を加算し、それを 2 倍することで、正しいオフセットを計算していました。

修正後のコード:

// Each entry is two bytes.
i = i * 2

この修正では、+ 1 の部分が削除されました。新しいコメントは単に「各エントリは2バイト」と述べており、もはや「先頭の未定義エントリをスキップする」という前提がなくなっていることを示唆しています。これは、シンボルテーブルの読み込みロジックが変更され、gnuVersion関数に渡されるインデックス i が、既に正しい相対位置を示しているため、追加のオフセット調整が不要になったことを意味します。

この変更により、f.gnuVersym スライスから正しいオフセットでバージョンシンボルデータを取得できるようになり、Linux上でのビルドが正常に完了するようになりました。

関連リンク

参考にした情報源リンク