strace コマンドの使い方|システムコール追跡と-pの失敗対処

コマンドリファレンス

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 はその依頼と返事だけを横から記録します

実際に 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=名前特定のシステムコールだけに絞るopenatconnect だけ見たいとき
-c個別行を出さず回数と時間を集計全体像・エラー総数の把握
-f子プロセス・スレッドも追うシェルスクリプトやデーモン
-ff -o 名前プロセスごとに別ファイルへ子が多くて混ざるとき
-p PID実行中のプロセスに後から接続すでに固まっている相手
-t / -tt時刻を付ける(-ttはマイクロ秒)ログと時刻を突き合わせる
-T各システムコールの所要時間どこで待たされたかを見る
-s 文字数文字列の表示長を変える(既定32)通信内容やパスが切れるとき
-yfd番号に対応するパスを併記「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/null0.47〜0.48秒約16倍
strace -e trace=openat0.37〜0.38秒約12倍

このとき記録されたシステムコールは25,150行でした。注意したいのは、-e trace= で絞っても速度はあまり戻らない点です。絞り込みは「出力を読みやすくする」ためのもので、「軽くする」ためのものではありません(strace 6.8 では --seccomp-bpf を併用しても、この条件では有意差は出ませんでした)。

したがって本番環境では次の前提で使います。

  • アクセスの多いプロセスに -p で付けると、体感で分かるレベルの遅延が発生すると考える
  • 常時流しっぱなしにせず、再現の瞬間だけ数秒〜数十秒に限定する
  • 出力先は必ず -o でファイルへ。端末に出すと表示処理でさらに遅くなる
  • ディスクを圧迫するため、-o の出力先の空き容量を先に確認する
  • 常時計測が必要なら strace ではなく、オーバーヘッドの小さい仕組み(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 で他のプロセスに後から接続する場合と、他ユーザーのプロセスを追う場合です。

    まとめ

  • strace はプログラムがOSに出した依頼(システムコール)を1行ずつ記録するコマンド。読むべきは = -1 の行
  • 出力は最初から多すぎるのが普通。-c で全体像 → -e trace= で個別行の順に降りる
  • -p の「Operation not permitted」は権限不足ではなく ptrace_scope=1 が原因。sudo を付けるのが基本対処
  • 子プロセスを持つ処理では -f が必須。読みにくければ -ff で分割保存
  • 実行時間は約16倍になる(実測)。本番では再現の瞬間だけに限定して使う
  • 参考

  • strace(1) — Linux manual page(全オプションの一次情報)
  • ptrace(2) — Linux manual page(straceの基盤となる仕組みとYamaの制限)
  • Yama — The Linux Kernel documentation(ptrace_scopeの各値の定義)
  • strace — 公式サイト(リリース情報)
  • 検証環境:Ubuntu 24.04.4 LTS(カーネル6.8.0-137)、strace 6.8、bash 5.2.21、GNU coreutils 9.4。実行時間の計測は同一マシン・同一条件で3回ずつ実施し、レンジを記載しています。

    Bash玄

    はじめまして!Bash玄です。

    エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

    このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

    # 好きなこと
    - シンプルなコードを書くこと
    - コマンドラインを快適にカスタマイズすること
    - 自動化で時間を生み出すこと

    # このサイトを読んでほしい人
    - Bashに興味があるけど、何から始めればいいかわからない人
    - 定型業務を自動化したい人
    - 効率よくターミナルを使いこなしたい人

    Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

    Bash玄をフォローする

    コメント