1. 程式人生 > 實用技巧 >GDB訊號處理

GDB訊號處理

GDB訊號處理

C、C++ 程式中,訊號常常作為程序間通訊的一種重要手段。舉個例子:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void display(){
    printf("http://c.biancheng.net/gdb/");
}
int main ()
{
    pid_t cpid;
    pid_t ppid;
    signal(SIGINT,display);
    if((cpid=fork())==0){
        printf("in cpid\n
"); ppid = getppid(); kill(ppid,SIGINT); }else{ wait(NULL); } return 0; }

上面程式中存在 2 個程序,分別為 cpid 子程序和 ppid 主程序。cpid 子程序通過 kill() 函式向 ppid 主程序傳送了 SIGINT 訊號,當主程序接收到此訊號時,會由等待狀態轉而執行 display() 函式。因此程式的執行結果為:

in cpid
http://c.biancheng.net/gdb/

其中,第一行為 cpid 子程序列印輸出的,第二行為 cpid 向 ppid 主程序發出 SIGINT 訊號後由 ppid 主程序列印輸出的。
值得一提的是,GDB 偵錯程式可以自動捕獲 C、C++ 程式中出現的訊號,並根據事先約定好的方式處理它(具體如何約定,本節後續會講)。Linux 系統中已經事先定義好了諸多中訊號,我們可以通過執行如下命令檢視:

[root@bogon demo]# kill -l
1) SIGHUP  2) SIGINT  3) SIGQUIT  4) SIGILL  5) SIGTRAP
6) SIGABRT  7) SIGBUS  8) SIGFPE  9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25
) SIGXFSZ ....... <-- 省略部分輸出

其中,每個訊號代表著不同的含義,以 SIGINT 訊號為例,它表示程式停止執行,該訊號可以通過按Ctrl+c組合鍵發出。換句話說,對於正在執行的程式,通過按Ctrl+c鍵向程式發出 SIGINT 訊號,可以使程式停止執行。
與此同時,GDB 偵錯程式提供了info handles指令,用於檢視 GDB 可以處理的訊號種類,以及各個訊號的具體處理方式。例如:

(gdb) info signals
Signal        Stop Print  Pass to program  Description

SIGHUP        Yes  Yes    Yes              Hangup
SIGINT        Yes  Yes    No               Interrupt
SIGQUIT       Yes  Yes    Yes              Quit
SIGILL        Yes  Yes    Yes              Illegal instruction
SIGTRAP       Yes  Yes    No               Trace/breakpoint trap
SIGABRT       Yes  Yes    Yes              Aborted
SIGEMT        Yes  Yes    Yes              Emulation trap
SIGFPE        Yes  Yes    Yes              Arithmetic exception
SIGKILL       Yes  Yes    Yes              Killed
......

其中各列的含義分別為:

  • Signal:各個訊號的名稱;
  • Stop:當訊號發生時,是否終止程式執行。Yes 表示終止,No 表示當訊號發生時程式認可繼續執行;
  • Print:當訊號發生時,是否要求 GDB 打印出一條提示資訊。Yes 表示列印,No 表示不列印;
  • Pass:當訊號發生時,該訊號是否對程式可見。Yes 表示程式可以捕捉到該資訊,No 表示程式不會捕捉到該資訊;
  • Description:對訊號所表示含義的簡單描述。

有關以上各個訊號所表示的具體含義,由於不是本節重點,這裡不做詳細贅述。感興趣的讀者可以自行查詢資料。

顯然,對於現有的所有訊號,GDB 偵錯程式會根據 Stop、Print 以及 Pass 列的值進行相應的處理。當然,GDB 偵錯程式提供了 handle 命令,由此我們就可以通過修改目標訊號 Stop、Print、Pass 列的值,除錯 GDB 偵錯程式對目標訊號的處理方式。

GDB handle命令

handle 命令的語法格式如下:

(gdb) handle signal mode

其中,signal 引數表示要設定的目標訊號,它通常為某個訊號的全名(SIGINT)或者簡稱(去除‘SIG’後的部分,如 INT);如果要指定所有訊號,可以用 all 表示。
mode 引數用於明確 GDB 處理該目標資訊的方式,其值可以是如下幾個:

    • nostop:當訊號發生時,GDB 不會暫停程式,其可以繼續執行,但會打印出一條提示資訊,告訴我們訊號已經發生;
    • stop:當訊號發生時,GDB 會暫停程式執行。
    • noprint:當訊號發生時,GDB 不會打印出任何提示資訊;
    • print:當訊號發生時,GDB 會打印出必要的提示資訊;
    • nopass(或者 ignore):GDB 捕獲目標訊號的同時,不允許程式自行處理該訊號;
    • pass(或者 noignore):GDB 除錯在捕獲目標訊號的同時,也允許程式自動處理該訊號。

注意,當 GDB 捕獲到訊號並暫停程式執行的那一刻,程式是捕獲不到訊號的,只有等到程式繼續執行時,訊號才能被程式捕獲。

接下來以一段 C 語言程式為例,為大家演示 GDB 偵錯程式處理訊號的過程。

#include <stdio.h>
#include <signal.h>
void display(){
    printf("http://c.biancheng.net/gdb/\n");
}
int main ()
{
    signal(SIGINT,display);
    while(1){
        sleep(1);
        printf("main\n");
    }
    return 0;
}

前面已經講過,SIGINT 訊號是可以通過Ctrl+c組合鍵發出的,因此上面程式執行時,除非我們手動發出 SIGINT 訊號,程式會馬上執行 display() 函式,否則一直輸出 "main" 字串。
下面我們用 GDB 偵錯程式除錯 main.exe 程式:

(gdb) info signals SIGINT
Signal   Stop   Print  Pass to program  Description
SIGINT   Yes    Yes    No                        Interrupt
(gdb) r
Starting program: ~/demo/main.exe
main
main
^C
Program received signal SIGINT, Interrupt.
0x00000037ee2accc0 in __nanosleep_nocancel () from /lib64/libc.so.6
(gdb) 

可以看到,通過執行info signals SIGINT命令,我們調取出了當前 GDB 偵錯程式對 SIGINT 訊號處理方式的預設設定,即當 SIGINT 訊號發生時,GDB 偵錯程式會暫停程式執行,同時打印出必要的提示資訊,並且不讓程式捕獲到該訊號。由此,當程式執行過程中按下Ctrl+c組合鍵後,並沒有執行 display() 函式,而是立即暫停了程式。
在此基礎上,我們繼續做如下除錯:

(gdb) handle SIGINT nostop
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal   Stop   Print  Pass to program  Description
SIGINT  No     Yes    No                        Interrupt
(gdb) handle SIGINT pass
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal   Stop   Print  Pass to program  Description
SIGINT  No     Yes    Yes                        Interrupt
(gdb) continue
Continuing.
main
main
main
^C
Program received signal SIGINT, Interrupt.
http://c.biancheng.net/gdb/
main
^Z
Program received signal SIGTSTP, Stopped (user).
0x00000037ee2accc0 in __nanosleep_nocancel () from /lib64/libc.so.6
(gdb)

可以看到,通過將 SIGINT 訊號 Stop 選項改為 No,將 Pass 選項改為 Yes,意味著當程式執行過程中發生 SIGINT 訊號時,程式不再中斷,並且可以捕獲到此訊號。因此,當程式執行過程中按下Ctrl+c組合鍵時,看到打印出了 "http://c.baincheng.net/gdb/" 資料。