信號機制和Android natvie crash捕捉

一、信號機制

image.png

函數(shù)運行在用戶態(tài),當(dāng)遇到系統(tǒng)調(diào)用、中斷或是異常的情況時,程序會進入內(nèi)核態(tài)。信號涉及到了這兩種狀態(tài)之間的轉(zhuǎn)換。

1、信號的接收

接收信號的任務(wù)是由內(nèi)核代理的,當(dāng)內(nèi)核接收到信號后,會將其放到對應(yīng)進程的信號隊列中,同時向進程發(fā)送一個中斷,使其陷入內(nèi)核態(tài)。

此時信號還只是在隊列中,對進程來說暫時是不知道有信號到來的。

2、信號的檢測

進程陷入內(nèi)核態(tài)后,有兩種場景會對信號進行檢測:

  • 進程從內(nèi)核態(tài)返回到用戶態(tài)前進行信號檢測
  • 進程在內(nèi)核態(tài)中,從睡眠狀態(tài)被喚醒的時候進行信號檢測
    當(dāng)發(fā)現(xiàn)有新信號時,便會進入下一步,信號的處理。

3、信號的處理

信號處理函數(shù)是運行在用戶態(tài)的,調(diào)用信號處理函數(shù)前,內(nèi)核會將當(dāng)前的內(nèi)核棧的內(nèi)容備份拷貝到用戶棧,然后修改命令寄存器(eip)指向信號處理函數(shù)。
接下來進程返回到用戶態(tài)中,執(zhí)行相應(yīng)的信號處理函數(shù)。
信號處理函數(shù)執(zhí)行完成后,還需要返回內(nèi)核態(tài),檢查是否還有其它信號未處理。
如果所有信號都處理完成,就會將內(nèi)核?;謴?fù)(從用戶棧的備份拷貝回來),同時恢復(fù)指令寄存器(eip)將其指向中斷前的運行位置,最后回到用戶態(tài)繼續(xù)執(zhí)行進程。

一個完整的信號處理流程便結(jié)束了,如果同時有多個信號到達,上面的處理流程會在第2步和第3步驟間重復(fù)進行。

二、信號定義和行為

1、信號的定義

所有的符合Unix規(guī)范(如POSIX)的系統(tǒng)都統(tǒng)一定義了SIGNAL的數(shù)量、含義和行為。
Android代碼中,signal的定義一般在 signum.h(Android代碼中,signal的定義一般在 signum.h)中。

一共有31個信號,其中1~15號信號為常用信號

/* Signals.  */  
#define SIGHUP      1   /* Hangup (POSIX).  */   
#define SIGINT      2   /* Interrupt (ANSI).  */   
#define SIGQUIT     3   /* Quit (POSIX).  */   
#define SIGILL      4   /* Illegal instruction (ANSI).  */   
#define SIGTRAP     5   /* Trace trap (POSIX).  */   
#define SIGABRT     6   /* Abort (ANSI).  */   
#define SIGIOT      6   /* IOT trap (4.2 BSD).  */   
#define SIGBUS      7   /* BUS error (4.2 BSD).  */   
#define SIGFPE      8   /* Floating-point exception (ANSI).  */   
#define SIGKILL     9   /* Kill, unblockable (POSIX).  */   
#define SIGUSR1     10  /* User-defined signal 1 (POSIX).  */   
#define SIGSEGV     11  /* Segmentation violation (ANSI).  */   
#define SIGUSR2     12  /* User-defined signal 2 (POSIX).  */   
#define SIGPIPE     13  /* Broken pipe (POSIX).  */   
#define SIGALRM     14  /* Alarm clock (POSIX).  */   
#define SIGTERM     15  /* Termination (ANSI).  */   
#define SIGSTKFLT   16  /* Stack fault.  */   
#define SIGCLD      SIGCHLD /* Same as SIGCHLD (System V).  */   
#define SIGCHLD     17  /* Child status has changed (POSIX).  */   
#define SIGCONT     18  /* Continue (POSIX).  */   
#define SIGSTOP     19  /* Stop, unblockable (POSIX).  */   
#define SIGTSTP     20  /* Keyboard stop (POSIX).  */   
#define SIGTTIN     21  /* Background read from tty (POSIX).  */   
#define SIGTTOU     22  /* Background write to tty (POSIX).  */   
#define SIGURG      23  /* Urgent condition on socket (4.2 BSD).  */   
#define SIGXCPU     24  /* CPU limit exceeded (4.2 BSD).  */   
#define SIGXFSZ     25  /* File size limit exceeded (4.2 BSD).  */   
#define SIGVTALRM   26  /* Virtual alarm clock (4.2 BSD).  */   
#define SIGPROF     27  /* Profiling alarm clock (4.2 BSD).  */   
#define SIGWINCH    28  /* Window size change (4.3 BSD, Sun).  */   
#define SIGPOLL     SIGIO   /* Pollable event occurred (System V).  */   
#define SIGIO       29  /* I/O now possible (4.2 BSD).  */   
#define SIGPWR      30  /* Power failure restart (System V).  */   
#define SIGSYS      31  /* Bad system call.  */   
#define SIGUNUSED   31  
插曲:什么是POSIX
POSIX表示可移植操作系統(tǒng)接口(Portable Operating System Interface of UNIX,縮寫為 POSIX ),POSIX標(biāo)準定義了操作系統(tǒng)應(yīng)該為應(yīng)用程序提供的接口標(biāo)準。
POSIX標(biāo)準意在期望獲得源代碼級別的軟件可移植性。換句話說,為一個POSIX兼容的操作系統(tǒng)編寫的程序,應(yīng)該可以在任何其它的POSIX操作系統(tǒng)(即使是來自另一個廠商)上編譯執(zhí)行。

簡單來說:
完成同一功能,不同內(nèi)核提供的系統(tǒng)調(diào)用(也就是一個函數(shù))是不同的。例如創(chuàng)建進程,linux下是fork函數(shù),windows下是creatprocess函數(shù)。
POSIX 要求 linux和windows都要實現(xiàn)基本的posix標(biāo)準,linux把fork函數(shù)封裝成posix_fork(隨便說的),windows把creatprocess函數(shù)也封裝成posix_fork,都聲明在unistd.h里。這樣,程序員編寫普通應(yīng)用時候,只用包含unistd.h,調(diào)用posix_fork函數(shù),程序就在源代碼級別可移植了。

2、常見信號的含義

SIGHUP - 1

本信號在用戶終端連接(正?;蚍钦?結(jié)束時發(fā)出, 通常是在終端的控制進程結(jié)束時, 通知同一session內(nèi)的各個作業(yè), 這時它們與控制終端不再關(guān)聯(lián)。
登錄Linux時,系統(tǒng)會分配給登錄用戶一個終端(Session)。在這個終端運行的所有程序,包括前臺進程組和后臺進程組,一般都屬于這個Session。當(dāng)用戶退出Linux登錄時,前臺進程組和后臺有對終端輸出的進程將會收到SIGHUP信號。這個信號的默認操作為終止進程,因此前臺進程組和后臺有終端輸出的進程就會中止。不過可以捕獲這個信號,比如wget能捕獲SIGHUP信號,并忽略它,這樣就算退出了Linux登錄,wget也能繼續(xù)下載。
此外,對于與終端脫離關(guān)系的守護進程,這個信號用于通知它重新讀取配置文件。

SIGINT - 2

程序終止(interrupt)信號,通常是Ctrl-C)時發(fā)出,用于通知前臺進程組終止進程。

SIGQUI - 3

和SIGINT類似, 但由QUIT字符(通常是Ctrl-)來控制.進程在因收到SIGQUIT退出時會產(chǎn)生core文件, 在這個意義上類似于一個程序錯誤信號。

SIGILL - 4

執(zhí)行了非法指令. 通常是因為可執(zhí)行文件本身出現(xiàn)錯誤, 或者試圖執(zhí)行數(shù)據(jù)段. 堆棧溢出時也有可能產(chǎn)生這個信號。

SIGTRAP - 5

由斷點指令或其它trap指令產(chǎn)生. 由debugger使用。

SIGABRT - 6

調(diào)用abort函數(shù)生成的信號。

SIGBUS - 7

非法地址,包括內(nèi)存地址對齊(alignment)出錯。比如訪問一個四個字長的整數(shù), 但其地址不是4的倍數(shù)。
它與SIGSEGV的區(qū)別在于后者是由于對合法存儲地址的非法訪問觸發(fā)的(如訪問不屬于自己存儲空間或只讀存儲空間)。

SIGFPE - 8

算術(shù)運算錯誤。不僅包括浮點運算錯誤, 還包括溢出及除數(shù)為0等其它所有的算術(shù)的錯誤。

SIGKILL - 9

用來立即結(jié)束程序的運行。本信號不能被阻塞、處理和忽略。如果管理員發(fā)現(xiàn)某個進程終止不了,可嘗試發(fā)送這個信號。

SIGUSR1 - 10

用戶自定義的信號1

SIGSEGV - 11

訪問非法地址。試圖訪問未分配給自己的內(nèi)存, 或試圖往沒有寫權(quán)限的內(nèi)存地址寫數(shù)據(jù).

SIGUSR2 -12

用戶自定義的信號1

SIGPIPE - 13

管道破裂。這個信號通常在進程間通信產(chǎn)生,比如采用FIFO(管道)通信的兩個進程,讀管道沒打開或者意外終止就往管道寫,寫進程會收到SIGPIPE信號。此外用Socket通信的兩個進程,寫進程在寫Socket的時候,讀進程已經(jīng)終止。

SIGALRM -14

時鐘定時信號, 計算的是實際的時間或時鐘時間. alarm函數(shù)使用該信號.

SIGTERM - 15

程序結(jié)束(terminate)信號, 與SIGKILL不同的是該信號可以被阻塞和處理。通常用來要求程序自己正常退出

3、如何產(chǎn)生信號

  • 在native應(yīng)用中 使用 kill() 或者raise()
raise(SIGILL);
kill(SIGILL);
  • java 應(yīng)用中使用 Procees.sendSignal()等
  • adb shell kill 命令向其他進程發(fā)送singal
adb root  
adb shell ps  
adb shell kill -3 513  
首先是切換到root用戶 (普通進程只能發(fā)個自己或者同組進程,而root可以發(fā)送signal給任何進程)。然后用 ps命令查看當(dāng)前系統(tǒng)中所有的進程信息。最后用kill命令發(fā)送SIGQUIT給進程號為513的進程。

4、信號處理行為

4.1 信號處理的方式一般有三種:

  • 忽略 接收到信號后不做任何反應(yīng)
  • 自定義 用自定義的信號處理函數(shù)來執(zhí)行特定的動作
  • 默認 收到信號后按默認得行為處理該信號。 這是多數(shù)應(yīng)用采取的處理方式。
信號處理的行為是以進程級的。就是說不同的進程可以分別設(shè)置不同的信號處理方式而互不干擾。同一進程中的不同線程雖然可以設(shè)置不同的信號屏蔽字,但是卻共享相同的信號處理方式 。

4.2 針對Android系統(tǒng),信號的特殊行為。

Android也是Linux系統(tǒng)。為了開發(fā)和調(diào)試的需要,android對一些信號的處理定義了額外的行為。

1. SIGQUIT ( 整型值為 3) 打印trace信息

傳統(tǒng)UNIX系統(tǒng)應(yīng)用,對SIGQUIT信號的默認行為是 "終止 + CORE",也就是產(chǎn)生core dump文件后,立即終于運行。

Android Dalvik應(yīng)用收到該信號后,會 打印改應(yīng)用中所有線程的當(dāng)前狀態(tài),并且并不是強制退出。
這些狀態(tài)通常保存在一個特定的叫做trace的文件中。一般的路徑是/data/anr/trace.txt

feifeideMacBook-Pro:s1 feifei$ adb shell ps | grep com.sogou.translate.example
u0_a70       18621  2972 4466496 109576 SyS_epoll_wait 7aced42ec0 S com.sogou.translate.example
u0_a70       18640  2972 4364644  69144 SyS_epoll_wait 7aced42ec0 S com.sogou.translate.example:logservice
feifeideMacBook-Pro:s1 feifei$ adb shell kill -3 18621
S1 AI Recorder:/data/anr # cd /data/anr/                                                                          
S1 AI Recorder:/data/anr # ls
trace_01 

----- pid 18621 at 2019-12-04 10:27:19 -----
Cmd line: com.sogou.translate.example
Build fingerprint: 'Sogou/sl8541e_1h10_oversea/sl8541e_1h10:8.1.0/OPM2.171019.012/02421:userdebug/test-keys'
ABI: 'arm64'


"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x7241fed0 self=0x7a4e8bea00
  | sysTid=18621 nice=-10 cgrp=default sched=0/0 handle=0x7ad35279a8
  | state=S schedstat=( 786261704 135017223 573 ) utm=71 stm=7 core=2 HZ=100
  | stack=0x7ff1799000-0x7ff179b000 stackSize=8MB
  | held mutexes=
  kernel: __switch_to+0xb0/0xbc
  kernel: SyS_epoll_wait+0x29c/0x378
  kernel: SyS_epoll_pwait+0xbc/0x130
  kernel: el0_svc_naked+0x24/0x28
  native: #00 pc 0000000000069ec0  /system/lib64/libc.so (__epoll_pwait+8)
  native: #01 pc 000000000001f734  /system/lib64/libc.so (epoll_pwait+52)
  native: #02 pc 0000000000015dcc  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  native: #03 pc 0000000000015cb4  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+108)
  native: #04 pc 000000000010f75c  /system/lib64/libandroid_runtime.so (???)
  native: #05 pc 0000000000bd096c  /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+140)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:325)
  at android.os.Looper.loop(Looper.java:142)
  at android.app.ActivityThread.main(ActivityThread.java:6789)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:449)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
2、對于很多其他的異常信號 (SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT ), Android進程 在退出前,會在/data/tombstones,生成 tombstone文件。
 adb shell ps | grep com.sogou.translate.example
 adb shell kill -11 18621
 adb pull /data/tombstones
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Native Crash TIME: 19573247
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Sogou/sl8541e_1h10_oversea/sl8541e_1h10:8.1.0/OPM2.171019.012/02421:userdebug/test-keys'
Revision: '0'
ABI: 'arm64'
pid: 22302, tid: 22319, name: Binder:22302_4  >>> com.sogou.iot.b1pro.launcher:ttsservice <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7b29df9480
    x0   00000000ffffffff  x1   0000000000000081  x2   000000007fffffff  x3   0000000000000000
    x4   0000000000000000  x5   0000000000000000  x6   0000000000000000  x7   0000007a45ff5000
    x8   000000003f4faf20  x9   0000000000000002  x10  0000007a2ca0d800  x11  0000000000000002
    x12  0000000000000001  x13  0000007a2c5ca800  x14  0000000000000000  x15  0000000000000001
    x16  0000007aceda4240  x17  0000007acecf67d0  x18  0000000000000004  x19  0000007a32ef9278
    x20  0000007a34d6bd98  x21  0000000000000000  x22  0000007a32ef9310  x23  00000000ffffffff
    x24  0000007a45e85000  x25  0000007a34d6b910  x26  000000000000013c  x27  0000000000000000
    x28  0000000000000050  x29  0000007a34d6bd80  x30  0000007a3244d5b4
    sp   0000007a34d6b4c0  pc   0000007a3244d728  pstate 00000000a0000000
    v0   00000000000000000000000000000000  v1   00000000211712c600000000211712c6
    v2   0000000000ffff5a0000000000000000  v3   00000000030000000000000000000000
    v4   00000000000000010000000000000000  v5   00000000000000030000000000000000
    v6   00000000000000000000000043420c74  v7   0000000000000000000000003ba3d70a
    v8   0000000000000000401921fb54442d18  v9   00000000000000000000000000000000
    v10  00000000000000000000000000000000  v11  00000000000000000000000000000000
    v12  00000000000000000000000000000000  v13  00000000000000000000000000000000
    v14  00000000000000000000000000000000  v15  00000000000000000000000000000000
    v16  000000000000000000000000433d0bdb  v17  0000000000000000000000004300f791
    v18  ffffffffffffffffffffffffffffffff  v19  00000000000000000000000000000000
    v20  000000000000000000000000429b0da8  v21  0000007a3244bad40000000040066666
    v22  3d9a026fbe6e77413de4d4f6bde37556  v23  be46eb12bab5227e3e42ccdd3ddc0298
    v24  bd81aadc3baa94113d553e38bd7e0cfa  v25  3d973dfebe6f2a7a3df6b7edbdeade23
    v26  be3550e13b58e3993e4a9fc03dce668e  v27  bd667f7d3c8acb883d67db0dbd3870f1
    v28  3d95a576be6d76b93e067243bdeb7287  v29  be2c0caf3b2d9cfa3e47bc863da722ea
    v30  bd2c2f113d0578df3d823731bcdbef07  v31  3d93ca5dbe68d8923e133c79bde8fffc
    fpsr 00000017  fpcr 00000000

backtrace:
    #00 pc 0000000000241728  /data/app/com.sogou.iot.b1pro.launcher-fmvk7osx2wimDuqXl484ZQ==/lib/arm64/libttsoff.so
    #01 pc 00000000002436dc  /data/app/com.sogou.iot.b1pro.launcher-fmvk7osx2wimDuqXl484ZQ==/lib/arm64/libttsoff.so
    #02 pc 0000000000253a38  /data/app/com.sogou.iot.b1pro.launcher-fmvk7osx2wimDuqXl484ZQ==/lib/arm64/libttsoff.so
    #03 pc 0000000000252308  /data/app/com.sogou.iot.b1pro.launcher-fmvk7osx2wimDuqXl484ZQ==/lib/arm64/libttsoff.so
    #04 pc 00000000002545fc  /data/app/com.sogou.iot.b1pro.launcher-fmvk7osx2wimDuqXl484ZQ==/lib/arm64/libttsoff.so (Java_com_sogou_speech_tts_TTSOffline_nativeSynthesize+124)
    #05 pc 000000000005d9b4  /data/app/com.sogou.iot.b1pro.launcher-fmvk7osx2wimDuqXl484ZQ==/oat/arm64/base.odex (offset 0x5a000)

stack:
         0000007a34d6b440  0000007a34d6b4b0
         0000007a34d6b448  f1ecdfe5bc67f748
         0000007a34d6b450  0000000000000000
         0000007a34d6b458  401921fb54442d18
         0000007a34d6b460  0000000000000050
         0000007a34d6b468  0000000000000000
         0000007a34d6b470  000000000000013c

4.3 android 捕捉native crash 需要注冊下面6個信號

Android 平臺 捕捉natvie crash 一般只需要安裝6個信號處理函數(shù)即可。

信號 信號值 含義 備注 在Android中默認行為
SIGSEGV 11 訪問無效地址 如試圖訪問未分配給自己的內(nèi)存 生成tombstone文件,然后退出
SIGBUS 7 非法地址 包括內(nèi)存地址對齊(alignment)出錯。 生成tombstone文件,然后退出
SIGABRT 6 調(diào)用abort函數(shù)生成的信號。 生成tombstone文件,然后退出
SIGFPE 8 浮點計算錯誤。 包括浮點運算錯誤, 還包括溢出及除數(shù)為0等算數(shù)運算錯誤 生成tombstone文件,然后退出
SIGILL 4 非法指令錯誤。 非法指令錯誤。 生成tombstone文件,然后退出
SIGTRAP 5 硬件錯誤(通常為斷點指令) 生成tombstone文件,然后退出

三、信號處理函數(shù)的使用

3.1、安裝信號處理函數(shù)

(1)sigaction函數(shù)的功能是檢查或修改與指定信號相關(guān)聯(lián)的處理動作(可同時兩種操作)

int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

signum參數(shù)指出要捕獲的信號類型,act參數(shù)指定新的信號處理方式,oldact參數(shù)輸出先前信號的處理方式(如果不為NULL的話)。

(2)struct sigaction結(jié)構(gòu)體介紹

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}

sa_handler代表新的信號處理函數(shù),僅接受一個參數(shù).
如:
void show_handler(int signo)
{
    printf("I got signal %d\n", signo);
}
sa_sigaction 同樣為信號處理函數(shù),相對于sa_handler可以獲得更多的信息:
void handler(int signo, siginfo_t *info, void *context);

第二個參數(shù)為一個siginfo_t結(jié)構(gòu)的指針,該結(jié)構(gòu)描述了信號產(chǎn)生的原因

struct siginfo_t
{
    int     si_signo;       // signal number
    int     si_errno;       // if nonzero, errno value from <errno.h>
    int     si_code;        // additional info (depends on signal)
    pid_t   si_pid;         // sending process ID
    uid_t   si_uid;         // sending process real user ID
    void    *si_addr;       // address that cased the fault
    int     si_status;      // exit value or signal number
    long    si_band;        // band number for SIGPOLL
    
    /* possibly other fileds also */

}

一般siginfo_t結(jié)構(gòu)至少包含si_signo和si_code成員。第三個參數(shù)context是一個無類型的指針,它可以被強制轉(zhuǎn)換為ucntext_t結(jié)構(gòu)類型,用于標(biāo)識信號傳遞時進程的上下文。

當(dāng)sig_action.sa_flags = SA_SIGINFO 時,會使用sa_sigaction作為信號處理函數(shù);否則使用 sa_handler 作為信號處理函數(shù)。


備注:
sa_sigaction和sa_handler字段,其實現(xiàn)可能使用同一存儲區(qū),所以應(yīng)用程序只能一次使用這兩個字段中的一個。
sa_mask 用來設(shè)置在處理該信號時暫時將sa_mask 指定的信號集擱置
sa_flags 用來設(shè)置信號處理的其他相關(guān)操作,下列的數(shù)值可用:
  • SA_RESETHAND:當(dāng)調(diào)用信號處理函數(shù)時,將信號的處理函數(shù)重置為缺省值SIG_DFL(默認處理方式)
  • SA_RESTART:如果信號中斷了進程的某個系統(tǒng)調(diào)用,則系統(tǒng)自動啟動該系統(tǒng)調(diào)用
  • SA_NODEFER :一般情況下, 當(dāng)信號處理函數(shù)運行時,內(nèi)核將阻塞該給定信號。但是如果設(shè)置了 SA_NODEFER標(biāo)記, 那么在該信號處理函數(shù)運行時,內(nèi)核將不會阻塞該信號
  • SA_SIGINFO:設(shè)置選擇sa_sigaction 作為信號處理函數(shù),否則選擇sa_handler作為信號處理函數(shù)。
  • SA_RESETHAND:信號處理之后重新設(shè)置為默認的處理方式。
選項 含義
SA_INTERRUPT 由此信號中斷的系統(tǒng)調(diào)用不會自動重啟
SA_NOCLDSTOP 若signo是SIGCHLD,當(dāng)子進程停止(作業(yè)控制)時,不產(chǎn)生此信號。當(dāng)子進程終止時,仍產(chǎn)生此信號(參加SA_NOCLDWAIT說明)。若已設(shè)置此標(biāo)志,則當(dāng)停止的進程繼續(xù)運行時,作為XSI擴展,不發(fā)送SIGCHLD信號。
SA_NOCLDWAIT 若signo是SIGCHLD,則當(dāng)調(diào)用進程的子進程終止時,不創(chuàng)建僵尸進程。若調(diào)用進程在后面調(diào)用wait,則調(diào)用進程阻塞,直到其所有子進程都終止,此時返回-1,并將errno設(shè)置為ECHILD。
SA_NODEFER 當(dāng)捕捉到此信號時,在執(zhí)行其信號處理函數(shù)時,系統(tǒng)不自動阻塞此信號(除非sa_mask包括了此信號)。
SA_ONSTACK 若用sigaltstack聲明了替換棧,則將此信號遞送給替換棧上的進程。
SA_RESETHAND 在此信號處理函數(shù)的入口處,將此信號的處理方式復(fù)位為SIG_DEF,并清除SA_SIGINFO標(biāo)志。但是,不能自動復(fù)位SIGILL和SIGTRAP這兩個信號的配置。設(shè)置此標(biāo)志是sigaction的行為如同SA_NODEFER標(biāo)志也設(shè)置了一樣。
SA_RESTART 由此信號中斷的系統(tǒng)調(diào)用會自動重啟動。
SA_SIGINFO 設(shè)置了該標(biāo)志后,會選擇sa_sigaction 作為信號處理函數(shù),否則選擇sa_handler作為信號處理函數(shù)。

(3)要用信號處理函數(shù)捕獲到native crash(SIGSEGV, SIGBUS等) 可以使用sig_action ,如下:

信號處理函數(shù)

void posix_signal_handler(int sig, siginfo_t *siginfo, void *context)
{
    (void)context;
    switch(sig)
    {
        case SIGSEGV:
            fputs("Caught SIGSEGV: Segmentation Fault\n", stderr);
            break;
        case SIGINT:
            fputs("Caught SIGINT: Interactive attention signal, (usually ctrl+c)\n",
                  stderr);
            break;
        case SIGFPE:
            switch(siginfo->si_code)
            {
                case FPE_INTDIV:
                    fputs("Caught SIGFPE: (integer divide by zero)\n", stderr);
                    break;
                case FPE_INTOVF:
                    fputs("Caught SIGFPE: (integer overflow)\n", stderr);
                    break;
                case FPE_FLTDIV:
                    fputs("Caught SIGFPE: (floating-point divide by zero)\n", stderr);
                    break;
                case FPE_FLTOVF:
                    fputs("Caught SIGFPE: (floating-point overflow)\n", stderr);
                    break;
                case FPE_FLTUND:
                    fputs("Caught SIGFPE: (floating-point underflow)\n", stderr);
                    break;
                case FPE_FLTRES:
                    fputs("Caught SIGFPE: (floating-point inexact result)\n", stderr);
                    break;
                case FPE_FLTINV:
                    fputs("Caught SIGFPE: (floating-point invalid operation)\n", stderr);
                    break;
                case FPE_FLTSUB:
                    fputs("Caught SIGFPE: (subscript out of range)\n", stderr);
                    break;
                default:
                    fputs("Caught SIGFPE: Arithmetic Exception\n", stderr);
                    break;
            }
        case SIGILL:
            switch(siginfo->si_code)
            {
                case ILL_ILLOPC:
                    fputs("Caught SIGILL: (illegal opcode)\n", stderr);
                    break;
                case ILL_ILLOPN:
                    fputs("Caught SIGILL: (illegal operand)\n", stderr);
                    break;
                case ILL_ILLADR:
                    fputs("Caught SIGILL: (illegal addressing mode)\n", stderr);
                    break;
                case ILL_ILLTRP:
                    fputs("Caught SIGILL: (illegal trap)\n", stderr);
                    break;
                case ILL_PRVOPC:
                    fputs("Caught SIGILL: (privileged opcode)\n", stderr);
                    break;
                case ILL_PRVREG:
                    fputs("Caught SIGILL: (privileged register)\n", stderr);
                    break;
                case ILL_COPROC:
                    fputs("Caught SIGILL: (coprocessor error)\n", stderr);
                    break;
                case ILL_BADSTK:
                    fputs("Caught SIGILL: (internal stack error)\n", stderr);
                    break;
                default:
                    fputs("Caught SIGILL: Illegal Instruction\n", stderr);
                    break;
            }
            break;
        case SIGTERM:
            fputs("Caught SIGTERM: a termination request was sent to the program\n",
                  stderr);
            break;
        case SIGABRT:
            fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr);
            break;
        default:
            break;
    }

    _Exit(1);
}

安裝信號處理函數(shù)


{
        struct sigaction sig_action = {};
        sig_action.sa_sigaction = posix_signal_handler;
        sigemptyset(&sig_action.sa_mask);

#ifdef __APPLE__
        /* for some reason we backtrace() doesn't work on osx
           when we use an alternate stack */
        sig_action.sa_flags = SA_SIGINFO;
#else
        sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
#endif

        if (sigaction(SIGSEGV, &sig_action, NULL) != 0) { err(1, "sigaction"); }
}

3.2 處理棧溢出 - sigaltstack()

Native crash 中有一種是堆棧溢出錯誤。調(diào)用函數(shù)時會將被調(diào)用函數(shù)入棧,并保存該函數(shù)中的局部變量等信息。當(dāng)棧滿了(太多次遞歸,棧上太多對象)時,系統(tǒng)會在同一個已經(jīng)滿了的棧上調(diào)用SIGSEGV的信號處理函數(shù),又再一次引起同樣的信號。

sigaltstack() 允許進程創(chuàng)建一個備用的棧,供信號處理函數(shù)使用。

int sigaltstack(const stack_t *ss, stack_t *oss);

該函數(shù)兩個個參數(shù)為均為stack_t類型的結(jié)構(gòu)體

typedef struct {
   void  *ss_sp;     /* Base address of stack */
   int    ss_flags;  /* Flags */
   size_t ss_size;   /* Number of bytes in stack */
}

要想創(chuàng)建一個新的可替換信號棧,ss_flags必須設(shè)置為0,ss_sp和ss_size分別指明可替換信號棧的起始地址和棧大小。

sigaltstack第一個參數(shù)為創(chuàng)建的新的可替換信號棧,第二個參數(shù)可以設(shè)置為NULL,如果不為NULL的話,將會將舊的可替換信號棧的信息保存在里面。函數(shù)成功返回0,失敗返回-1.

使用可替換信號棧的步驟如下:

  • 在內(nèi)存中分配一塊區(qū)域作為可替換信號棧
  • 使用sinalstack()函數(shù)通知系統(tǒng) 存在一個可以替換的信號棧。
  • 使用sigaction()函數(shù)建立信號處理函數(shù)的時候,通過將sa_flags設(shè)置為SA_ONSTACK來告訴系統(tǒng)信號處理函數(shù)將在可替換信號棧上面運行。

示例代碼:


stack_t stack;
memset(&stack, 0, sizeof(stack));
/* Reserver the system default stack size. We don't need that much by the way. */
stack.ss_size = SIGSTKSZ;
stack.ss_sp = malloc(stack.ss_size);
stack.ss_flags = 0;
/* Install alternate stack size. Be sure the memory region is valid until you revert it. */
if (stack.ss_sp != NULL && sigaltstack(&stack, NULL) == 0) {
  ...
}

3.3 兼容舊的信號處理函數(shù)

我們在java虛擬機上運行,某些信號可能在之前已經(jīng)被安裝過信號處理函數(shù)。例如,SIGSEGV經(jīng)常用于處理NullPointerException.
所以,你必須先調(diào)用舊的信號處理函數(shù),以防把上下文環(huán)境搞亂。舊的信號處理函數(shù)要么不進行處理直接返回,要么調(diào)用abort()(這樣我們就有最后一次機會通過SIGABRT的信號處理函數(shù)處理這個信號,所以在捕獲SIGABRT的信號處理函數(shù)里邊,我們是最后才調(diào)用舊的信號處理函數(shù))

static void my_handler(const int code, siginfo_t *const si, void *const sc) {
  /* Call previous handler. */
  old_handler.sa_sigaction(code, si, sc);
...

}

3.4 提取崩潰信息

信號處理函數(shù)中有豐富的信息,供我們分析crash發(fā)生的原因

/*信號處理函數(shù)*/
void (*sa_sigaction)(const int code, siginfo_t *const si, void * const sc) 

siginfo_t {
   int      si_signo;     /* Signal number 信號量 */
   int      si_errno;     /* An errno value */
   int      si_code;      /* Signal code 錯誤碼 */
   }

1、 signo 和code

發(fā)生native crash之后,logcat中會打出如下一句信息:
signal 11 (SIGSEGV), code 0 (SI_USER), fault addr 0x0
sigaction 第二個參數(shù)為siginfo_t結(jié)構(gòu)體,其中的 si_signo和si_code 可以得出crash的大致原因

2、 收集發(fā)生crash的地址(pc):

sigaction回調(diào)函數(shù)的第三個參數(shù)是一個指向ucontext_t的指針,ucontext_t收集了寄存器數(shù)值(還有各種處理器特定的信息)。
在x86-64架構(gòu),pc值是存在uc_mcontext.gregs[REG_RIP];
在arm架構(gòu),則是uc_mcontext.arm_pc。不過在Android上,ucontext_t結(jié)構(gòu)體沒有在任何系統(tǒng)頭文件定義,所以要自己去引入一份定義。

3、找出crash的pc,屬于哪個二進制文件或so。分析是哪個so庫出現(xiàn)了奔潰,奔潰在哪個函數(shù)。

(1) dladdr() 根據(jù)pc值獲得共享庫名字和相對偏移地址

dladdr 可以將pc程序計數(shù)器指向的決定地址 轉(zhuǎn)換成Dl_info 對象。從而計算出pc指向地址 所在的動態(tài)庫名字,該動態(tài)庫的基地址,pc地址最近的符號名等。

Dl_info 結(jié)構(gòu)體個字段的意義

typedef struct {
  /* Pathname of shared object that contains address. */
  const char* dli_fname; //共享庫的完全路徑名
  /* Address at which shared object is loaded. */
  void* dli_fbase;     //該共享庫的基地址
  /* Name of nearest symbol with address lower than addr. */
  const char* dli_sname; //和指定pc值最近的符號名
  /* Exact address of symbol named in dli_sname. */
  void* dli_saddr;      //dli_name 符號名的絕對地址
} Dl_info;

dladdr()用法:

    Dl_info info;  
if (dladdr(addr, &info) != 0 && info.dli_fname != NULL) {  
  void * const nearest = info.dli_saddr;  
  //相對偏移地址
  const uintptr_t addr_relative =  
    ((uintptr_t) addr - (uintptr_t) info.dli_fbase);  
  ...  
}

4、如何根據(jù)pc 手動分析出 動態(tài)庫的名字和相對地址

(1)linux進程的地址分布空間
image.png

用戶進程部分分段存儲內(nèi)容如下表所示(按地址遞減順序):

名稱 存儲內(nèi)容
局部變量、函數(shù)參數(shù)、返回地址等
動態(tài)分配的內(nèi)存
BSS段 未初始化或初值為0的全局變量和靜態(tài)局部變量
數(shù)據(jù)段 已初始化且初值非0的全局變量和靜態(tài)局部變量
代碼段 執(zhí)行代碼、字符串字面值、只讀變量

任何一個程序通常都包括代碼段和數(shù)據(jù)段,這些代碼和數(shù)據(jù)本身都是靜態(tài)的。程序要想運行,首先要由操作系統(tǒng)負責(zé)為其創(chuàng)建進程,并在進程的虛擬地址空間中為其代碼段和數(shù)據(jù)段建立映射。光有代碼段和數(shù)據(jù)段是不夠的,進程在運行過程中還要有其動態(tài)環(huán)境,其中最重要的就是堆棧。

上圖中Random stack offset和Random mmap offset等隨機值意在防止惡意程序。Linux通過對棧、內(nèi)存映射段、堆的起始地址加上隨機偏移量來打亂布局,以免惡意程序通過計算訪問棧、庫函數(shù)等地址。

棧(stack),作為進程的臨時數(shù)據(jù)區(qū),增長方向是從高地址到低地址。

(2) /proc/self/maps: 查看各模塊加載在內(nèi)存中的地址范圍(絕對地址范圍)

在Linux系統(tǒng)中,/proc/self/maps保存了各個程序段在內(nèi)存中的加載地址范圍,grep出共享庫的名字,就可以知道共享庫的加載基值是多少。

feifeideMacBook-Pro:s1 feifei$ adb shell ps | grep com.sogou.translate.example
u0_a83       24460  2977 4455820 111148 0                   0 S com.sogou.translate.example
u0_a83       24478  2977 4362888  71852 0                   0 S com.sogou.translate.example:logservice
查看24460 com.sogou.translate.example 進程對應(yīng)的map文件
adb shell cat /proc/24460/maps

6fb7751000-6fb77e6000 r-xp 00000000 103:19 61608                         /data/app/com.sogou.translate.example-xMTUqjEw8dDDCbBi01nLgg==/lib/arm64/libbreakpad-core.so
6fb77e6000-6fb77f6000 ---p 00000000 00:00 0 
6fb77f6000-6fb77fb000 r--p 00095000 103:19 61608                         /data/app/com.sogou.translate.example-xMTUqjEw8dDDCbBi01nLgg==/lib/arm64/libbreakpad-core.so
6fb77fb000-6fb77fc000 rw-p 0009a000 103:19 61608                         /data/app/com.sogou.translate.example-xMTUqjEw8dDDCbBi01nLgg==/lib/arm64/libbreakpad-core.so
6fb77fc000-6fb77fd000 rw-p 00000000 00:00 0                              [anon:.bss]
6fb7800000-6fb7a00000 rw-p 00000000 00:00 0                              [anon:libc_malloc]
6fb7a45000-6fb7a47000 r-xp 00000000 103:14 1811                          /system/lib64/vndk-sp/libion.so
6fb7a47000-6fb7a48000 r--p 00001000 103:14 1811                          /system/lib64/vndk-sp/libion.so
6fb7a48000-6fb7a49000 rw-p 00002000 103:14 1811                          /system/lib64/vndk-sp/libion.so
6fb7ab5000-6fb7ab6000 ---p 00000000 00:00 0                              [anon:thread stack guard page]

可見libbreakpad-core.so的地址范圍是6fb7751000-6fb77e6000,基地址為6fb7751000

(3) 利用readelf查看共享庫的符號表
安裝

在linux下,用readelf來看ELF頭部或者其它各section的內(nèi)容,用objdump來對指定的內(nèi)容(.text, .data等)進行反匯編。

但是mac os X下沒有這兩個命令,可以用brew來安裝

brew update && brew install binutils

將libnutils 安裝路徑加入到環(huán)境變量

vim ~/.bash_profile

添加一行:
PATH=${PATH}:/usr/local/Cellar/binutils/2.33.1/bin

然后執(zhí)行:
source ~/.bash_profile
readelf -s 查看so庫的符號表

參數(shù) -s,symbols 顯示符號表段中的項(如果有數(shù)據(jù)的話)

 readelf -s libbreakpad-core.so 
 

部分結(jié)果如下:

 354: 000000000007d794     3 OBJECT  GLOBAL DEFAULT   12 _ZTSDu
   355: 000000000004a180   176 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk116allocator_t
   356: 00000000000298a0   812 FUNC    GLOBAL DEFAULT   11 _Z7tryDumpv
   357: 00000000000385e4    88 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk19allocatorINS
   358: 0000000000041920    52 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk116allocator_t
   359: 00000000000a5940    24 OBJECT  GLOBAL DEFAULT   18 _ZTIN10__cxxabiv116__shim
   360: 000000000002a89c    44 FUNC    GLOBAL DEFAULT   11 _Z23call_dangerous_functi
   361: 0000000000061f78    84 FUNC    GLOBAL DEFAULT   11 _ZNSt12length_errorD2Ev
   362: 0000000000038f88   316 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk112basic_strin
   363: 000000000004f6f8   704 FUNC    WEAK   DEFAULT   11 _ZN15google_breakpad6CpuS
   364: 0000000000045500    52 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk116allocator_t
   365: 0000000000045b2c   140 FUNC    WEAK   DEFAULT   11 _ZNKSt6__ndk16vectorIPN15
   366: 0000000000057890    32 FUNC    WEAK   DEFAULT   11 _ZN15google_breakpad9GetO
   367: 00000000000367cc   132 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk117__compresse
   368: 0000000000049290   168 FUNC    GLOBAL DEFAULT   11 _ZN15google_breakpad13Wri
   369: 000000000003e018   180 FUNC    WEAK   DEFAULT   11 _ZN15google_breakpad15was
   370: 0000000000047ca0    96 FUNC    WEAK   DEFAULT   11 _ZNSt6__ndk116allocator_t
   371: 00000000000aa048     1 OBJECT  GLOBAL DEFAULT   22 global_interruptSystemCra

dangerous_function 和
global_interruptSystemCrash 都是so庫中定義的函數(shù),可以看到這兩個函數(shù)符號加載到內(nèi)存中的偏移量。

四 演示demo 代碼

下面是一個mac 或linux平臺上運行的sigaction()的小demo,用來熟悉sinal處理函數(shù)的特性。

TestSignalPosix.c

//
// Created by 飛飛 on 2020-01-22.
//

#include <stdio.h>
#include <signal.h>
#include <assert.h>
#include <stdlib.h>
#include <err.h>
#include <execinfo.h>
#include <string.h>

int  divide_by_zero();
void cause_segfault();
void stack_overflow();
void infinite_loop();
void illegal_instruction();
void cause_calamity();

void set_signal_handler_4_posix();
void posix_signal_handler(int sig, siginfo_t *siginfo, void *context);

static char const * icky_global_program_name;
int addr2line(char const * const program_name, void const * const addr);
void posix_print_stack_trace();


const int kExceptionSignals[] = {
        SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP
};

//--- 信號量個數(shù)
const int kNumHandledSignals =
        sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]);

//--- 每個信號的原有信號處理函數(shù)
struct sigaction old_handlers[kNumHandledSignals];

int main(int argc, char * argv[])
{
    (void)argc;

    /* store off program path so we can use it later */
    icky_global_program_name = argv[0];
    char* exceptionType = argv[1];

    set_signal_handler_4_posix();

    cause_calamity(exceptionType);


    printf("OMG! Nothing bad happend!");
    return 0;
}

void cause_calamity(char* type)
{
    /* uncomment one of the following error conditions to cause a calamity of
     your choosing! */

    if(strcmp(type,"1") == 0){
        fputs("cause_calamity type = 1 ,create a  divide_by_zero error \n", stderr);
        (void)divide_by_zero();
    } else if(strcmp(type,"2") == 0){
        fputs("cause_calamity type = 2 ,create a  cause_segfault error \n", stderr);
         cause_segfault();
    } else if(strcmp(type,"3") == 0){
        fputs("cause_calamity type = 3 ,create a assert(false)  error\n", stderr);
//         assert(false);
    } else if(strcmp(type,"4") == 0){
        fputs("cause_calamity type = 4 ,,create a infinite_loop \n", stderr);
        infinite_loop();
    } else if(strcmp(type,"5") == 0){
        fputs("cause_calamity type = 5 ,,create a illegal_instruction \n", stderr);
        illegal_instruction();
    } else if(strcmp(type,"6") == 0){
        fputs("cause_calamity type = 6 ,,create a stack_overflow\n", stderr);
        stack_overflow();
    }



    // infinite_loop();
    // illegal_instruction();
    // stack_overflow();
}

int divide_by_zero()
{
    int a = 1;
    int b = 0;
    return a / b;
}

void cause_segfault()
{
    int * p = (int*)0x12345678;
    *p = 0;
}

void stack_overflow();
void stack_overflow()
{
    int foo[1000]; //allocate something big on the stack
    (void)foo;
    stack_overflow();
}

/* break out with ctrl+c to test SIGINT handling */
void infinite_loop()
{
    while(1) {};
}

void illegal_instruction()
{
    /* I couldn't find an easy way to cause this one, so I'm cheating */
    raise(SIGILL);
}



void posix_signal_handler(int sig, siginfo_t *siginfo, void *context)
{

    fputs("\n\n",stderr);

    printf("received signal no:%d,code:%d\n",sig,siginfo->si_code);


    fputs("\n\n",stderr);
    (void)context;
    switch(sig)
    {
        case SIGSEGV:
            fputs("Caught SIGSEGV: Segmentation Fault\n", stderr);
            break;
        case SIGINT:
            fputs("Caught SIGINT: Interactive attention signal, (usually ctrl+c)\n",
                  stderr);
            break;
        case SIGFPE:
            switch(siginfo->si_code)
            {
                case FPE_INTDIV:
                    fputs("Caught SIGFPE: (integer divide by zero)\n", stderr);
                    break;
                case FPE_INTOVF:
                    fputs("Caught SIGFPE: (integer overflow)\n", stderr);
                    break;
                case FPE_FLTDIV:
                    fputs("Caught SIGFPE: (floating-point divide by zero)\n", stderr);
                    break;
                case FPE_FLTOVF:
                    fputs("Caught SIGFPE: (floating-point overflow)\n", stderr);
                    break;
                case FPE_FLTUND:
                    fputs("Caught SIGFPE: (floating-point underflow)\n", stderr);
                    break;
                case FPE_FLTRES:
                    fputs("Caught SIGFPE: (floating-point inexact result)\n", stderr);
                    break;
                case FPE_FLTINV:
                    fputs("Caught SIGFPE: (floating-point invalid operation)\n", stderr);
                    break;
                case FPE_FLTSUB:
                    fputs("Caught SIGFPE: (subscript out of range)\n", stderr);
                    break;
                default:
                    fputs("Caught SIGFPE: Arithmetic Exception\n", stderr);
                    break;
            }
        case SIGILL:
            switch(siginfo->si_code)
            {
                case ILL_ILLOPC:
                    fputs("Caught SIGILL: (illegal opcode)\n", stderr);
                    break;
                case ILL_ILLOPN:
                    fputs("Caught SIGILL: (illegal operand)\n", stderr);
                    break;
                case ILL_ILLADR:
                    fputs("Caught SIGILL: (illegal addressing mode)\n", stderr);
                    break;
                case ILL_ILLTRP:
                    fputs("Caught SIGILL: (illegal trap)\n", stderr);
                    break;
                case ILL_PRVOPC:
                    fputs("Caught SIGILL: (privileged opcode)\n", stderr);
                    break;
                case ILL_PRVREG:
                    fputs("Caught SIGILL: (privileged register)\n", stderr);
                    break;
                case ILL_COPROC:
                    fputs("Caught SIGILL: (coprocessor error)\n", stderr);
                    break;
                case ILL_BADSTK:
                    fputs("Caught SIGILL: (internal stack error)\n", stderr);
                    break;
                default:
                    fputs("Caught SIGILL: Illegal Instruction\n", stderr);
                    break;
            }
            break;
        case SIGTERM:
            fputs("Caught SIGTERM: a termination request was sent to the program\n",
                  stderr);
            break;
        case SIGABRT:
            fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr);
            break;
        default:
            break;
    }
    posix_print_stack_trace();

    fputs("\n\n",stderr);

    _Exit(1);

}

static uint8_t alternate_stack[SIGSTKSZ];
void set_signal_handler_4_posix()
{
    /* setup alternate stack */
    {
        stack_t ss = {};
        /* malloc is usually used here, I'm not 100% sure my static allocation
           is valid but it seems to work just fine. */
        ss.ss_sp = (void*)alternate_stack;
        ss.ss_size = SIGSTKSZ;
        ss.ss_flags = 0;

        if (sigaltstack(&ss, NULL) != 0) { err(1, "sigaltstack"); }
    }

    /* register our signal handlers */
    {
        struct sigaction sig_action = {};
        sig_action.sa_sigaction = posix_signal_handler;
        sigemptyset(&sig_action.sa_mask);

#ifdef __APPLE__
        /* for some reason we backtrace() doesn't work on osx
           when we use an alternate stack */
        sig_action.sa_flags = SA_SIGINFO;
#else
        sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
#endif

        for(int i = 0;i<kNumHandledSignals;i++ ){
            printf("install sigaction for signal  %d\n",kExceptionSignals[i]);
            if(sigaction(kExceptionSignals[i], &sig_action, &old_handlers[i])==-1){
                printf("set sinalaction failed,just return");
            }
        }
    }
}


#define MAX_STACK_FRAMES 64
static void *stack_traces[MAX_STACK_FRAMES];
void posix_print_stack_trace()
{

    fputs("\n\n",stderr);
    int i, trace_size = 0;
    char **messages = (char **)NULL;

    trace_size = backtrace(stack_traces, MAX_STACK_FRAMES);
    messages = backtrace_symbols(stack_traces, trace_size);

    /* skip the first couple stack frames (as they are this function and
       our handler) and also skip the last frame as it's (always?) junk. */
    // for (i = 3; i < (trace_size - 1); ++i)
    // we'll use this for now so you can see what's going on
    for (i = 0; i < trace_size; ++i)
    {
        if (addr2line(icky_global_program_name, stack_traces[i]) != 0)
        {
            printf("  error determining line # for: %s\n", messages[i]);
        }

    }
    if (messages) { free(messages); }
}


/* Resolve symbol name and source location given the path to the executable
   and an address */
int addr2line(char const * const program_name, void const * const addr)
{
    char addr2line_cmd[512] = {0};

    /* have addr2line map the address to the relent line in the code */
#ifdef __APPLE__
    /* apple does things differently... */
    sprintf(addr2line_cmd,"atos -o %.256s %p", program_name, addr);
#else
    sprintf(addr2line_cmd,"addr2line -f -p -e %.256s %p", program_name, addr);
#endif

    /* This will print a nicely formatted string specifying the
       function and source line of the address */
//    sprintf("addr2line: %s",addr2line_cmd);
    return system(addr2line_cmd);
}

編譯指令:

mac上:
gcc -lpthread  -g -fno-pie TestSignalPosix.c -o TestSignalPosix

linux上:
gcc -lpthread  -g  TestSignalPosix.c -o TestSignalPosix

gcc 參數(shù)

  • -l 指定gcc 需要加載的動態(tài)鏈接庫。
  • -g 為gdb調(diào)試用,會做兩件事情:創(chuàng)建符號表,符號表包含了程序中使用的變量名稱的列表;關(guān)閉所有的優(yōu)化機制,以便程序執(zhí)行過程中嚴格按照原來的C代碼進行。

測試結(jié)果:

feifeideMacBook-Pro:cpp feifei$ ./TestSignalPosix 2
install sigaction for signal  11
install sigaction for signal  6
install sigaction for signal  8
install sigaction for signal  4
install sigaction for signal  10
install sigaction for signal  5
cause_calamity type = 2 ,create a  cause_segfault error 


received signal no:11,code:1


Caught SIGSEGV: Segmentation Fault



posix_print_stack_trace (in TestSignalPosix) (TestSignalPosix.c:271)
posix_signal_handler (in TestSignalPosix) (TestSignalPosix.c:217)
0x7fff7024cb5d
0x0000ffff (in TestSignalPosix)
cause_calamity (in TestSignalPosix) (TestSignalPosix.c:67)
main (in TestSignalPosix) (TestSignalPosix.c:52)
0x7fff700673d5


五、 參考文獻:

http://www.itdecent.cn/p/78a363ea48df

sigaction demo:https://spin.atomicobject.com/2013/01/13/exceptions-stack-traces-c/

信號定義和行為:https://blog.csdn.net/yeyuwuhen1203/article/details/78031391

信號處理函數(shù):https://blog.csdn.net/weibo1230123/article/details/81411827

natvie crash 捕獲:https://blog.csdn.net/mba16c35/article/details/54178067

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、信號及信號來源 信號本質(zhì) 信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一...
    丶Em1tu0F閱讀 1,511評論 0 1
  • 信號本質(zhì) 軟中斷信號(signal,又簡稱為信號)用來通知進程發(fā)生了異步事件。在軟件層次上是對中斷機制的一種模擬,...
    飛揚code閱讀 846評論 0 2
  • 文/tangsl(簡書作者) 原文鏈接:http://www.itdecent.cn/p/2b993a4b913e...
    西葫蘆炒胖子閱讀 3,952評論 0 5
  • 對于 Linux來說,實際信號是軟中斷,許多重要的程序都需要處理信號。信號,為 Linux 提供了一種處理異步事件...
    故事狗閱讀 86,301評論 2 63
  • 計算機系統(tǒng)漫游 代碼從文本到可執(zhí)行文件的過程(c語言示例):預(yù)處理階段,處理 #inlcude , #defin...
    willdimagine閱讀 3,855評論 0 5

友情鏈接更多精彩內(nèi)容