XcodeでC++デバッグ時にSTLの関数にステップインするには
Xcode(というかlldb)で、C++のプロジェクトをデバッグするとき、通常ではSTLの関数にステップインできません。(ただし、後述するように、ステップインできるものもあります)
つまり、デバッグ時に
std::shared_ptr<MyClass> p = std::make_unique<MyClass>(a, b, c);
のstd::make_unique
関数や、
std::function<void(int, int)> f = [](int n, int m) {
std::cout << n + m << std::endl;
};
f(10, 20);
のstd::function
の関数呼び出し演算子の中にステップインしようとしても、自動的にステップオーバーされてしまい、呼び出し階層を潜っていくことができません。
MyClassのコンストラクタやf
に設定した関数の中にブレークポイントを仕掛けておけば、そこで実行が止まるので、それを利用する手もあります。しかし、ステップインするときに予めブレークポイントを仕掛けておかないといけないのは不便です。また、std::function
にいろいろな関数を設定する状況では、どの関数が実際に呼ばれるのかは、ステップインしてみないと分からない場合もあります。
LLDBの設定を変更する
関数呼び出しを自動的にステップオーバーする挙動は、LLDBのtarget.process.thread.step-avoid-regexp
というオプションによって制御されています。このオプションに正規表現のパターンを設定しておくと、そのパターンにマッチする関数へのステップインが無視されて、自動でステップオーバーされるようになります。
Xcodeでは、Shift-Command-C
というショートカットでLLDBのコンソールが表示できるので、その画面上で、このオプションの設定値を変更できます。
(lldb) settings show target.process.thread.step-avoid-regexp
target.process.thread.step-avoid-regexp (regex) = ^std::
(lldb) settings set target.process.thread.step-avoid-regexp ""
上は、LLDBのコンソール画面で、target.process.thread.step-avoid-regexp
オプションを変更したときのログです。先頭に(LLDB)
とついている行がコマンドを実行した行で、先頭に何もついていない行が、実行結果を表しています。
最初に、lldb
のsettings show
コマンドによって、このパラメータの値を確認しています。Xcode起動時には、lldbのデフォルト値として、ここに^std::
が設定されているため、既定でstd
名前空間にある関数やクラスのメンバ関数がスキップされるようになっています。
次のsettings set
コマンドでは、これに空文字列""
を設定しています。これによって、どの関数もスキップせずにステップインできるようになります。(空文字列のときは特別に二重引用符を使用していますが、それ以外の文字列を指定するときは、二重引用符は必要ありません。空文字列を指定する時以外で二重引用符を付けると、それもパターンの一部と認識されてしまいます。)
ここには正規表現を記述できるので、以下のように独自のパターンを設定できます。ただし、先読み/戻り読みのような拡張した文法は使用できないようです。(最新のLLDBではわかりませんが)
-
^(std::|boost::)
- STLだけでなく、
boost
ライブラリもスキップする。
- STLだけでなく、
-
^std::(__1::)?([^_f]|f[^u]|fu[^n]|fun[^c])
- STLをスキップするが、
std::function
はスキップしない
- STLをスキップするが、
これをもとの状態に戻すには、
settings set target.process.thread.step-avoid-regexp ^std::
のようにして、^std::
を再度設定します。
このオプションの設定値は、デバッグ実行中に動的に変更できます。
そのため、特定の箇所をデバッグしている時にだけ手動で値を変更して、ステップインする必要のない箇所をスキップするように設定すると、効率的にデバッグができます。
また、~/.lldbinit
や~/.lldbinit-Xcode
というファイルを用意しておき、そのファイルに設定を記載しておけば、デバッガ起動時にその設定を自動で有効にできます。
settings set target.process.thread.step-avoid-regexp ^(std::|boost::)
注意点
一つ注意点として、関数テンプレートに対しては、正規表現とのマッチがうまくいかない場合があります。
上記のオプションに設定した正規表現とマッチを試行する関数の名前は、関数のマングリング(mangling)されたシンボル名をデマングル(demangle)したものになります。関数テンプレートのシンボル名には、通常の関数と異なり、戻り値の型の情報も含まれているため、それをdemangleした時には、関数名の前に戻り値の型が現れます。
namespace ns1 {
int foo() { return 0; } // __ZN3ns13fooEv
template<class T>
int bar() { return 0; } // __ZN3ns13barIiEEiv
}
% c++filt __ZN3ns13fooEv
ns1::foo()
% c++filt __ZN3ns13barIiEEiv
int ns1::bar<int>()
このように、関数テンプレートであるbar()
は、戻り値の型の情報をシンボル名に含んでいるため、デマングルした文字列の先頭に、戻り値の型であるint
が含まれるようになります。
このため、ns1
名前空間にある関数を除外しようと思って、正規表現に^ns1::
を指定しても、bar()
関数はそのパターンにはマッチせず、ステップインできてしまう、ということになります(ステップインしてしまったならステップアウトすればいいだけの話ではありますが)。
冒頭で言及した、
通常ではSTLの関数にステップインできません。(ただし、後述するように、ステップインできるものもあります)
というのも、つまりこのことで、デフォルトで^std::
が設定されているにもかかわらず、特定の関数(関数テンプレートであり、戻り値の型がstd::
名前空間以外で定義された型になるもの)については、ステップインできる、ということになります。
参考サイト
- https://stackoverflow.com/questions/19413181/step-into-stl-sources-in-xcode-5 (元ネタ)
- https://qiita.com/dealforest/items/e3a5284badd17733ccc1 (lldbinitファイルについて)
- https://www.slideshare.net/Cryolite/lambda-in-templatefinal (関数テンプレートのシンボル名に戻り値の型が含まれている理由について)
- http://itanium-cxx-abi.github.io/cxx-abi/abi.html (5.1.3と5.1.5.3に、シンボル名に戻り値の型が含まれるケースについての記載がある)
補足
std::function
の関数呼び出し演算子にステップインできるようになっても、引数の適用とかでステップ数が多いので、std::function
に設定した関数に到達するまで何度もステップインし続ける必要があって、実は結構面倒です。
なので結局、std::function
に設定する関数に直接ブレークポイント仕掛けられるならそのほうが早いことが多いです。