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

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

このコミットは、Go言語のツールチェインの一部である cmd/nm コマンドが、WindowsのPE (Portable Executable) 形式の実行ファイルに対してシンボルサイズを表示できるようにする変更です。具体的には、シンボルのアドレスをソートし、次のシンボルの開始アドレスとの差分からシンボルサイズを推測するロジックが追加されています。また、Windowsにおける RuntimeGogoBytes の定義が更新され、関連するテストのスキップが削除されています。

コミット

commit 6e8c7f5bb241852f052a9b4a2f20f3e33d0ec7b2
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Sat Apr 19 14:47:20 2014 +1000

    cmd/nm: print symbol sizes for windows pe executables
    
    Fixes #6973
    
    LGTM=r
    R=golang-codereviews, r
    CC=golang-codereviews
    https://golang.org/cl/88820043

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

https://github.com/golang/go/commit/6e8c7f5bb241852f052a9b4a2f20f3e33d0ec7b2

元コミット内容

cmd/nm: print symbol sizes for windows pe executables

Fixes #6973

LGTM=r
R=golang-codereviews, r
CC=golang-codereviews
https://golang.org/cl/88820043

変更の背景

この変更の背景には、Go言語の cmd/nm ツールがWindowsのPE実行ファイルに対してシンボルサイズを正確に表示できないという問題(Issue #6973)がありました。nm コマンドは、実行ファイルやオブジェクトファイル内のシンボル(関数名、変数名など)の情報を表示するために使用されます。シンボルサイズは、コードの最適化やデバッグにおいて重要な情報となります。

従来の nm コマンドは、ELF (Executable and Linkable Format) 形式のファイル(主にUnix系システムで使用)に対してはシンボルサイズを適切に表示できましたが、WindowsのPE形式に対しては対応が不十分でした。このため、Windows環境でGoプログラムのバイナリを解析する際に、シンボルサイズに関する情報が得られないという制約がありました。

このコミットは、このギャップを埋め、Windows環境においても nm コマンドがより有用な情報を提供できるようにすることを目的としています。

前提知識の解説

cmd/nm コマンド

cmd/nm は、Go言語の標準ツールチェインに含まれるコマンドラインユーティリティです。その名前はUnix系の nm コマンドに由来しており、コンパイルされたGoプログラムのバイナリファイル(実行ファイルやアーカイブファイル)に含まれるシンボル(関数、変数、型など)の情報を表示するために使用されます。表示される情報には、シンボルのアドレス、型、そしてこのコミットで追加されたシンボルサイズなどが含まれます。デバッグ、プロファイリング、バイナリ解析などの目的で利用されます。

PE (Portable Executable) 形式

PE (Portable Executable) は、Microsoft Windowsオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、DLL (Dynamic Link Library) などのファイル形式です。PE形式は、Unix系システムで広く使われるELF形式と同様に、プログラムの実行に必要なコード、データ、リソース、メタデータなどを構造化して格納します。PEファイルは、ヘッダ、セクションテーブル、セクションデータなどから構成され、ローダーがプログラムをメモリにロードし、実行するために必要な情報を提供します。

シンボルとシンボルサイズ

プログラムにおける「シンボル」とは、関数名、グローバル変数名、静的変数名など、プログラム内の特定のメモリ位置やエンティティを識別するための名前です。コンパイルされたバイナリファイルには、これらのシンボルとそれらが参照するメモリ上のアドレスとのマッピング情報(シンボルテーブル)が含まれています。

「シンボルサイズ」とは、特定のシンボルが占めるメモリ領域のバイト数です。例えば、関数のシンボルサイズはその関数の機械語コードが占めるバイト数を示し、変数のシンボルサイズはその変数が占めるメモリのバイト数を示します。シンボルサイズは、バイナリのサイズ分析、メモリ使用量の最適化、特定のコードブロックの範囲特定などに役立ちます。

RuntimeGogoBytes

RuntimeGogoBytes は、Goランタイム内部で使用される定数で、特定のデータ構造やスタックフレームのサイズに関連する可能性があります。Goランタイムは、ガベージコレクション、スケジューリング、メモリ管理など、Goプログラムの実行を支える低レベルな処理を担当しており、これらの処理には特定のアーキテクチャやOSに依存する定数が必要となることがあります。このコミットでは、Windows環境におけるこの定数の値が調整されています。

技術的詳細

このコミットの主要な技術的変更は、src/cmd/nm/pe.go におけるシンボルサイズ計算ロジックの追加です。

  1. シンボルアドレスの収集: PEファイルから読み取られた各シンボルのアドレス (sym.Addr) を addrs という uint64 型のスライスに収集します。
  2. アドレスのソート: 収集したシンボルアドレスのリスト addrs を昇順にソートします。これは、sort.Sort(uint64s(addrs)) によって行われます。uint64ssort.Interface を実装したカスタム型であると推測されます。
  3. シンボルサイズの推測: 各シンボル syms[i] について、そのシンボルアドレス syms[i].Addr よりも大きい最初のアドレスをソート済み addrs リストから検索します。これは sort.Search 関数を使用して効率的に行われます。
    • j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr })
    • sort.Search は、指定された条件を満たす最小のインデックス j を返します。この場合、addrs[j]syms[i].Addr よりも大きい最初のアドレスとなります。
  4. サイズの計算: jaddrs の範囲内 (j < len(addrs)) であれば、現在のシンボル syms[i] のサイズは、次のシンボルの開始アドレス (addrs[j]) から現在のシンボルの開始アドレス (syms[i].Addr) を引いた値として計算されます。
    • syms[i].Size = int64(addrs[j] - syms[i].Addr)
    • このアプローチは、シンボルがメモリ上で連続して配置されているという仮定に基づいています。つまり、あるシンボルの直後に次のシンボルが配置されている場合、その間の距離が最初のシンボルのサイズであると見なします。最後のシンボルについては、次のシンボルが存在しないため、サイズは計算されません(または0となります)。

src/pkg/runtime/arch_amd64.h の変更は、Windows環境における RuntimeGogoBytes の値を 80 に設定しています。これは、Solaris環境と同じ値であり、特定のランタイム内部構造のサイズがWindowsとSolarisで共通であることを示唆しています。

src/pkg/runtime/runtime_test.go の変更は、Issue #6973 が修正されたため、Windows環境で TestRuntimeGogoBytes テストをスキップするロジックを削除しています。これは、このコミットによって nm コマンドの機能が改善され、関連するテストがWindowsでも正しく実行できるようになったことを意味します。

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

src/cmd/nm/pe.go

--- a/src/cmd/nm/pe.go
+++ b/src/cmd/nm/pe.go
@@ -9,6 +9,7 @@ package main
 import (
 	"debug/pe"
 	"os"
+	"sort"
 )
 
 func peSymbols(f *os.File) []Sym {
@@ -18,6 +19,10 @@ func peSymbols(f *os.File) []Sym {
 		return nil
 	}
 
+	// Build sorted list of addresses of all symbols.
+	// We infer the size of a symbol by looking at where the next symbol begins.
+	var addrs []uint64
+
 	var imageBase uint64
 	switch oh := p.OptionalHeader.(type) {
 	case *pe.OptionalHeader32:
@@ -78,6 +83,15 @@ func peSymbols(f *os.File) []Sym {
 			sym.Addr += imageBase + uint64(sect.VirtualAddress)
 		}
 		syms = append(syms, sym)
+		addrs = append(addrs, sym.Addr)
+	}
+
+	sort.Sort(uint64s(addrs))
+	for i := range syms {
+		j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr })
+		if j < len(addrs) {
+			syms[i].Size = int64(addrs[j] - syms[i].Addr)
+		}
 	}
 
 	return syms

src/pkg/runtime/arch_amd64.h

--- a/src/pkg/runtime/arch_amd64.h
+++ b/src/pkg/runtime/arch_amd64.h
@@ -8,9 +8,13 @@ enum {
 	CacheLineSize = 64,
 #ifdef GOOS_solaris
 	RuntimeGogoBytes = 80,
+#else
+#ifdef GOOS_windows
+\tRuntimeGogoBytes = 80,
 #else
 	RuntimeGogoBytes = 64,
-#endif
+#endif	// Windows
+#endif	// Solaris
 	PhysPageSize = 4096,
 	PCQuantum = 1
 };

src/pkg/runtime/runtime_test.go

--- a/src/pkg/runtime/runtime_test.go
+++ b/src/pkg/runtime/runtime_test.go
@@ -95,10 +95,6 @@ func BenchmarkDeferMany(b *testing.B) {
 // The value reported will include the padding between runtime.gogo and the
 // next function in memory. That's fine.
 func TestRuntimeGogoBytes(t *testing.T) {
-\t// TODO(brainman): delete when issue 6973 is fixed.
-\tif GOOS == "windows" {
-\t\tt.Skip("skipping broken test on windows")
-\t}\n \tdir, err := ioutil.TempDir("", "go-build")
 \tif err != nil {
 \t\tt.Fatalf("failed to create temp directory: %v", err)

コアとなるコードの解説

src/cmd/nm/pe.go の変更

このファイルは、Windows PEファイルからシンボル情報を抽出するロジックを扱っています。

  1. import "sort" の追加: シンボルアドレスをソートするために sort パッケージがインポートされました。
  2. addrs スライスの導入: var addrs []uint64 が追加され、PEファイルから読み取られたすべてのシンボルのアドレスを格納するために使用されます。
  3. シンボルアドレスの収集: for ループ内で各シンボル sym が処理される際に、そのアドレス sym.Addraddrs スライスに追加されます。
  4. アドレスのソート: sort.Sort(uint64s(addrs)) により、収集されたシンボルアドレスが昇順にソートされます。uint64s[]uint64sort.Interface に適合させるためのラッパー型であると推測されます。
  5. シンボルサイズの計算:
    • for i := range syms ループで、元のシンボルリスト syms の各シンボルを反復処理します。
    • j := sort.Search(len(addrs), func(x int) bool { return addrs[x] > syms[i].Addr }) は、現在のシンボル syms[i] のアドレス syms[i].Addr よりも大きい最初のアドレスが addrs スライスのどこにあるかを効率的に見つけます。sort.Search は二分探索アルゴリズムを使用します。
    • if j < len(addrs) は、次のシンボルが存在するかどうかを確認します。存在する場合、つまり jaddrs スライスの範囲内である場合、シンボルサイズが計算されます。
    • syms[i].Size = int64(addrs[j] - syms[i].Addr) は、現在のシンボルのサイズを、次のシンボルの開始アドレスと現在のシンボルの開始アドレスの差として計算します。これにより、シンボルが占めるメモリ領域の大きさが推測されます。

この変更により、cmd/nm はWindows PE実行ファイルに対しても、シンボル名、アドレスに加えて、そのシンボルが占めるメモリサイズを表示できるようになりました。

src/pkg/runtime/arch_amd64.h の変更

このファイルは、amd64 アーキテクチャにおけるGoランタイムの定数を定義しています。

  • #ifdef GOOS_windows ブロックが追加され、Windows環境での RuntimeGogoBytes の値が 80 に設定されました。これは、既存の GOOS_solaris の定義と一致しています。これにより、WindowsとSolarisで RuntimeGogoBytes の値が統一され、ランタイムの挙動がより一貫したものになります。

src/pkg/runtime/runtime_test.go の変更

このファイルは、Goランタイムのテストコードを含んでいます。

  • TestRuntimeGogoBytes 関数から、Windows環境でテストをスキップする以下の行が削除されました。
    // TODO(brainman): delete when issue 6973 is fixed.
    if GOOS == "windows" {
    	t.Skip("skipping broken test on windows")
    }
    
    これは、cmd/nm の変更によって Issue #6973 が解決され、TestRuntimeGogoBytes がWindowsでも正しく動作するようになったことを示しています。テストのスキップが不要になったことで、Windows環境でのランタイムの健全性がより確実に検証されるようになります。

関連リンク

  • Go言語の nm コマンドに関する公式ドキュメント(もしあれば、Goの公式ツールのドキュメントを参照)
  • PEファイル形式に関するMicrosoftの公式ドキュメント
  • Go言語のランタイムに関するドキュメントやブログ記事

参考にした情報源リンク