使用 strace 跟蹤用戶進程和 Linux 內核之間的交互。linux
系統調用是程序從內核請求服務的一種編程方式,而 strace
是一個功能強大的工具,可以讓你跟蹤用戶進程與 Linux 內核之間的交互。git
要了解操做系統的工做原理,首先須要瞭解系統調用的工做原理。操做系統的主要功能之一是爲用戶程序提供抽象機制。github
操做系統能夠大體分爲兩種模式:shell
系統調用與函數調用很是類似,這意味着它們都接受並處理參數而後返回值。惟一的區別是系統調用進入內核,而函數調用不進入。從用戶空間切換到內核空間是使用特殊的 trap 機制完成的。編程
經過使用系統庫(在 Linux 系統上又稱爲 glibc),大部分系統調用對用戶隱藏了。儘管系統調用本質上是通用的,可是發出系統調用的機制在很大程度上取決於機器(架構)。ruby
本文經過使用一些常規命令並使用 strace
分析每一個命令進行的系統調用來探索一些實際示例。這些示例使用 Red Hat Enterprise Linux,可是這些命令運行在其餘 Linux 發行版上應該也是相同的:bash
[root@sandbox ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.7 (Maipo)
[root@sandbox ~]#
[root@sandbox ~]# uname -r
3.10.0-1062.el7.x86_64
[root@sandbox ~]#
複製代碼
首先,確保在系統上安裝了必需的工具。你可使用下面的 rpm
命令來驗證是否安裝了 strace
。若是安裝了,則可使用 -V
選項檢查 strace
實用程序的版本號:架構
[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]#
[root@sandbox ~]# strace -V
strace -- version 4.12
[root@sandbox ~]#
複製代碼
若是沒有安裝,運行命令安裝:函數
yum install strace
複製代碼
出於本示例的目的,在 /tmp
中建立一個測試目錄,並使用 touch
命令建立兩個文件:工具
[root@sandbox ~]# cd /tmp/
[root@sandbox tmp]#
[root@sandbox tmp]# mkdir testdir
[root@sandbox tmp]#
[root@sandbox tmp]# touch testdir/file1
[root@sandbox tmp]# touch testdir/file2
[root@sandbox tmp]#
複製代碼
(我使用 /tmp
目錄是由於每一個人均可以訪問它,可是你能夠根據須要選擇另外一個目錄。)
在 testdir
目錄下使用 ls
命令驗證該文件已經建立:
[root@sandbox tmp]# ls testdir/
file1 file2
[root@sandbox tmp]#
複製代碼
你可能天天都在使用 ls
命令,而沒有意識到系統調用在其下面發揮的做用。抽象地來講,該命令的工做方式以下:
命令行工具 -> 從系統庫(glibc)調用函數 -> 調用系統調用
ls
命令內部從 Linux 上的系統庫(即 glibc)調用函數。這些庫去調用完成大部分工做的系統調用。
若是你想知道從 glibc 庫中調用了哪些函數,請使用 ltrace
命令,而後跟上常規的 ls testdir/
命令:
ltrace ls testdir/
複製代碼
若是沒有安裝 ltrace
,鍵入以下命令安裝:
yum install ltrace
複製代碼
大量的輸出會被堆到屏幕上;沒必要擔憂,只需繼續就行。ltrace
命令輸出中與該示例有關的一些重要庫函數包括:
opendir("testdir/") = { 3 }
readdir({ 3 }) = { 101879119, "." }
readdir({ 3 }) = { 134, ".." }
readdir({ 3 }) = { 101879120, "file1" }
strlen("file1") = 5
memcpy(0x1665be0, "file1\0", 6) = 0x1665be0
readdir({ 3 }) = { 101879122, "file2" }
strlen("file2") = 5
memcpy(0x166dcb0, "file2\0", 6) = 0x166dcb0
readdir({ 3 }) = nil
closedir({ 3 })
複製代碼
經過查看上面的輸出,你或許能夠了解正在發生的事情。opendir
庫函數打開一個名爲 testdir
的目錄,而後調用 readdir
函數,該函數讀取目錄的內容。最後,有一個對 closedir
函數的調用,該函數將關閉先前打開的目錄。如今請先忽略其餘 strlen
和 memcpy
功能。
你能夠看到正在調用哪些庫函數,可是本文將重點介紹由系統庫函數調用的系統調用。
與上述相似,要了解調用了哪些系統調用,只需將 strace
放在 ls testdir
命令以前,以下所示。 再次,一堆亂碼丟到了你的屏幕上,你能夠按照如下步驟進行操做:
[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL) = 0x1f12000
<<< truncated strace output >>>
write(1, "file1 file2\n", 13file1 file2
) = 13
close(1) = 0
munmap(0x7fd002c8d000, 4096) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
[root@sandbox tmp]#
複製代碼
運行 strace
命令後屏幕上的輸出就是運行 ls
命令的系統調用。每一個系統調用都爲操做系統提供了特定的用途,能夠將它們大體分爲如下幾個部分:
分析顯示到屏幕上的信息的一種更簡單的方法是使用 strace
方便的 -o
標誌將輸出記錄到文件中。在 -o
標誌後添加一個合適的文件名,而後再次運行命令:
[root@sandbox tmp]# strace -o trace.log ls testdir/
file1 file2
[root@sandbox tmp]#
複製代碼
此次,沒有任何輸出干擾屏幕顯示,ls
命令如預期般工做,顯示了文件名並將全部輸出記錄到文件 trace.log
中。僅僅是一個簡單的 ls
命令,該文件就有近 100 行內容:
[root@sandbox tmp]# ls -l trace.log
-rw-r--r--. 1 root root 7809 Oct 12 13:52 trace.log
[root@sandbox tmp]#
[root@sandbox tmp]# wc -l trace.log
114 trace.log
[root@sandbox tmp]#
複製代碼
讓咱們看一下這個示例的 trace.log
文件的第一行:
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
複製代碼
execve
是正在執行的系統調用的名稱。=
後的數字(在這種狀況下爲 0
)是 execve
系統調用的返回值。如今的輸出彷佛還不太嚇人,對吧。你能夠應用相同的邏輯來理解其餘行。
如今,將關注點集中在你調用的單個命令上,即 ls testdir
。你知道命令 ls
使用的目錄名稱,那麼爲何不在 trace.log
文件中使用 grep
查找 testdir
並查看獲得的結果呢?讓咱們詳細查看一下結果的每一行:
[root@sandbox tmp]# grep testdir trace.log
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[root@sandbox tmp]#
複製代碼
回顧一下上面對 execve
的分析,你能說一下這個系統調用的做用嗎?
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
複製代碼
你無需記住全部系統調用或它們所作的事情,由於你能夠在須要時參考文檔。手冊頁能夠解救你!在運行 man
命令以前,請確保已安裝如下軟件包:
[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#
複製代碼
請記住,你須要在 man
命令和系統調用名稱之間添加 2
。若是使用 man man
閱讀 man
命令的手冊頁,你會看到第 2 節是爲系統調用保留的。一樣,若是你須要有關庫函數的信息,則須要在 man
和庫函數名稱之間添加一個 3
。
如下是手冊的章節編號及其包含的頁面類型:
1
:可執行的程序或 shell 命令2
:系統調用(由內核提供的函數)3
:庫調用(在程序的庫內的函數)4
:特殊文件(一般出如今 /dev
)使用系統調用名稱運行如下 man
命令以查看該系統調用的文檔:
man 2 execve
複製代碼
按照 execve
手冊頁,這將執行在參數中傳遞的程序(在本例中爲 ls
)。能夠爲 ls
提供其餘參數,例如本例中的 testdir
。所以,此係統調用僅以 testdir
做爲參數運行 ls
:
execve - execute program
DESCRIPTION
execve() executes the program pointed to by filename
複製代碼
下一個系統調用,名爲 stat
,它使用 testdir
參數:
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
複製代碼
使用 man 2 stat
訪問該文檔。stat
是獲取文件狀態的系統調用,請記住,Linux 中的一切都是文件,包括目錄。
接下來,openat
系統調用將打開 testdir
。密切注意返回的 3
。這是一個文件描述符,將在之後的系統調用中使用:
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
複製代碼
到如今爲止一切都挺好。如今,打開 trace.log
文件,並轉到 openat
系統調用以後的行。你會看到 getdents
系統調用被調用,該調用完成了執行 ls testdir
命令所需的大部分操做。如今,從 trace.log
文件中用 grep
獲取 getdents
:
[root@sandbox tmp]# grep getdents trace.log
getdents(3, /* 4 entries */, 32768) = 112
getdents(3, /* 0 entries */, 32768) = 0
[root@sandbox tmp]#
複製代碼
getdents
的手冊頁將其描述爲 「獲取目錄項」,這就是你要執行的操做。注意,getdents
的參數是 3
,這是來自上面 openat
系統調用的文件描述符。
如今有了目錄列表,你須要一種在終端中顯示它的方法。所以,在日誌中用 grep
搜索另外一個用於寫入終端的系統調用 write
:
[root@sandbox tmp]# grep write trace.log
write(1, "file1 file2\n", 13) = 13
[root@sandbox tmp]#
複製代碼
在這些參數中,你能夠看到將要顯示的文件名:file1
和 file2
。關於第一個參數(1
),請記住在 Linux 中,當運行任何進程時,默認狀況下會爲其打開三個文件描述符。如下是默認的文件描述符:
0
:標準輸入1
:標準輸出2
:標準錯誤所以,write
系統調用將在標準顯示(就是這個終端,由 1
所標識的)上顯示 file1
和 file2
。
如今你知道哪一個系統調用完成了 ls testdir/
命令的大部分工做。可是在 trace.log
文件中其它的 100 多個系統調用呢?操做系統必須作不少內務處理才能運行一個進程,所以,你在該日誌文件中看到的不少內容都是進程初始化和清理。閱讀整個 trace.log
文件,並嘗試瞭解 ls
命令是怎麼工做起來的。
既然你知道了如何分析給定命令的系統調用,那麼就能夠將該知識用於其餘命令來了解正在執行哪些系統調用。strace
提供了許多有用的命令行標誌,使你更容易使用,下面將對其中一些進行描述。
默認狀況下,strace
並不包含全部系統調用信息。可是,它有一個方便的 -v
冗餘選項,能夠在每一個系統調用中提供附加信息:
strace -v ls testdir
複製代碼
在運行 strace
命令時始終使用 -f
選項是一種好的做法。它容許 strace
對當前正在跟蹤的進程建立的任何子進程進行跟蹤:
strace -f ls testdir
複製代碼
假設你只須要系統調用的名稱、運行的次數以及每一個系統調用花費的時間百分比。你可使用 -c
標誌來獲取這些統計信息:
strace -c ls testdir/
複製代碼
假設你想專一於特定的系統調用,例如專一於 open
系統調用,而忽略其他部分。你可使用-e
標誌跟上系統調用的名稱:
[root@sandbox tmp]# strace -e open ls testdir
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
file1 file2
+++ exited with 0 +++
[root@sandbox tmp]#
複製代碼
若是你想關注多個系統調用怎麼辦?不用擔憂,你一樣可使用 -e
命令行標誌,並用逗號分隔開兩個系統調用的名稱。例如,要查看 write
和 getdents
系統調用:
[root@sandbox tmp]# strace -e write,getdents ls testdir
getdents(3, /* 4 entries */, 32768) = 112
getdents(3, /* 0 entries */, 32768) = 0
write(1, "file1 file2\n", 13file1 file2
) = 13
+++ exited with 0 +++
[root@sandbox tmp]#
複製代碼
到目前爲止,這些示例是明確地運行的命令進行了跟蹤。可是,要跟蹤已經運行並正在執行的命令又怎麼辦呢?例如,若是要跟蹤用來長時間運行進程的守護程序,該怎麼辦?爲此,strace
提供了一個特殊的 -p
標誌,你能夠向其提供進程 ID。
咱們的示例不在守護程序上運行 strace
,而是以 cat
命令爲例,若是你將文件名做爲參數,一般 cat
會顯示文件的內容。若是沒有給出參數,cat
命令會在終端上等待用戶輸入文本。輸入文本後,它將重複給定的文本,直到用戶按下 Ctrl + C
退出爲止。
從一個終端運行 cat
命令;它會向你顯示一個提示,並等待在那裏(記住 cat
仍在運行且還沒有退出):
[root@sandbox tmp]# cat
複製代碼
在另外一個終端上,使用 ps
命令找到進程標識符(PID):
[root@sandbox ~]# ps -ef | grep cat
root 22443 20164 0 14:19 pts/0 00:00:00 cat
root 22482 20300 0 14:20 pts/1 00:00:00 grep --color=auto cat
[root@sandbox ~]#
複製代碼
如今,使用 -p
標誌和 PID(在上面使用 ps
找到)對運行中的進程運行 strace
。運行 strace
以後,其輸出說明了所接駁的進程的內容及其 PID。如今,strace
正在跟蹤 cat
命令進行的系統調用。看到的第一個系統調用是 read
,它正在等待文件描述符 0
(標準輸入,這是運行 cat
命令的終端)的輸入:
[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,
複製代碼
如今,返回到你運行 cat
命令的終端,並輸入一些文本。我出於演示目的輸入了 x0x0
。注意 cat
是如何簡單地重複我輸入的內容的。所以,x0x0
出現了兩次。我輸入了第一個,第二個是 cat
命令重複的輸出:
[root@sandbox tmp]# cat
x0x0
x0x0
複製代碼
返回到將 strace
接駁到 cat
進程的終端。如今你會看到兩個額外的系統調用:較早的 read
系統調用,如今在終端中讀取 x0x0
,另外一個爲 write
,它將 x0x0
寫回到終端,而後是再一個新的 read
,正在等待從終端讀取。請注意,標準輸入(0
)和標準輸出(1
)都在同一終端中:
[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536) = 5
write(1, "x0x0\n", 5) = 5
read(0,
複製代碼
想象一下,對守護進程運行 strace
以查看其在後臺執行的全部操做時這有多大幫助。按下 Ctrl + C
殺死 cat
命令;因爲該進程再也不運行,所以這也會終止你的 strace
會話。
若是要查看全部的系統調用的時間戳,只需將 -t
選項與 strace
一塊兒使用:
[root@sandbox ~]#strace -t ls testdir/
14:24:47 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
14:24:47 brk(NULL) = 0x1f07000
14:24:47 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2530bc8000
14:24:47 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
14:24:47 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
複製代碼
若是你想知道兩次系統調用之間所花費的時間怎麼辦?strace
有一個方便的 -r
命令,該命令顯示執行每一個系統調用所花費的時間。很是有用,不是嗎?
[root@sandbox ~]#strace -r ls testdir/
0.000000 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
0.000368 brk(NULL) = 0x1966000
0.000073 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb6b1155000
0.000047 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000119 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
複製代碼
strace
實用程序很是有助於理解 Linux 上的系統調用。要了解它的其它命令行標誌,請參考手冊頁和在線文檔。
via: opensource.com/article/19/…
做者:Gaurav Kamathe 選題:lujun9972 譯者:wxy 校對:wxy