From: by scz , <Unix編程/應用問答FAQ>
2.0 理解SIGBUS與SIGSEGV
Q: SIGSEGV我能理解,但有時碰上SIGBUS,這該如何理解。
A: nkwht@SMTH
nkwht用Google擷取這樣一些知識。有多種可能導致SIGBUS訊號:
1) 硬體故障,不用說,程式員最常碰上的肯定不是這種情形。
2) Linux平台上執行malloc(),如果沒有足夠的RAM,Linux不是讓malloc()失敗返回,
而是向當前進程分發SIGBUS訊號。
注: 對該點執懷疑態度,有機會可自行測試確認當前系統反應。
3) 某些架構上訪問資料時有對齊的要求,比如只能從4位元組邊界上讀取一個4位元組的
資料類型。IA-32架構沒有硬性要求對齊,儘管未對齊的訪問降低執行效率。另外
一些架構,比如SPARC、m68k,要求對齊訪問,否則向當前進程分發SIGBUS訊號。
SIGBUS與SIGSEGV訊號一樣,可以正常捕獲。SIGBUS的預設行為是終止當前進程併產
生core dump。
A: Marc Rochkind <rochkind@basepath.com
>
SIGBUS與SIGSEGV訊號的一般區別如下:
1) SIGBUS(Bus error)意味著指標所對應的地址是有效地址,但匯流排不能正常使用該
指標。通常是未對齊的資料訪問所致。
2) SIGSEGV(Segment fault)意味著指標所對應的地址是無效地址,沒有實體記憶體對
應該地址。
A: scz <scz@nsfocus.com
> 2002-11-20
參"2.4 如何編程擷取棧底地址"中如何捕獲SIGBUS與SIGSEGV訊號,並利用sigsetjmp、
siglongjmp重獲控制權。
測試表明,在x86/Linux、x86/Solaris、SPARC/Solaris平台上,越過棧底的地址訪
問導致SIGSEGV訊號。在x86/FreeBSD、x86/NetBSD、x86/OpenBSD平台上,越過棧底
的地址訪問導致SIGBUS訊號,而不是SIGSEGV訊號。
下面舉例解釋一下,什麼叫未對齊的資料訪問。
--------------------------------------------------------------------------
/*
* Test: SPARC/Solaris 8 64-bit kernel mode
* gcc -Wall -pipe -g -o bus bus.c
*/
#include <stdio.h>
#include <stdlib.h>
int main ( int argc, char * argv[] )
{
unsigned int i = 0x12345678;
unsigned short int *q = NULL;
unsigned char *p = ( unsigned char * )&i;
*p = 0x00;
q = ( unsigned short int * )( p + 1 );
*q = 0x0000;
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
$ ./bus
匯流排錯誤 (core dumped)
$ gdb ./bus core
GNU gdb 5.0
#0 0x1084c in main (argc=1, argv=0xffbefc54) at bus.c:16
16 *q = 0x0000;
(gdb) disas main
Dump of assembler code for function main:
0x10810 <main> : save %sp, -128, %sp
0x10814 <main+4> : st %i0, [ %fp + 0x44 ]
0x10818 <main+8> : st %i1, [ %fp + 0x48 ]
0x1081c <main+12>: sethi %hi(0x12345400), %o1
0x10820 <main+16>: or %o1, 0x278, %o0 ! 0x12345678
0x10824 <main+20>: st %o0, [ %fp + -20 ]
0x10828 <main+24>: clr [ %fp + -24 ]
0x1082c <main+28>: add %fp, -20, %o0
0x10830 <main+32>: st %o0, [ %fp + -28 ]
0x10834 <main+36>: ld [ %fp + -28 ], %o0
0x10838 <main+40>: clrb [ %o0 ]
0x1083c <main+44>: ld [ %fp + -28 ], %o0
0x10840 <main+48>: add %o0, 1, %o1
0x10844 <main+52>: st %o1, [ %fp + -24 ]
0x10848 <main+56>: ld [ %fp + -24 ], %o0
0x1084c <main+60>: clrh [ %o0 ]
0x10850 <main+64>: clr %i0
0x10854 <main+68>: b 0x1085c <main+76>
0x10858 <main+72>: nop
0x1085c <main+76>: ret
0x10860 <main+80>: restore
End of assembler dump.
(gdb) i r pc
pc 0x1084c 67660
(gdb) i r o0
o0 0xffbefbdd -4260899
(gdb) x/3bx 0xffbefbdd
0xffbefbdd: 0x34 0x56 0x78
(gdb)
從C語言來說,執行"*q = 0x0000;"時導致SIGBUS了。從彙編指令來說,執行"clrh [%o0]"
時導致SIGBUS了,寄存器%o0值為0xffbefbdd,這個地址未對齊在雙位元組邊界上。
注意,gcc編譯時間並未指定-O<n>進行最佳化,但仍然使用clrh,而不是兩次clrb。類似
的彙編指令有ldw、lduh等等。有人可能碰上讀操作也導致SIGBUS,覺得不可理解,
其實讀寫導致SIGBUS沒有本質區別,比如ldw只能讀4位元組邊界上的地址。
bus.c是顯式的未對齊。程式員實際最容易面對的是隱式未對齊,主要來自指標的強
制類型轉換。下面舉例說明這種情形。
--------------------------------------------------------------------------
/*
* Test: SPARC/Solaris 8 64-bit kernel mode
* gcc -Wall -pipe -g -o other_bus other_bus.c
*/
#include <stdio.h>
#include <stdlib.h>
int main ( int argc, char * argv[] )
{
unsigned int i = 0x12345678;
unsigned short int j = 0x0000;
j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
$ ./other_bus
匯流排錯誤 (core dumped)
$ gdb ./other_bus core
GNU gdb 5.0
#0 main (argc=1, argv=0xffbefc44) at other_bus.c:13
13 j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
(gdb) disas main
Dump of assembler code for function main:
0x10810 <main> : save %sp, -120, %sp
0x10814 <main+4> : st %i0, [ %fp + 0x44 ]
0x10818 <main+8> : st %i1, [ %fp + 0x48 ]
0x1081c <main+12>: sethi %hi(0x12345400), %o1
0x10820 <main+16>: or %o1, 0x278, %o0 ! 0x12345678
0x10824 <main+20>: st %o0, [ %fp + -20 ]
0x10828 <main+24>: clrh [ %fp + -22 ]
0x1082c <main+28>: lduh [ %fp + -19 ], %o0
0x10830 <main+32>: sth %o0, [ %fp + -22 ]
0x10834 <main+36>: clr %i0
0x10838 <main+40>: b 0x10840 <main+48>
0x1083c <main+44>: nop
0x10840 <main+48>: ret
0x10844 <main+52>: restore
End of assembler dump.
(gdb) i r pc
pc 0x1082c 67628
(gdb)
因此在SPARC架構上編程,一定要留神強制類型轉換,務必清楚自己正在幹什麼,有
沒有隱患。
D: yuhuan@SMTH 2004-01-30 11:48
參Linux的mmap(2)手冊頁
--------------------------------------------------------------------------
使用映射可能涉及到如下訊號
SIGSEGV
試圖對唯讀映射地區進行寫操作
SIGBUS
試圖訪問一塊無檔案內容對應的記憶體地區,比如超過檔案尾的記憶體地區,或者以
前有檔案內容對應,現在為另一進程截斷過的記憶體地區。
著作權聲明
:轉載時請以超連結形式標明文章原始出處和作者資訊及本聲明
http://bigwhite.blogbus.com/logs/12296535.html
SIGBUS和SIGSEGV也許是我們在平時遇到的次數最多的兩個記憶體錯誤
訊號。記憶體問題一直是最令我們頭疼的事情,弄清楚兩個訊號的發生緣由對我們很好的理解程式的運行是大有裨益的。
我們來看兩段程式:
//testsigsegv.c
int main() {
char *pc = (char*)0x00001111;
*pc = 17;
}
//testsigbus.c
int main() {
int *pi = (int*)0x00001111;
*pi = 17;
}
上面的代碼那麼的相似,我們也同樣用gcc編譯
(加上-g選項,便於gdb調試
;平台Solaris
Sparc),執行結果也都是dump core
。
但通過GDB對core進行觀察,你會發現細微的不同。第一個例子出的core原因是:Program terminated with signal
11, Segmentation fault. 而第二個例子的core則提示:Program terminated with signal
10, Bus error. 兩者有什麼不同呢?這兩段代碼的共同點都是將一個非法地址賦值給指標變數,然後試圖寫資料到這個地址。
如果要說清楚這個問題,我們就要結合彙編
碼和一些電腦的體繫結構
的知識來共同分析了。
先來看testsigsegv.c的彙編碼:
... ...
main:
!#PROLOGUE# 0
save %sp, -120, %sp
!#PROLOGUE# 1
sethi %hi(4096), %i0
or %i0, 273, %i0
st %i0, [%fp-20]
ld [%fp-20], %i1
mov 17, %i0
stb %i0, [%i1]
nop
ret
restore
... ...
我們關注的是這句:stb %i0, [%i1]
從
電腦底層的執行角度來說,過程是如何的呢?%i0寄存器裡儲存的是立即數17,我們要將之儲存到寄存器%i1的值指向的記憶體位址。這一過程對於CPU來
說其指揮執行的正常過程是:將寄存器%i0中的值送上資料匯流排,將寄存器%i1的值送到地址匯流排,然後使能控制匯流排上的寫訊號完成這一向記憶體寫1
byte資料的過程。
我們再看testsigbus.c的彙編碼:
... ...
main:
!#PROLOGUE# 0
save %sp, -120, %sp
!#PROLOGUE# 1
sethi %hi(4096), %i0
or %i0, 273, %i0
st %i0, [%fp-20]
ld [%fp-20], %i1
mov 17, %i0
st %i0, [%i1]
nop
ret
restore
... ...
同
樣最後一句:st %i0,
[%i1],CPU執行的過程與testsigsegv.c中的一致(只是要儲存資料長度是4位元組),那為什麼產生錯誤的原因不同呢?一個是
SIGSEGV,而另一個是SIGBUS。這裡涉及到的就是對記憶體位址的校正的問題了,包括對記憶體位址是否對齊
的校正以及該記憶體位址是否合法的校正。
我們假設如果首先進行的記憶體位址是否合法的校正(是否歸屬於使用者進程的地址空間),那麼我們回顧一下,這兩個程式中的地址0x00001111顯然都不合法,按照這種流程,兩個程式都應該是SIGSEGV導致的core
才對,但是事實並非如此。那難道是先校正記憶體位址的對齊?我們再看這種思路是否合理?
testsigsegv.c
中,0x00001111這個地址值被賦給了char
*pc;也就是告訴CPU通過這個地址我們要存取一個位元組的值,對於一個位元組長度的資料,無所謂對齊,所以該地址通過對齊校正;並被放到地址匯流排上了。而
在testsigbus.c裡,0x00001111這個地址值被賦給了int
*pi;也就是告訴CPU通過這個地址我們要存取一個起碼4個位元組的值,那麼對於長度4個位元組的對象,其存放地址起碼要被4整除才可以,而
0x00001111這個值顯然不能滿足要求,也就不能通過記憶體對齊的校正。也就是說SIGBUS這個訊號在地址被放到地址匯流排之後被檢查出來的不符合對
齊的錯誤;而SIGSEGV則是在地址已經放到地址匯流排上後,由後續流程中的某個設施檢查出來的記憶體違法訪問錯誤。
一般我們平時遇到SIGBUS時總是因為地址未對齊
導致的,而SIGSEGV則是由於記憶體位址不合法造成的。