红联Linux门户
Linux帮助

Linux编程遇到的SIGBUS信号

发布时间:2017-04-26 10:30:45来源:linux网站作者:普朗克常量
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 -g
root@ubuntu:AC_PRODUCT_SVN5449# gdb a.out 
GNU gdb (GDB) 7.5.91.20130417-cvs-ubuntu
Copyright (C) 2017 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) r
Starting 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:25
25      *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)
31
a
0
0
0
0
ok
并没有收到信号(测试的系统为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报了总线错误。
 
本文永久更新地址:http://www.linuxdiyf.com/linux/30303.html