Liunux 编程遇到的SIGBUS信号

来源:互联网 发布:统赢慢走丝编程软件 编辑:程序博客网 时间:2024/06/05 17:03


  linux下编程我们最常遇到的一个信号应该是段错误信号SIGSEGV,一般表示你访问了一个不合法地址。但有时会遇到SIGBUS信号,这个信号在我的印象中是硬件故障的意思,平时没太关注,但最近一个进程老打印收到这个信号,想来这信号里面应该还有其他蹊跷。

什么时候会产生这个信号?

  UNIX高级编程上讲:指示一个实现定义的硬件故障。当出现某种类型的内存故障时,实现常常产生此种信号。
  就实际编程中,遇到这个信号往往是如下的情况:
  1. 未对齐的地址访问
  2. mmap时,访问映射区已经不存在的部分(如用文件长度映射了一个文件,但在引用该映射区之前,另一个进程已将该文件截断)

编程测试

未对齐的内存访问
x86默认是可以访问未对齐的地址的,所以测试需要加额外的汇编指令。

#include <stdlib.h>int main(int argc, char **argv) {    int *iptr;    char *cptr;#if defined(__GNUC__)# if defined(__i386__)    /* Enable Alignment Checking on x86 */    __asm__("pushf\norl $0x40000,(%esp)\npopf");# elif defined(__x86_64__)      /* Enable Alignment Checking on x86_64 */    __asm__("pushf\norl $0x40000,(%rsp)\npopf");# endif#endif    /* malloc() always provides aligned memory */    cptr = malloc(sizeof(int) + 1);    /* Increment the pointer by one, making it misaligned */    iptr = (int *) ++cptr;    /* Dereference it as an int pointer, causing an unaligned access */    *iptr = 42;    return 0;}

执行结果,在*iptr = 42这行出错,收到SIGBUS信号

root@ubuntu:AC_PRODUCT_SVN5449# gcc bus_test.c -groot@ubuntu:AC_PRODUCT_SVN5449# gdb a.out GNU gdb (GDB) 7.5.91.20130417-cvs-ubuntuCopyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "i686-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/a.out...done.(gdb) rStarting program: /home/work_sdc1/tenda3/AC6_ITB01/AC_PRODUCT_SVN5449/a.out Program received signal SIGBUS, Bus error.0x0804844f in main (argc=1, argv=0xbffff5e4) at bus_test.c:2525      *iptr = 42;(gdb) 

mmap时访问截短的文件

测试C程序,映射一个文件,然后在getchar处等待,这时用外部程序修改文件内容,使文件长度变短,然后这边终端输入字符唤醒程序,观察是否收到SIGBUS信号。

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <signal.h>#include <sys/mman.h>#include <unistd.h>void handle_sigbus(int sig){    printf("SIGBUS!\n");    _exit(0);}int main(){    int i;    char *p, tmp;    struct stat st;    int fd = -1;    fd = open("aaa.txt", O_RDWR);    stat("aaa.txt", &st);    p = (char*)mmap(NULL, st.st_size, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);    signal(SIGBUS, handle_sigbus);    getchar();    for (i = 0; i < st.st_size; i++) {        tmp = p[i];        printf("%x\n", tmp);    }    printf("ok\n");}

执行:

leon@leon-ubuntu:~$ gcc mmap_test.c leon@leon-ubuntu:~$ echo 12345 > aaa.txt leon@leon-ubuntu:~$ leon@leon-ubuntu:~$ ./a.out (再另一个终端输入执行leon@leon-ubuntu:~$ echo 1 > aaa.txt)31a0000ok

并没有收到信号(测试的系统为ubuntu 16.04),只是访问的文件内容变为了0。这里的结果比较怪异,和其他人的测试结果不符,后续还要在其他操作系统上测试。
在arm板子上(内核2.6.36.4)测试也没有问题,直接偏移越界出文件几个字节,没有报错,打印为0,偏移越界1M长度,报段错误。

思考

  未对齐的内存访问和系统架构相关性很大,而作为嵌入式开发者,需要考虑代码的可移植性,这一点需要额外关注。而且感觉我们的程序中,也时而会有这样的未对齐地址访问的代码。例如在处理网络数据时:如下一段代码

#include <stdlib.h>#pragma pack(1)struct packet{    unsigned char enable;    int data;};#pragma pack()int main(int argc, char **argv) {#if defined(__GNUC__)# if defined(__i386__)    /* Enable Alignment Checking on x86 */    __asm__("pushf\norl $0x40000,(%esp)\npopf");# elif defined(__x86_64__)     /* Enable Alignment Checking on x86_64 */    __asm__("pushf\norl $0x40000,(%rsp)\npopf");# endif#endif    struct packet pkt;    pkt.data = 12;    return 0;}

  因为是网络数据包,所以1字节对齐,在给结构体初始化赋值时因为未对齐的地址访问出现SIGBUS信号。那么要怎么弄呢,是否说在处理这种情况时都使用memcpy来赋值?

是否应该忽略SIGBUS信号?
首先需要关注,忽略掉这个信号会发生什么

#include <stdlib.h>#include <signal.h>#pragma pack(1)struct packet{    unsigned char enable;    int data;};#pragma pack()void bad_sig_handle(int sig){    printf("rcv signal: %d\n", sig);}int main(int argc, char **argv) {#if defined(__GNUC__)# if defined(__i386__)    /* Enable Alignment Checking on x86 */    __asm__("pushf\norl $0x40000,(%esp)\npopf");# elif defined(__x86_64__)     /* Enable Alignment Checking on x86_64 */    __asm__("pushf\norl $0x40000,(%rsp)\npopf");# endif#endif    struct packet pkt;    signal(SIGBUS, bad_sig_handle);    pkt.data = 12;    printf("pkt.data = %d\n", pkt.data);    return 0;}

在测试时发现无法捕捉这个信号,没有看到打印,还是收到SIGBUS报了总线错误。怪哉!

参考:
https://en.wikipedia.org/wiki/Bus_error
http://blog.csdn.net/u010944778/article/details/47375299

0 0
原创粉丝点击