strace 入門 — システムコールを覗いて障害を切り分ける
strace とは何か?
プロセスが発行するシステムコール(カーネルへの処理要求)とシグナルをリアルタイムで記録・表示するデバッグツール。ファイルを開く・ネットワーク接続する・プロセスを fork するといった動作をすべて"生"で観測できる。
どんなときに使うか
- ログに何も出ない、エラーメッセージが不親切なとき
- どの設定ファイルが実際に読み込まれているか不明なとき
- 「Permission denied」が発生するがどのパスが拒否されているか不明なとき
- ネットワーク接続が失敗する原因を追いたいとき
なぜ strace が障害切り分けで役立つのか?
アプリケーションはどれほど複雑でも、最終的にはカーネルが提供するシステムコールを通じてハードウェアや OS リソースにアクセスする。strace はその接点を直接監視するため、ソースコードがなくても「プロセスが実際に何をしようとして失敗したか」を確認できる。
errno: ENOENT(ファイルが存在しない)、errno: EACCES(権限なし)、errno: ECONNREFUSED(接続拒否)といったカーネルレベルのエラーが直接見える。ログに「エラーが発生しました」としか出ない場合でも、strace なら根本原因まで到達できる。
どうやってインストールするのか?
多くのディストリビューションには標準で含まれているが、なければパッケージマネージャで導入する。
# Ubuntu / Debian sudo apt install strace # RHEL / CentOS / Fedora sudo dnf install strace
インストール確認:
strace --version
strace -- version 6.x ...
基本的な使い方
コマンドを直接追跡する
strace ls /tmp
大量の出力が流れる。最後の数行に終了ステータスが表示される。
execve("/bin/ls", ["ls", "/tmp"], 0x7fff... /* 20 vars */) = 0
brk(NULL) = 0x55d3e...
...
openat(AT_FDCWD, "/tmp", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
...
+++ exited with 0 +++
実行中プロセスにアタッチする
sudo strace -p PID
PID は ps aux や pgrep で取得する。
# プロセス名から PID を探す pgrep nginx # アタッチ sudo strace -p 12345
-p でのアタッチには通常 root 権限が必要(自分のプロセスは不要)。本番サービスにアタッチするとパフォーマンスに影響するため注意。
子プロセスも追跡する
マルチプロセスのサービスでは -f が必須。
strace -f -p PID
特定のシステムコールだけ追跡するには?
出力が膨大なため、-e trace= で絞り込む。
strace -e trace=openat ls /tmp
よく使うフィルタ:
| フィルタ | 追跡対象 |
|---|---|
openat |
ファイルを開く |
read,write |
読み書き |
connect |
ネットワーク接続 |
execve |
プロセス起動 |
file |
ファイル系全般 |
network |
ネットワーク系全般 |
# ファイルアクセスだけ追跡 strace -e trace=file command # ネットワーク接続だけ追跡 strace -e trace=network command
strace の出力をどう読み解くか?
基本フォーマット:
システムコール名(引数...) = 戻り値
成功例:
openat(AT_FDCWD, "/etc/hosts", O_RDONLY) = 3
失敗例(ENOENT — ファイルなし):
openat(AT_FDCWD, "/etc/myapp.conf", O_RDONLY) = -1 ENOENT (No such file or directory)
失敗例(EACCES — 権限なし):
openat(AT_FDCWD, "/root/secret", O_RDONLY) = -1 EACCES (Permission denied)
戻り値の読み方
= 0以上: 成功(ファイルディスクリプタ番号や処理済みバイト数)= -1: 失敗。続くENOENT等が具体的な理由
定番の障害切り分けパターン
パターン1:設定ファイルが読み込まれない
strace -e trace=openat myapp 2>&1 | grep "ENOENT"
openat(AT_FDCWD, "/etc/myapp/config.yaml", O_RDONLY) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/user/.myapp/config.yaml", O_RDONLY) = -1 ENOENT (No such file or directory)
アプリが探しているパスを直接確認できる。
パターン2:Permission denied の原因を特定する
strace -e trace=openat myapp 2>&1 | grep "EACCES"
openat(AT_FDCWD, "/var/run/myapp.pid", O_WRONLY|O_CREAT) = -1 EACCES (Permission denied)
どのパスに書き込もうとして拒否されたかが判明する。
パターン3:接続失敗の原因を確認する
strace -e trace=network myapp 2>&1 | grep -E "connect|ECONNREFUSED"
connect(3, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)
ポート番号やアドレスが意図通りか確認できる。
パターン4:出力をファイルに保存して後で解析する
strace -o /tmp/trace.log -f myapp grep ENOENT /tmp/trace.log
-o でファイル保存すると stderr が汚れず、後から grep や less で調査しやすい。
実務で役立つオプション一覧
| オプション | 効果 |
|---|---|
-p PID |
実行中プロセスにアタッチ |
-f |
fork/clone した子プロセスも追跡 |
-e trace= |
追跡するシステムコールを絞る |
-o ファイル |
出力を stderr ではなくファイルへ |
-t |
各行の先頭に時刻を表示 |
-tt |
マイクロ秒精度のタイムスタンプ |
-T |
各システムコールの所要時間を表示 |
-c |
統計サマリを表示(回数・時間・エラー) |
-s 256 |
文字列の最大表示長を変更(デフォルト 32) |
-q |
"Attaching..." 等のメッセージを抑制 |
-c での統計確認
strace -c ls /tmp
% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 35.14 0.000147 14 10 mmap 18.42 0.000077 11 7 openat ... ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000418 48 2 total
どのシステムコールに時間がかかっているか一覧で把握できる。