參考這篇文章:
http://blog.chinaunix.net/uid-24599332-id-2122898.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。這里涉及到的就是對內存地址的校驗的問題了,包括對內存地址是否對齊的校驗以及該內存地址是否合法的校驗。
注:gdb看匯編代碼,
這可以通過disassemble命令或x命令或類似的命令:
http://blog.csdn.net/mergerly/article/details/8538272
[root@localhost test]# gdb ./a.out -q (gdb) list 1 #include<stdio.h> 2 #include<malloc.h> 3 4 int callee(int a, int b, int c, int d, int e) 5 { 6 return 1; 7 } 8 9 int main(){ 10 callee(1,2,3,4,5); (gdb) disassemble main Dump of assembler code for function main: 0x0000000000400463 <main+0>: push %rbp 0x0000000000400464 <main+1>: mov %rsp,%rbp 0x0000000000400467 <main+4>: mov $0x5,%r8d 0x000000000040046d <main+10>: mov $0x4,%ecx 0x0000000000400472 <main+15>: mov $0x3,%edx 0x0000000000400477 <main+20>: mov $0x2,%esi 0x000000000040047c <main+25>: mov $0x1,%edi 0x0000000000400481 <main+30>: callq 0x400448 <callee> 0x0000000000400486 <main+35>: mov $0x2,%eax 0x000000000040048b <main+40>: leaveq 0x000000000040048c <main+41>: retq End of assembler dump. (gdb) x/10i main 0x400463 <main>: push %rbp 0x400464 <main+1>: mov %rsp,%rbp 0x400467 <main+4>: mov $0x5,%r8d 0x40046d <main+10>: mov $0x4,%ecx 0x400472 <main+15>: mov $0x3,%edx 0x400477 <main+20>: mov $0x2,%esi 0x40047c <main+25>: mov $0x1,%edi 0x400481 <main+30>: callq 0x400448 <callee> 0x400486 <main+35>: mov $0x2,%eax 0x40048b <main+40>: leaveq (gdb)
回到SIGSEGV和SIGBUS
我們假設如果首先進行的內存地址是否合法的校驗(是否歸屬於用戶進程的地址空間),那么我們回顧一下,這兩個程序中的地址0x00001111顯然都不合法,按照這種流程,兩個程序都應該是SIGSEGV導致的core才對,但是事實並非如此。那難道是先校驗內存地址的對齊?我們再看這種思路是否合理?
testsigsegv.c中,0x00001111這個地址值被賦給了char *pc;也就是告訴CPU通過這個地址我們要存取一個字節的值,對於一個字節長度的數據,無所謂對齊,所以該地址通過對齊校驗;
並被放到地址總線上了。而在testsigbus.c里,0x00001111這個地址值被賦給了int *pi;也就是告訴CPU通過這個地址我們要存取一個起碼4個字節的值,那么對於長度4個字節的對象,其存放地址起碼要被4整除才可以,而0x00001111這個值顯然不能滿足要求,也就不能通過內存對齊的校驗(也就是說地址也要被4整除,否則不對齊)。也就是說SIGBUS這個信號在地址被放到地址總線之后被檢查出來的不符合對齊的錯誤;而SIGSEGV則是在地址已經放到地址總線上后,由后續流程中的某個設施檢查出來的內存違法訪問錯誤。
一般我們平時遇到SIGBUS時總是因為地址未對齊導致的,而SIGSEGV則是由於內存地址不合法造成的。
2) SIGSEGV(Segment fault)意味着指針所對應的地址是無效地址,沒有物理內存對應該地址。
使用映射可能涉及到如下信號
SIGSEGV 試圖對只讀映射區域進行寫操作
SIGBUS 試圖訪問一塊無文件內容對應的內存區域,比如超過文件尾的內存區域,或者以前有文件內容對應,現在為另一進程截斷過的內存區域。
最后gdb -c core <exec file>