信號(hào)機(jī)制和Android natvie crash捕捉

一闹炉、信號(hào)機(jī)制

image.png

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

1赚哗、信號(hào)的接收

接收信號(hào)的任務(wù)是由內(nèi)核代理的,當(dāng)內(nèi)核接收到信號(hào)后铁坎,會(huì)將其放到對(duì)應(yīng)進(jìn)程的信號(hào)隊(duì)列中蜂奸,同時(shí)向進(jìn)程發(fā)送一個(gè)中斷,使其陷入內(nèi)核態(tài)硬萍。

此時(shí)信號(hào)還只是在隊(duì)列中扩所,對(duì)進(jìn)程來說暫時(shí)是不知道有信號(hào)到來的。

2朴乖、信號(hào)的檢測(cè)

進(jìn)程陷入內(nèi)核態(tài)后祖屏,有兩種場(chǎng)景會(huì)對(duì)信號(hào)進(jìn)行檢測(cè):

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

3恢着、信號(hào)的處理

信號(hào)處理函數(shù)是運(yùn)行在用戶態(tài)的株婴,調(diào)用信號(hào)處理函數(shù)前悄蕾,內(nèi)核會(huì)將當(dāng)前的內(nèi)核棧的內(nèi)容備份拷貝到用戶棧纬霞,然后修改命令寄存器(eip)指向信號(hào)處理函數(shù)。
接下來進(jìn)程返回到用戶態(tài)中遣鼓,執(zhí)行相應(yīng)的信號(hào)處理函數(shù)滨溉。
信號(hào)處理函數(shù)執(zhí)行完成后,還需要返回內(nèi)核態(tài)埠通,檢查是否還有其它信號(hào)未處理赎离。
如果所有信號(hào)都處理完成,就會(huì)將內(nèi)核椂巳瑁恢復(fù)(從用戶棧的備份拷貝回來)梁剔,同時(shí)恢復(fù)指令寄存器(eip)將其指向中斷前的運(yùn)行位置,最后回到用戶態(tài)繼續(xù)執(zhí)行進(jìn)程舞蔽。

一個(gè)完整的信號(hào)處理流程便結(jié)束了荣病,如果同時(shí)有多個(gè)信號(hào)到達(dá),上面的處理流程會(huì)在第2步和第3步驟間重復(fù)進(jìn)行渗柿。

二众雷、信號(hào)定義和行為

1、信號(hào)的定義

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

一共有31個(gè)信號(hào),其中1~15號(hào)信號(hào)為常用信號(hào)

/* 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)準(zhǔn)定義了操作系統(tǒng)應(yīng)該為應(yīng)用程序提供的接口標(biāo)準(zhǔn)。
POSIX標(biāo)準(zhǔn)意在期望獲得源代碼級(jí)別的軟件可移植性悯嗓。換句話說件舵,為一個(gè)POSIX兼容的操作系統(tǒng)編寫的程序,應(yīng)該可以在任何其它的POSIX操作系統(tǒng)(即使是來自另一個(gè)廠商)上編譯執(zhí)行脯厨。

簡(jiǎn)單來說:
完成同一功能铅祸,不同內(nèi)核提供的系統(tǒng)調(diào)用(也就是一個(gè)函數(shù))是不同的。例如創(chuàng)建進(jìn)程合武,linux下是fork函數(shù)临梗,windows下是creatprocess函數(shù)。
POSIX 要求 linux和windows都要實(shí)現(xiàn)基本的posix標(biāo)準(zhǔn)稼跳,linux把fork函數(shù)封裝成posix_fork(隨便說的)盟庞,windows把creatprocess函數(shù)也封裝成posix_fork,都聲明在unistd.h里汤善。這樣什猖,程序員編寫普通應(yīng)用時(shí)候票彪,只用包含unistd.h,調(diào)用posix_fork函數(shù)不狮,程序就在源代碼級(jí)別可移植了抹镊。

2、常見信號(hào)的含義

SIGHUP - 1

本信號(hào)在用戶終端連接(正郴绨粒或非正常)結(jié)束時(shí)發(fā)出, 通常是在終端的控制進(jìn)程結(jié)束時(shí), 通知同一session內(nèi)的各個(gè)作業(yè), 這時(shí)它們與控制終端不再關(guān)聯(lián)。
登錄Linux時(shí)颈渊,系統(tǒng)會(huì)分配給登錄用戶一個(gè)終端(Session)遂黍。在這個(gè)終端運(yùn)行的所有程序,包括前臺(tái)進(jìn)程組和后臺(tái)進(jìn)程組俊嗽,一般都屬于這個(gè)Session雾家。當(dāng)用戶退出Linux登錄時(shí),前臺(tái)進(jìn)程組和后臺(tái)有對(duì)終端輸出的進(jìn)程將會(huì)收到SIGHUP信號(hào)绍豁。這個(gè)信號(hào)的默認(rèn)操作為終止進(jìn)程芯咧,因此前臺(tái)進(jìn)程組和后臺(tái)有終端輸出的進(jìn)程就會(huì)中止。不過可以捕獲這個(gè)信號(hào)竹揍,比如wget能捕獲SIGHUP信號(hào)敬飒,并忽略它,這樣就算退出了Linux登錄芬位,wget也能繼續(xù)下載无拗。
此外,對(duì)于與終端脫離關(guān)系的守護(hù)進(jìn)程昧碉,這個(gè)信號(hào)用于通知它重新讀取配置文件英染。

SIGINT - 2

程序終止(interrupt)信號(hào),通常是Ctrl-C)時(shí)發(fā)出被饿,用于通知前臺(tái)進(jìn)程組終止進(jìn)程四康。

SIGQUI - 3

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

SIGILL - 4

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

SIGTRAP - 5

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

SIGABRT - 6

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

SIGBUS - 7

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

SIGFPE - 8

算術(shù)運(yùn)算錯(cuò)誤撼泛。不僅包括浮點(diǎn)運(yùn)算錯(cuò)誤, 還包括溢出及除數(shù)為0等其它所有的算術(shù)的錯(cuò)誤。

SIGKILL - 9

用來立即結(jié)束程序的運(yùn)行澡谭。本信號(hào)不能被阻塞愿题、處理和忽略损俭。如果管理員發(fā)現(xiàn)某個(gè)進(jìn)程終止不了,可嘗試發(fā)送這個(gè)信號(hào)潘酗。

SIGUSR1 - 10

用戶自定義的信號(hào)1

SIGSEGV - 11

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

SIGUSR2 -12

用戶自定義的信號(hào)1

SIGPIPE - 13

管道破裂。這個(gè)信號(hào)通常在進(jìn)程間通信產(chǎn)生仔夺,比如采用FIFO(管道)通信的兩個(gè)進(jìn)程琐脏,讀管道沒打開或者意外終止就往管道寫,寫進(jìn)程會(huì)收到SIGPIPE信號(hào)缸兔。此外用Socket通信的兩個(gè)進(jìn)程日裙,寫進(jìn)程在寫Socket的時(shí)候,讀進(jìn)程已經(jīng)終止惰蜜。

SIGALRM -14

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

SIGTERM - 15

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

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

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

4联四、信號(hào)處理行為

4.1 信號(hào)處理的方式一般有三種:

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

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

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

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

傳統(tǒng)UNIX系統(tǒng)應(yīng)用恐似,對(duì)SIGQUIT信號(hào)的默認(rèn)行為是 "終止 + CORE"杜跷,也就是產(chǎn)生core dump文件后,立即終于運(yùn)行矫夷。

Android Dalvik應(yīng)用收到該信號(hào)后葛闷,會(huì) 打印改應(yīng)用中所有線程的當(dāng)前狀態(tài),并且并不是強(qiáng)制退出双藕。
這些狀態(tài)通常保存在一個(gè)特定的叫做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、對(duì)于很多其他的異常信號(hào) (SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT ), Android進(jìn)程 在退出前忧陪,會(huì)在/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 需要注冊(cè)下面6個(gè)信號(hào)

Android 平臺(tái) 捕捉natvie crash 一般只需要安裝6個(gè)信號(hào)處理函數(shù)即可。

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

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

3.1沥匈、安裝信號(hào)處理函數(shù)

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

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

signum參數(shù)指出要捕獲的信號(hào)類型蔗喂,act參數(shù)指定新的信號(hào)處理方式,oldact參數(shù)輸出先前信號(hào)的處理方式(如果不為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代表新的信號(hào)處理函數(shù),僅接受一個(gè)參數(shù).
如:
void show_handler(int signo)
{
    printf("I got signal %d\n", signo);
}
sa_sigaction 同樣為信號(hào)處理函數(shù)辨萍,相對(duì)于sa_handler可以獲得更多的信息:
void handler(int signo, siginfo_t *info, void *context);

第二個(gè)參數(shù)為一個(gè)siginfo_t結(jié)構(gòu)的指針棋恼,該結(jié)構(gòu)描述了信號(hào)產(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成員。第三個(gè)參數(shù)context是一個(gè)無類型的指針锈玉,它可以被強(qiáng)制轉(zhuǎn)換為ucntext_t結(jié)構(gòu)類型爪飘,用于標(biāo)識(shí)信號(hào)傳遞時(shí)進(jìn)程的上下文。

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


備注:
sa_sigaction和sa_handler字段师崎,其實(shí)現(xiàn)可能使用同一存儲(chǔ)區(qū),所以應(yīng)用程序只能一次使用這兩個(gè)字段中的一個(gè)椅棺。
sa_mask 用來設(shè)置在處理該信號(hào)時(shí)暫時(shí)將sa_mask 指定的信號(hào)集擱置
sa_flags 用來設(shè)置信號(hào)處理的其他相關(guān)操作犁罩,下列的數(shù)值可用:
  • SA_RESETHAND:當(dāng)調(diào)用信號(hào)處理函數(shù)時(shí),將信號(hào)的處理函數(shù)重置為缺省值SIG_DFL(默認(rèn)處理方式)
  • SA_RESTART:如果信號(hào)中斷了進(jìn)程的某個(gè)系統(tǒng)調(diào)用两疚,則系統(tǒng)自動(dòng)啟動(dòng)該系統(tǒng)調(diào)用
  • SA_NODEFER :一般情況下床估, 當(dāng)信號(hào)處理函數(shù)運(yùn)行時(shí),內(nèi)核將阻塞該給定信號(hào)诱渤。但是如果設(shè)置了 SA_NODEFER標(biāo)記丐巫, 那么在該信號(hào)處理函數(shù)運(yùn)行時(shí),內(nèi)核將不會(huì)阻塞該信號(hào)
  • SA_SIGINFO:設(shè)置選擇sa_sigaction 作為信號(hào)處理函數(shù)勺美,否則選擇sa_handler作為信號(hào)處理函數(shù)递胧。
  • SA_RESETHAND:信號(hào)處理之后重新設(shè)置為默認(rèn)的處理方式。
選項(xiàng) 含義
SA_INTERRUPT 由此信號(hào)中斷的系統(tǒng)調(diào)用不會(huì)自動(dòng)重啟
SA_NOCLDSTOP 若signo是SIGCHLD赡茸,當(dāng)子進(jìn)程停止(作業(yè)控制)時(shí)缎脾,不產(chǎn)生此信號(hào)。當(dāng)子進(jìn)程終止時(shí)占卧,仍產(chǎn)生此信號(hào)(參加SA_NOCLDWAIT說明)赊锚。若已設(shè)置此標(biāo)志治筒,則當(dāng)停止的進(jìn)程繼續(xù)運(yùn)行時(shí),作為XSI擴(kuò)展舷蒲,不發(fā)送SIGCHLD信號(hào)耸袜。
SA_NOCLDWAIT 若signo是SIGCHLD,則當(dāng)調(diào)用進(jìn)程的子進(jìn)程終止時(shí)牲平,不創(chuàng)建僵尸進(jìn)程堤框。若調(diào)用進(jìn)程在后面調(diào)用wait,則調(diào)用進(jìn)程阻塞纵柿,直到其所有子進(jìn)程都終止蜈抓,此時(shí)返回-1,并將errno設(shè)置為ECHILD昂儒。
SA_NODEFER 當(dāng)捕捉到此信號(hào)時(shí)沟使,在執(zhí)行其信號(hào)處理函數(shù)時(shí),系統(tǒng)不自動(dòng)阻塞此信號(hào)(除非sa_mask包括了此信號(hào))渊跋。
SA_ONSTACK 若用sigaltstack聲明了替換棧腊嗡,則將此信號(hào)遞送給替換棧上的進(jìn)程。
SA_RESETHAND 在此信號(hào)處理函數(shù)的入口處拾酝,將此信號(hào)的處理方式復(fù)位為SIG_DEF燕少,并清除SA_SIGINFO標(biāo)志。但是蒿囤,不能自動(dòng)復(fù)位SIGILL和SIGTRAP這兩個(gè)信號(hào)的配置客们。設(shè)置此標(biāo)志是sigaction的行為如同SA_NODEFER標(biāo)志也設(shè)置了一樣。
SA_RESTART 由此信號(hào)中斷的系統(tǒng)調(diào)用會(huì)自動(dòng)重啟動(dòng)材诽。
SA_SIGINFO 設(shè)置了該標(biāo)志后,會(huì)選擇sa_sigaction 作為信號(hào)處理函數(shù),否則選擇sa_handler作為信號(hào)處理函數(shù)底挫。

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

信號(hào)處理函數(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);
}

安裝信號(hào)處理函數(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 處理?xiàng)R绯? - sigaltstack()

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

sigaltstack() 允許進(jìn)程創(chuàng)建一個(gè)備用的棧,供信號(hào)處理函數(shù)使用拒逮。

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

該函數(shù)兩個(gè)個(gè)參數(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)建一個(gè)新的可替換信號(hào)棧,ss_flags必須設(shè)置為0臀规,ss_sp和ss_size分別指明可替換信號(hào)棧的起始地址和棧大小滩援。

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

使用可替換信號(hào)棧的步驟如下:

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

示例代碼:


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 兼容舊的信號(hào)處理函數(shù)

我們?cè)趈ava虛擬機(jī)上運(yùn)行畔塔,某些信號(hào)可能在之前已經(jīng)被安裝過信號(hào)處理函數(shù)潭辈。例如,SIGSEGV經(jīng)常用于處理NullPointerException.
所以澈吨,你必須先調(diào)用舊的信號(hào)處理函數(shù)把敢,以防把上下文環(huán)境搞亂。舊的信號(hào)處理函數(shù)要么不進(jìn)行處理直接返回谅辣,要么調(diào)用abort()(這樣我們就有最后一次機(jī)會(huì)通過SIGABRT的信號(hào)處理函數(shù)處理這個(gè)信號(hào)修赞,所以在捕獲SIGABRT的信號(hào)處理函數(shù)里邊,我們是最后才調(diào)用舊的信號(hà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 提取崩潰信息

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

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

siginfo_t {
   int      si_signo;     /* Signal number 信號(hào)量 */
   int      si_errno;     /* An errno value */
   int      si_code;      /* Signal code 錯(cuò)誤碼 */
   }

1柏副、 signo 和code

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

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

sigaction回調(diào)函數(shù)的第三個(gè)參數(shù)是一個(gè)指向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确憨,屬于哪個(gè)二進(jìn)制文件或so。分析是哪個(gè)so庫(kù)出現(xiàn)了奔潰瓤的,奔潰在哪個(gè)函數(shù)休弃。

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

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

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

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

dladdr()用法:

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

4稽坤、如何根據(jù)pc 手動(dòng)分析出 動(dòng)態(tài)庫(kù)的名字和相對(duì)地址

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

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

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

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

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

棧(stack)缤谎,作為進(jìn)程的臨時(shí)數(shù)據(jù)區(qū),增長(zhǎng)方向是從高地址到低地址抒倚。

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

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

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 進(jìn)程對(duì)應(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查看共享庫(kù)的符號(hào)表
安裝

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

但是mac os X下沒有這兩個(gè)命令斟赚,可以用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庫(kù)的符號(hào)表

參數(shù) -s,symbols 顯示符號(hào)表段中的項(xiàng)(如果有數(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庫(kù)中定義的函數(shù)着降,可以看到這兩個(gè)函數(shù)符號(hào)加載到內(nèi)存中的偏移量。

四 演示demo 代碼

下面是一個(gè)mac 或linux平臺(tái)上運(yùn)行的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
};

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

//--- 每個(gè)信號(hào)的原有信號(hào)處理函數(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 需要加載的動(dòng)態(tài)鏈接庫(kù)。
  • -g 為gdb調(diào)試用发侵,會(huì)做兩件事情:創(chuàng)建符號(hào)表交掏,符號(hào)表包含了程序中使用的變量名稱的列表;關(guān)閉所有的優(yōu)化機(jī)制,以便程序執(zhí)行過程中嚴(yán)格按照原來的C代碼進(jìn)行器紧。

測(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


五耀销、 參考文獻(xiàn):

http://www.reibang.com/p/78a363ea48df

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

信號(hào)定義和行為:https://blog.csdn.net/yeyuwuhen1203/article/details/78031391

信號(hào)處理函數(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子熊尉,更是在濱河造成了極大的恐慌罐柳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狰住,死亡現(xiàn)場(chǎng)離奇詭異张吉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)催植,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門肮蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人创南,你說我怎么就攤上這事伦忠。” “怎么了稿辙?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵昆码,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我邻储,道長(zhǎng)赋咽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任吨娜,我火速辦了婚禮脓匿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宦赠。我一直安慰自己陪毡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布袱瓮。 她就那樣靜靜地躺著缤骨,像睡著了一般爱咬。 火紅的嫁衣襯著肌膚如雪尺借。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天精拟,我揣著相機(jī)與錄音燎斩,去河邊找鬼。 笑死蜂绎,一個(gè)胖子當(dāng)著我的面吹牛栅表,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播师枣,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼怪瓶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了践美?” 一聲冷哼從身側(cè)響起洗贰,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤找岖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后敛滋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體许布,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年绎晃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜜唾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庶艾,死狀恐怖袁余,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咱揍,我是刑警寧澤泌霍,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站述召,受9級(jí)特大地震影響朱转,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜积暖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一藤为、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夺刑,春花似錦缅疟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至沼填,卻和暖如春桅咆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坞笙。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工岩饼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人薛夜。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓籍茧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梯澜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寞冯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一、信號(hào)及信號(hào)來源 信號(hào)本質(zhì) 信號(hào)是在軟件層次上對(duì)中斷機(jī)制的一種模擬,在原理上吮龄,一個(gè)進(jìn)程收到一個(gè)信號(hào)與處理器收到一...
    丶Em1tu0F閱讀 1,426評(píng)論 0 1
  • 信號(hào)本質(zhì) 軟中斷信號(hào)(signal檬某,又簡(jiǎn)稱為信號(hào))用來通知進(jìn)程發(fā)生了異步事件。在軟件層次上是對(duì)中斷機(jī)制的一種模擬螟蝙,...
    飛揚(yáng)code閱讀 741評(píng)論 0 2
  • 文/tangsl(簡(jiǎn)書作者) 原文鏈接:http://www.reibang.com/p/2b993a4b913e...
    西葫蘆炒胖子閱讀 3,744評(píng)論 0 5
  • 對(duì)于 Linux來說恢恼,實(shí)際信號(hào)是軟中斷,許多重要的程序都需要處理信號(hào)胰默。信號(hào)场斑,為 Linux 提供了一種處理異步事件...
    故事狗閱讀 84,661評(píng)論 2 62
  • 計(jì)算機(jī)系統(tǒng)漫游 代碼從文本到可執(zhí)行文件的過程(c語(yǔ)言示例):預(yù)處理階段,處理 #inlcude 牵署, #defin...
    willdimagine閱讀 3,561評(píng)論 0 5