strace用法学习
一、strace是什么?
? ? 按照strace官網的描述,strace是一個可用于診斷、調試和教學的Linux用戶空間跟蹤器。我們用它來監控用戶空間進程和內核的交互,比如系統調用、信號傳遞、進程狀態變更等。
strace底層使用內核的ptrace特性來實現其功能。在運維的日常工作中,故障處理和問題診斷是個主要的內容,也是必備的技能。strace作為一種動態跟蹤工具,能夠幫助運維高效地定位進程和服務故障。它像是一個偵探,通過系統調用的蛛絲馬跡,告訴你異常的真相。
二、strace能做什么?
? ? 運維工程師都是實踐派的人,我們還是先來個例子吧。
? ? 我們從別的機器copy了一個叫做some_server的軟件包過來,開發說直接啟動就行,啥都不用改。可是嘗試啟動時卻報錯,根本起不來!
? ? 啟動命令:??
./some_server ../conf/some_server.conf? ? 輸出:
FATAL: InitLogFile failed iRet: -1! Init error: -1655為什么起不來呢?從日志看,似乎是初始化日志文件失敗,真相到底怎樣呢?我們用strace來看看。
strace -tt -f ./some_server ../conf/some_server.conf? ? 我們注意到,在輸出InitLogFile failed錯誤的前一行,有一個open系統調用:
23:14:24.448034 open("/usr/local/apps/some_server/log//server_agent.log", O_RDWR|O_CREAT|O_APPEND)? ? 它嘗試打開文件/usr/local/apps/some_server/log//server_agent.log來寫(不存在則創建),可是卻出錯了,返回碼是-1,系統錯誤號errorno為ENOENT。查下open系統調用的手冊頁:
man 2 open? ? 搜索ENOENT這個錯誤號errno的解釋
ENOENT O_CREAT is not set and the named file does not exist. Or, a directory component in pathname does not exist or is a dangling symbolic link.? ? 這里說的比較清楚,因為我們例子中的open選項指定了O_CREAT選項,這里errno為ENOENT的原因是日志路徑中某個部分不存在或者是一個失效的符號鏈接。我們來一級以及看下路徑中的哪部分不存在:
ls -l /usr/local/apps/some_server/log ls: cannot access /usr/local/apps/some_server/log: No such file or directory ls -l /usr/local/apps/some_server total 8 drwxr-xr-x 2 root users 4096 May 14 23:13 bin drwxr-xr-x 2 root users 4096 May 14 22:48 conf? ? 原來是log子目錄不存在!上層目錄都是存在的。手工創建log子目錄后,服務就能正常啟動了。
? ? 回過頭來,strace究竟能做什么呢?
它能夠打開應用進程的這個黑盒,通過系統調用的線索,告訴你進程大概在干嘛。三、strace怎么用?
? ? 既然strace是用來跟蹤用戶空間進程的系統調用和信號的,在進入strace使用的主題之前, 我們得先理解什么是系統調用。
? ? 關于系統調用:
? ? 按維基百科中的解釋,在計算機中,系統調用,又稱為系統呼叫,指運行在用戶空間的程序向操作系統內核請求需要更高全線運行的服務。
? ? 系統調用提供用戶程序與操作系統之間的接口。操作系統的進程空間分為用戶空間和內核空間:
- ? ? 操作系統內核直接運行在硬件上,提供設備管理、內存管理、任務調度等功能。
- ? ? 用戶空間通過API請求內核空間的服務來完成其功能---內核提供給用戶空間的這些API,就是系統調用。
? ? 在Linux系統上,應用代碼通過glibc庫封裝的函數,間接使用系統調用。
? ? Linux內核目前有300多個系統調用,詳細的列表可以通過syscalls手冊頁查看。這些系統調用主要分為幾類:
文件和設備訪問類 比如open/close/read/write/chmod等 進程管理類 fork/clone/execve/exit/getpid等 信號類 signal/sigaction/kill 等 內存管理 brk/mmap/mlock等 進程間通信 IPC shmget/semget * 信號量,共享內存,消息隊列等 網絡通信 socket/connect/sendto/sendmsg等 其他? ? 熟悉Linux系統調用/系統編程,能夠讓我們在使用strace時得心應手。不過,對于運維的問題定位來說,知道strace這個工具,會查系統調用手冊,就差不多夠了。
想要深入了解的同學,建議閱讀《Linux系統編程》,《Unix環境高級編程》等書籍。? ?我們回到strace的使用上來。strace有兩種運行模式:
? ?一種是通過它啟動要跟蹤的進程。用法很簡單,在原本的命令前加上strace即可。比如我們要跟蹤"ls -lh /var/log/messages"這個命令的執行,可以這樣:
strace ls -lh /var/log/messages? ? 另外一種運行模式,是跟蹤已經在運行的進程,在不中斷進程執行的情況下,理解它在干嘛。這種情況,給strace傳遞個-p pid選項即可。
? ? 比如,有個在運行的some_server服務,第一步,查看pid:? ?
pidof some_server17553? ? 得到其pid 17553然后就可以用strace跟蹤其執行:
strace -p 17553? ? 完成跟蹤時,按ctrl+C結束strace即可。
? ? strace有一些選項可以調整其行為,我們這里介紹下其中幾個比較常用的,然后通過實例講解其實際應用效果。
四、strace常用選項:
? ? 從一個示例命令來看:
strace -tt -T -v -f -e trace=file -o /data/log/strace.log -s 1024 -p 23489-tt 在每行輸出的前面,顯示毫秒級別的時間-T 顯示每次系統調用所花費的時間-v 對于某些相關調用,把完整的環境變量,文件stat結構等打出來。-f 跟蹤目標進程,以及目標進程創建的所有子進程-e 控制要跟蹤的事件和跟蹤行為,比如指定要跟蹤的系統調用名稱-o 把strace的輸出單獨寫到指定的文件-s 當系統調用的某個參數是字符串時,最多輸出指定長度的內容,默認是32個字節-p 指定要跟蹤的進程pid,要同時跟蹤多個pid,重復多次-p選項即可。? ?示例:跟蹤nginx,看其啟動時都訪問了哪些文件
strace -tt -T -f -e trace=file -o /data/log/strace.log -s 1024 ./nginx? ?輸出中,第一列顯示的是進程的pid,接著是毫秒級別的時間,這個是-tt選項的效果。
? ?每一行的最后一列,顯示了該調用所花的時間,是-T選項的結果。
? ?這里的輸出只顯示和文件訪問有關的內容,這是因為我們通過-e trace=file選項指定了。
五、strace問題定位案例
? ? 1. 定位進程異常退出
? ? ? ? 問題:機器上有個叫做run.sh的常駐腳本,運行一分鐘后會死掉。需要查出死因。
? ? ? ? 定位:進程還在運行時,通過ps命令獲取其pid,假設我們得到的pid是24298
strace -o strace.log -tt -p 24298? ? ? ? 查看strace.log,我們在最后2行看到如下內容:
22:47:42.803937 wait4(-1, <unfinished ...> 22:47:43.228422 +++ killed by SIGKILL +++? ? ? ? 這里可以看出,進程是被其他進程用KILL信號殺死的。
? ? ? ? 實際上,通過分析,我們發現機器上別的服務有個監控腳本,它監控一個叫做run.sh的進程,當發現run.sh進程數大于2時,就會把它殺死重啟。結果導致我們這個run.sh腳本被誤殺。
? ? ? ? 進程被殺退出時,strace會輸出killed by SIGX(SIGX代表發送給進程的信號)等,那么,進程自己退出會輸出什么呢?
? ? ? ? 這里有個叫做test_exit的程序,其代碼如下:
#include <stdio.h> #include <stdlib.h>int main(int argc, char **argv) {exit(1); }? ? ? ? ?我們strace看下它退出時strace上能看到什么痕跡。
strace -tt - e trace=process -f ./test_exit? ? ? ? ? 說明:-e trace=process表示只跟蹤和進程管理相關的系統調用。
? ? ? ? ? 輸出:
23:07:24:672849 execve("./test_exit", ["./test_exit"], [/* 35 vars */]) = 0 23:07:24.674665 arch_prctl(ARCH_SET_FS, 0x7f1c0eca7740) = 0 23:07:24.675108 exit_group(1) = ? 23:07:24.675259 +++ exited with 1 +++? ? ? ? ? 可以看出,進程自己退出時(調用exit函數,或者從main函數返回),最終調用的是exit_group系統調用,并且strace會輸出exited with X (X為退出碼)。
? ? ? ? ? 可能有人會疑惑,代碼里面明明調用的是exit,怎么顯示為exit_group?
這是因為這里的exit函數不是系統調用,而是glibc庫提供的一個函數,exit函數的調用最終會轉化為exit_group系統調用,它會退出當前進程的所有線程。實際上,有一個叫做_exit()的系統調用(注意exit前面的下劃線),線程退出時最終會調用它。? ? ? 2.定位共享內存異常
? ? ? ? 有個服務啟動時報錯:
shmget 267264 30097568: Invalid argument Can not get shm ... exit!? ? ? ? 錯誤啟動大概告訴我們是獲取共享內存出錯,通過strace看下:
strace -tt -f -e trace=ipc ./a_mon_svr ../conf/a_mon_svr.conf? ? ? ? 輸出:
22:46:36.351798 shmget(0x5feb, 12000, 0666) = 0 22:46:36.351939 shmat(0, 0, 0) = ? Process 21406 attached 22:46:36.355439 shmget(0x41400, 30097568, 0666) = -1 EINVAL (Invalid argument) shmget 267264 30097568: Invalid argument Can not get shm...exit!? ? ? ? 這里,我們通過-e trace=ipc選項,讓strace只跟蹤和進程通信相關的系統調用。
? ? ? ? 從strace輸出,我們知道是shmget系統調用出錯了,errno是EINVAL。同樣,查詢下shmget手冊頁,搜索EINVAL的錯誤碼的說明:
EINVAL A new segment was to be created and size < SHMMIN or size > SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment? ? ? ? ?翻譯下,shmget設置EINVAL錯誤碼的原因為下列之一:
- ?要創建的共享內存段比SHMMIN小(一般是1個字節)
- 要創建的共享內存段比SHMMAX大(內核參數kernel.shmmax配置)
- 指定key的共享內存段已存在,其大小和調用shmget時傳遞的值不同。
? ? ? ? ?從strace輸出看,我們要連的共享內存key 0x41400,指定的大小是30097568字節,明顯與第1、2種情況不匹配。那只剩下第三種情況。使用ipcs看下是否真的是大小不匹配:
ipcs -m | grep 41400 key shmid owner perms bytes nattch status 0x00041400 1015822 root 666 30095516 1? ? ? ? ? ? 可以看到,已經0x41400這個key已經存在,并且其大小為30095516字節,和我們調用參數中的30097568不匹配,于是產生了這個錯誤。
? ? ? ? ? 在我們這個案例里面,導致共享內存大小不一致的原因,是一組程序中,其中一個編譯為32位,另外一個編譯為64位,代碼里面使用了long這個變長int數據類型。
把兩個程序都編譯為64位解決了這個問題。? ? ? ? ?這里特別說下strace的-e trace選項。
? ? ? ? ?要跟蹤某個具體的系統調用,-e trace=xxx即可。但有時候我們要跟蹤一類系統調用,比如所有和文件名有關的調用、所有和內存分配有關的調用。
? ? ? ? ?如果人工輸入每一個具體的系統調用名稱,可能容易遺漏。于是strace提供了幾類常用的系統調用組合名字。
-e trace=file 跟蹤和文件訪問相關的調用(參數中有文件名) -e trace=process 和進程管理相關的調用,比如fork/exec/exit_group -e trace=network 和網絡通信相關的調用,比如socket/sendto/connect -e trace=signal 信號發送和處理相關,比如kill/sigaction -e trace=desc 和文件描述符相關,比如write/read/select/epoll等 -e trace=ipc 進程間通信相關,比如shmget等? ? ? ? ?絕大多數情況,我們使用上面的組合名字就夠了。實在需要跟蹤具體的系統調用時,可能需要注意C庫實現的差異。
比如我們知道創建進程用的是fork系統調用,但在glibc里面,fork的調用實際上映射到更底層的clone系統嗲用。使用strace時,得指定-e trace=clone,指定-e trace=fork什么也匹配不上。? ? ? 3.性能分析
? ? ? ? 假設有個需求,統計Linux 4.5.4 版本內核中的代碼行數(包含匯編和C代碼)。這里提供兩個Shell腳本實現:
? ? ? ?poor_script.sh:
!/bin/bash total_line=0 while read filename; doline=$(wc -l $filename | awk `{print $1}`)(( total_line += line )) done < <(find linux-4.5.4 -type f ( -iname `.c` -o -iname '.h' -o -iname `*.S`)) echo "total line: $total_line"? ? ? ? ?good_script.sh
!/bin/bash find linux-4.5.4 -type f (-iname '.c' -o -iname '.h' -o -iname '*.S') -print0 \ | wc -l --files0-from - | tail -n 1? ? ? ? ? ?兩段代碼實現的目的是一樣的。我們通過strace的-c選項來分別統計兩種版本的系統調用情況和其所花的時間(使用-f同時統計子進程的情況)
? ? ? ? ? ? 從兩個輸出可以看出,good_script.sh只需要2秒就可以得到結果: 19613114行。它大部分的調用(calls)開銷是文件操作(read/open/write/close)等,統計代碼行數本來就是干這些事情。
? ? ? ? ? ?而poor_script.sh完成同樣的任務則花了539秒。它大部分的調用開銷都在進程和內存管理上(wait4/mmap/getpid...)。
? ? ? ? ? ?實際上,從兩個圖中clone系統調用的次數,我們可以看出good_script.sh只需要啟動3個進程,而poor_script.sh完成整個任務居然啟動了126335個進程!
而進程創建和銷毀的代價是相當高的,性能不差才怪。? 六、總結
? ? ? 當發現進程或服務異常時,我們可以通過strace來跟蹤其系統調用,"看看它在干啥",進而找到異常的原因。熟悉常用系統調用,能夠更好地理解和使用strace。
? ? ? ?當然,萬能的strace也不是真正的萬能。當目標進程卡死在用戶態時,strace就沒有輸出了。
? ? ? ?這個時候我們需要其他的跟蹤手段,比如gdb/perf/SystemTap等。
總結
以上是生活随笔為你收集整理的strace用法学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《深入剖析NGINX》学习记录
- 下一篇: pstack命令学习