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

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

このコミットは、Go言語のプロファイリングツールである pprof がWindows環境で正しく動作するようにするための修正を含んでいます。具体的には、pprof がPerlスクリプトとして実行される際の挙動と、シンボル解決に使用される nm ツールの呼び出し方法がWindows向けに調整されています。

コミット

commit 58064a7cab9d39ff9b58c94e76e441dc238343b9
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Sat Aug 18 17:03:32 2012 +1000

    pprof: make it work on windows again
    
    - pprof is a perl script, so go command should invoke
      perl instead of trying to run pprof directly;
    - pprof should use "go tool nm" unconditionally on windows,
      no one else can extract symbols from Go program;
    - pprof should use "go tool nm" instead of "6nm".
    
    Fixes #3879.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6445082

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

https://github.com/golang/go/commit/58064a7cab9d39ff9b58c94e76e441dc238343b9

元コミット内容

このコミットは、pprof ツールがWindows上で再び動作するようにするための変更です。主な変更点は以下の通りです。

  • pprof はPerlスクリプトであるため、go コマンドは pprof を直接実行しようとするのではなく、perl インタープリタを介して呼び出すように修正されました。
  • Windows上では、Goプログラムからシンボルを抽出できるのは go tool nm のみであるため、pprof は無条件に go tool nm を使用するように変更されました。
  • pprof6nm の代わりに go tool nm を使用するように修正されました。

この変更は、Issue #3879 を修正するものです。

変更の背景

このコミットが作成された2012年当時、Go言語の pprof ツールはPerlスクリプトとして実装されており、Windows環境での互換性に問題がありました。具体的には、以下の点が課題となっていました。

  1. Perl依存性: pprof を実行するためには、ユーザーが別途Perl環境(例: 64-bit Perl, msys-perl)をインストールする必要がありました。これは、Goツールチェーンの一部として提供されるツールとしては、追加の依存関係となり、ユーザーにとって負担でした。
  2. パスの問題: pprof スクリプトは、Windowsのパス形式や一時ファイルの作成において、Unixライクなパスを期待することがあり、問題を引き起こしていました。
  3. ツール連携の問題: go tool コマンドがPerlスクリプトを正しく扱えなかったり、fileaddr2line といった外部ツールを見つけられなかったりする問題が発生していました。特に、Goバイナリからシンボル情報を抽出する際に、Windows環境では 6nm のような特定のツールが利用できない、あるいは go tool nm のようなGoツールチェーンが提供する専用のツールを使用する必要がありました。

これらの問題により、Windowsユーザーは pprof を利用してGoプログラムのプロファイリングを行うことが困難でした。このコミットは、これらの互換性の問題を解決し、Windows上での pprof の利用を可能にすることを目的としています。

現代のGo言語では、pprof の機能はGoツールチェーン自体に統合されており、go tool pprof はGoバイナリとして提供されるため、Perlのインストールは不要になっています。しかし、このコミットがなされた時点では、Perlスクリプトとしての pprof の挙動をWindows環境に適合させることが喫緊の課題でした。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  • pprof: Go言語のプロファイリングツール。CPU使用率、メモリ割り当て、ゴルーチン、ブロック、ミューテックス競合などのプロファイルデータを収集し、可視化するために使用されます。このコミットがなされた時点では、pprof はPerlで書かれたスクリプトでした。

  • Perl: スクリプト言語の一つ。テキスト処理やシステム管理によく用いられます。pprof がPerlスクリプトであったため、実行にはPerlインタープリタが必要でした。

  • go tool: Go言語のツールチェーンの一部で、様々な補助ツール(例: go tool compile, go tool link, go tool nm など)を実行するためのコマンドです。

  • nm: オブジェクトファイルや実行可能ファイルからシンボル(関数名、変数名など)をリストアップするためのUnix系コマンドラインツール。Go言語のツールチェーンには、Goバイナリに特化した go tool nm が存在します。

  • 6nm: 2012年当時のGo言語のツールチェーンにおいて、amd64 アーキテクチャ向けの nm ツールを指す可能性のある名称です。Goの初期のツールチェーンでは、アーキテクチャごとにツール名がプレフィックス(例: 6g for amd64 compiler, 8g for 386 compiler)で区別されていました。このコミットでは、6nm の代わりに go tool nm を使用するように変更されています。

  • Windowsの実行可能ファイル: Windowsでは、実行可能ファイルは通常 .exe 拡張子を持ちます。また、Perlスクリプトを直接実行するのではなく、perl.exe を介してスクリプトファイルを指定して実行する必要があります。

  • Issue #3879: Go言語のGitHubリポジトリで管理されている、pprof スクリプトがWindowsで動作しないという問題報告です。このコミットはこのIssueを解決するために作成されました。

技術的詳細

このコミットは、主に misc/pprof (Perlスクリプト) と src/cmd/go/tool.go (Goコマンドのツール実行ロジック) の2つのファイルにわたる変更を含んでいます。

misc/pprof の変更点

misc/pprof は、pprof ツールのPerlスクリプト本体です。このスクリプトは、プロファイルデータを解析し、シンボル情報を取得するために nm などの外部ツールを呼び出します。

  • Windows判定ロジックの追加:

    +    $obj_tool_map{"is_windows"} = "true";
    

    ConfigureObjTools サブルーチン内で、Windows環境である場合に $obj_tool_map{"is_windows"}"true" に設定する行が追加されました。これにより、スクリプト内でWindows固有の挙動を制御するためのフラグが提供されます。

  • GetProcedureBoundaries サブルーチンの変更: このサブルーチンは、実行可能ファイルからプロシージャ(関数)の境界情報を取得するために、様々な nm コマンドを試行します。 変更前は、Unix系の nm コマンドのバリエーションや、Goバイナリ向けの 6nm を試していました。また、nm_pdb (WindowsのPDBファイルからシンボルを抽出するツール) が存在する場合に、それも試行するロジックがありました。

    変更後、特に重要なのは以下の部分です。

    -                     # 6nm is for Go binaries
    -                     "6nm $image 2>/dev/null | sort");
    +                     # go tool nm is for Go binaries
    +                     "go tool nm $image 2>/dev/null | sort");
    

    6nm の代わりに go tool nm を使用するように変更されました。これは、Goバイナリのシンボル抽出には go tool nm がより適切であり、Windows環境でも利用可能であることを示唆しています。

    さらに、Windows環境でのシンボル抽出ロジックが簡素化されました。

    -  # If the executable is an MS Windows PDB-format executable, we'll
    -  # have set up obj_tool_map("nm_pdb").  In this case, we actually
    -  # want to use both unix nm and windows-specific nm_pdb, since
    -  # PDB-format executables can apparently include dwarf .o files.
    -  if (exists $obj_tool_map{"nm_pdb"}) {
    -    my $nm_pdb = $obj_tool_map{"nm_pdb"};
    -    push(@nm_commands, "$nm_pdb --demangle $image 2>/dev/null");
    +  # If the executable is an MS Windows Go executable, we'll
    +  # have set up obj_tool_map("is_windows").
    +  if (exists $obj_tool_map{"is_windows"}) {
    +    @nm_commands = ("go tool nm $image 2>/dev/null | sort");
    

    nm_pdb の存在チェックから、is_windows フラグのチェックに変更され、Windows環境では go tool nm のみをシンボル抽出コマンドとして使用するように強制されます。これは、Windows上ではGoプログラムのシンボル抽出に go tool nm が唯一信頼できる方法であるという認識に基づいています。

src/cmd/go/tool.go の変更点

src/cmd/go/tool.go は、go tool コマンドがどのように外部ツール(この場合は pprof)を実行するかを定義するGo言語のソースファイルです。

  • Windowsでの .exe 拡張子付与の条件変更:

    func tool(name string) string {
    	p := filepath.Join(toolDir, name)
    	if toolIsWindows && name != "pprof" { // 変更点: name != "pprof" の追加
    		p += toolWindowsExtension
    	}
    	return p
    }
    

    tool 関数は、ツール名から実行可能ファイルのパスを構築します。Windowsでは通常 .exe 拡張子が付与されますが、pprof の場合はPerlスクリプトであるため、直接 .exe を付与すると問題が発生します。この変更により、pprof の場合は .exe 拡張子が付与されないようになりました。これは、pprof がPerlインタープリタによって実行されることを前提としているためです。

  • pprof のPerl経由での実行ロジック:

    func runTool(cmd *Command, args []string) {
        // ... 既存のコード ...
    	if toolIsWindows && toolName == "pprof" {
    		args = append([]string{"perl", toolPath}, args[1:]...)
    		var err error
    		toolPath, err = exec.LookPath("perl")
    		if err != nil {
    			fmt.Fprintf(os.Stderr, "go tool: perl not found\\n")
    			setExitStatus(3)
    			return
    		}
    	}
        // ... 既存のコード ...
    }
    

    runTool 関数は、実際にツールを実行するロジックを含んでいます。Windows環境で実行されるツールが pprof である場合、以下の処理が追加されました。

    1. args スライス(コマンドライン引数)の先頭に "perl"toolPath (pprofスクリプトのパス) を追加します。これにより、perl /path/to/pprof [original_args] の形式でコマンドが構築されます。
    2. exec.LookPath("perl") を使用して、システムパスから perl インタープリタの実行可能ファイルを探します。
    3. perl が見つからない場合は、エラーメッセージを出力し、終了ステータスを設定して終了します。これは、pprof を実行するために perl が必須であることを明示しています。

これらの変更により、go tool pprof コマンドがWindows上で実行された際に、pprof スクリプトが正しくPerlインタープリタによって呼び出され、かつGoバイナリのシンボル解決に適切な go tool nm が使用されるようになりました。

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

misc/pprof

--- a/misc/pprof
+++ b/misc/pprof
@@ -4599,6 +4599,7 @@ sub ConfigureObjTools {
     # in the same directory as pprof.
     $obj_tool_map{"nm_pdb"} = "nm-pdb";
     $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";
+    $obj_tool_map{"is_windows"} = "true";
   }
 
   if ($file_type =~ /Mach-O/) {
@@ -4806,16 +4807,13 @@ sub GetProcedureBoundaries {
                      " $image 2>/dev/null $cppfilt_flag",
                      "$nm -D -n $flatten_flag $demangle_flag" .
                      " $image 2>/dev/null $cppfilt_flag",
-                     # 6nm is for Go binaries
-                     "6nm $image 2>/dev/null | sort");
-\
-  # If the executable is an MS Windows PDB-format executable, we'll
-  # have set up obj_tool_map("nm_pdb").  In this case, we actually
-  # want to use both unix nm and windows-specific nm_pdb, since
-  # PDB-format executables can apparently include dwarf .o files.
-  if (exists $obj_tool_map{"nm_pdb"}) {
-    my $nm_pdb = $obj_tool_map{"nm_pdb"};
-    push(@nm_commands, "$nm_pdb --demangle $image 2>/dev/null");
+                     # go tool nm is for Go binaries
+                     "go tool nm $image 2>/dev/null | sort");
+\
+  # If the executable is an MS Windows Go executable, we'll
+  # have set up obj_tool_map("is_windows").
+  if (exists $obj_tool_map{"is_windows"}) {
+    @nm_commands = ("go tool nm $image 2>/dev/null | sort");
   }
 
   foreach my $nm_command (@nm_commands) {

src/cmd/go/tool.go

--- a/src/cmd/go/tool.go
+++ b/src/cmd/go/tool.go
@@ -47,7 +47,7 @@ const toolWindowsExtension = ".exe"
 
 func tool(name string) string {
 	p := filepath.Join(toolDir, name)
-	if toolIsWindows {
+	if toolIsWindows && name != "pprof" {
 		p += toolWindowsExtension
 	}
 	return p
@@ -76,6 +76,16 @@ func runTool(cmd *Command, args []string) {
 		setExitStatus(3)
 		return
 	}\
+\tif toolIsWindows && toolName == "pprof" {\
+\t\targs = append([]string{"perl", toolPath}, args[1:]...)\
+\t\tvar err error\
+\t\ttoolPath, err = exec.LookPath("perl")\
+\t\tif err != nil {\
+\t\t\tfmt.Fprintf(os.Stderr, "go tool: perl not found\\n")
+\t\t\tsetExitStatus(3)\
+\t\t\treturn
+\t\t}\
+\t}\
 
 	if toolN {
 		fmt.Printf("%s %s\\n", toolPath, strings.Join(args[1:], " "))

コアとなるコードの解説

misc/pprof の変更

  • $obj_tool_map{"is_windows"} = "true"; の追加: これは、pprof スクリプトがWindows環境で実行されていることを内部的に認識するためのフラグです。このフラグの導入により、スクリプトはOSに依存する挙動をより適切に制御できるようになります。

  • 6nm から go tool nm への変更: GetProcedureBoundaries サブルーチンは、実行可能ファイルからシンボル情報を抽出するために様々な nm コマンドを試します。以前はGoバイナリ向けに 6nm を試していましたが、この変更により go tool nm を使用するようになりました。これは、go tool nm がGoツールチェーンの一部として提供され、Goバイナリのシンボル抽出に特化しているため、より信頼性が高いと判断されたためです。

  • Windows環境での nm コマンドの強制: if (exists $obj_tool_map{"is_windows"}) ブロック内で、@nm_commands("go tool nm $image 2>/dev/null | sort") のみで上書きされるようになりました。これは、Windows上ではGoバイナリのシンボル抽出には go tool nm が唯一の適切なツールであり、他の nm コマンドのバリエーションを試す必要がないことを意味します。これにより、Windows環境でのシンボル解決のロジックが簡素化され、堅牢性が向上しました。

src/cmd/go/tool.go の変更

  • tool 関数の name != "pprof" 条件追加: tool 関数は、go tool コマンドが実行するツールのパスを構築します。Windowsでは通常、実行可能ファイルに .exe 拡張子が付与されます。しかし、pprof はPerlスクリプトであるため、pprof.exe というファイルは存在しません。この変更により、pprof の場合は .exe 拡張子が付与されなくなり、go tool pprofpprof スクリプト自体を指すようになります。

  • runTool 関数のPerl経由での pprof 実行ロジック: この変更は、Windows上で go tool pprof が実行された際の挙動を根本的に変えます。

    1. args = append([]string{"perl", toolPath}, args[1:]...): これは、go tool pprof [args] というコマンドが、内部的に perl /path/to/pprof [args] という形式に変換されることを意味します。これにより、Windowsが pprof スクリプトを直接実行しようとするのではなく、perl インタープリタが pprof スクリプトを解釈して実行するようになります。
    2. exec.LookPath("perl"): perl インタープリタがシステムパス上に存在するかどうかを確認します。もし存在しない場合、pprof は実行できないため、エラーメッセージが表示されます。これは、pprof がPerlスクリプトであるという依存関係を明示的に処理するものです。

これらの変更は、Windows環境における pprof の実行フローを、Perlスクリプトとしての性質とWindowsの実行可能ファイルの扱いに合わせて調整し、互換性の問題を解決しています。

関連リンク

参考にした情報源リンク

  • The Go issue 3879, titled "pprof script doesn't work under Windows," is a very old issue from 2012 that highlighted compatibility problems with the pprof tool on Windows.
  • Historically, the pprof tool distributed with Go was a Perl script, which led to several challenges on Windows, including:
    • Perl Dependency: Users often needed to install a compatible Perl environment (e.g., 64-bit Perl, msys-perl) to run pprof.
    • Pathing Issues: The script struggled with Windows-style paths and temporary file creation, often expecting Unix-like paths.
    • Tooling Integration: Issues arose with go tool not correctly handling the Perl script or finding necessary external tools like file or addr2line.
  • Modern pprof on Windows: The good news is that the Go pprof tool has evolved significantly since issue 3879 was reported. The pprof functionality is now largely integrated into the Go toolchain itself, meaning go tool pprof is a Go binary and no longer relies on a separate Perl installation.
  • For modern Go versions, pprof is a robust tool for visualizing and analyzing profiling data (CPU, memory, goroutine, etc.) on Windows.
  • https://go.dev/doc/diagnose (Go Diagnostics documentation)
  • https://pkg.go.dev/cmd/go (Go command documentation)
  • https://pkg.go.dev/cmd/pprof (pprof documentation)
  • https://en.wikipedia.org/wiki/Perl (Perl on Wikipedia)
  • https://en.wikipedia.org/wiki/Nm_(Unix) (nm (Unix) on Wikipedia)