一闹炉、信號(hào)機(jī)制
函數(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)程的地址分布空間
用戶進(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