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

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

このコミットは、Go言語のプロファイリングツールであるpprofスクリプトがWindows環境でシンボル名を正しくデマングルできない問題(Issue 6034)を修正するものです。具体的には、Perlスクリプトであるmisc/pprofが、Windowsにおけるパスの取り扱い、一時ファイルの生成、およびnmaddr2lineといったバイナリユーティリティの挙動の違いに対応するための変更が含まれています。

コミット

  • コミットハッシュ: 88d544e01dbae663e7d041feb74045aeebbc6518
  • Author: Shivakumar GN shivakumar.gn@gmail.com
  • Date: Fri Aug 9 13:42:24 2013 +1000
  • Subject: misc/pprof: pprof on windows does not provide demangled names

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

https://github.com/golang/go/commit/88d544e01dbae663e7d041feb74045aeebbc6518

元コミット内容

misc/pprof: pprof on windows does not provide demangled names

Fixes #6034.

R=golang-dev, bradfitz, alex.brainman, dan.kortschak
CC=golang-dev
https://golang.org/cl/12311044

変更の背景

このコミットの背景には、Go言語のプロファイリングツールであるpprofがWindowsオペレーティングシステム上で期待通りに機能しないという問題がありました。具体的には、pprofが生成するプロファイルレポートにおいて、関数名がC++の記法でエンコードされた「マングルされた」状態のままで表示され、人間が読みやすい「デマングルされた」形式に変換されないという課題がありました。これは、GoのバイナリがC/C++のツールチェインと連携する際に、シンボル解決のメカニズムがWindows固有の環境で適切に動作しないことに起因していました。

Issue 6034「pprof on windows is broken」として報告されたこの問題は、WindowsユーザーがGoアプリケーションのパフォーマンスプロファイリングを行う上で大きな障壁となっていました。pprofスクリプトはPerlで書かれており、内部でnmaddr2lineといった外部のバイナリユーティリティを呼び出してシンボル情報を取得しています。これらのユーティリティの挙動や、シェルコマンドのリダイレクト(例: >/dev/null 2>&1)のパスがUnix系OSとWindows系OSで異なるため、Windows環境でのpprofの動作に不具合が生じていました。

このコミットは、これらの環境差異を吸収し、Windows上でもpprofがシンボル名を正しくデマングルして表示できるようにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念とツールに関する知識が役立ちます。

  1. pprof: Go言語の標準的なプロファイリングツールです。CPU使用率、メモリ割り当て、ゴルーチンスタックトレースなど、様々なパフォーマンスデータを収集し、可視化することができます。pprofは、Goプログラムから生成されたプロファイルデータを解析し、グラフやテキスト形式でレポートを出力します。このコミットで修正されているのは、misc/pprofというPerlスクリプト版のpprofであり、これはGo言語自体が提供するgo tool pprofとは異なる、より汎用的なプロファイリングツールとして機能していました。

  2. デマングル (Demangling): C++などの言語では、関数オーバーロードや名前空間をサポートするために、コンパイラが関数名や変数名を一意なシンボル名に変換します。この変換プロセスを「マングリング (Mangling)」と呼びます。例えば、void MyClass::myFunction(int)のような関数は、コンパイル後に_ZN7MyClass10myFunctionEiのようなマングルされたシンボル名になることがあります。デマングルとは、このマングルされたシンボル名を元の人間が読みやすい形式に戻すプロセスです。プロファイリングツールがデマングル機能を持たない場合、プロファイルレポートに関数名がマングルされたまま表示され、可読性が著しく低下します。

  3. nm コマンド: Unix系システムで利用されるコマンドラインユーティリティで、オブジェクトファイル、アーカイブ、共有ライブラリ内のシンボル(関数名、変数名など)をリスト表示します。デバッグ情報が含まれている場合、シンボルのアドレスや型などの詳細も表示できます。--demangleオプションをサポートしている場合、マングルされたC++シンボル名をデマングルして表示することができます。

  4. addr2line コマンド: Unix系システムで利用されるコマンドラインユーティリティで、実行可能ファイル内のアドレスをソースファイルのファイル名と行番号に変換します。プロファイリングツールがスタックトレースのアドレスから実際のソースコード上の位置を特定する際に使用されます。

  5. objdump コマンド: Unix系システムで利用されるコマンドラインユーティリティで、オブジェクトファイルや実行可能ファイルに関する様々な情報を表示します。逆アセンブルされたコード、セクションヘッダ、シンボルテーブルなどを確認できます。

  6. Perlの system 関数: Perlの組み込み関数で、外部コマンドを実行します。system("command arg1 arg2")のように使用し、コマンドの終了ステータスを返します。

  7. Perlの $^O 変数: Perlの特殊変数で、現在のオペレーティングシステムの名前を格納しています。例えば、Linuxではlinux、WindowsではMSWin32、Cygwinではcygwin、MSYSではmsysといった値が入ります。この変数を参照することで、スクリプトが実行されているOSを判別し、OS固有の処理を分岐させることができます。

  8. /dev/nullNUL: Unix系システムでは、/dev/nullは書き込まれたデータをすべて破棄し、読み込み時には常にEOF(End Of File)を返す特殊なファイルです。コマンドの標準出力や標準エラー出力を破棄したい場合によく使われます。Windowsでは、これに相当するデバイスとしてNULがあります。

  9. File::Temp Perlモジュール: Perlで一時ファイルを安全に作成するためのモジュールです。一時ファイルの命名規則やクリーンアップを自動的に処理してくれます。

技術的詳細

このコミットでは、misc/pprofスクリプトがWindows環境で正しく動作するように、主に以下の技術的変更が加えられています。

  1. OS判定とDEVNULLの定義: Perlの特殊変数$^Oを使用して現在のOSを判定し、それに応じて$OS変数と$DEVNULL変数を設定しています。

    • my $OS = $^O;
    • my $DEVNULL = "/dev/null";
    • if ($^O =~ /MSWin32|cygwin|msys/) { $OS = "windows"; $DEVNULL = "NUL"; } これにより、system関数などで外部コマンドを実行する際に、標準出力や標準エラー出力をリダイレクトするパスをOSに合わせて動的に切り替えることができるようになりました。例えば、>/dev/null 2>&1というUnix系のリダイレクトを、Windowsでは>NUL 2>&1に自動的に変換します。
  2. 一時ファイルの安全な生成: 一時ファイルのパスをハードコードされた/tmp/pprof$$のような形式から、File::Tempモジュールを使用して安全に生成するように変更されました。

    • $main::tmpfile_sym = File::Temp->new()->filename;
    • $main::tmpfile_ps = File::Temp->new()->filename; これにより、OSに依存しない一時ファイルパスの生成と、スクリプト終了時の適切なクリーンアップが保証されます。Windowsでは/tmpディレクトリが存在しない場合があるため、この変更はクロスプラットフォーム対応において重要です。
  3. 外部コマンド実行時のリダイレクトパスの修正: RunGV, Disassemble, MapToSymbols, GetProcedureBoundariesなどのサブルーチン内で、外部コマンド(gv, objdump, addr2line, nmなど)を実行する際の標準出力/エラー出力のリダイレクトパスを、先ほど定義した$DEVNULL変数を使用するように変更しました。

    • 例: if (!system("$GV --version >/dev/null 2>&1"))if (!system("$GV --version >$DEVNULL 2>&1")) に変更。 これにより、Windows環境でもコマンドの出力が正しく破棄され、エラーチェックが機能するようになります。
  4. ConfigureObjToolsにおけるfileコマンドのパス修正: 実行可能ファイルのタイプを判別するために使用されるfileコマンドのパスとリダイレクトも、Windows環境を考慮して修正されました。

    • my $file_cmd = "/usr/bin/file -L $prog_file 2>$DEVNULL || /usr/bin/file $prog_file 2>$DEVNULL";
    • if ($^O eq "MSWin32") { $file_cmd = "file -L $prog_file 2>NUL || file $prog_file 2>NUL"; } これにより、Windows上でもfileコマンドが正しく実行され、64-bitバイナリやMS Windowsバイナリの判別が可能になります。
  5. Windowsにおけるnmコマンドの挙動の調整: GetProcedureBoundariesサブルーチン内で、nmコマンドの--demangleおよび-fオプションのサポートをチェックする際に、リダイレクトパスを$DEVNULLを使用するように変更しました。 さらに重要な変更として、Windows環境でのnmコマンドの呼び出しロジックが簡素化されました。以前はobj_tool_map{"is_windows"}というフラグに基づいてgo tool nmのみを使用する特別な処理がありましたが、このコミットではその特殊な分岐が削除され、他のOSと同様にnmコマンドの様々なバリエーション(--demangle, -Dなど)を試行する汎用的なロジックに統合されました。

    • 以前のコード:
      # 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");
      }
      
    • 変更後のコードでは、このifブロックが削除され、@nm_commandsの定義がWindows固有の考慮なしに統一されました。これは、go tool nmがWindows上でも適切に機能するようになったか、あるいはpprofスクリプトがより汎用的なnmの呼び出しパターンでWindowsのデマングル問題を解決できるようになったことを示唆しています。

これらの変更により、pprofスクリプトはWindows環境のファイルシステム、シェル、およびバイナリユーティリティの挙動の違いを吸収し、シンボルデマングル機能が正しく動作するようになりました。

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

--- a/misc/pprof
+++ b/misc/pprof
@@ -78,6 +78,7 @@
 use strict;
 use warnings;
 use Getopt::Long;
+use File::Temp;
 
 my $PPROF_VERSION = "1.5";
 
@@ -134,6 +135,13 @@ my @prefix_list = ();
 my $sep_symbol = '_fini';
 my $sep_address = undef;
 
+my $OS = $^O;
+my $DEVNULL = "/dev/null";
+if ($^O =~ /MSWin32|cygwin|msys/) {
+\t$OS = "windows";
+\t$DEVNULL = "NUL";
+}
+
 ##### Argument parsing #####
 
 sub usage_string {
@@ -286,8 +294,9 @@ sub Init() {
   # Setup tmp-file name and handler to clean it up.\n  # We do this in the very beginning so that we can use\n  # error() and cleanup() function anytime here after.\n-  $main::tmpfile_sym = "/tmp/pprof$$.sym";\n-  $main::tmpfile_ps = "/tmp/pprof$$\";\n+  $main::tmpfile_sym = File::Temp->new()->filename;\n+  $main::tmpfile_ps = File::Temp->new()->filename;\n+  \n   $main::next_tmpfile = 0;\n   $SIG{'INT'} = \&sighandler;\n 
@@ -696,7 +705,7 @@ sub RunGV {
   my $fname = shift;\n   my $bg = shift;       # "" or " &" if we should run in background\n-  if (!system("$GV --version >/dev/null 2>&1")) {\n+  if (!system("$GV --version >$DEVNULL 2>&1")) {\n     # Options using double dash are supported by this gv version.\n     # Also, turn on noantialias to better handle bug in gv for\n     # postscript files with large dimensions.\n@@ -1246,7 +1255,7 @@ sub Disassemble {\
                     "--start-address=0x$start_addr " .\n                     "--stop-address=0x$end_addr $prog");\n \n-  if (system("$objdump --help >/dev/null 2>&1") != 0) {\n+  if (system("$objdump --help >$DEVNULL 2>&1") != 0) {\n     # objdump must not exist.  Fall back to go tool objdump.\n     $objdump = "go tool objdump";\n     $cmd = "$objdump $prog 0x$start_addr 0x$end_addr";\n@@ -4426,7 +4435,7 @@ sub MapToSymbols {\
 \n   # If "addr2line" isn't installed on the system at all, just use\n   # nm to get what info we can (function names, but not line numbers).\n-  if (system("$addr2line --help >/dev/null 2>&1") != 0) {\n+  if (system("$addr2line --help >$DEVNULL 2>&1") != 0) {\n     MapSymbolsWithNM($image, $offset, $pclist, $symbols);\n     return;\n   }\n@@ -4444,7 +4453,7 @@ sub MapToSymbols {\
   if (defined($sep_address)) {\n     # Only add "-i" to addr2line if the binary supports it.\n     # addr2line --help returns 0, but not if it sees an unknown flag first.\n-    if (system("$cmd -i --help >/dev/null 2>&1") == 0) {\n+    if (system("$cmd -i --help >$DEVNULL 2>&1") == 0) {\n       $cmd .= " -i";\n     } else {\n       $sep_address = undef;   # no need for sep_address if we don't support -i\n@@ -4599,7 +4608,12 @@ sub ConfigureObjTools {\
   (-e $prog_file)  || error("$prog_file does not exist.\\n");\n \n   # Follow symlinks (at least for systems where "file" supports that)\n-  my $file_type = `/usr/bin/file -L $prog_file 2>/dev/null || /usr/bin/file $prog_file`;\n+  my $file_cmd = "/usr/bin/file -L $prog_file 2>$DEVNULL || /usr/bin/file $prog_file 2>$DEVNULL";\n+  if ($^O eq "MSWin32") {\n+    $file_cmd = "file -L $prog_file 2>NUL || file $prog_file 2>NUL";\n+  }\n+  my $file_type = `$file_cmd`;\n+\n   if ($file_type =~ /64-bit/) {\n     # Change $address_length to 16 if the program file is ELF 64-bit.\n     # We can't detect this from many (most?) heap or lock contention\n@@ -4608,14 +4622,13 @@ sub ConfigureObjTools {\
   if ($file_type =~ /MS Windows/) {\n+  if (($file_type =~ /MS Windows/) || ($OS eq "windows")) {\n     # For windows, we provide a version of nm and addr2line as part of\n     # the opensource release, which is capable of parsing\n     # Windows-style PDB executables.  It should live in the path, or\n     # in the same directory as pprof.\n     $obj_tool_map{"nm_pdb"} = "nm-pdb";\n     $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb";\n-    $obj_tool_map{"is_windows"} = "true";\n   }\n \n   if ($file_type =~ /Mach-O/) {\
@@ -4801,29 +4814,23 @@ sub GetProcedureBoundaries {\
   # in an incompatible way.  So first we test whether our nm supports\n   # --demangle and -f.\n   my $demangle_flag = "";\n-  if (system("$nm --demangle $image >/dev/null 2>&1") == 0) {\n+  if (system("$nm --demangle $image >$DEVNULL 2>&1") == 0) {\n     # In this mode, we do "nm --demangle <foo>"\n     $demangle_flag = "--demangle";\n   }\n   my $flatten_flag = "";\n-  if (system("$nm -f $image >/dev/null 2>&1") == 0) {\n+  if (system("$nm -f $image >$DEVNULL 2>&1") == 0) {\n     $flatten_flag = "-f";\n   }\n \n   # Finally, in the case $image isn't a debug library, we try again with\n   # -D to at least get *exported* symbols.  If we can't use --demangle, too bad.\n   my @nm_commands = ("$nm -n $flatten_flag $demangle_flag" .\n-                     " $image 2>/dev/null",\n+                     " $image 2>$DEVNULL",\n                      "$nm -D -n $flatten_flag $demangle_flag" .\n-                     " $image 2>/dev/null",\n+                     " $image 2>$DEVNULL",\n                      # go tool nm is for Go binaries\n-                     "go tool nm $image 2>/dev/null | sort");\n-\n-  # If the executable is an MS Windows Go executable, we'll\n-  # have set up obj_tool_map("is_windows").\n-  if (exists $obj_tool_map{"is_windows"}) {\n-    @nm_commands = ("go tool nm $image 2>/dev/null | sort");\n-  }\n+                     "go tool nm $image 2>$DEVNULL | sort");\n \n   foreach my $nm_command (@nm_commands) {\
     my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp);\

コアとなるコードの解説

このコミットの核となる変更は、misc/pprofスクリプトがWindows環境で外部コマンドを呼び出す際の挙動を、より堅牢かつクロスプラットフォームに対応させる点にあります。

  1. OS判定と$DEVNULLの導入:

    • 追加された以下のコードは、Perlの$^O変数(現在のOSを示す)を利用して、スクリプトがWindows環境(MSWin32, cygwin, msysのいずれか)で実行されているかを判定します。
    • my $OS = $^O;
    • my $DEVNULL = "/dev/null";
    • if ($^O =~ /MSWin32|cygwin|msys/) { $OS = "windows"; $DEVNULL = "NUL"; }
    • これにより、Unix系OSでは/dev/nullが、WindowsではNULが、コマンドの出力を破棄するためのデバイスとして動的に選択されます。これは、system関数で外部コマンドを実行し、その出力を無視したい場合に不可欠です。
  2. 一時ファイルの生成方法の変更:

    • 以前は/tmp/pprof$$.sym/tmp/pprof$$のように、Unix系の/tmpディレクトリとプロセスID($$)を組み合わせたハードコードされたパスで一時ファイルを生成していました。
    • $main::tmpfile_sym = File::Temp->new()->filename;
    • $main::tmpfile_ps = File::Temp->new()->filename;
    • この変更により、PerlのFile::Tempモジュールが使用されます。File::Tempは、OSに依存しない安全な一時ファイル名を生成し、スクリプト終了時に自動的にクリーンアップする機能を提供します。Windowsでは/tmpディレクトリが存在しないことが多いため、この変更はWindows互換性を確保する上で非常に重要です。
  3. 外部コマンドのリダイレクトパスの統一:

    • RunGV, Disassemble, MapToSymbols, GetProcedureBoundariesといった関数内で、system関数やバッククォート(`)で外部コマンドを実行する際に、標準出力や標準エラー出力を破棄するためのリダイレクトパスが、ハードコードされた/dev/nullから、動的に設定される$DEVNULL変数に置き換えられました。
    • 例: if (!system("$GV --version >/dev/null 2>&1"))if (!system("$GV --version >$DEVNULL 2>&1")) に変更。
    • これにより、gv, objdump, addr2line, nmなどのツールがWindows環境でも正しく実行され、エラーメッセージがユーザーに表示されることなく、スクリプトが意図した通りに動作するようになります。
  4. ConfigureObjToolsにおけるfileコマンドのパス修正:

    • 実行可能ファイルのタイプ(例: 64-bit、MS Windows形式)を判別するために使用されるfileコマンドの呼び出しも、$DEVNULLを使用するように変更されました。
    • さらに、Windows環境ではfileコマンドのパスが/usr/bin/fileではない可能性があるため、if ($^O eq "MSWin32") { $file_cmd = "file -L $prog_file 2>NUL || file $prog_file 2>NUL"; }というOS固有のパス設定が追加されました。これにより、Windows上でもfileコマンドが正しく見つかり、実行されるようになります。
  5. Windowsにおけるnmコマンドの特殊処理の削除:

    • 以前のコードでは、obj_tool_map{"is_windows"}というフラグが存在する場合、nmコマンドの呼び出しをgo tool nm $image 2>/dev/null | sortに限定していました。これは、Windowsにおけるnmの挙動が他のOSと大きく異なっていたためと考えられます。
    • このコミットでは、このif (exists $obj_tool_map{"is_windows"})ブロックが完全に削除されました。これは、go tool nmがWindows上でも十分に機能するようになったか、あるいはpprofスクリプトがnm--demangle-fオプションのサポートをより汎用的にチェックするようになったため、Windows固有の特殊な呼び出しが不要になったことを示唆しています。結果として、Windowsでも他のOSと同様に、nmコマンドの様々なバリエーションを試行して最適なデマングル方法を見つけるロジックが適用されるようになりました。

これらの変更は、pprofスクリプトがWindowsのファイルシステム、シェル、および外部バイナリの挙動の違いを適切に処理できるようにすることで、Windows上でのデマングル機能の不具合を解消し、クロスプラットフォーム互換性を向上させています。

関連リンク

参考にした情報源リンク