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

[インデックス 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_versc++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は、実行ファイルのシンボル情報を解析するためにnmc++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の不適切な挙動を回避するために、その使用を完全に停止した点にあります。

  1. c++filtの削除:

    • misc/pprofスクリプトは、obj_tool_mapというハッシュマップで利用可能なオブジェクトツールを定義していました。このマップから"c++filt" => "c++filt"のエントリが削除されました。これにより、スクリプトはもはやc++filtというツールを認識しなくなります。
    • スクリプト内のコメントで明示的に「c++filtに関するすべての言及は、このスクリプトから削除されました」と述べられています。これは、GoがC++を使用しないこと、そしてmacOS上のc++filtがシンボル情報を破壊するという二つの理由に基づいています。
  2. シンボル取得ロジックの変更:

    • FetchSymbolsサブルーチンでは、以前は$command_line | $cppfilt |のようにc++filtをパイプしてシンボルをデマングルしようとしていました。この変更により、単に$command_line |となり、c++filtを通さずにシンボルを取得するようになりました。
    • GetProcedureBoundariesサブルーチンでは、nmコマンドが--demangleオプションをサポートしているかどうかを最初に確認し、サポートしていればそれを使用します。以前は、--demangleが利用できない場合にnm <foo> | c++filtというフォールバックロジックが存在しました。このフォールバックパスが完全に削除されました。
    • 具体的には、$cppfilt_flagという変数が削除され、nm_commands配列の構築時に$cppfilt_flagが連結されていた部分が削除されました。これにより、pprofnm --demangleが機能しない場合でも、c++filtに頼ることはなくなりました。
  3. macOS固有の問題への対処:

    • コミットメッセージに示されているように、macOSに同梱されているc++filtのバージョンは非常に古く、Goのシンボルに対して誤ったデマングル処理を行っていました。これは、GoのシンボルがC++のシンボルマングリング規則に従っていないため、c++filtがそれをC++のシンボルとして解釈しようとすると、予期せぬ結果(例: アドレスの欠落、誤った型情報の付加)が生じるためです。
    • GoのバイナリはC++のコードを含まないため、Goのプロファイリングにおいてc++filtを使用する必要性は本来ありません。この変更は、不要な依存関係を削除し、特定の環境(macOS)でのツールの誤動作を防ぐためのクリーンアップと見なせます。
  4. 非macOSシステムへの影響:

    • コミットメッセージにもあるように、macOS以外のシステムでは、nm --demangleが正常に機能することが多いため、この変更はそれらのシステムには影響を与えません。pprofは引き続きnm --demangleを使用してシンボルをデマングルしようとしますが、それが利用できない場合でもc++filtにフォールバックすることはありません。Goのバイナリのプロファイリングにおいては、go tool nmが最も正確なシンボル情報を提供するため、最終的にはそちらが優先されるべきです。

この変更は、特定の環境におけるツールの脆弱性を排除し、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への参照と使用を完全に排除しています。

  1. obj_tool_mapからのc++filtの削除:

    • my %obj_tool_map = (...) の部分で、pprofが利用する外部ツールが定義されています。以前はここに"c++filt" => "c++filt"というエントリがありましたが、これが削除されました。これにより、スクリプトはc++filtを認識しなくなり、その後のコードで$obj_tool_map{"c++filt"}を参照しようとすると未定義エラーが発生するか、単に空の値が返されるようになります。
  2. 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が不要になったため、またはその使用が問題を引き起こすためです。
  3. 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は不要であり、むしろ有害であることが示されたため、その使用を完全に停止するという判断が下されました。

関連リンク

参考にした情報源リンク