strace是Linux系統下的一個用來跟蹤系統調用的工具,它的實現基礎是ptrace系統調用。使用strace工具能夠跟蹤一個程序執行過程當中發生的系統調用。函數
我這裏講到的內容有一點點和mips體系相關,不過不熟悉mips也不影響閱讀。工具
ptrace系統調用提供了一種方法來跟蹤和控制進程的執行,它能夠讀取和修改進程地址空間中的內容,包括寄存器的值。ptrace主要用於實現斷點調試和跟蹤系統調用。該系統調用的原型以下:post
long ptrace(enum __ptrace_request request, pid_t pid, void *addr,void *data);this
ptrace的四個參數的含義爲:調試
1. request:用於選擇一個操做,見下文。code
2. pid:目標進程即被跟蹤進程的pid。blog
3. addr和data用於修改和拷貝被跟蹤進程的進程地址空間的數據。進程
下面的內容中將用父進程指代跟蹤者,用子進程指代被跟蹤者。實際上,在一個進程被跟蹤以後,跟蹤者進程會在某種意義上充當被跟蹤進程的父進程(如使用ps命令就能夠看到他們的父子關係),而子進程真正的父進程被保存在其task_struct結構的real_parent成員中。ip
父進程跟蹤一個進程的方式有兩種:1.調用fork(),而後子進程打上PTRACE_TRACEME標記,並執行exec。2.父進程能夠給本身打上PTRACE_ATTACH標記來跟蹤一個已有進程。ci
一個進程被跟蹤後,他只要接收到一個信號(即便這個信號被設置爲忽略)就會中止運行(SIGKILL除外),而後父進程會在每次調用wait()時獲得子進程中止運行的通知,這時父進程就能夠檢測和修改子進程了,隨後父進程可讓子進程繼續運行。
當父進程不想跟蹤了,能夠經過設置PTRACE_KILL標記來終止子進程的運行。也能夠經過設置PTRACE_DETACH標記讓子進程解除被跟蹤,繼續正常運行。
strace工具是一個用戶態的應用程序,用來追蹤進程的系統調用。它的基礎就是ptrace系統調用。安裝strace以後,就可使用strace命令了。
最簡單的strace命令的用法就是:strace PROG,PROG是要執行的程序。strace命令執行的結果就是按照調用順序打印出全部的系統調用,包括函數名、參數列表以及返回值。
使用strace跟蹤一個進程的系統調用的基本流程如圖1所示
從圖中能夠看出strace作了如下幾件事情:
1. 設置SIGCHLD 信號的處理函數,這個處理函數只要不是SIG_IGN便可。因爲子進程中止後是經過SIGCHLD信號通知父進程的,因此這裏要防止SIGCHLD信號被忽略。
2. 建立子進程,在子進程中調用ptrace(PTRACE_TRACEME,0L, 0L, 0L)使其被父進程跟蹤,並經過execv函數執行被跟蹤的程序。
3. 經過wait()等待子進程中止,並得到子進程中止時的狀態status。
4. 經過子進程的狀態查看子進程是否已正常退出,若是是,則再也不跟蹤,隨後調用ptrace發送PTRACE_DETACH請求解除跟蹤關係。
5. 子進程中止後,打印系統調用的函數名、參數和返回值。具體流程見圖2。
6. 經過PTRACE_SYSCALL讓子進程繼續運行,因爲這個請求會讓子進程在系統調用的入口處和系統調用完成時都會中止並通知父進程,這樣,父進程就能夠在系統調用開始以前得到參數,結束以後得到返回值。
在系統調用的入口和結束時子進程中止運行時,這時父進程認爲子進程是由於收到SIGTRAP信號而中止的。因此父進程在wait()後能夠經過SIGTRAP來與其餘信號區分開。
Strace中爲每一個要跟蹤的進程維護了一個TCB(Trace Control Block)結構,定義以下。它保存了當前發生的系統調用的信息
/* Trace Control Block */ struct tcb { int flags; /* See below for TCB_ values */ int pid; /* Process Id of this entry */ int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW*/ int u_error; /* Error code */ long scno; /* System call number */ long u_arg[MAX_ARGS]; /* System call arguments */ long u_rval; /* Return value */ int curcol; /* Output column for this process */ FILE *outf; /* Output file for this process */ const char *auxstr;/*Auxiliary info from syscall (see RVAL_STR) */ const struct_sysent *s_ent;/* sysent[scno] or dummy struct for bad scno */ struct timeval stime;/*System time usage as of last process wait */ struct timeval dtime; /* Delta for system time usage */ struct timeval etime; /* Syscall entry time */ /* Support fortracing forked processes: */ long inst[2]; /* Saved clone args (badly named) */ };
上面已經提到,子進程會在系統調用先後各中止一次,因此打印系統調用信息時分爲兩個階段:在系統調用開始時能夠獲取系統調用號和參數,在系統調用結束時能夠獲取系統調用的返回結果。經過給tcb結構的flags字段清除和添加TCB_INSYSCALL標誌位來區分系統調用的開始和結束。
例如編寫一個使用printf打印「Hello world」的程序hello.c,使用strace跟蹤該程序的系統調用能夠看到以下結果:
# ./strace ./hello execve("./hello ", ["./hello "], [/* 7 vars */])= 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaad000 stat("/etc/ld.so.cache", 0x7faf4ca8) = -1 ENOENT (No such file or directory) open("/tmp/libgcc_s.so.1", O_RDONLY) = -1 ENOENT (No such file or directory) open("/lib/libgcc_s.so.1", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=1565445, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000 read(3,"\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\263\200\0\0\0004"...,4096) = 4096 mmap(NULL, 241664, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aabe000 mmap(0x2aabe000, 169308, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aabe000 mmap(0x2aaf8000, 2400, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x2a000) = 0x2aaf8000 close(3) = 0 munmap(0x2aaae000, 4096) = 0 open("/tmp/libc.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) open("/lib/libc.so.0", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000 read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\252\200\0\0\0004"...,4096) = 4096 mmap(NULL, 471040, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aaf9000 mmap(0x2aaf9000, 380336, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aaf9000 mmap(0x2ab65000, 8088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x5c000) = 0x2ab65000 mmap(0x2ab67000, 19376, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2ab67000 close(3) = 0 munmap(0x2aaae000, 4096) = 0 open("/tmp/libc.so.0", O_RDONLY) = -1 ENOENT (No such file or directory) open("/lib/libc.so.0", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0 close(3) = 0 stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755,st_size=22604, ...}) = 0 mprotect(0x2ab65000, 4096, PROT_READ) = 0 mprotect(0x2aabc000, 4096, PROT_READ) = 0 ioctl(0, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0 ioctl(1, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0 write(1, "Hello world\n", 12Hello world ) = 12 exit(0) = ? +++ exited with 0 +++ #
從結果能夠看出,執行該程序調用了不少系統調用,並最終經過write系統調用打印出「Hello world」。
跟蹤一個正在運行的進程,使用-p選項加上進程的pid。
跟蹤某個特定的系統調用,使用-e選項加上系統調用名。
例如,跟蹤進程727的epoll_wait系統調用:strace -e epoll_wait -p 727
進程設置這個request目的是讓本身被父進程跟蹤。任何發送到該子進程的信號(除了SIGKILL)都會致使他停下來,並在父進程wait()的時候通知到父進程。另外,子進程後續調用exec會致使子進程本身收到一個SIGTRAP信號,這是爲了讓父進程有機會在exec的新程序開始執行前得到控制權。
除非一個進程知道父進程要跟蹤他,通常不會去設置這個request。設置這個請求時,pid,addr和data三個參數都會被忽略。
這個request只供子進程設置,其餘的request都是隻供父進程使用的。相應的,下面的request中,參數pid爲被跟蹤的子進程的pid。
將pid指定的進程做爲本身要跟蹤的進程,並開始跟蹤。這和子進程本身調用PTRACE_TRACEME的效果相同。
設置這個request時,子進程會首先收到一個SIGSTOP信號,但並不會中止,只是致使跟蹤者進程第一次被中斷,從而開始跟蹤,不然只能等到子進程接收到第一個信號時纔開始跟蹤。以後當子進程有待決信號時,進程老是會暫停,這時父進程經過SIGCHLD信號獲得通知。在子進程暫停前,父進程可以使用wait函數等待。
參數addr和data會被忽略。
讓被停掉的子進程繼續運行,而當子進程再次接收到信號時會暫停。
參數data若是被設置爲一個非零值而且不是SIGSTOP,那data就是父進程發送給子進程的信號,不然,不給子進程發送信號。這樣一來,父進程能夠控制是否向子進程發送一個信號。
參數addr會被忽略。
讓中止的子進程繼續運行(同PTRACE_CONT),而當子進程再次接收到信號時會暫停,另外,在子進程中發生系統調用時,在系統調用的入口和結束時子進程也會中止,這時父進程認爲子進程是由於收到SIGTRAP信號而中止的。
因爲子進程在系統調用的入口和結束時都會中止,父進程就能夠在系統調用入口處中止後,得到系統調用的參數信息,而在系統調用結束時中止後,得到系統調用的返回值。
參數addr會被忽略。
讓中止的子進程繼續運行(同PTRACE_CONT),可是在這以前會先與跟蹤它的父進程解除PTRACE_ATTACH或PTRACE_TRACEME時的關係。
參數addr會被忽略。
給子進程發送一個SIGKILL信號來終止子進程。addr和data參數會被忽略。
讀取進程地址空間中addr地址處的內容,讀出的長度爲一個word(4字節),做爲ptrace()的返回值(long型)返回。Linux中的代碼和數據的地址空間並非分離的,因此這兩個request實際上意義相同。
參數data會被忽略。
在進程的USER區域讀取一個word的長度。參數addr是指相對USER開頭的offset,結果做爲返回值。參數data會被忽略。
在mips中的進程自身信息和進程地址空間中並無所謂的USER區域,在內核中經過參數addr的值,判斷應該返回什麼結果