本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはMobageオープンプラットフォームの開発に携わっている横江直輔(zentooo)さんで、テーマは「Perlプログラマのためのstrace入門」です。
strace──システムコールレベルでのデバッグツール
straceとは何か
straceとは、manによれば「システムコールとシグナルをトレースする(ためのツール)」です。システムコールとシグナルはいずれも重要なAPIですが、本稿ではアプリケーション内で発行されているシステムコールをトレースするためのツールとしてのstraceに注目します。
システムコールとは何か
straceでシステムコールの流れをトレースするにあたりシステムコールとは何かという話題を避けては通れませんので、ここで簡単に説明しておきます。
Linuxを始めとする現代のOSでは、私たちユーザが作ったアプリケーションが直接ディスクを読み書きしたり、ネットワーク通信を行うことは、一部の例外を除いてありません。アプリケーションのコードだけを見ると外部のリソースを直接操作しているように思える部分も、実際はアプリケーションはOSに「こういう処理をしてね」とお願いし、OSに働いてもらって、その結果を受け取るという動作をしています。この「こういう処理をしてね」とOSにお願いする機能を提供しているのがシステムコールです。ユーザが作成したプログラムに対してOSが公開しているAPIがシステムコールである、とも言えます。また、我々の書いたアプリケーションが低いレベルにおいて「何をできるか」の限界はOSが定めている、とも言えます。
たとえば、多くのWebアプリケーションで行われている「ログをファイルに書く」という操作では、open(2)、lseek(2)、write(2)、close(2)などのシステムコールが呼ばれます。なお、一般に各種システムコールを表記する際にはwrite(2)のようにmanのセクション番号を付けることが多いです。本稿でも一般名詞・動詞などと区別するためにこの表記を採用します。
すべてのシステムコールがそうというわけではありませんが、「アプリケーションはOSの提供するAPIの上で、さまざまなリソースを借りたうえで処理を行っている」というモデルで考えると、しっくりくる部分が多いかと思います。アプリケーションで必要になる「借りる」操作をシステムコールに対応させていくつか挙げてみると、次のようになります。
- 自分と同じプロセスを複製したい
- →fork(2)を使って新しいプロセスを「借りる」
- メモリを確保したい
- →brk(2)を使ってメモリ空間を「借りる」
- ネットワーク通信がしたい
- →socket(2)を使ってネットワークスタックの機能を「借りる」
- ディスクに書き込みたい
- →open(2)を使ってディスクデバイスを「借りる」
なお、本稿では各種システムコールのリファレンス的な意味における詳細な説明は行いません。必要に応じて手元のLinux環境でmanを参照するか、『ふつうのLinuxプログラミング』(注1)、『The Linux Programming Interface』(注2)などを参照してください。
straceの使い方
本節では、straceのコマンドラインツールとしての使い方、出力結果、オプションなどについて説明します。
straceはCentOSやDebian GNU/Linuxなど業務で使われることの多いLinuxディストリビューションではデフォルトでインストールされていると思いますが、もしシステム管理の都合上インストールしていないなどの場合は、システム管理者にかけあってインストールしてもらうなどしてください。
最もシンプルな使い方
straceの最もシンプルな使い方は以下です。$cmd
の部分には実行可能なコマンドが入ると考えてください。
たとえば$cmd
がcat hoge
であった場合、上記コマンドの実行結果の出力は次のようになります(hogeというファイルには「This is hoge」という文字列が書かれているものとします)。
なんとなく「cat hoge
がやっていそうな内容だなぁ」ということがおわかりいただけるかと思います。この場合、strace経由で起動したコマンドに対してstraceはattachしており、そのコマンドが発行したシステムコールを見ることができます。
straceは、最も基本的な情報として各システムコールの引数、戻り値を表示します。各システムコールへの引数は見たままの値であり、戻り値は各行の右側に=
付きで表記されています。たとえば上記の例において、最初のopen(2)の引数はhoge
およびO_RDONLY
であり、その戻り値は3
となります。
既存プロセスへのattach
特にWebアプリケーションのデバッグ目的の場合、すでに立ち上がっているプロセスのトレースを行いたい場合のほうが多いと思います。その場合は次のようにコマンドラインオプションを指定します[3]。
表示される内容は先ほどの例とほぼ同じなので、出力例は割愛します。事実上、straceを利用する多くの場面では-p付きで実行することになります。
straceのコマンドラインオプション
ここでは、straceのよく使うコマンドラインオプションについて解説します。
-ttオプション──タイムスタンプの表示
-ttオプションを指定することで、各システムコールにおおむねどのくらいの時間がかかっているかをマイクロ秒単位で表示できます。
「おおむね」と書いたのは、-ttオプションで表示されるのは「あるシステムコールを実行した時点での時間」であり、それらの差分を見ることで「おおむね」の実行時間を知ることができるという意味においてです。たとえば次の出力例で2行目のioctlと1行目のacceptの行の左端に表示されている時間(ともに太字)の差分を取ると4.4秒くらいになりますが、これは「acceptを実行し、ブロックして、リクエストを受け付け、次にioctlを実行する」までの時間です。これを「実行時間」と呼ぶのはいささか乱暴なので、筆者は「差を見るとおおよその実行時間がわかる」と言うことにしています。
straceには-tt以外にも時間表示用のオプションがありますが、-ttが最もヒューマンリーダブルかつある程度時間の粒度が細かく、またアプリケーションのアクセスログなどと付き合わせる際にも使いやすい出力になるので、筆者はいつも-ttを使っています。
-sオプション──表示する文字列の最大長の指定
-sオプションで数値を指定することにより、read(2)やwrite(2)に渡されているバッファの中身を省略せずに見ることができます(-ttオプションの出力例の最後の行を見ると「...」となっている部分がありますが、そこがまさに省略された部分です)。このオプションは、strace を使って実際のHTTP 通信の中身や、memcachedなどのミドルウェアとの通信の中身を詳しく見たい場合に利用します。
次の出力例では、-ttオプションの出力例では省略されていたHTTPヘッダの内容がすべて表示されていることがわかります。
利用上の注意
筆者の経験上、straceでattachすることによって著しいパフォーマンスの劣化などが起こることはありません。ただし、サービスの本番環境などでサーバプロセスにattachするような場合は、事前にシステム管理者などに問題ないかを確認してから慎重に利用することをお勧めします。
<続きの(2)はこちら。>