[インデックス 17114] ファイルの概要
このコミットは、Go言語のプロファイリングツールであるpprof
スクリプトがWindows環境でシンボル名を正しくデマングルできない問題(Issue 6034)を修正するものです。具体的には、Perlスクリプトであるmisc/pprof
が、Windowsにおけるパスの取り扱い、一時ファイルの生成、およびnm
やaddr2line
といったバイナリユーティリティの挙動の違いに対応するための変更が含まれています。
コミット
- コミットハッシュ:
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で書かれており、内部でnm
やaddr2line
といった外部のバイナリユーティリティを呼び出してシンボル情報を取得しています。これらのユーティリティの挙動や、シェルコマンドのリダイレクト(例: >/dev/null 2>&1
)のパスがUnix系OSとWindows系OSで異なるため、Windows環境でのpprof
の動作に不具合が生じていました。
このコミットは、これらの環境差異を吸収し、Windows上でもpprof
がシンボル名を正しくデマングルして表示できるようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の技術的な概念とツールに関する知識が役立ちます。
-
pprof: Go言語の標準的なプロファイリングツールです。CPU使用率、メモリ割り当て、ゴルーチンスタックトレースなど、様々なパフォーマンスデータを収集し、可視化することができます。
pprof
は、Goプログラムから生成されたプロファイルデータを解析し、グラフやテキスト形式でレポートを出力します。このコミットで修正されているのは、misc/pprof
というPerlスクリプト版のpprof
であり、これはGo言語自体が提供するgo tool pprof
とは異なる、より汎用的なプロファイリングツールとして機能していました。 -
デマングル (Demangling): C++などの言語では、関数オーバーロードや名前空間をサポートするために、コンパイラが関数名や変数名を一意なシンボル名に変換します。この変換プロセスを「マングリング (Mangling)」と呼びます。例えば、
void MyClass::myFunction(int)
のような関数は、コンパイル後に_ZN7MyClass10myFunctionEi
のようなマングルされたシンボル名になることがあります。デマングルとは、このマングルされたシンボル名を元の人間が読みやすい形式に戻すプロセスです。プロファイリングツールがデマングル機能を持たない場合、プロファイルレポートに関数名がマングルされたまま表示され、可読性が著しく低下します。 -
nm
コマンド: Unix系システムで利用されるコマンドラインユーティリティで、オブジェクトファイル、アーカイブ、共有ライブラリ内のシンボル(関数名、変数名など)をリスト表示します。デバッグ情報が含まれている場合、シンボルのアドレスや型などの詳細も表示できます。--demangle
オプションをサポートしている場合、マングルされたC++シンボル名をデマングルして表示することができます。 -
addr2line
コマンド: Unix系システムで利用されるコマンドラインユーティリティで、実行可能ファイル内のアドレスをソースファイルのファイル名と行番号に変換します。プロファイリングツールがスタックトレースのアドレスから実際のソースコード上の位置を特定する際に使用されます。 -
objdump
コマンド: Unix系システムで利用されるコマンドラインユーティリティで、オブジェクトファイルや実行可能ファイルに関する様々な情報を表示します。逆アセンブルされたコード、セクションヘッダ、シンボルテーブルなどを確認できます。 -
Perlの
system
関数: Perlの組み込み関数で、外部コマンドを実行します。system("command arg1 arg2")
のように使用し、コマンドの終了ステータスを返します。 -
Perlの
$^O
変数: Perlの特殊変数で、現在のオペレーティングシステムの名前を格納しています。例えば、Linuxではlinux
、WindowsではMSWin32
、Cygwinではcygwin
、MSYSではmsys
といった値が入ります。この変数を参照することで、スクリプトが実行されているOSを判別し、OS固有の処理を分岐させることができます。 -
/dev/null
とNUL
: Unix系システムでは、/dev/null
は書き込まれたデータをすべて破棄し、読み込み時には常にEOF(End Of File)を返す特殊なファイルです。コマンドの標準出力や標準エラー出力を破棄したい場合によく使われます。Windowsでは、これに相当するデバイスとしてNUL
があります。 -
File::Temp
Perlモジュール: Perlで一時ファイルを安全に作成するためのモジュールです。一時ファイルの命名規則やクリーンアップを自動的に処理してくれます。
技術的詳細
このコミットでは、misc/pprof
スクリプトがWindows環境で正しく動作するように、主に以下の技術的変更が加えられています。
-
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
に自動的に変換します。
-
一時ファイルの安全な生成: 一時ファイルのパスをハードコードされた
/tmp/pprof$$
のような形式から、File::Temp
モジュールを使用して安全に生成するように変更されました。$main::tmpfile_sym = File::Temp->new()->filename;
$main::tmpfile_ps = File::Temp->new()->filename;
これにより、OSに依存しない一時ファイルパスの生成と、スクリプト終了時の適切なクリーンアップが保証されます。Windowsでは/tmp
ディレクトリが存在しない場合があるため、この変更はクロスプラットフォーム対応において重要です。
-
外部コマンド実行時のリダイレクトパスの修正:
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環境でもコマンドの出力が正しく破棄され、エラーチェックが機能するようになります。
- 例:
-
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バイナリの判別が可能になります。
-
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環境で外部コマンドを呼び出す際の挙動を、より堅牢かつクロスプラットフォームに対応させる点にあります。
-
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
関数で外部コマンドを実行し、その出力を無視したい場合に不可欠です。
- 追加された以下のコードは、Perlの
-
一時ファイルの生成方法の変更:
- 以前は
/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互換性を確保する上で非常に重要です。
- 以前は
-
外部コマンドのリダイレクトパスの統一:
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環境でも正しく実行され、エラーメッセージがユーザーに表示されることなく、スクリプトが意図した通りに動作するようになります。
-
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
コマンドが正しく見つかり、実行されるようになります。
- 実行可能ファイルのタイプ(例: 64-bit、MS Windows形式)を判別するために使用される
-
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上でのデマングル機能の不具合を解消し、クロスプラットフォーム互換性を向上させています。
関連リンク
- Go Issue 6034: https://github.com/golang/go/issues/6034
- Go CL 12311044: https://golang.org/cl/12311044
参考にした情報源リンク
- Go issue 6034 - Web search results
- Perl
system
function documentation - Perl special variable
$^O
documentation - Perl
File::Temp
module documentation - Unix
nm
command man page (general concept) - Unix
addr2line
command man page (general concept) - Unix
objdump
command man page (general concept) - What is /dev/null?
- Windows NUL device
- C++ Name Mangling
- Go pprof documentation