文章目錄
- 1. 前言
- 2. 什么是 strace ?
- 3. 使用 strace
- 3.1 追蹤指定進程
- 3.1.1 通過程序名追蹤進程
- 3.1.2 通過 進程 ID (PID) 追蹤程序
- 3.1.3 追蹤 子進程 或 線程
- 3.2 系統調用情況統計
- 3.3 追蹤過濾
- 3.3.1 追蹤指定的系統調用集合
- 3.3.2 追蹤對指定文件句柄集合操作的系統調用
- 3.3.3 追蹤訪問指定路徑的系統調用
- 3.3.4 追蹤返回成功的系統調用
- 3.3.5 追蹤返回錯誤碼的系統調用
- 3.3.6 指定系統調用類別
- 3.4 輸出控制
- 3.4.1 輸出時間戳
- 3.4.2 將追蹤結果寫入文件
- 3.5 其它選項
- 4. 交叉編譯 strace
- 5. 參考資料
1. 前言
限于作者能力水平,本文可能存在謬誤,因此而給讀者帶來的損失,作者不做任何承諾。
2. 什么是 strace ?
strace
是 Linux
下一款診斷、調試、追蹤
工具,它可以用來監控和獲取
內核的系統調用、信號投遞、進程狀態變更
等信息。其基本實現機制是通過 ptrace()
系統調用,來獲取內核相關信息。對 strace
實現原理感興趣的讀者,可直接閱讀 strace
源碼,或參考博文 Linux:系統調用追蹤原理簡析 。
3. 使用 strace
3.1 追蹤指定進程
3.1.1 通過程序名追蹤進程
在程序啟動時,可通過 strace
跟蹤程序運行期間的所有系統調用。如:
# strace ls
execve("/bin/ls", ["ls"], 0xbeb73e50 /* 12 vars */) = 0
brk(NULL) = 0xc3000
uname({sysname="Linux", nodename="(none)", ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f0e000
...
stat64(".", {st_mode=S_IFDIR|0755, st_size=232, ...}) = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
getdents64(3, 0xc30b0 /* 3 entries */, 32768) = 80
lstat64("./tcp_server", {st_mode=S_IFREG|0755, st_size=11192, ...}) = 0
getdents64(3, 0xc30b0 /* 0 entries */, 32768) = 0
close(3)
...
exit_group(0) = ?
+++ exited with 0 +++
上面的輸出,每一行顯示一個系統調用,包含系統調用名、調用參數、以及返回值
(=
后的內容),譬如輸出:
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
表示調用了 open()
,打開的是當前目錄 "."
,傳遞的 flags 參數為 O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY
,返回值為 3
。
3.1.2 通過 進程 ID (PID) 追蹤程序
首先找到目標進程的 PID:
# ps -ef | grep -v grep | grep InfiniteDsp160 root ./InfiniteDsp
然后進行追蹤:
# strace -p 160
strace: Process 160 attached
read(27, "\1\0\0\0", 4) = 4
[......]
3.1.3 追蹤 子進程 或 線程
對于調用 fork()
或 clone()
等系列接口的程序,在 3.1.1,3.1.2
基礎上,需再加上 -f
或 --follow-forks
命令行參數來進程追蹤:
strace -f <program>
strace -fp <PID>
如有一個 PID 為 2622 的多線程程序
,我們用 strace
來追蹤它:
# strace -fp 2622
strace: Process 2622 attached with 4 threads
[pid 2627] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2626] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2625] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
[pid 2622] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2626] <... restart_syscall resumed> ) = 0
從輸出看到,PID 為 2622 的進程有 4 個線程
,同時輸出每行開頭都帶上了線程的 PID
,這樣就能知道每個系統調用是由哪個線程發起的。
3.2 系統調用情況統計
通過 -c
或 -C
命令行參數,統計系統調用消耗的總時間、調用總次數、每次消耗的平均時間、出錯總次數
等信息,并在結束追蹤時輸出這些信息。-c
和 -C
的差別在于:-c
不會在追蹤期間輸出系統調用情況,而 -C
則相反。看一個簡單的例子:
# strace -C -fp 2622
strace: Process 2622 attached with 4 threads
[pid 2626] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2627] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
[pid 2625] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
[pid 2622] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2626] <... restart_syscall resumed> ) = 0
[......]
^Cstrace: Process 2622 detached<detached ...>
strace: Process 2625 detached
strace: Process 2626 detached
strace: Process 2627 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------84.95 0.000948 474 2 restart_syscall8.42 0.000094 47 2 2 inotify_add_watch6.63 0.000074 25 3 2 recvmsg
------ ----------- ----------- --------- --------- ----------------
100.00 0.001116 7 4 total
3.3 追蹤過濾
3.3.1 追蹤指定的系統調用集合
有時候并不想追蹤程序所有的系統調用,通過下列選項
-e trace=syscall_set
-e t=syscall_set
--trace=syscall_set
指定想追蹤
或不想追蹤
的系統調用集合。如只想追蹤程序的 open()
調用:
# strace -e t=open /bin/ls
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/proc/filesystems", O_RDONLY) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[......]
+++ exited with 0 +++
strace
只輸出程序運行過程中所有 open()
調用。如果想追蹤多個系統調用,可以用 ,
分隔,如:
# strace -e t=open,read /bin/ls
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260Z\0\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\25\0\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\r\0\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260`\0\0\0\0\0\0"..., 832) = 832
open("/proc/filesystems", O_RDONLY) = 3
read(3, "nodev\tsysfs\nnodev\trootfs\nnodev\tr"..., 1024) = 429
read(3, "", 1024) = 0
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[......]
+++ exited with 0 +++
3.3.2 追蹤對指定文件句柄集合操作的系統調用
通過下列選項:
-e trace-fd=set
-e trace-fds=set
-e fd=set
-e fds=set
追蹤對指定文件句柄集合操作的系統調用。如追蹤所有對文件句柄 3
的操作:
# strace -e fd=3 /bin/ls
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\235h\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0777, st_size=898752, ...}) = 0
mmap2(NULL, 968040, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6e01000
mmap2(0xb6ee8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xd7000) = 0xb6ee8000
close(3) = 0
getdents64(3, 0xc30b0 /* 14 entries */, 32768) = 456
getdents64(3, 0xc30b0 /* 0 entries */, 32768) = 0
close(3) = 0
[......]
+++ exited with 0 +++
3.3.3 追蹤訪問指定路徑的系統調用
通過下列選項:
-P path
--trace-path=path
追蹤指定訪問目錄的系統調用。
# strace --trace-path=/var /bin/ls /var
stat64("/var", {st_mode=S_IFDIR|0777, st_size=672, ...}) = 0
openat(AT_FDCWD, "/var", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
getdents64(3, 0xc30b0 /* 10 entries */, 32768) = 256
getdents64(3, 0xc30b0 /* 0 entries */, 32768) = 0
close(3) = 0
[......]
+++ exited with 0 +++
3.3.4 追蹤返回成功的系統調用
通過下列選項:
-z
--successful-only
只追蹤返回成功的系統調用。
3.3.5 追蹤返回錯誤碼的系統調用
通過下列選項:
-Z
--failed-only
只追蹤返回錯誤碼的系統調用。
3.3.6 指定系統調用類別
從前面知道,下列選項
-e trace=syscall_set
-e t=syscall_set
--trace=syscall_set
可以指定要追蹤的系統調用集合。前面已經示范了通過具體的名字來指定集合,這里介紹通過類別指定系統調用集合
的方式,即 syscall_set
還可以通過如下方式指定:
/regex : 正則表達式
%file, file: 追蹤文件操作相關的系統調用(open,close,access,...)
%process, process: 追蹤進程操作相關的系統調用(exec,...)
%net, %network, network: 追蹤網絡操作相關的系統調用(exec,...)
......
3.4 輸出控制
3.4.1 輸出時間戳
選項
-r
--relative-timestamps[=precision]
輸出相對時間(相對于程序啟動時間):
# strace -r /bin/ls0.000000 execve("/bin/ls", ["/bin/ls"], [/* 26 vars */]) = 00.000589 brk(NULL) = 0x188c0000.000323 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)0.000285 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)0.000209 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 30.000335 fstat(3, {st_mode=S_IFREG|0644, st_size=100481, ...}) = 00.000566 mmap(NULL, 100481, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9a94f300000.000304 close(3) = 0[......]0.000096 exit_group(0) = ?0.000101 +++ exited with 0 +++
選項
-t
--absolute-timestamps
輸出絕對時間:
# strace -t /bin/ls
11:18:35 execve("/bin/ls", ["/bin/ls"], [/* 26 vars */]) = 0
11:18:35 brk(NULL) = 0x14f0000
11:18:35 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
11:18:35 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
[......]
選項
-T
--syscall-times[=precision]
輸出每個系統調用消耗的時間:
# strace -T /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 26 vars */]) = 0 <0.000381>
brk(NULL) = 0x1701000 <0.000126>
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000135>
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000094>
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000084>
[......]
3.4.2 將追蹤結果寫入文件
選項
-o filename
--output=filename
將追蹤結果寫入文件。
3.5 其它選項
strace
的更多使用方法可參考其手冊 https://www.man7.org/linux/man-pages/man1/strace.1.html 。
4. 交叉編譯 strace
先獲取 strace
源碼(當前版本為 6.6
):
git clone https://gitlab.com/strace/strace.git
交叉編譯 strace
源碼(假定目標平臺為 ARM32
,交叉編譯器為 arm-linux-gnueabihf-gcc
):
./bootstrap
CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld RANLIB=arm-linux-gnueabihf-ranlib \
./configure --prefix=$(pwd)/out --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf
make && make install
將在 out
目錄下生成 strace
程序:
$ tree out
out
├── bin
│ ├── strace
│ └── strace-log-merge
└── share└── man└── man1├── strace.1└── strace-log-merge.1
5. 參考資料
[1] https://strace.io/
[2] https://www.man7.org/linux/man-pages/man1/strace.1.html