「Permission deniedが出ているのに、どのファイルへのアクセスが拒否されているのかわからない」
プロセスの内部動作を追跡して原因を特定するには、
strace(エストレース)コマンドを使います。この記事では、
strace コマンドの基本的な使い方から、プロセスのシステムコール追跡、ファイルアクセスの調査、ネットワーク通信の確認、そして実務でのトラブルシューティング手順まで解説します。RHEL 9.4 / Rocky Linux 9.4 / Ubuntu 24.04 LTS で動作確認済みです。
この記事のポイント
・strace コマンド名 でシステムコールをリアルタイム追跡できる
・-e trace=open,read でファイル操作だけに絞り込める
・-p PID で実行中のプロセスにアタッチして調査できる
・Permission deniedの原因ファイルを特定するのに最適
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
straceコマンドとは?(システムコール追跡ツール)
strace は、プロセスが発行するシステムコール(カーネルへの要求)とシグナルを追跡するデバッグツールです。アプリケーションがファイルを開く、ネットワーク通信をする、プロセスを生成するといった動作は、すべてカーネルのシステムコールを介して行われます。
strace はこのシステムコールを1行ずつ表示してくれるため、「プログラムが内部で何をしているか」を丸裸にできます。ログに何も残らないトラブルでも、
strace で追跡すれば「どのファイルを開こうとして失敗したか」「どのポートに接続しようとしてタイムアウトしたか」を特定できます。straceのインストール
多くのディストリビューションではstrace がデフォルトでインストールされていますが、最小構成のサーバーでは入っていない場合があります。# straceがインストール済みか確認する $ which strace /usr/bin/strace # インストールされていない場合(RHEL / Rocky Linux) $ sudo dnf install strace # インストールされていない場合(Ubuntu / Debian) $ sudo apt install strace # バージョン確認 $ strace --version strace -- version 6.7
基本的な使い方
1. コマンドのシステムコールを追跡する
最も基本的な使い方は、追跡したいコマンドの前にstrace を付けて実行するだけです。# lsコマンドのシステムコールを追跡する $ strace ls /tmp execve("/usr/bin/ls", ["ls", "/tmp"], 0x7ffd8a3e5c00 /* 28 vars */) = 0 brk(NULL) = 0x55f8a4b23000 arch_prctl(0x3001, 0x7ffcf3d5a400) = -1 EINVAL (Invalid argument) 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 ... openat(AT_FDCWD, "/tmp", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 getdents64(3, 0x55f8a4b24e80 /* 5 entries */, 32768) = 152 getdents64(3, 0x55f8a4b24e80 /* 0 entries */, 32768) = 0 close(3) = 0 ... +++ exited with 0 +++
openat はファイルを開く操作、read はファイルから読み込む操作を表しています。行末の = 0 や = 3 は戻り値で、負の値やエラー名が付いていれば失敗を意味します。2. 出力をファイルに保存する(-o オプション)
strace の出力は標準エラー出力(stderr)に書かれるため、コマンド本来の出力と混ざってしまいます。-o オプションでファイルに保存するのが実務的です。# straceの出力を trace.log に保存する $ strace -o /tmp/trace.log ls /var/log anaconda audit btmp chrony ... # 保存されたトレースログを確認する $ wc -l /tmp/trace.log 142 /tmp/trace.log $ head -5 /tmp/trace.log execve("/usr/bin/ls", ["ls", "/var/log"], 0x7ffd2b6c4e90 /* 28 vars */) = 0 brk(NULL) = 0x562a1c3d1000 arch_prctl(0x3001, 0x7ffc8d7b9a00) = -1 EINVAL (Invalid argument) 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
3. タイムスタンプを付ける(-t / -tt / -T オプション)
トラブルシューティングでは「いつ」そのシステムコールが発生したかが重要です。# 秒単位のタイムスタンプを付ける $ strace -t ls /tmp 2>&1 | head -3 14:32:05 execve("/usr/bin/ls", ["ls", "/tmp"], ...) = 0 14:32:05 brk(NULL) = 0x55a3c8d21000 14:32:05 access("/etc/ld.so.preload", R_OK) = -1 ENOENT # マイクロ秒単位のタイムスタンプを付ける $ strace -tt ls /tmp 2>&1 | head -3 14:32:15.123456 execve("/usr/bin/ls", ["ls", "/tmp"], ...) = 0 14:32:15.124789 brk(NULL) = 0x55a3c8d21000 14:32:15.124891 access("/etc/ld.so.preload", R_OK) = -1 ENOENT # 各システムコールの所要時間を表示する $ strace -T ls /tmp 2>&1 | head -3 execve("/usr/bin/ls", ["ls", "/tmp"], ...) = 0 <0.000891> brk(NULL) = 0x55a3c8d21000 <0.000012> access("/etc/ld.so.preload", R_OK) = -1 ENOENT <0.000018>
-T は各システムコールの実行にかかった時間を <秒数> で表示します。処理が遅い箇所を特定するのに使えます。フィルタリングで必要な情報だけ取得する
4. システムコールの種類で絞り込む(-e trace=)
strace の出力は膨大になるため、目的に応じてフィルタリングするのが実務の基本です。# ファイル操作に関するシステムコールだけを表示する $ strace -e trace=open,openat,read,write ls /var/log 2>&1 | head -10 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0...", 832) = 832 openat(AT_FDCWD, "/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0...", 832) = 832 openat(AT_FDCWD, "/var/log", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 ... # ネットワーク関連のシステムコールだけを表示する $ strace -e trace=network curl -s https://example.com -o /dev/null 2>&1 | head -8 socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 5 connect(5, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("93.184.xxx.xxx")}, 16) = -1 EINPROGRESS getsockopt(5, SOL_SOCKET, SO_ERROR, [0], [4]) = 0 ... # プロセス生成に関するシステムコールだけを表示する $ strace -e trace=process bash -c "echo hello" 2>&1 | head -5 execve("/usr/bin/bash", ["bash", "-c", "echo hello"], ...) = 0 arch_prctl(0x3001, 0x7ffc3a2d6f10) = -1 EINVAL (Invalid argument) arch_prctl(ARCH_SET_FS, 0x7f4a3c1c7740) = 0 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|...) = 12345 wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 12345
・-e trace=file:ファイル名を引数に取るシステムコール(open, stat, chmod等)を追跡する
・-e trace=network:ネットワーク関連(socket, connect, bind等)を追跡する
・-e trace=process:プロセス管理(fork, exec, wait等)を追跡する
・-e trace=signal:シグナル関連(kill, sigaction等)を追跡する
・-e trace=memory:メモリマッピング(mmap, brk等)を追跡する
5. 特定のシステムコールだけを除外する(-e trace=!)
逆に、ノイズになるシステムコールを除外したい場合は!(否定)を使います。# mmap, mprotect, brk を除外して表示する $ strace -e 'trace=!mmap,mprotect,brk' ls /tmp 2>&1 | head -5 execve("/usr/bin/ls", ["ls", "/tmp"], ...) = 0 arch_prctl(0x3001, 0x7ffcc5a40f70) = -1 EINVAL (Invalid argument) 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 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=23456, ...}, AT_EMPTY_PATH) = 0
実行中のプロセスを追跡する(-p オプション)
6. PIDを指定してアタッチする
すでに動いているデーモンやサービスのトラブルシューティングでは、-p オプションでプロセスにアタッチします。# httpdのプロセスIDを確認する $ pgrep -f httpd 1234 1235 1236 # PID 1234 のプロセスにアタッチしてファイル操作を追跡する $ sudo strace -p 1234 -e trace=file -o /tmp/httpd_trace.log strace: Process 1234 attached (Ctrl+C で停止するまで追跡が続く) # 追跡結果を確認する $ grep -i "error\|ENOENT\|EACCES" /tmp/httpd_trace.log openat(AT_FDCWD, "/var/www/html/missing.html", O_RDONLY) = -1 ENOENT (No such file or directory)
root 権限(または CAP_SYS_PTRACE)が必要です。7. 子プロセスも含めて追跡する(-f オプション)
Apache や Nginx のようにワーカープロセスを fork するサービスでは、-f を付けないと子プロセスの動作を見落とします。# 子プロセスも含めて追跡する $ sudo strace -f -p 1234 -o /tmp/httpd_all.log # どのPIDがどのシステムコールを発行したか確認する $ grep "openat" /tmp/httpd_all.log | head -3 1234 openat(AT_FDCWD, "/etc/httpd/conf/httpd.conf", O_RDONLY) = 7 1235 openat(AT_FDCWD, "/var/www/html/index.html", O_RDONLY) = 8 1236 openat(AT_FDCWD, "/var/log/httpd/access_log", O_WRONLY|O_APPEND) = 9
-f を付けると各行の先頭にPIDが表示されるため、どの子プロセスで問題が起きているかを特定できます。実務で使えるトラブルシューティング例
8. Permission deniedの原因ファイルを特定する
「サービスが起動しない」「ファイルにアクセスできない」という場面で、straceを使えば具体的にどのファイルで権限エラーが発生しているかを特定できます。# Permission denied が出るプロセスを追跡してエラーだけ抽出する $ strace -e trace=file -o /tmp/perm.log ./myapp ./myapp: Permission denied $ grep EACCES /tmp/perm.log openat(AT_FDCWD, "/var/lib/myapp/data.db", O_RDWR) = -1 EACCES (Permission denied) # 原因が判明:/var/lib/myapp/data.db への書き込み権限がない $ ls -l /var/lib/myapp/data.db -rw-r--r--. 1 root root 4096 4月 10 15:00 /var/lib/myapp/data.db # 所有者を変更して解決する $ sudo chown myapp:myapp /var/lib/myapp/data.db
9. ファイルが見つからない(ENOENT)エラーを調査する
設定ファイルやライブラリの配置ミスで起きる「No such file or directory」も、straceで即座に原因を突き止められます。# 設定ファイルの読み込みエラーを調査する $ strace -e trace=openat nginx -t 2>&1 | grep ENOENT openat(AT_FDCWD, "/etc/nginx/modules-enabled/50-mod-http.conf", O_RDONLY) = -1 ENOENT openat(AT_FDCWD, "/etc/nginx/sites-enabled/default", O_RDONLY) = -1 ENOENT # nginx が参照しようとしている設定ファイルが存在しないことが判明
10. 処理が遅い箇所を特定する(-T オプション)
レスポンスが遅いプロセスの原因を調べるには、各システムコールの所要時間を-T で表示して、時間がかかっている箇所を見つけます。# 各システムコールの所要時間を記録する $ strace -T -o /tmp/slow.log ./slow_script.sh # 所要時間が1秒以上のシステムコールを抽出する $ grep -E '<[0-9]+\.' /tmp/slow.log | sort -t'<' -k2 -rn | head -5 connect(3, {sa_family=AF_INET, sin_port=htons(3306), ...}, 16) = 0 <5.002134> read(3, "HTTP/1.1 200 OK\r\n...", 4096) = 1024 <2.501023> nanosleep({tv_sec=1, tv_nsec=0}, NULL) = 0 <1.000245> # DBへのconnectに5秒かかっていることが判明
11. システムコールの統計情報を取得する(-c オプション)
個々のシステムコールを見る前に、まず全体像を把握したい場合は-c が便利です。# システムコールの統計サマリを表示する $ strace -c ls /var/log 2>&1 % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 25.00 0.000156 11 14 mmap 18.75 0.000117 9 13 close 15.63 0.000098 10 10 3 openat 12.50 0.000078 8 10 read 9.38 0.000059 10 6 mprotect 6.25 0.000039 8 5 newfstatat ... ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000625 78 5 total
errors 列に数字が入っているシステムコールがあれば、そこからエラーの原因を追跡します。% time 列で時間を消費しているシステムコールがパフォーマンスのボトルネックです。「Operation not permitted」が出た時の対処法
strace でプロセスにアタッチしようとして Operation not permitted が出る場合は、以下を確認してください。・root権限で実行する:
sudo strace -p PID で実行する。一般ユーザーが他ユーザーのプロセスを追跡することはできない・ptrace_scopeの設定:Ubuntu等ではカーネルパラメータ
kernel.yama.ptrace_scope がデフォルトで1(親プロセスのみ許可)になっている# ptrace_scope の現在の設定を確認する $ cat /proc/sys/kernel/yama/ptrace_scope 1 # 一時的にすべてのプロセスへのアタッチを許可する(再起動で元に戻る) $ sudo sysctl kernel.yama.ptrace_scope=0 kernel.yama.ptrace_scope = 0 # 恒久的に変更する場合は /etc/sysctl.d/ に設定ファイルを作成する $ echo "kernel.yama.ptrace_scope = 0" | sudo tee /etc/sysctl.d/99-ptrace.conf $ sudo sysctl -p /etc/sysctl.d/99-ptrace.conf
ptrace_scope=0 に変更するとセキュリティリスクになるため、調査が終わったら必ず元の値に戻してください。【注意】straceのパフォーマンスへの影響
strace はプロセスの全システムコールに割り込むため、追跡中のプロセスは通常の2~5倍遅くなります。本番環境で長時間アタッチし続けると、レスポンス劣化やタイムアウトの原因になりかねません。本番サーバーでは以下の点を必ず守ってください。・追跡時間を最小限にする:問題の再現に必要な時間だけアタッチし、すぐに Ctrl+C で切り離す
・-e traceでフィルタリングする:全システムコールの追跡は避け、目的のカテゴリだけに絞る
・-o でファイルに出力する:ターミナルへの大量出力自体がオーバーヘッドになるため、ファイル保存が鉄則
・検証環境で先に試す:可能であれば問題を検証環境で再現してからstraceを実行する
本記事のまとめ
strace コマンドの使い方を解説しました。主要な操作を以下のテーブルにまとめます。| やりたいこと | コマンド |
|---|---|
| コマンドのシステムコールを追跡する | strace コマンド名 |
| 出力をファイルに保存する | strace -o ファイル名 コマンド名 |
| タイムスタンプを付ける | strace -tt コマンド名 |
| 各システムコールの所要時間を表示する | strace -T コマンド名 |
| ファイル操作だけに絞り込む | strace -e trace=file コマンド名 |
| ネットワーク操作だけに絞り込む | strace -e trace=network コマンド名 |
| 実行中のプロセスにアタッチする | sudo strace -p PID |
| 子プロセスも含めて追跡する | sudo strace -f -p PID |
| システムコールの統計を取得する | strace -c コマンド名 |
strace はログに何も残らないトラブルで真価を発揮するツールです。「原因不明」のエラーに遭遇したら、まず strace でシステムコールを追跡してみてください。Permission deniedの原因ファイル特定、ライブラリの読み込みエラー調査、パフォーマンスボトルネックの切り分けなど、幅広い場面で活用できます。関連記事:lsofコマンドでプロセスが使用中のファイルを確認する方法も合わせて参照すると、プロセスのトラブルシューティングの幅が広がります。
関連記事:ssコマンドでソケット情報を確認する方法と組み合わせて、ネットワーク関連の問題を多角的に調査できます。
「原因不明」のトラブルに、手も足も出ない経験はありませんか?
straceでシステムコールを追えるようになると、ログに何も残らないトラブルでも原因を特定できるようになります。しかし、straceが真価を発揮するのは、Linuxの基礎~ファイル権限、プロセス管理、ネットワークの仕組み~が身についてこそです。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
