在 Linux 上用 strace 來理解系統調用

在 Linux 上用 strace 來理解系統調用

使用 strace 跟蹤用戶進程和 Linux 內核之間的交互。-- Gaurav Kamathe(作者)

系統調用(system call)是程序從內核請求服務的一種編程方式,而 strace 是一個功能強大的工具,可讓你跟蹤用戶進程與 Linux 內核之間的交互。

要了解操作系統的工作原理,首先需要了解系統調用的工作原理。操作系統的主要功能之一是為用戶程序提供抽象機制。

操作系統可以大致分為兩種模式:

  • 內核模式:操作系統內核使用的一種強大的特權模式
  • 用戶模式:大多數用戶應用程序運行的地方 用戶大多使用命令行實用程序和圖形用戶界面(GUI)來執行日常任務。系統調用在後臺靜默運行,與內核交互以完成工作。

系統調用與函數調用非常相似,這意味著它們都接受並處理參數然後返回值。唯一的區別是系統調用進入內核,而函數調用不進入。從用戶空間切換到內核空間是使用特殊的 trap 機制完成的。

通過使用系統庫(在 Linux 系統上又稱為 glibc),大部分系統調用對用戶隱藏了。儘管系統調用本質上是通用的,但是發出系統調用的機制在很大程度上取決於機器(架構)。

本文通過使用一些常規命令並使用 strace 分析每個命令進行的系統調用來探索一些實際示例。這些示例使用 Red Hat Enterprise Linux,但是這些命令運行在其他 Linux 發行版上應該也是相同的:

首先,確保在系統上安裝了必需的工具。你可以使用下面的 rpm 命令來驗證是否安裝了 strace。如果安裝了,則可以使用 -V 選項檢查 strace 實用程序的版本號:

如果沒有安裝,運行命令安裝:

出於本示例的目的,在 /tmp 中創建一個測試目錄,並使用 touch 命令創建兩個文件:

(我使用 /tmp 目錄是因為每個人都可以訪問它,但是你可以根據需要選擇另一個目錄。)

在 testdir 目錄下使用 ls 命令驗證該文件已經創建:

你可能每天都在使用 ls 命令,而沒有意識到系統調用在其下面發揮的作用。抽象地來說,該命令的工作方式如下:

命令行工具 -> 從系統庫(glibc)調用函數 -> 調用系統調用

ls 命令內部從 Linux 上的系統庫(即 glibc)調用函數。這些庫去調用完成大部分工作的系統調用。

如果你想知道從 glibc 庫中調用了哪些函數,請使用 ltrace 命令,然後跟上常規的 ls testdir/命令:

如果沒有安裝 ltrace,鍵入如下命令安裝:

大量的輸出會被堆到屏幕上;不必擔心,只需繼續就行。ltrace 命令輸出中與該示例有關的一些重要庫函數包括:

通過查看上面的輸出,你或許可以瞭解正在發生的事情。opendir 庫函數打開一個名為 testdir 的目錄,然後調用 readdir 函數,該函數讀取目錄的內容。最後,有一個對 closedir 函數的調用,該函數將關閉先前打開的目錄。現在請先忽略其他 strlen 和 memcpy 功能。

你可以看到正在調用哪些庫函數,但是本文將重點介紹由系統庫函數調用的系統調用。

與上述類似,要了解調用了哪些系統調用,只需將 strace 放在 ls testdir 命令之前,如下所示。 再次,一堆亂碼丟到了你的屏幕上,你可以按照以下步驟進行操作:

運行 strace 命令後屏幕上的輸出就是運行 ls 命令的系統調用。每個系統調用都為操作系統提供了特定的用途,可以將它們大致分為以下幾個部分:

  • 進程管理系統調用
  • 文件管理系統調用
  • 目錄和文件系統管理系統調用
  • 其他系統調用

分析顯示到屏幕上的信息的一種更簡單的方法是使用 strace 方便的 -o 標誌將輸出記錄到文件中。在 -o 標誌後添加一個合適的文件名,然後再次運行命令:

這次,沒有任何輸出干擾屏幕顯示,ls 命令如預期般工作,顯示了文件名並將所有輸出記錄到文件 trace.log 中。僅僅是一個簡單的 ls 命令,該文件就有近 100 行內容:

讓我們看一下這個示例的 trace.log 文件的第一行:

  • 該行的第一個單詞 execve 是正在執行的系統調用的名稱。
  • 括號內的文本是提供給該系統調用的參數。
  • 符號 = 後的數字(在這種情況下為 0)是 execve 系統調用的返回值。

現在的輸出似乎還不太嚇人,對吧。你可以應用相同的邏輯來理解其他行。

現在,將關注點集中在你調用的單個命令上,即 ls testdir。你知道命令 ls 使用的目錄名稱,那麼為什麼不在 trace.log 文件中使用 grep 查找 testdir 並查看得到的結果呢?讓我們詳細查看一下結果的每一行:

回顧一下上面對 execve 的分析,你能說一下這個系統調用的作用嗎?

你無需記住所有系統調用或它們所做的事情,因為你可以在需要時參考文檔。手冊頁可以解救你!在運行 man 命令之前,請確保已安裝以下軟件包:

請記住,你需要在 man 命令和系統調用名稱之間添加 2。如果使用 man man 閱讀 man 命令的手冊頁,你會看到第 2 節是為系統調用保留的。同樣,如果你需要有關庫函數的信息,則需要在 man 和庫函數名稱之間添加一個 3。

以下是手冊的章節編號及其包含的頁面類型:

  • 1:可執行的程序或 shell 命令
  • 2:系統調用(由內核提供的函數)
  • 3:庫調用(在程序的庫內的函數)
  • 4:特殊文件(通常出現在 /dev)

使用系統調用名稱運行以下 man 命令以查看該系統調用的文檔:

按照 execve 手冊頁,這將執行在參數中傳遞的程序(在本例中為 ls)。可以為 ls 提供其他參數,例如本例中的 testdir。因此,此係統調用僅以 testdir 作為參數運行 ls:

下一個系統調用,名為 stat,它使用 testdir 參數:

使用 man 2 stat 訪問該文檔。stat 是獲取文件狀態的系統調用,請記住,Linux 中的一切都是文件,包括目錄。

接下來,openat 系統調用將打開 testdir。密切注意返回的 3。這是一個文件描述符,將在以後的系統調用中使用:

到現在為止一切都挺好。現在,打開 trace.log 文件,並轉到 openat 系統調用之後的行。你會看到 getdents 系統調用被調用,該調用完成了執行 ls testdir 命令所需的大部分操作。現在,從 trace.log 文件中用 grep 獲取 getdents:

getdents 的手冊頁將其描述為 “獲取目錄項”,這就是你要執行的操作。注意,getdents 的參數是 3,這是來自上面 openat 系統調用的文件描述符。

現在有了目錄列表,你需要一種在終端中顯示它的方法。因此,在日誌中用 grep 搜索另一個用於寫入終端的系統調用 write:

在這些參數中,你可以看到將要顯示的文件名:file1 和 file2。關於第一個參數(1),請記住在 Linux 中,當運行任何進程時,默認情況下會為其打開三個文件描述符。以下是默認的文件描述符:

  • 0:標準輸入
  • 1:標準輸出
  • 2:標準錯誤

因此,write 系統調用將在標準顯示(就是這個終端,由 1 所標識的)上顯示 file1 和 file2。

現在你知道哪個系統調用完成了 ls testdir/ 命令的大部分工作。但是在 trace.log 文件中其它的 100 多個系統調用呢?操作系統必須做很多內務處理才能運行一個進程,因此,你在該日誌文件中看到的很多內容都是進程初始化和清理。閱讀整個 trace.log 文件,並嘗試瞭解 ls 命令是怎麼工作起來的。

既然你知道了如何分析給定命令的系統調用,那麼就可以將該知識用於其他命令來了解正在執行哪些系統調用。strace 提供了許多有用的命令行標誌,使你更容易使用,下面將對其中一些進行描述。

默認情況下,strace 並不包含所有系統調用信息。但是,它有一個方便的 -v 冗餘選項,可以在每個系統調用中提供附加信息:

在運行 strace 命令時始終使用 -f 選項是一種好的作法。它允許 strace 對當前正在跟蹤的進程創建的任何子進程進行跟蹤:

假設你只需要系統調用的名稱、運行的次數以及每個系統調用花費的時間百分比。你可以使用 -c 標誌來獲取這些統計信息:

假設你想專注於特定的系統調用,例如專注於 open 系統調用,而忽略其餘部分。你可以使用-e 標誌跟上系統調用的名稱:

如果你想關注多個系統調用怎麼辦?不用擔心,你同樣可以使用 -e 命令行標誌,並用逗號分隔開兩個系統調用的名稱。例如,要查看 write 和 getdents 系統調用:

到目前為止,這些示例是明確地運行的命令進行了跟蹤。但是,要跟蹤已經運行並正在執行的命令又怎麼辦呢?例如,如果要跟蹤用來長時間運行進程的守護程序,該怎麼辦?為此,strace 提供了一個特殊的 -p 標誌,你可以向其提供進程 ID。

我們的示例不在守護程序上運行 strace,而是以 cat 命令為例,如果你將文件名作為參數,通常 cat 會顯示文件的內容。如果沒有給出參數,cat 命令會在終端上等待用戶輸入文本。輸入文本後,它將重複給定的文本,直到用戶按下 Ctrl + C 退出為止。

從一個終端運行 cat 命令;它會向你顯示一個提示,並等待在那裡(記住 cat 仍在運行且尚未退出):

在另一個終端上,使用 ps 命令找到進程標識符(PID):

現在,使用 -p 標誌和 PID(在上面使用 ps 找到)對運行中的進程運行 strace。運行 strace 之後,其輸出說明了所接駁的進程的內容及其 PID。現在,strace 正在跟蹤 cat 命令進行的系統調用。看到的第一個系統調用是 read,它正在等待文件描述符 0(標準輸入,這是運行 cat 命令的終端)的輸入:

現在,返回到你運行 cat 命令的終端,並輸入一些文本。我出於演示目的輸入了 x0x0。注意 cat 是如何簡單地重複我輸入的內容的。因此,x0x0 出現了兩次。我輸入了第一個,第二個是 cat 命令重複的輸出:

返回到將 strace 接駁到 cat 進程的終端。現在你會看到兩個額外的系統調用:較早的 read 系統調用,現在在終端中讀取 x0x0,另一個為 write,它將 x0x0 寫回到終端,然後是再一個新的 read,正在等待從終端讀取。請注意,標準輸入(0)和標準輸出(1)都在同一終端中:

想象一下,對守護進程運行 strace 以查看其在後臺執行的所有操作時這有多大幫助。按下 Ctrl + C 殺死 cat 命令;由於該進程不再運行,因此這也會終止你的 strace 會話。

如果要查看所有的系統調用的時間戳,只需將 -t 選項與 strace 一起使用:

如果你想知道兩次系統調用之間所花費的時間怎麼辦?strace 有一個方便的 -r 命令,該命令顯示執行每個系統調用所花費的時間。非常有用,不是嗎?

總結

strace 實用程序非常有助於理解 Linux 上的系統調用。要了解它的其它命令行標誌,請參考手冊頁和在線文檔。


via: https://opensource.com/article/19/10/strace

作者: Gaurav Kamathe 選題: lujun9972 譯者: wxy 校對: wxy

本文由 LCTT 原創編譯, Linux中國 榮譽推出


分享到:


相關文章: