strace は「そのコマンドがOSに対して何を頼んだか」を1行ずつ表示するコマンドです。ログにも画面にも理由が出ないのに失敗する、どのファイルを読んでいるか分からない、どこで固まっているか分からない——こうした「外から見ても分からない」状況を、内側から見えるようにします。
使い方自体は簡単で、調べたいコマンドの前に strace を付けるだけです。
strace ls # 画面に出しながら追跡する
strace -o trace.log ls # ファイルに保存する(実務ではこちら)
strace -c ls # 何のシステムコールが何回出たか集計する
この記事ではUbuntu 24.04の実機で strace 6.8 を動かし、実際の出力を見ながら「読み方」と「絞り込み方」を解説します。あわせて、多くの入門記事が触れていない2点——Ubuntu/Debianで strace -p PID がほぼ必ず失敗する理由と、strace を付けると実行時間が何倍になるかの実測——も扱います。
- straceコマンドとは|プログラムとカーネルの境界を記録するツール
- straceが入っていない場合のインストール
- straceコマンドの主要オプション一覧
- 目的別の逆引き|何を調べたいときにstraceを使うか
- 実践1|コマンドがどの設定ファイルを読んでいるか特定する
- 実践2|Permission deniedがどのファイルで起きたか1行で突き止める
- 実践3|-cで全体像とエラー件数をつかむ
- 実行中のプロセスに-pで接続する|Ubuntu・Debianで失敗する原因
- 子プロセスも追う|-fと-ff
- 出力を読みやすくする|-tt・-T・-s・-y
- 本番環境での注意|straceを付けると実行時間は約16倍になる
- straceで分からないとき|lsof・ltrace・perfとの使い分け
- straceコマンドのよくある質問
- まとめ
- 参考
- 関連記事
straceコマンドとは|プログラムとカーネルの境界を記録するツール
プログラムは、ファイルを開く・ネットワークに接続する・時刻を取得するといった処理を自力ではできません。すべてカーネルに依頼して代行してもらいます。この依頼のことをシステムコールと呼び、strace はその依頼と返事だけを横から記録します。
実際に ls を追跡した先頭部分です。
strace -o base.log ls
head -5 base.log
execve("/usr/bin/ls", ["ls"], 0x7ffd12e430e0 /* 21 vars */) = 0
brk(NULL) = 0x647d7e99f000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7cba8c555000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
1行の形は常に同じで、「システムコール名(引数, …) = 戻り値」です。読むべき場所は決まっています。
| 見る場所 | 意味 | 例 |
|---|---|---|
| 左端の名前 | カーネルへの依頼の種類 | openat=ファイルを開く |
| 括弧の中 | 依頼の中身(パスやモード) | "/etc/ld.so.cache", O_RDONLY |
= の右 | 結果。0以上なら成功 | = 3(ファイルディスクリプタ3番) |
= -1 の後ろ | 失敗の理由(errno) | ENOENT (No such file or directory) |
トラブル調査で最初に探すのは = -1 の行です。ここに書かれた ENOENT(ファイルが無い)や EACCES(権限が無い)が、失敗の一次原因そのものです。
なお ls のようなごく小さなコマンドでも、システムコールは76行出ました。strace の出力は最初から多すぎるのが普通で、実務では後述の絞り込みとセットで使います。
straceが入っていない場合のインストール
最小構成のサーバーやコンテナには入っていないことがあります。ディストリビューションごとのパッケージ名はいずれも strace です。
sudo apt install strace # Ubuntu / Debian
sudo dnf install strace # RHEL / AlmaLinux / Rocky / Fedora
strace -V # 導入確認
strace -- version 6.8
後述する --seccomp-bpf のような比較的新しいオプションは版に依存するため、挙動が記事と違うときはまず strace -V を確認してください。
straceコマンドの主要オプション一覧
数十個のオプションがありますが、実務で使うのは次の範囲でほぼ足ります。
| オプション | 働き | 使いどころ |
|---|---|---|
-o ファイル | 結果をファイルに出す | ほぼ必須。画面が埋まるのを防ぐ |
-e trace=名前 | 特定のシステムコールだけに絞る | openat や connect だけ見たいとき |
-c | 個別行を出さず回数と時間を集計 | 全体像・エラー総数の把握 |
-f | 子プロセス・スレッドも追う | シェルスクリプトやデーモン |
-ff -o 名前 | プロセスごとに別ファイルへ | 子が多くて混ざるとき |
-p PID | 実行中のプロセスに後から接続 | すでに固まっている相手 |
-t / -tt | 時刻を付ける(-ttはマイクロ秒) | ログと時刻を突き合わせる |
-T | 各システムコールの所要時間 | どこで待たされたかを見る |
-s 文字数 | 文字列の表示長を変える(既定32) | 通信内容やパスが切れるとき |
-y | fd番号に対応するパスを併記 | 「3番って何のファイル?」の解消 |
目的別の逆引き|何を調べたいときにstraceを使うか
strace は「とりあえず流す」ツールではありません。調べたいことを先に決めてから、対応する絞り込みを付けるのが正しい使い方です。
| 調べたいこと | 打つコマンド |
|---|---|
| どの設定ファイルを読んでいるか | strace -e trace=openat -o t.log コマンド |
| 権限エラーがどこで出ているか | strace -o t.log コマンド → grep EACCES t.log |
| 存在しないファイルを探していないか | grep ENOENT t.log |
| どこで固まっているか | strace -p PID(最後の行が犯人) |
| どこで時間を使っているか | strace -c -o t.log コマンド |
| 通信相手はどこか | strace -e trace=connect -o t.log コマンド |
| 子プロセスが何を起動しているか | strace -f -e trace=execve -o t.log コマンド |
以下、よく使う3パターンを実際の出力で見ていきます。
実践1|コマンドがどの設定ファイルを読んでいるか特定する
「設定を変えたのに反映されない」という場面では、そのコマンドが本当にその設定ファイルを読んでいるのかを確認するのが最短です。ファイルを開くシステムコール openat だけに絞ります。
strace -e trace=openat -o date.log date
grep -vE 'ld.so|libc' date.log
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 3
+++ exited with 0 +++
date が時刻の表示にあたって /etc/localtime を読んでいることが分かります。タイムゾーンを変更したのに反映されない場合、変更すべき実体はこのファイルだと、推測ではなく出力で確定できます。ライブラリ読み込みの行はノイズなので grep -v で落としています。
同じ手順は、設定ファイルの場所が分からないミドルウェアにもそのまま使えます。「マニュアルに書かれた場所」ではなく「実際に開いた場所」が分かるのが strace の価値です。
実践2|Permission deniedがどのファイルで起きたか1行で突き止める
権限エラーは、メッセージだけでは「どのファイルで拒否されたか」が分からないことがあります。読めないファイルを cat して確かめます。
mkdir -p secret && echo hi > secret/data.txt && chmod 000 secret/data.txt
strace -e trace=openat -o perm.log cat secret/data.txt
grep EACCES perm.log
openat(AT_FDCWD, "secret/data.txt", O_RDONLY) = -1 EACCES (Permission denied)
拒否されたパスと、その時の開き方(O_RDONLY=読み込み)が1行で確定します。設定ファイルを何十個も読むプログラムでは、この1行を見つけられるかどうかが調査時間を大きく変えます。
拒否された場所が分かった後の直し方——所有者・親ディレクトリ・ACL・マウントオプションのどれが原因かの切り分け——は、Permission deniedの原因と直し方|6つの切り分け手順にまとめています。
実践3|-cで全体像とエラー件数をつかむ
個別の行を追う前に -c で集計すると、どこを見るべきかの当たりが付きます。
strace -c -o summary.log ls
cat summary.log
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
41.44 0.000092 5 18 mmap
13.06 0.000029 5 5 mprotect
8.11 0.000018 2 7 openat
6.76 0.000015 15 1 munmap
5.86 0.000013 1 9 close
4.50 0.000010 5 2 2 statfs
------ ----------- ----------- --------- --------- ----------------
100.00 0.000222 3 74 6 total
注目すべきは右から2列目の errors 列です。ここに数字が立っているシステムコールが、失敗を返しているものです。上の例では statfs が2回とも失敗しています。
失敗するコマンドで実行すると、この列の意味がはっきりします。
strace -c -o c2.log cat secret/data.txt nofile.txt
grep -E 'openat|total' c2.log
0.00 0.000000 0 18 14 openat
100.00 0.000000 0 66 15 total
18回のファイルオープンのうち14回が失敗しています。「エラーはファイルオープンで起きている」とここで方向を定めてから、-e trace=openat で個別行を見に行く——これが出力量に飲まれない進め方です。
実行中のプロセスに-pで接続する|Ubuntu・Debianで失敗する原因
すでに動いている(固まっている)プロセスは -p PID で後から追跡できます。ところが Ubuntu や Debian の標準設定では、これがほぼ必ず失敗します。
sleep 300 &
strace -p $!
strace: attach: ptrace(PTRACE_SEIZE, 115422): Operation not permitted
自分が起動した、自分の権限のプロセスなのに拒否されます。原因は権限(sudoの有無)ではなく、Yama というカーネルのセキュリティ機能です。値を確認します。
cat /proc/sys/kernel/yama/ptrace_scope
1
| 値 | 追跡できる相手 | 備考 |
|---|---|---|
0 | 同一ユーザーのプロセスすべて | 従来の挙動 |
1 | 自分の子孫プロセスだけ | Ubuntu・Debianの既定値 |
2 | 管理者権限がある場合のみ | より厳しい設定 |
3 | 誰も追跡できない | 再起動するまで戻せない |
既定の 1 では、strace 自身が親でないプロセスには接続できません。上の例は sleep がシェルの子であって strace の子ではないため、条件を満たさずに弾かれています。対処は2つです。
# 対処A: 管理者権限で実行する(推奨・その場限り)
sudo strace -p PID
# 対処B: 一時的に制限を外す(再起動で元に戻る)
sudo sysctl -w kernel.yama.ptrace_scope=0
対処Bはサーバー全体の保護を下げる変更です。他プロセスのメモリを覗ける状態になるため、共用サーバーや本番環境では対処Aを使い、Bを使う場合も調査後に =1 へ戻してください。/etc/sysctl.d/ に書いて永続化するのは、開発用マシン以外では避けるのが無難です。
なお strace コマンド の形で最初から起動する場合は strace 自身が親になるため、この制限には一切かかりません。「起動から追う」なら設定変更は不要、「動いているものに後から付く」なら権限が要ると覚えておくと迷いません。Dockerコンテナ内で同じエラーが出る場合は、コンテナに --cap-add=SYS_PTRACE が必要です。
子プロセスも追う|-fと-ff
シェルスクリプトやデーモンは、実際の処理を子プロセスにやらせます。-f を付けないと親だけを見て「何もしていない」と誤読します。
strace -f -e trace=execve -o exec.log sh -c 'ls -1 /tmp/stracelab | head -2'
grep execve exec.log
115618 execve("/usr/bin/sh", ["sh", "-c", "ls -1 /tmp/stracelab | head -2"], 0x7fff0b7f9718 /* 21 vars */) = 0
115619 execve("/usr/bin/ls", ["ls", "-1", "/tmp/stracelab"], 0x640b8ac53898 /* 21 vars */ <unfinished ...>
115620 execve("/usr/bin/head", ["head", "-2"], 0x640b8ac538c8 /* 21 vars */ <unfinished ...>
行頭の数字がPIDで、パイプの左右がそれぞれ別プロセスとして起動していることが見て取れます。<unfinished ...> は「別プロセスの行が割り込んだので、続きは後の行にある」という意味です。
子が多くて読みにくいときは -ff を使うと、-o で指定した名前にPIDを付けてプロセスごとに分割保存されます。
strace -ff -o out ./script.sh # out.115618, out.115619 ... と分かれる
出力を読みやすくする|-tt・-T・-s・-y
既定の出力は情報が省かれています。目的に応じて足します。
時刻と所要時間を出す(-tt -T)。「どこで待たされたか」は所要時間で分かります。
strace -tt -T -e trace=clock_nanosleep -o t.log sleep 0.2
cat t.log
17:49:45.102750 clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=200000000}, 0x7ffd6c137ff0) = 0 <0.200377>
行末の <0.200377> が、そのシステムコールの中に留まっていた秒数です。応答が遅いプログラムでは、この数字が大きい行が待ちの正体になります。
文字列の切り詰めを解除する(-s)。既定では文字列は32文字で打ち切られ、末尾が "..." になります。
strace -e trace=write -o s1.log sh -c 'echo ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij'
strace -s 100 -e trace=write -o s2.log sh -c 'echo ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij'
write(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"..., 47) = 47 ← 既定(32文字で切れる)
write(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghij\n", 47) = 47 ← -s 100
末尾の = 47 は実際に書き込まれたバイト数で、こちらは切り詰められません。「表示が32文字で切れているだけで、データは欠けていない」と読み分けてください。長いパスやHTTPリクエストを見るときは -s 200 程度を付けます。
fd番号をパスに変換する(-y)。read(3, ...) の「3」が何のファイルか、既定では分かりません。
echo "hello strace" > sample.txt
strace -y -e trace=read -o y.log head -1 sample.txt
read(3</tmp/stracelab/sample.txt>, "hello strace\n", 8192) = 13
番号の隣にパスが併記されます。同じ3番が処理の途中で別のファイルに使い回されることもあるため、複数ファイルを扱うプログラムでは -y をほぼ必須と考えてよいです。開いているファイルをプロセス単位で一覧したい場合は、lsof – プロセスが開いているファイル(ソケット含む)を一覧表示すると併用します。
本番環境での注意|straceを付けると実行時間は約16倍になる
strace はシステムコールのたびに対象プロセスを止めて記録するため、相応に遅くなります。同じ処理を3回ずつ実行して比較しました(対象:find /usr/share/doc -type f、ファイル6,224件)。
/usr/bin/time -f "%e" find /usr/share/doc -type f > /dev/null
/usr/bin/time -f "%e" strace -o /dev/null find /usr/share/doc -type f > /dev/null
| 実行方法 | 実行時間 | 素の実行との比 |
|---|---|---|
| strace なし | 0.03秒 | 1.0倍 |
strace -o /dev/null | 0.47〜0.48秒 | 約16倍 |
strace -e trace=openat | 0.37〜0.38秒 | 約12倍 |
このとき記録されたシステムコールは25,150行でした。注意したいのは、-e trace= で絞っても速度はあまり戻らない点です。絞り込みは「出力を読みやすくする」ためのもので、「軽くする」ためのものではありません(strace 6.8 では --seccomp-bpf を併用しても、この条件では有意差は出ませんでした)。
したがって本番環境では次の前提で使います。
-p で付けると、体感で分かるレベルの遅延が発生すると考える-o でファイルへ。端末に出すと表示処理でさらに遅くなる-o の出力先の空き容量を先に確認するperf や eBPF系のツール)を検討するstraceで分からないとき|lsof・ltrace・perfとの使い分け
strace が見せるのはカーネルへの依頼だけです。プログラム内部の計算やライブラリ内で完結する処理は映りません。目的がずれていると、いくら眺めても答えは出てきません。
| 知りたいこと | 適したツール | 理由 |
|---|---|---|
| OSに何を頼んだか(ファイル・通信・権限) | strace | システムコール単位で記録する |
| いま何のファイルを開いているか(一覧) | lsof | その瞬間の状態を一覧できる |
| どのライブラリ関数を呼んだか | ltrace | ライブラリ呼び出しを追う |
| CPUをどこで使っているか | perf | 関数単位でサンプリングする |
| 通信の中身(パケット) | tcpdump | ネットワーク層で記録する |
| プロセスの存在・状態 | ps / top | まず生死と資源使用量を見る |
実務では「ps – 実行中のプロセスを表示するコマンドで対象を特定 → lsof で開いているものを見る → それでも分からなければ strace で依頼の中身を見る」という順に降りていくと無駄がありません。通信内容そのものを見たい場合はtcpdumpの使い方|Linuxパケットキャプチャとフィルタ解析の実践ガイドへ進みます。
straceコマンドのよくある質問
strace -p が「Operation not permitted」で失敗します
cat /proc/sys/kernel/yama/ptrace_scope の値を確認してください。1(Ubuntu・Debianの既定)なら、自分の子孫以外は追跡できません。sudo strace -p PID で実行するのが基本の対処です。詳しくは本記事の該当セクションを参照してください。
出力が多すぎて読めません
先に -c で集計して errors 列を見るか、-e trace=openat のように種類を絞ります。ファイル操作全般をまとめて見たい場合は -e trace=%file、通信なら -e trace=%network という指定も使えます。
straceを付けると挙動が変わってしまいます
実行速度が大きく落ちるため、タイムアウトやタイミング依存の処理では結果が変わり得ます。-e trace= で対象を絞り、追跡する時間そのものを短くしてください。それでも再現しない場合は、strace の遅延自体が症状を隠している可能性があります。
出力の = -1 ENOENT は全部エラーですか
いいえ。プログラムは設定ファイルやライブラリを複数の候補パスに順番に探しに行くのが普通で、見つからなかった候補は ENOENT になります。これは正常動作です。問題を示すのは、本来あるはずのファイルに対する ENOENT と、EACCES(権限不足)です。
rootでないと使えませんか
strace コマンド の形で自分が起動する場合は一般ユーザーで使えます。root権限が要るのは、-p で他のプロセスに後から接続する場合と、他ユーザーのプロセスを追う場合です。
まとめ
= -1 の行-c で全体像 → -e trace= で個別行の順に降りる-p の「Operation not permitted」は権限不足ではなく ptrace_scope=1 が原因。sudo を付けるのが基本対処-f が必須。読みにくければ -ff で分割保存参考
検証環境:Ubuntu 24.04.4 LTS(カーネル6.8.0-137)、strace 6.8、bash 5.2.21、GNU coreutils 9.4。実行時間の計測は同一マシン・同一条件で3回ずつ実施し、レンジを記載しています。

コメント