[インデックス 15701] ファイルの概要
このコミットは、Go言語のプロファイリングツールであるpprof
が、macOS環境で発生するc++filt
コマンドの問題を回避するために行われた変更を記録しています。具体的には、macOSに同梱されている古いバージョンのc++filt
がシンボルデマングル処理を正しく行わず、むしろシンボル情報を破壊してしまう問題を解決するため、pprof
スクリプトからc++filt
の使用を完全に削除しています。
コミット
commit a25c403b3a21c84c7ece8b00b9229b00a31e6d40
Author: Russ Cox <rsc@golang.org>
Date: Mon Mar 11 18:15:23 2013 -0400
cmd/pprof: never use c++filt
The copy of c++filt shipped on OS X is six years old,
and in our case it does far more mangling than it
does demangling. People on non-OS X systems will
have a working nm --demangle, so this won't affect them.
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.8.2
BuildVersion: 12C2034
$ c++filt --version
GNU c++filt 070207 20070207
Copyright 2005 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License. This program has absolutely no warranty.
$
$ go tool nm -n revcomp | grep quoteWith
4f560 T strconv.quoteWith
$ go tool nm -n revcomp | grep quoteWith | c++filt
f560 T strconv.quoteWith
$
$ nm -n revcomp | grep quoteWith
000000000004f560 t _strconv.quoteWith
$ nm -n revcomp | grep quoteWith | c++filt
000000000004f560 unsigned short _strconv.quoteWith
$
Fixes #4818.
R=golang-dev, r, bradfitz
CC=golang-dev
https://golang.org/cl/7729043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a25c403b3a21c84c7ece8b00b9229b00a31e6d40
元コミット内容
このコミットの目的は、cmd/pprof
ツールがc++filt
を使用しないようにすることです。
その理由は、macOSに同梱されているc++filt
のバージョンが6年前(コミット当時)のものであり、シンボルをデマングルするよりも、むしろマングル(シンボル名を破壊)してしまう傾向が強かったためです。macOS以外のシステムでは、nm --demangle
が正常に機能するため、この変更はそれらのシステムには影響を与えません。
コミットメッセージには、具体的なsw_vers
とc++filt --version
の出力例が示されており、macOS 10.8.2環境でc++filt
が2007年2月7日版であることが確認できます。
また、go tool nm
と通常のnm
コマンドの出力にc++filt
をパイプした場合の挙動の違いが示されています。
go tool nm -n revcomp | grep quoteWith
の出力は4f560 T strconv.quoteWith
ですが、これにc++filt
をパイプするとf560 T strconv.quoteWith
となり、アドレスの先頭が欠落しています。nm -n revcomp | grep quoteWith
の出力は000000000004f560 t _strconv.quoteWith
ですが、これにc++filt
をパイプすると000000000004f560 unsigned short _strconv.quoteWith
となり、型情報が追加されるものの、Goのシンボル名がC++のシンボルとして誤って解釈されていることが示唆されます。
この変更は、GoのIssue #4818を修正するものです。
変更の背景
Go言語のプロファイリングツールであるpprof
は、実行ファイルのシンボル情報を解析するためにnm
やc++filt
といった外部ツールを利用していました。特にc++filt
は、C++のコンパイラが生成するマングルされた(人間には読みにくい形式に変換された)シンボル名を、元の関数名や変数名にデマングル(復元)するために使用されます。これは、プロファイル結果をより人間が理解しやすい形で表示するために重要です。
しかし、当時のmacOSに標準でインストールされていたc++filt
のバージョンが非常に古く(2007年製)、Goのバイナリに含まれるシンボルに対して期待通りのデマングル処理を行わず、むしろシンボル情報を破損させてしまうという問題が報告されていました(Issue #4818)。具体的には、アドレス情報が欠落したり、GoのシンボルがC++のシンボルとして誤って解釈され、不正確な型情報が付加されたりする現象が発生していました。
Go言語はC++とは異なるシンボルマングリングスキームを使用しており、c++filt
がGoのシンボルを正しくデマングルすることは元々期待されていませんでした。しかし、pprof
スクリプトが汎用的なシンボルデマングル機構としてc++filt
を試行していたため、macOS環境での予期せぬ挙動が問題となりました。
この問題は、プロファイル結果の可読性を著しく損なうため、pprof
のmacOSユーザーにとって深刻なものでした。GoのバイナリはC++のシンボルを含まないため、c++filt
を使用する必要性は本来ありません。したがって、このコミットは、macOSにおけるc++filt
の互換性問題を回避し、pprof
の安定性と正確性を向上させることを目的としています。
前提知識の解説
1. プロファイリングとpprof
- プロファイリング: ソフトウェアの実行中にその性能特性(CPU使用率、メモリ使用量、関数呼び出し回数など)を測定・分析するプロセスです。これにより、パフォーマンスのボトルネックを特定し、最適化の指針を得ることができます。
- Goの
pprof
: Go言語には標準でプロファイリング機能が組み込まれており、runtime/pprof
パッケージを通じて利用できます。生成されたプロファイルデータは、go tool pprof
コマンドや、このコミットで変更されているmisc/pprof
スクリプト(Perlスクリプト)を使って可視化・解析されます。pprof
は、関数名やファイル名、行番号といったシンボル情報を用いて、プロファイル結果を人間が理解しやすい形で表示します。
2. シンボルとシンボルマングリング/デマングリング
- シンボル: コンパイルされたプログラムにおいて、関数、変数、クラス、メソッドなどの名前を表す識別子です。実行ファイルやライブラリには、これらのシンボルとそれらがメモリ上のどこに配置されているかを示す情報(シンボルテーブル)が含まれています。
- シンボルマングリング (Symbol Mangling): C++のような言語では、関数オーバーロードや名前空間、テンプレートなど、同じ名前でも異なるシグネチャを持つエンティティが存在します。コンパイラは、これらのエンティティを一意に識別するために、元の人間が読める名前を、リンカが処理できるような特殊な形式(マングルされた名前)に変換します。例えば、
void MyClass::myMethod(int, float)
のような関数は、_ZN7MyClass8myMethodEif
のような形式にマングルされることがあります。 - シンボルデマングリング (Symbol Demangling): マングルされたシンボル名を、元の人間が読める形式(例:
MyClass::myMethod(int, float)
)に復元するプロセスです。デバッガやプロファイラ、エラーログなどが、マングルされた名前をユーザーフレンドリーな形式で表示するためにデマングリングを行います。
3. nm
コマンド
nm
は、オブジェクトファイル、アーカイブ、または実行ファイル内のシンボル(関数名、変数名など)をリスト表示するためのUnix系コマンドラインユーティリティです。nm
の出力には、シンボルのアドレス、型(T: テキストセグメントの関数、D: データセグメントの初期化済みデータなど)、そしてシンボル名が含まれます。- 多くの
nm
実装(特にGNU Binutilsに含まれるもの)は、C++のシンボルをデマングルするための--demangle
オプションを提供しています。
4. c++filt
コマンド
c++filt
は、C++のコンパイラによってマングルされたシンボル名をデマングルするためのスタンドアロンのユーティリティです。通常、nm
コマンドが--demangle
オプションをサポートしていない場合や、他のツールからの出力をデマングルしたい場合に、パイプと組み合わせて使用されます。- 例:
nm my_program | c++filt
5. Goのシンボルとgo tool nm
- Go言語のコンパイラは、C++とは異なる独自のシンボルマングリングスキームを使用しています。Goの関数名やメソッド名は、通常、パッケージパスと関数名が結合された形式(例:
main.main
,strconv.quoteWith
)で表現されます。 go tool nm
は、Goのツールチェインに含まれるnm
のGo版であり、Goのバイナリに特化してシンボル情報を抽出します。これは、通常のシステムnm
がGoのバイナリを正しく解析できない場合があるためです。go tool nm
は、Goのシンボルをデマングルする必要がないため、通常はマングルされていない形式で出力します。
技術的詳細
このコミットの技術的詳細の核心は、pprof
スクリプトがシンボルデマングル処理において、macOS環境におけるc++filt
の不適切な挙動を回避するために、その使用を完全に停止した点にあります。
-
c++filt
の削除:misc/pprof
スクリプトは、obj_tool_map
というハッシュマップで利用可能なオブジェクトツールを定義していました。このマップから"c++filt" => "c++filt"
のエントリが削除されました。これにより、スクリプトはもはやc++filt
というツールを認識しなくなります。- スクリプト内のコメントで明示的に「
c++filt
に関するすべての言及は、このスクリプトから削除されました」と述べられています。これは、GoがC++を使用しないこと、そしてmacOS上のc++filt
がシンボル情報を破壊するという二つの理由に基づいています。
-
シンボル取得ロジックの変更:
FetchSymbols
サブルーチンでは、以前は$command_line | $cppfilt |
のようにc++filt
をパイプしてシンボルをデマングルしようとしていました。この変更により、単に$command_line |
となり、c++filt
を通さずにシンボルを取得するようになりました。GetProcedureBoundaries
サブルーチンでは、nm
コマンドが--demangle
オプションをサポートしているかどうかを最初に確認し、サポートしていればそれを使用します。以前は、--demangle
が利用できない場合にnm <foo> | c++filt
というフォールバックロジックが存在しました。このフォールバックパスが完全に削除されました。- 具体的には、
$cppfilt_flag
という変数が削除され、nm_commands
配列の構築時に$cppfilt_flag
が連結されていた部分が削除されました。これにより、pprof
はnm --demangle
が機能しない場合でも、c++filt
に頼ることはなくなりました。
-
macOS固有の問題への対処:
- コミットメッセージに示されているように、macOSに同梱されている
c++filt
のバージョンは非常に古く、Goのシンボルに対して誤ったデマングル処理を行っていました。これは、GoのシンボルがC++のシンボルマングリング規則に従っていないため、c++filt
がそれをC++のシンボルとして解釈しようとすると、予期せぬ結果(例: アドレスの欠落、誤った型情報の付加)が生じるためです。 - GoのバイナリはC++のコードを含まないため、Goのプロファイリングにおいて
c++filt
を使用する必要性は本来ありません。この変更は、不要な依存関係を削除し、特定の環境(macOS)でのツールの誤動作を防ぐためのクリーンアップと見なせます。
- コミットメッセージに示されているように、macOSに同梱されている
-
非macOSシステムへの影響:
- コミットメッセージにもあるように、macOS以外のシステムでは、
nm --demangle
が正常に機能することが多いため、この変更はそれらのシステムには影響を与えません。pprof
は引き続きnm --demangle
を使用してシンボルをデマングルしようとしますが、それが利用できない場合でもc++filt
にフォールバックすることはありません。Goのバイナリのプロファイリングにおいては、go tool nm
が最も正確なシンボル情報を提供するため、最終的にはそちらが優先されるべきです。
- コミットメッセージにもあるように、macOS以外のシステムでは、
この変更は、特定の環境におけるツールの脆弱性を排除し、Goのプロファイリングツールの堅牢性を向上させるための重要なステップでした。
コアとなるコードの変更箇所
変更はmisc/pprof
ファイルに対して行われています。
--- a/misc/pprof
+++ b/misc/pprof
@@ -81,6 +81,11 @@ use Getopt::Long;
my $PPROF_VERSION = "1.5";
+# NOTE: All mentions of c++filt have been expunged from this script
+# because (1) we don't use C++, and (2) the copy of c++filt that ships
+# on OS X is from 2007 and destroys nm output by "demangling" the
+# first two columns (address and symbol type).
+#
# These are the object tools we use which can come from a
# user-specified location using --tools, from the PPROF_TOOLS
# environment variable, or from the environment.
@@ -88,7 +93,6 @@ my %obj_tool_map = (
"objdump" => "objdump",
"nm" => "nm",
"addr2line" => "addr2line",
- "c++filt" => "c++filt",
## ConfigureObjTools may add architecture-specific entries:
#\"nm_pdb\" => \"nm-pdb\", # for reading windows (PDB-format) executables
#\"addr2line_pdb\" => \"addr2line-pdb\", # ditto
@@ -3093,9 +3097,7 @@ sub FetchSymbols {\
my $url = SymbolPageURL();
$url = ResolveRedirectionForCurl($url);\
my $command_line = "$CURL -sd '\@$main::tmpfile_sym' '$url'";\
- # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols.\
- my $cppfilt = $obj_tool_map{\"c++filt\"};\
- open(SYMBOL, "$command_line | $cppfilt |\") or error($command_line);\
+ open(SYMBOL, "$command_line |\") or error($command_line);\
ReadSymbols(*SYMBOL{IO}, $symbol_map);\
close(SYMBOL);\
}\
@@ -4790,7 +4792,6 @@ sub GetProcedureBoundaries {\
}\
\
my $nm = $obj_tool_map{\"nm\"};\
- my $cppfilt = $obj_tool_map{\"c++filt\"};\
\
# nm can fail for two reasons: 1) $image isn\'t a debug library; 2) nm\
# binary doesn\'t support --demangle. In addition, for OS X we need\
@@ -4799,27 +4800,21 @@ sub GetProcedureBoundaries {\
# in an incompatible way. So first we test whether our nm supports\
# --demangle and -f.\
my $demangle_flag = "";\
- my $cppfilt_flag = "";\
if (system("$nm --demangle $image >/dev/null 2>&1") == 0) {\
# In this mode, we do "nm --demangle <foo>"\
$demangle_flag = "--demangle";\
- $cppfilt_flag = "";\
- } elsif (system("$cppfilt $image >/dev/null 2>&1") == 0) {\
- # In this mode, we do "nm <foo> | c++filt"\
- $cppfilt_flag = " | $cppfilt";\
- };\
+ }\
my $flatten_flag = "";\
if (system("$nm -f $image >/dev/null 2>&1") == 0) {\
$flatten_flag = "-f";\
}\
\
- # Finally, in the case $imagie isn\'t a debug library, we try again with\
- # -D to at least get *exported* symbols. If we can\'t use --demangle,\
- # we use c++filt instead, if it exists on this system.\
+ # Finally, in the case $image isn\'t a debug library, we try again with\
+ # -D to at least get *exported* symbols. If we can\'t use --demangle, too bad.\
my @nm_commands = ("$nm -n $flatten_flag $demangle_flag" .\
- " $image 2>/dev/null $cppfilt_flag",\
+ " $image 2>/dev/null",\
"$nm -D -n $flatten_flag $demangle_flag" .\
- " $image 2>/dev/null $cppfilt_flag",\
+ " $image 2>/dev/null",\
# go tool nm is for Go binaries\
"go tool nm $image 2>/dev/null | sort");\
\
コアとなるコードの解説
このコミットは、Perlスクリプトであるmisc/pprof
からc++filt
への参照と使用を完全に排除しています。
-
obj_tool_map
からのc++filt
の削除:my %obj_tool_map = (...)
の部分で、pprof
が利用する外部ツールが定義されています。以前はここに"c++filt" => "c++filt"
というエントリがありましたが、これが削除されました。これにより、スクリプトはc++filt
を認識しなくなり、その後のコードで$obj_tool_map{"c++filt"}
を参照しようとすると未定義エラーが発生するか、単に空の値が返されるようになります。
-
FetchSymbols
サブルーチンでの変更:FetchSymbols
は、シンボル情報を取得する役割を担っています。- 変更前は、
my $cppfilt = $obj_tool_map{"c++filt"};
でc++filt
のパスを取得し、open(SYMBOL, "$command_line | $cppfilt |")
のように、取得したシンボルデータをc++filt
にパイプしてデマングルしようとしていました。 - 変更後は、
$cppfilt
変数の宣言と、c++filt
へのパイプ処理が削除され、open(SYMBOL, "$command_line |")
と直接シンボルデータを読み込むようになりました。これは、c++filt
が不要になったため、またはその使用が問題を引き起こすためです。
-
GetProcedureBoundaries
サブルーチンでの変更:GetProcedureBoundaries
は、プロシージャ(関数)の境界情報を取得するためにnm
コマンドを使用します。- 変更前は、
my $cppfilt = $obj_tool_map{"c++filt"};
でc++filt
のパスを取得していました。 - 重要な変更点として、
nm
が--demangle
オプションをサポートしているかどうかのチェックに加えて、以前はc++filt
が利用可能であればnm <foo> | c++filt
というフォールバックロジックがありました。具体的には、elsif (system("$cppfilt $image >/dev/null 2>&1") == 0)
のブロックと、それに続く$cppfilt_flag = " | $cppfilt";
の行が削除されました。 - これにより、
nm --demangle
が機能しない場合でも、c++filt
を使用するという選択肢が完全に排除されました。 - 最後に、
@nm_commands
配列の構築時にも、以前は$cppfilt_flag
が連結されていましたが、これも削除されました。これにより、nm
コマンドの実行時にc++filt
へのパイプが生成されなくなりました。
これらの変更は、pprof
スクリプトがGoのバイナリのシンボルを処理する際に、macOS上の古いc++filt
によって引き起こされる問題を根本的に解決することを目的としています。GoのバイナリはC++のシンボルを含まないため、c++filt
は不要であり、むしろ有害であることが示されたため、その使用を完全に停止するという判断が下されました。
関連リンク
- Go Issue #4818: https://github.com/golang/go/issues/4818
- Go CL 7729043: https://golang.org/cl/7729043 (このコミットに対応するGoの変更リスト)
参考にした情報源リンク
- GNU Binutils
c++filt
man page: https://linux.die.net/man/1/c++filt - GNU Binutils
nm
man page: https://linux.die.net/man/1/nm - Go プロファイリング (公式ドキュメント): https://go.dev/doc/diagnostics#profiling
- Go
pprof
ツール (公式ドキュメント): https://pkg.go.dev/cmd/pprof - シンボルマングリング (Wikipedia): https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%83%BB%E3%83%9E%E3%83%B3%E3%82%B0%E3%83%AA%E3%83%B3%E3%82%B0
- Goのシンボルと
go tool nm
に関する議論 (Stack Overflowなど): (具体的なURLは省略しますが、GoのシンボルがC++とは異なること、go tool nm
の必要性について言及されている情報源を参照しました。)