造輪子-strace(一)

東北碼農發表於2022-01-02

見字如面,我是東北碼農。

本文是造輪子-strace的第一篇,我們先介紹strace的功能、使用。下一篇我們來用程式碼實現一下strace的功能,造個輪子。今天我們先觀察、使用輪子。

1、什麼是strace

strace是一個Linux的工具,用於記錄process的system call的引數、返回值等資訊。

strace是我們除錯、排障的好幫手,尤其是沒有原始碼或不方便重新編譯的時候。

1.1、小試牛刀

strace使用時非常方便,最簡單的方式就是在指令前面加上strace。接下來以telnet為例來看看telnet時呼叫了哪些system call。

先直接執行一下telnet:

root@xxx:~$ telnet 220.181.38.148 80
Trying 220.181.38.148...
Connected to 220.181.38.148.
Escape character is '^]'.
Connection closed by foreign host.

我們再用strace執行一下telnet:

root@xxx:~$ strace telnet 220.181.38.148 80

...下面省略了一部分...
Trying 220.181.38.148...
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_IP, IP_TOS, [16], 4)  = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("220.181.38.148")}, 16) = 0
Connected to 220.181.38.148.
Escape character is '^]'.
setsockopt(3, SOL_SOCKET, SO_OOBINLINE, [1], 4) = 0
recvfrom(3, "", 8191, 0, NULL, NULL)    = 0
Connection closed by foreign host.
+++ exited with 1 +++

神奇吧,把telnet呼叫的system call的引數和返回值列印了出來。上面我只擷取了一部分關於網路的system call。

1.2、什麼是system call(系統呼叫)

strace可以跟蹤system call,那麼什麼是system call呢?

為了安全,Linux 中分為使用者態和核心態兩種執行狀態。對於普通程式,平時都是執行在使用者態下,僅擁有基本的執行能力。當進行一些敏感操作,比如說要開啟檔案(open)然後進行寫入(write)、分配記憶體(malloc)時,就會切換到核心態。核心態進行相應的檢查,如果通過了,則按照程式的要求執行相應的操作,分配相應的資源。這種機制被稱為system call(系統呼叫),使用者態程式發起呼叫,切換到核心態,核心態完成,返回使用者態繼續執行,是使用者態唯一主動切換到核心態的合法手段(exception 和 interrupt 是被動切換)。

關於系統呼叫的詳細定義可以通過 man syscalls 檢視,它列出了目前 Linux Kernel 提供的系統呼叫 ABI 。我們熟悉的呼叫比如 open, read ,close 之類的都屬於系統呼叫,但它們都經過了 C 庫 (glibc)的封裝。實際上,只要符合 ABI 規範,我們可以自己用匯編程式碼來進行呼叫。

2、怎麼使用strace

學習strace的各種用法,各種引數,推薦兩個途徑

下面介紹一下我認為比較有用的一些用法。

2.1、兩種trace模式

strace也有兩種trace模式:

  • strace啟動模式:就像上面telnet的例子,正常啟動指令前面加strace即可。
  • attach模式:strace -p pid。

strace和gdb很像,二者實現的時候都是依賴ptrace。

2.2、過濾

如果不加過濾,strace會列印所有的system call。即使簡單的ls指令,也會呼叫多達167個system call。所以我們需要根據需求對輸出進行一些過濾。

root@xxx:~/code/case/case20_ptrace$ strace ls 2>&1 |wc -l
167
2.2.1、按system call型別過濾

可以按照system call型別過濾,我比較常用的有檔案相關、記憶體相關、網路相關。

-e trace=%file     Trace all system calls which take a file name as an argument.
         %memory   Trace all memory mapping related system calls.
         %network  Trace all the network related system calls.
         %process  Trace all system calls which involve process management.

例如只看檔案相關的system call

***@xxx:~$ strace -e trace=%file cat ./out
execve("/usr/bin/cat", ["cat", "./out"], 0x7ffd2b5f1f08 /* 19 vars */) = 0
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, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "./out", O_RDONLY)     = 3
2.2.2、按system call名稱過濾

指定system call名稱,例如只跟蹤connect函式

***@xxx:~$ strace -e trace=connect telnet baidu.com 80
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("172.30.224.1")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("220.181.38.148")}, 16) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("220.181.38.251")}, 16) = 0
Trying 220.181.38.148...
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("220.181.38.148")}, 16) = 0
Connected to baidu.com.
Escape character is '^]'.
^Cstrace: Process 371 detached

2.3、system call故障注入

我們可以藉助strace,觀察process在system call失敗時的表現。例如我們可以觀察一下,cat開啟檔案失敗時的表現。

在測試時很有用,起到了類似hook的效果。可以測試程式在記憶體不足、檔案訪問許可權不足、網路不通等異常情況的表現。

root@xxx:~$ strace -e trace=openat -e fault=openat cat ./out
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
cat: error while loading shared libraries: libc.so.6: cannot open shared object file: Error 38
+++ exited with 127 +++

strace輸出:“ (Function not implemented) (INJECTED)”。cat在開啟檔案失敗時輸出:“cat: error while loading shared libraries: libc.so.6: cannot open shared object file: Error 38”

2.4、system call資訊統計

strace除了可以列印每次system call資訊外,還可以使用統計模式。
在效能測試時比較有用。

root@xxx:~$ strace -c -e trace=network telnet baidu.com 80
Trying 220.181.38.148...
Connected to baidu.com.
Escape character is '^]'.
telnet> q
Connection closed.
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 22.93    0.001428         204         7         2 connect
 13.12    0.000817         272         3           sendto
 13.05    0.000813         135         6           socket
 11.24    0.000700         233         3           setsockopt
 10.44    0.000650         216         3           recvmsg
  9.55    0.000595         198         3           getsockname
  7.10    0.000442         221         2           recvfrom
  5.15    0.000321         321         1           sendmmsg
  3.72    0.000232         232         1           bind
  3.71    0.000231         231         1           shutdown
------ ----------- ----------- --------- --------- ----------------
100.00    0.006229                    30         2 total
  • time:時間佔比
  • seconds:總執行時間
  • usecs/call:平均每次執行時間(微秒)
  • calls:執行次數
  • errors:執行出錯次數

3、什麼時候使用strace?

3.1、診斷、除錯

strace的man手冊裡是這麼介紹自己的

strace  is  a useful diagnostic, instructional, and debugging tool.  
System administrators, diagnosticians andtrouble-shooters will find it invaluable 
for solving problems with programs for which the source is not  readily  available  since  they  do  not  need to be recompiled in order to trace them.

strace是診斷、除錯工具。尤其是程式碼不可用的時候,因為無需重新編譯就可以跟蹤system call。
例如一個程式呼叫system call失敗退出,但是程式沒輸出關鍵日誌。通過strace就可以獲取呼叫的引數、返回值、err no。迅速定位問題。

3.2、學習用途

一個程式的實現,無外乎資料+演算法+system call。一些“神奇”的程式往往要藉助system call實現。例如tcpdump如何實現抓包,gdb如何實現除錯。我們都可以使用strace來找靈感。

可以通過strace來了解程式呼叫了哪些system call,來大致摸清程式實現的骨架。甚至一會我們會利用strace來學習strace是如何實現的。

最後,全網同名,求關注、點贊、轉發,謝謝~

 

相關文章