如下是使用Ptrace跟蹤進程收到的異常信號的正文內容,本文是網友投稿或本站會員創做,不表明本站觀點: html
最初的想法,咱們在調試程序的時候,程序有時會出現進程收到SIGSEGV信號,異常退出。若是使用GDB,經過攔截該信號,並打印堆棧來實現,但在嵌入式的環境裏,須要在嵌入式設備註入gdb server,來進行調試,比較麻煩。 linux
個人一個想法,可以直接寫一個程序,跑在設備裏,攔截到特殊信號後,就打印堆棧呢? 函數
有兩個難點: 測試
一、 怎麼樣實現信號的攔截,發現信號後,開始打印堆棧。 google
二、 打印堆棧,最好可以連帶調用函數時的參數值。 調試
信號的攔截 server
一開始我想到了使用ptrace,由於gdb是以它爲基礎的,gdb可以作到,它也可以作到。但經過查資料,ptrace主要是攔截系統調用,或者進行單步跟蹤,沒有談及怎麼樣跟蹤信號。那麼在主程序,fork出子程序後,註冊一個信號處理函數,在信號處理函數中打印出堆棧是否可行呢? htm
通過測試發現,在exec以後,起來的進程會沖掉原來該子進程的代碼段,從而註冊的信號處理事件失效。 進程
這條路是否到頭了呢? 事件
繼續想到,既然exec會沖掉原來的代碼段,那麼我是否能夠採用把程序起來後,經過黑客的手段注入代碼,並執行來實現呢?
理論上來說,應該可行,但難度比較大。
峯迴路轉……
在linux內核分析的時候,發現這麼一段話:
在do_signal中,首先它檢查current接收進程是否正受某一進程的監控:既然這樣,do_signal就調用notify_parent()和schedule讓監控進程知道進行的信號處理。
這說明signal的信號處理確定是可以被監控的。
經過google發現,strace是可以攔截signal的。我經過分析strace的源代碼,發現了strace是怎麼樣監控信號。
Child=fork()
If(child==0)
{
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
Exec //執行待監控的進程
}
Else
{
pid = wait4(-1, &status, wait4_options, cflag ? &ru : NULL);
if (WIFSIGNALED(status)) //檢查是否由於信號掛起
if (WIFEXITED(status))
if (!WIFSTOPPED(status))
if (WSTOPSIG(status) != SIGTRAP) //注意SIGSEGC信號就是經過這裏處理的。
也就是說,對於信號的攔截,它是經過標記爲trace以後,經過wai的status來判斷的。而不是象通常針對系統調用的跟蹤。
既然找到了地方,接下來的就很容易了,打印堆棧:
long ebp=0,eip=0,i=0;
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid,NULL,®s);
tprintf("EIP: %lx ----EBP:%lx/n",regs.eip,regs.ebp); //首先得到當前的ebp寄存器中的地址,該地址指向了堆棧中的幀。
ebp = regs.ebp;
eip = regs.eip;
for(;;)
{
i++;
tprintf("%d: eip:%lx ebp:%lx/n",i,eip,ebp);
eip = ptrace(PTRACE_PEEKDATA,pid,ebp+4,NULL); //根據幀的結構,eip的地址在ebp地址+4,把來把其解析成函數,具體的文本就全靠它了。
ebp = ptrace(PTRACE_PEEKDATA,pid,ebp,NULL); //注意先後兩行的順序,這個命令使ebp指向堆棧中的上一個幀
if( 0 == ebp)
break;
}
這樣,就可以把堆棧中,各個函數所運行的地址拿到。
你可使用objdump –D 反編譯你所跟蹤的進程,來得到其對應的函數。
由於打印堆棧不是在所跟蹤的進程內,因此不可使用backtrace_symbols來對其進行解析,怎麼樣把其解析成直觀的函數調用,目前我考慮本身實現反彙編來編寫,打印出函數的名稱。
採用backtrace_symbols有個侷限,在編譯程序時必須加上-rdynamic才行,按理說經過objdump反編譯能夠看到,應該更可靠一些。