Processor Trace を使ってデバッグ時に詳細なトレースを取得する

Broadwell 世代から、いくつかのCPUではProcessor Traceと呼ばれる機能が付いています。簡単に使いかたを説明します。

Intel Processor Trace

それなりの規模のCPUにはプロセッサ内で発生したイベントを収集する仕組みがあります。よく使うものだと、パフォーマンスカウンタのイベント等ですね。

Haswell以前では、これらのイベントは割り込みを経由して収集されていました。ただ、割り込み経由だと、それなりにオーバーヘッドが大きくなってしまうので、
全てのイベントを収集することは難しく、サンプリングベースのものが主流でした。

Intel Processor Trace は、メモリ経由で、CPU内のイベントを収集する仕組みです。
これを使うと、メモリ経由で大量のイベントを受け取れるようになるので、イベントをサンプリングではなく、全て収集することが可能になります。
また、トレース一個あたりのオーバーヘッドが少ないので、実行中に発生する分岐の履歴なども取得できるようになっており、これをデバッグに活用することも可能です。

今回は、gdb 経由で、このProcessor Traceを使ってみます。
あわせて、 IDF2015の資料一覧から、SPCS012 Zoom in on Your Code with Intel(R) Processor Trace and Supporting Tools も参照するとよいかもしれません。

環境

Linux 4.2 以降、 GDB 7.10 以降の比較的新しい環境が必要です。

GDB でProcessor Traceを有効にするには、GDBのビルド時にintel-pt.hがincludeできる必要があります。(“internal-error: Unexpected branch trace format.”と出るときは有効になっていません)

intel-pt.h は https://github.com/01org/processor-trace に含まれているので、先にビルドしておきます。

使いかた

以下で使っているコードを https://bitbucket.org/fixstars/blog/src/master/small_test/processor-trace.c に置いておきます。最適化で消えないようにするため少し無理な処理が入っていますが、関数の深くでSEGVする処理です。

まずビルドします。事前準備は必要ありません。いつもどおりにビルドすれば良いです。シンボルが必要なら -g を付けましょう。

GDB を起動します。

実行を開始し、トレースを取得したい箇所まで進めます。今回は main から全部取ってもよいので start します。

目的のところまで実行が進んだら record pt を実行します。record はレコード開始コマンド、pt は Intel Processor Traceでレコードを取得するという意味です。

これで準備終わりです。実行を続けます。

SEGV しました。この時点で、ここまでの実行履歴がメモリに置かれています。

“record function-call-history” で、関数呼び出しの履歴がとれます。引数に /c を付けると、呼び出し履歴をツリーにして表示できます。

表示するカーソルがあり、 /c のあとに – を付けると逆方向に、+を付ける(もしくは何も付けない)と、順方向にカーソルが進みます。 45,55のようにすると、45〜55番目の履歴を表示できます

“record instruction-history” で、実行した命令の履歴がとれます。reverse-step すると、この履歴を逆方向に辿っていけます。
(副作用は保存されていないので、既存のGDBのReverse Debugのように、変数の状態が復元されるわけではないです

比較

トレースの機能自体はそれほど新しいものではなく、GDBにもこの機能はありました。また、組み込みCPUにも似たような機能はあります。

これらと比較した時の利点は

  • 今のデスクトップCPUの速さでトレースが収集できる
  • ハードウェア実装なのでデバッグシンボルが無くても呼び出し履歴が取れる

と、いったところでしょうか。

今回使った例では、SEGVする前に10万回ほどループを回しているのですが、これは GDB のソフトウェア実装のトレースだと、15秒ぐらいかかります。
一方、Processor Traceだと、これは一瞬で終わります。

シンボル情報を必要としないので、”gcc -O2 processor-trace.c -s -fno-asynchronous-unwind-tables”のようにコンパイルされたバイナリでも、いくらかの情報が得られるようになります。

/i を付けると、対応するinstruction history の範囲を取得できます。 record instruction-history でその範囲を指定すると対応する命令が取得できます

この機能は、JITコンパイルして出した機械語をデバッグする時などに有効かもしれません。

まとめ

Processor Trace の紹介と GDB から使う方法でした。

まあ、デバッグ時にぱっと適切に使うのも難しい気もしますが、よくわからない理由でシンボルが見えなくてバックトレース表示できないとかそういう時に思い出すと何かの役に立つかもしれないです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です