除錯跟蹤利器---strace

水枂發表於2020-11-25

通過這篇文章你會學習到strace的用法,strace可以幫助你高效地定位程式中的一些錯誤,關於strace的用處有很多,可以自行發掘

前面我們講解了gdb除錯程式,這篇文章介紹另一個除錯跟蹤工具strace,同樣你可以在linux下執行man strace檢視幫助資訊

(一)starce是什麼

我們直接看man列印的幫助資訊

 strace - trace system calls and signals

根據上面的描述我們可以知道strace主要是跟蹤系統的呼叫和訊號的傳遞,其實我們還可以用它來監視使用者程式和核心的互動,它能通過系統呼叫來偵測程式執行的詳細過程

在Linux世界,程式不能直接訪問硬體裝置,當程式需要訪問硬體裝置(比如讀取磁碟檔案,接收網路資料等等)時,必須由使用者態模式切換至核心態模式,通 過系統呼叫訪問硬體裝置。strace可以跟蹤到一個程式產生的系統呼叫,包括引數,返回值,執行消耗的時間。

(二)strace如何使用

引數引數說明
-c統計每一系統呼叫的所執行的時間,次數和出錯的次數等.
-d輸出strace關於標準錯誤的除錯資訊.
-f跟蹤由fork呼叫所產生的子程式.
-ff如果提供-o filename,則所有程式的跟蹤結果輸出到相應的filename.pid中,pid是各程式的程式號.
-F嘗試跟蹤vfork呼叫.在-f時,vfork不被跟蹤.
-h輸出簡要的幫助資訊.
-i輸出系統呼叫的入口指標.
-q禁止輸出關於脫離的訊息.
-r列印出相對時間關於,每一個系統呼叫.
-t在輸出中的每一行前加上時間資訊.
-tt在輸出中的每一行前加上時間資訊,微秒級.
-ttt微秒級輸出,以秒了表示時間.
-T顯示每一呼叫所耗的時間.
-v輸出所有的系統呼叫.一些呼叫關於環境變數,狀態,輸入輸出等呼叫由於使用頻繁,預設不輸出.
-V輸出strace的版本資訊.
-x以十六進位制形式輸出非標準字串
-xx所有字串以十六進位制形式輸出.

-e expr :指定一個表示式,用來控制如何跟蹤,由於格式比較多我們單獨拿出來

格式如下:

[qualifier=][!]value1[,value2]...

qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用來限定的符號或數字.預設的 qualifier是 trace.感嘆號是否定符號.

-e expr引數設定
-e trace=set只跟蹤指定的系統 呼叫.例如:-e trace=open,close,rean,write表示只跟蹤這四個系統呼叫.預設的為set=all.
-e trace=file只跟蹤有關檔案操作的系統呼叫.
-e trace=process只跟蹤有關程式控制的系統呼叫.
-e trace=network跟蹤與網路有關的所有系統呼叫.
-e strace=signal跟蹤所有與系統訊號有關的 系統呼叫
-e trace=ipc跟蹤所有與程式通訊有關的系統呼叫
-e abbrev=set設定 strace輸出的系統呼叫的結果集.-v 等與 abbrev=none.預設為abbrev=all.
-e raw=set將指 定的系統呼叫的引數以十六進位制顯示.
-e signal=set指定跟蹤的系統訊號.預設為all.如 signal=!SIGIO(或者signal=!io),表示不跟蹤SIGIO訊號.
-e read=set輸出從指定檔案中讀出 的資料.例如:
-o filename將strace的輸出寫入檔案filename
-p pid跟蹤指定的程式pid.
-s strsize指定輸出的字串的最大長度.預設為32.檔名一直全部輸出.
-u username以username 的UID和GID執行被跟蹤的命令

示例:
(1)根據指定pid程式跟蹤

strace -o output.txt -T -tt -e trace=all -p 28979

上面的含義是 跟蹤28979程式的所有系統呼叫(-e trace=all),並統計系統呼叫的花費時間,以及開始時間(並以視覺化的時分秒格式顯示),最後將記錄結果存在output.txt檔案裡面。
(2)根據程式名跟蹤

strace -o output.txt -T -tt -e trace=all  ./a.out

(三)strace的一個分析例項

test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc,char * argv[])
{
   char buff[256]={0};
   FILE* file=NULL;
   file=fopen(argv[1],"r");
   if(file == NULL)
   {
     printf("fopen error\n");
//     return -1;
   }
   fread(buff,sizeof(buff),1,file);
   printf("buff=%s\n",buff);
   return 0;
}

我們執行:

gcc test.c -o test 
./test  

上面我們執行時不輸入任何引數,肯定會報段錯誤

fopen error
段錯誤 (核心已轉儲)

我們這裡使用strace檢視資訊:

root@lvirtual-machine:~/test# strace  -T -tt -e trace=all  ./test
22:32:53.349314 execve("./test", ["./test"], [/* 60 vars */]) = 0 <0.000440>
22:32:53.350129 brk(NULL)               = 0xb3f000 <0.000097>
22:32:53.350417 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000094>
22:32:53.350769 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000062>
22:32:53.351014 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000127>
22:32:53.351316 fstat(3, {st_mode=S_IFREG|0644, st_size=96373, ...}) = 0 <0.000105>
22:32:53.351585 mmap(NULL, 96373, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0bec216000 <0.000065>
22:32:53.351793 close(3)                = 0 <0.000052>
22:32:53.352046 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000068>
22:32:53.352313 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000106>
22:32:53.352597 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832 <0.000061>
22:32:53.352789 fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0 <0.000110>
22:32:53.353092 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0bec215000 <0.000122>
22:32:53.353409 mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0bebc3f000 <0.000117>
22:32:53.353629 mprotect(0x7f0bebdff000, 2097152, PROT_NONE) = 0 <0.000115>
22:32:53.353841 mmap(0x7f0bebfff000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f0bebfff000 <0.000110>
22:32:53.354073 mmap(0x7f0bec005000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0bec005000 <0.000060>
22:32:53.354297 close(3)                = 0 <0.000035>
22:32:53.354556 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0bec214000 <0.000060>
22:32:53.354736 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0bec213000 <0.000045>
22:32:53.354888 arch_prctl(ARCH_SET_FS, 0x7f0bec214700) = 0 <0.000040>
22:32:53.355125 mprotect(0x7f0bebfff000, 16384, PROT_READ) = 0 <0.000051>
22:32:53.355288 mprotect(0x600000, 4096, PROT_READ) = 0 <0.000050>
22:32:53.355487 mprotect(0x7f0bec22e000, 4096, PROT_READ) = 0 <0.000085>
22:32:53.355724 munmap(0x7f0bec216000, 96373) = 0 <0.000094>
22:32:53.356015 brk(NULL)               = 0xb3f000 <0.000072>
22:32:53.356203 brk(0xb60000)           = 0xb60000 <0.000075>
22:32:53.356426 open(NULL, O_RDONLY)    = -1 EFAULT (Bad address) <0.000078>
22:32:53.356664 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 11), ...}) = 0 <0.000065>
22:32:53.356879 write(1, "fopen error\n", 12fopen error
) = 12 <0.000065>
22:32:53.357116 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
22:32:53.542212 +++ killed by SIGSEGV (core dumped) +++
段錯誤 (核心已轉儲)

上面輸出的資訊非常多,但是我們可以找到我們需要的

open(NULL, O_RDONLY)    = -1 EFAULT (Bad address) 

我們使用starce也就是根據這些系統呼叫去過濾出我們需要的資訊


當我們發現程式執行異常時,我們可以使用strace來跟蹤其系統呼叫,看看這些系統呼叫有沒有異常,進而找到異常的原因。

相關文章