文章目錄
1.strace簡介
-
Strace是Linux中一個調試和跟蹤工具。
(1)可以通過strace找到問題出現在user層還是kernel層
(2)進程不能直接訪問硬件設備,當進程需要訪問硬件設備(比如讀取磁盤文件,接收網絡數據等等)時,必須由用戶態模式切換至內核態模式,通 過系統調用訪問硬件設備 -
man strace
2.strace的2種用法
- strace常用組合
strace -p <PID>
strace -cp <PID> 「c」選項用來彙總各個操作的總耗時
eg:clone的time很大,則可以單獨跟蹤以下clone
strace -T -e clone -p <PID>
通過「T」選項可以獲取操作實際消耗的時間,通過「e」選項可以跟蹤某個操作
- (1)一種是通過它啓動要跟蹤的進程 ,在原本的命令前加上strace即可
strace ls -lh /var/log/messages
strace ./可執行程序
- (2)另外一種運行模式,給strace傳遞-p pid 選項
strace -p 17553
- strace的常用選項
-c 統計每一系統調用的所執行的時間,次數和出錯的次數等.
-d 輸出strace關於標準錯誤的調試信息.
-f 除了跟蹤當前進程外,還跟蹤由fork調用所產生的子進程.
-ff 如果提供-o filename,則所有進程的跟蹤結果輸出到相應的filename.pid中,pid是各進程的進程號.
-F 嘗試跟蹤vfork調用.在-f時,vfork不被跟蹤.
-h 輸出簡要的幫助信息.
-i 輸出系統調用的入口指針寄存器值.
-q 禁止輸出關於結合(attaching)、脫離(detaching)的消息,當輸出重定向到一個文件時,自動抑制此類消息.
-r 打印出相對時間關於每一個系統調用,即連續的系統調用起點之間的時間差,與-t對應.
-t 打印各個系統調用被調用時的絕對時間秒級,觀察程序各部分的執行時間可以用此選項。
-tt 在輸出中的每一行前加上時間信息,微秒級.
-ttt 在每行輸出前添加相對時間信息,格式爲”自紀元時間起經歷的秒數.微秒數”
-T 顯示每一調用所耗的時間,其時間開銷在輸出行最右側的尖括號內.
-v 冗餘顯示模式:顯示系統調用中argv[]envp[]stat、termio(s)等數組/結構體參數所有的元素/成員內容.
-V 輸出strace的版本信息.
-x 以十六進制形式輸出非標準字符串 。
-xx 所有字符串以十六進制形式輸出.
-a column 設置返回值的輸出位置.默認爲40,即"="出現在第40列.
-e expr 指定一個表達式,用來控制如何跟蹤.
-e trace=set 只跟蹤指定的系統 調用.例如:-e trace=open.
-e trace=file 只跟蹤有關文件操作的系統調用.
-e trace=process 只跟蹤有關進程控制的系統調用.
-e trace=network 跟蹤與網絡有關的所有系統調用.
-e trace=signal 跟蹤所有與系統信號有關的 系統調用
-e trace=ipc 跟蹤所有與進程通訊有關的系統調用
-e abbrev=set 設定 strace輸出的系統調用的結果集.-v 等與 abbrev=none.默認爲abbrev=all.
-e raw=set 將指 定的系統調用的參數以十六進制顯示.
-e signal=set 指定跟蹤的系統信號.默認爲all.如signal=!SIGIO,表示不跟蹤SIGIO信號.
-e read=set 輸出從指定文件中讀出 的數據.例如: -e read=3,5 -e write=set
-E var 從命令的環境變量列表中移除var。
-E var=val 將var=val放入命令的環境變量列表.
-o filename 將strace的輸出寫入文件filename,而不是顯示到標準錯誤輸出(stderr).
-p pid 跟蹤指定的進程pid,可指定多達32個(-p pid)選項以同時跟蹤多個進程。該選項常用於調試後臺進程.
-s strsize 限制每行輸出中字符串(如read參數)的最大顯示長度,默認32字節。但文件名總是完整顯示
-S sortby 按指定規則對-c選項的輸出直方圖進行排序。sortby取值可爲time、calls、name和nothing(默認 time)
-u username 以username 的UID和GID執行被跟蹤的命令
- eg:
strace -o output.txt -T -tt -e trace=all -p 28979
表示的含義是,跟蹤28979進程的所有系統調用(-e trace=all),並統計系統調用的花費時間,以及開始時間(並以可視化的時分秒格式顯示),
最後將記錄結果存在output.txt文件裏面。
strace -f -e trace=read,write -p 17151 -o log
跟蹤進程17151及子進程中read和write系統調用,輸出到log文件
注意:
-e trace=open,close,rean,write表示只跟蹤這四個系統調用.默認的爲set=all
- strace記錄程序所產生的每次系統調用的格式爲:
(1)單獨爲一行
(2)每行起始爲系統調用的函數名,括號內爲參數,該調用的返回值則顯示在等號右側。
當參數爲bit時,使用方括號並用空格隔開每項參數
sigprocmask(SIG_BLOCK,[CHLD TTOU],[ ]) = 0 第二個參數代表信號SIGCHLD和SIGTTOU
若bit型參數全部置位,則輸出如:
sigprocmask(SIG_UNBLOCK,~[ ],NULL) = 0
3.strace使用案例
- strace用法舉例,測試程序test1.c運行過程中的系統調用情況:
#include <stdio.h>
int main(void)
{
fputs("hello", stdout);
sleep(2);
//這裏一開始輸出了換行符,所以前面的hello就被輸出到屏幕上了。
fputs("\nworld\n", stdout);
sleep(2);
return 0;
}
@Cpl-WH-30:~/test$ gcc test1.c -o test1
@Cpl-WH-30:~/test$ strace ./test1
execve("./test1", ["./test1"], [/* 30 vars */]) = 0
brk(0) = 0x12b7000
...
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=143984, ...}) = 0
mmap(NULL, 143984, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7feee4d32000
close(3) = 0
...
fstat(1, {st_mode=S_IFCHR|0622, st_rdev=makedev(136, 14), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7feee4d55000
...
nanosleep({2, 0}, 0x7ffd00227380) = 0
write(1, "hello\n", 6hello
) = 6
...
nanosleep({2, 0}, 0x7ffd00227380) = 0
write(1, "world", 5world) = 5
exit_group(0) = ?
+++ exited with 0 +++
-
分析如下:
(1)第3行表示通過系統調用execve來建立一個進程,本例中爲test1對應的進程,在控制檯中執行各種命令,比如ls、cd時,都是通過系統調用execve來建立它們的進程的,通過strace可以看到程序運行的細節。
(2)第4行brk通過傳遞的addr來重新設置program break,成功則返回0,否則返回-1。
brk(0)的參數是一個地址,假如已經知道了堆的起始地址,還有堆的大小,那麼就可以據此修改 brk() 中的地址參數已達到調整堆的目的。
以0作爲參數調用brk,返回值爲內存管理的起始地址(若在子進程中調用malloc,則從0x12b7000地址開始分配空間)
(3) 第6~8行表示打開動態連接庫的過程,如果程序是靜態連接的,這幾個步驟將不需要。
(4)第14~19行是程序的處理過程,nanosleep()使得進程進入睡眠狀態,指定時候後喚醒進程,sleep()基於其實現 ,然後寫入hello和world
(5)20~21行表示退出進程中所有的線程。 -
test2.c代碼使用死循環模擬用戶態掛死,調用sleep模擬內核態程序掛死,然後利用strace工具分析原因。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
getpid(); //該系統調用起到標識作用
if(argc < 2)
{
printf("hang (user|system)\n");
return 1;
}
if(!strcmp(argv[1], "user"))
while(1);
else if(!strcmp(argv[1], "system"))
sleep(500);
return 0;
}
...
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0
...
close(3) = 0
...
munmap(0x7f21e695f000, 143984) = 0
getpid() = 8446
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({500, 0},
-
分析:
(1)第3行,調用read,從/lib/i386-linux-gnu/libc.so.6該libc庫文件中讀取832bytes,即讀取ELF頭信息 。
(2)分析:
用戶態掛死情況下,strace在getpid()一行輸出之後沒有其他系統調用輸出;
進程在內核態掛死,最後一行的系統調用nanosleep不能完整顯示,這裏nanosleep沒有返回值表示該調用尚未完成。
(3) 結論:
使用strace跟蹤掛死程序,如果最後一行系統調用顯示完整,程序在邏輯代碼處掛死;
如果最後一行系統調用顯示不完整,程序在該系統調用處掛死。
當程序掛死在系統調用處,可以查看相應系統調用的man手冊,瞭解在什麼情況下該系統調用會出現掛死情況。 -
eg:定位共享內存異常
-WH-30:~/$ 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!
- 分析:
ipcs -m | grep 41400
key shmid owner perms bytes nattch status
0x00041400 1015822 root 666 30095516 1
可以看到,已經0x41400這個key已經存在,並且其大小爲30095516字節,和我們調用參數中的30097568不匹配,於是產生了這個錯誤。
4.五種利用strace查故障的簡單方法
- (1)找出程序在startup的時候讀取的哪個config文件?
$ strace php 2>&1 | grep php.ini
open("/usr/local/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/lib/php.ini", O_RDONLY) = 4
lstat64("/usr/local/lib/php.ini", {st_mode=S_IFLNK|0777, st_size=27, ...}) = 0
readlink("/usr/local/lib/php.ini", "/usr/local/Zend/etc/php.ini", 4096) = 27
lstat64("/usr/local/Zend/etc/php.ini", {st_mode=S_IFREG|0664, st_size=40971, ...}) = 0
更精緻的方法
$ strace -e open php 2>&1 | grep php.ini
open("/usr/local/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/lib/php.ini", O_RDONLY) = 4
- (2)爲什麼這個程序沒有打開我的文件?
strace -e open,access 2>&1 | grep your-filename
- (3) 某個進程現在在做什麼?
root@dev:~# strace -p 15427
Process 15427 attached - interrupt to quit
futex(0x402f4900, FUTEX_WAIT, 2, NULL
Process 15427 detached
這個例子裏面,它在調用futex()的時候掛起了。
- (4) 是誰偷走了時間?
root@dev:~# strace -c -p 11084 root@dev:~# strace -c >/dev/null ls
Process 11084 attached - interrupt to quit
Process 11084 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
94.59 0.001014 48 21 select
2.89 0.000031 1 21 getppid
2.52 0.000027 1 21 time
------ ----------- ----------- --------- --------- ----------------
100.00 0.001072 63 total
在這個例子中,程序花了絕大部分時間在等待select()。
它在每一個slect()調用這件調用getpid()和time(),這是一種典型的事件循環
- (4)爲什麼 無法連接到服務器?
strace只返回你的進程相關的系統調用產生的數據。
如果你要從100個連接到統一個數據服務器的運行進程裏面找出一個連接所做的事情,用strace就比tcpdump簡單得多。
$ strace -e poll,select,connect,recvfrom,sendto nc www.news.com 80
.。。。。。。。。
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("216.239.122.102")}, 16) = -1 EINPROGRESS (Operation now in progress)
。。。。
最後,它發起一個connect()請求到得到的IP地址,注意到返回值是EINPROGRESS。這意味這connect是非阻塞的,nc希望繼續處理,
然後它調用slect(),連接建立後,select返回成功。
5.strace解決高負載服務器的top
- top技巧
運行 top 時,按「1」打開 CPU 列表,按「shift+p」以 CPU 排序
- 分析
(1)CPU 主要是被若干個 PHP 進程佔用了,同時 PHP 進程佔用的比較多的內存,不過系統內存尚有結餘,SWAP 也不嚴重
(2)在 CPU 列表中能看到 CPU 主要消耗在內核態「sy」,而不是用戶態「us」
6.linux下的調試工具ltrace與strace
-
ltrace能夠跟蹤進程的庫函數調用,它會顯現出哪個庫函數被調用,而strace則是跟蹤程序的每個系統調用
-
strace在跟蹤系統調用的時候不需要動態庫,而ltrace是根據動態庫來分析程序運行的,所以ltrace也只能跟蹤動態庫,不能跟蹤靜態庫
-
用ltrace和strace都可以發現程序在哪個系統調用時發生了性能瓶徑
-
參考:strace工具使用手冊,linux神器strace,strace命令詳解,linux下的調試工具ltrace與strace