理解SIGBUS与SIGSEGV

来源:互联网 发布:超声波捕鸟器淘宝网 编辑:程序博客网 时间:2024/05/17 01:39

理解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
SIGBUS与SIGSEGV信号的一般区别如下:
1) SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该
    指针。通常是未对齐的数据访问所致。
2) SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对
    应该地址。
A: scz   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 
#include
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     :       save   %sp, -128, %sp
0x10814   :       st   %i0, [ %fp + 0x44 ]
0x10818   :       st   %i1, [ %fp + 0x48 ]
0x1081c :       sethi   %hi(0x12345400), %o1
0x10820 :       or   %o1, 0x278, %o0      ! 0x12345678
0x10824 :       st   %o0, [ %fp + -20 ]
0x10828 :       clr   [ %fp + -24 ]
0x1082c :       add   %fp, -20, %o0
0x10830 :       st   %o0, [ %fp + -28 ]
0x10834 :       ld   [ %fp + -28 ], %o0
0x10838 :       clrb   [ %o0 ]
0x1083c :       ld   [ %fp + -28 ], %o0
0x10840 :       add   %o0, 1, %o1
0x10844 :       st   %o1, [ %fp + -24 ]
0x10848 :       ld   [ %fp + -24 ], %o0
0x1084c :       clrh   [ %o0 ]
0x10850 :       clr   %i0
0x10854 :       b   0x1085c 
0x10858 :       nop 
0x1085c :       ret 
0x10860 :       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进行优化,但仍然使用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 
#include
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     :       save   %sp, -120, %sp
0x10814   :       st   %i0, [ %fp + 0x44 ]
0x10818   :       st   %i1, [ %fp + 0x48 ]
0x1081c :       sethi   %hi(0x12345400), %o1
0x10820 :       or   %o1, 0x278, %o0      ! 0x12345678
0x10824 :       st   %o0, [ %fp + -20 ]
0x10828 :       clrh   [ %fp + -22 ]
0x1082c :       lduh   [ %fp + -19 ], %o0
0x10830 :       sth   %o0, [ %fp + -22 ]
0x10834 :       clr   %i0
0x10838 :       b   0x10840 
0x1083c :       nop 
0x10840 :       ret 
0x10844 :       restore 
End of assembler dump.
(gdb) i r pc 
pc              0x1082c   67628
(gdb)
因此在SPARC架构上编程,一定要留神强制类型转换,务必清楚自己正在干什么,有
没有隐患。

参Linux的mmap(2)手册页
--------------------------------------------------------------------------
使用映射可能涉及到如下信号
SIGSEGV
     试图对只读映射区域进行写操作
SIGBUS
     试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以
     前有文件内容对应,现在为另一进程截断过的内存区域。
--------------------------------------------------------------------------
原文地址:http://hi.baidu.com/estellejiang/blog/item/3d806b4e0d372308b2de0522.html

原创粉丝点击