引言html
「Oops,系統掛死了..."node
「Oops,程序崩潰了..."linux
「Oops,命令執行報錯..."socket
對於維護人員來講,這樣的悲劇天天都在上演。理想狀況下,系統或應用程序的錯誤日誌提供了足夠全面的信息,經過查看相關日誌,維護人員就能很快地定位出問題發生的緣由。但現實狀況,許多錯誤日誌打印模凌兩可,更多地描述了出錯時的現象(好比"could not open file","connect to XXX time out"),而非出錯的緣由。ide
錯誤日誌不能知足定位問題的需求,咱們能從更「深層」的方面着手分析嗎?程序或命令的執行,須要經過系統調用(system call)與操做系統產生交互,其實咱們能夠經過觀察這些系統調用及其參數、返回值,界定出錯的範圍,甚至找出問題出現的根因。函數
在Linux中,strace就是這樣一款工具。經過它,咱們能夠跟蹤程序執行過程當中產生的系統調用及接收到的信號,幫助咱們分析程序或命令執行中遇到的異常狀況。工具
一個簡單的例子學習
如何使用strace對程序進行跟蹤,如何查看相應的輸出?下面咱們經過一個例子來講明。ui
1.被跟蹤程序示例google
//main.c #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> main( ) { fd ; i = ; fd = open( 「/tmp/foo」, O_RDONLY ) ; ( fd < ) i=; i=; i; }
以上程序嘗試以只讀的方式打開/tmp/foo文件,而後退出,其中只使用了open這一個系統調用函數。以後咱們對該程序進行編譯,生成可執行文件:
lx@LX:~$ gcc main.c -o main
2.strace跟蹤輸出
使用如下命令,咱們將使用strace對以上程序進行跟蹤,並將結果重定向至main.strace文件:
lx@LX:~$ strace -o main.strace ./main
接下來咱們來看main.strace文件的內容:
lx@LX:~$ cat main.strace execve("./main", ["./main"], [/* 43 vars */]) = 0 brk(0) = 0x9ac4000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7739000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=80682, ...}) = 0 mmap2(NULL, 80682, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7725000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0 mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x56d000 mprotect(0x6c7000, 4096, PROT_NONE) = 0 mmap2(0x6c8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x6c8000 mmap2(0x6cb000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6cb000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7724000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb77248d0, limit:1048575, seg_32bit:1, contents:0, read_exec_ only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0x6c8000, 8192, PROT_READ) = 0 mprotect(0x8049000, 4096, PROT_READ) = 0 mprotect(0x4b0000, 4096, PROT_READ) = 0 munmap(0xb7725000, 80682) = 0 open("/tmp/foo", O_RDONLY) = -1 ENOENT (No such file or directory) exit_group(5) = ? //標紅的行號爲方便說明而添加,非strace執行輸出
看到這一堆輸出,是否心生畏難情緒?不用擔憂,下面咱們對輸出逐條進行分析。
strace跟蹤程序與系統交互時產生的系統調用,以上每一行就對應一個系統調用,格式爲:
系統調用的名稱( 參數... ) = 返回值 錯誤標誌和描述
Line 1: 對於命令行下執行的程序,execve(或exec系列調用中的某一個)均爲strace輸出系統調用中的第一個。strace首先調用fork或clone函數新建一個子進程,而後在子進程中調用exec載入須要執行的程序(這裏爲./main)
Line 2: 以0做爲參數調用brk,返回值爲內存管理的起始地址(若在子進程中調用malloc,則從0x9ac4000地址開始分配空間)
Line 3: 調用access函數檢驗/etc/ld.so.nohwcap是否存在
Line 4: 使用mmap2函數進行匿名內存映射,以此來獲取8192bytes內存空間,該空間起始地址爲0xb7739000,關於匿名內存映射,能夠看這裏
Line 6: 調用open函數嘗試打開/etc/ld.so.cache文件,返回文件描述符爲3
Line 7: fstat64函數獲取/etc/ld.so.cache文件信息
Line 8: 調用mmap2函數將/etc/ld.so.cache文件映射至內存,關於使用mmap映射文件至內存,能夠看這裏
Line 9: close關閉文件描述符爲3指向的/etc/ld.so.cache文件
Line12: 調用read,從/lib/i386-linux-gnu/libc.so.6該libc庫文件中讀取512bytes,即讀取ELF頭信息
Line15: 使用mprotect函數對0x6c7000起始的4096bytes空間進行保護(PROT_NONE表示不能訪問,PROT_READ表示能夠讀取)
Line24: 調用munmap函數,將/etc/ld.so.cache文件從內存中去映射,與Line 8的mmap2對應
Line25: 對應源碼中使用到的惟一的系統調用——open函數,使用其打開/tmp/foo文件
Line26: 子進程結束,退出碼爲5(爲何退出值爲5?返回前面程序示例部分看看源碼吧:)
3.輸出分析
呼呼!看完這麼多系統調用函數,是否是有點摸不着北?讓咱們從總體入手,回到主題strace上來。
從上面輸出能夠發現,真正能與源碼對應上的只有open這一個系統調用(Line25),其餘系統調用幾乎都用於進行進程初始化工做:裝載被執行程序、載入libc函數庫、設置內存映射等。
源碼中的if語句或其餘代碼在相應strace輸出中並無體現,由於它們並無喚起系統調用。strace只關心程序與系統之間產生的交互,於是strace不適用於程序邏輯代碼的排錯和分析。
對於Linux中幾百個系統調用,上面strace輸出的幾個只是冰山一角,想要更深刻地瞭解Linux系統調用,那就man一下吧!
man 系統調用名稱 man ld.so
strace經常使用選項
該節介紹常常用到的幾個strace命令選項,以及在什麼時候使用這些選項合適。
1.跟蹤子進程
默認狀況下,strace只跟蹤指定的進程,而不對指定進程中新建的子進程進行跟蹤。使用-f選項,可對進程中新建的子進程進行跟蹤,並在輸出結果中打印相應進程PID:
mprotect(, , PROT_READ) = munmap(, ) = clone(Process attached child_stack=, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=) = [pid ] fstat64(, {st_mode=S_IFCHR|, st_rdev=makedev(, ), ...}) = [pid ] fstat64(, {st_mode=S_IFCHR|, st_rdev=makedev(, ), ...}) = [pid ] mmap2(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, <unfinished ...> [pid ] mmap2(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, ) =
對多進程程序、命令和腳本使用strace進行跟蹤的時,通常打開-f選項。
2.記錄系統調用時間
strace還能夠記錄程序與系統交互時,各個系統調用發生時的時間信息,有r、t、tt、ttt、T等幾個選項,它們記錄時間的方式爲:
-T: 記錄各個系統調用花費的時間,精確到微秒
-r: 以第一個系統調用(一般爲execve)計時,精確到微秒
-t: 時:分:秒
-tt: 時:分:秒 . 微秒
-ttt: 計算機紀元以來的秒數 . 微秒
比較經常使用的爲T選項,由於其提供了每一個系統調用花費時間。而其餘選項的時間記錄既包含系統調用時間,又算上用戶級代碼執行用時,參考意義就小一些。對部分時間選項咱們能夠組合起來使用,例如:
strace -Tr ./main execve(「./main」, [「main」], []) = fcntl64(, F_GETFD)= <> fcntl64(, F_GETFD)= <> fcntl64(, F_GETFD)= <> uname({sys=」Linux」, node=」ion」, ...}) = <> geteuid32()= <> getuid32()= <> getegid32()= <> getgid32()= <> ……
最左邊一列爲-r選項對應的時間輸出,最右邊一列爲-T選項對應的輸出。
3.跟蹤正在運行的進程
使用strace對運行中的程序進行跟蹤,使用命令「strace -p PID」便可,命令執行以後,被跟蹤的進程照常執行,strace的其餘選項也適用於運行中的進程跟蹤。
使用strace處理程序掛死
最後咱們經過一個程序示例,學習使用strace分析程序掛死的方法。
1.掛死程序源碼
//hang.c #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <.h> main( argc, ** argv) { getpid(); //該系統調用起到標識做用 (argc < ) { printf(); ; } (!strcmp(argv[], )) (); (!strcmp(argv[], )) sleep(); ; }
可向該程序傳送user和system參數,以上代碼使用死循環模擬用戶態掛死,調用sleep模擬內核態程序掛死。
2.strace跟蹤輸出
用戶態掛死跟蹤輸出:
lx@LX:~$ gcc hang.c -o hang lx@LX:~$ strace ./hang user …… mprotect(, , PROT_READ) = mprotect(, , PROT_READ) = munmap(, ) = getpid() =
內核態掛死跟蹤輸出:
lx@LX:~$ strace ./hang system …… mprotect(, , PROT_READ) = mprotect(, , PROT_READ) = munmap(, ) = getpid() = rt_sigprocmask(SIG_BLOCK, [CHLD], [], ) = rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], }, ) = rt_sigprocmask(SIG_SETMASK, [], NULL, ) = nanosleep({, },
3.輸出分析
用戶態掛死狀況下,strace在getpid()一行輸出以後沒有其餘系統調用輸出;進程在內核態掛死,最後一行的系統調用nanosleep不能完整顯示,這裏nanosleep沒有返回值表示該調用還沒有完成。
於是咱們能夠得出如下結論:使用strace跟蹤掛死程序,若是最後一行系統調用顯示完整,程序在邏輯代碼處掛死;若是最後一行系統調用顯示不完整,程序在該系統調用處掛死。
當程序掛死在系統調用處,咱們能夠查看相應系統調用的man手冊,瞭解在什麼狀況下該系統調用會出現掛死狀況。另外,系統調用的參數也爲咱們提供了一些信息,例如掛死在以下系統調用:
read(16,
那咱們能夠知道read函數正在對文件描述符爲16的文件或socket進行讀取,進一步地,咱們可使用lsof工具,獲取對應於文件描述符爲16的文件名、該文件被哪些進程佔用等信息。
小結
本文對Linux中經常使用的問題診斷工具strace進行了介紹,經過程序示例,介紹了strace的使用方法、輸出格式以及使用strace分析程序掛死問題的方法,另外對strace工具的幾個經常使用選項進行了說明,描述了這幾個選項適用的場景。
下次再遇到程序掛死、命令執行報錯的問題,若是從程序日誌和系統日誌中看不出問題出現的緣由,先別急着google或找高手幫忙,別忘了一個強大的工具它就在那裏,不離不棄,strace一下吧!