6) SIGABRT

  1. man手册上说是由abort函数产生的,先介绍一下abort函数:

函数原型 void abort(void);
头文件 stdlib.h
功能 给自己发送SIGABRT信号
参数
返回值

  2. 事实上不止是abort函数能产生SIGABRT信号,assert也可以,assert实际上并不是一个函数,它是一个宏,但用的时候当成一个函数来用:

原型 void assert(scalar expression);
头文件 assert.h
功能 判断传入的参数是否为真,如果为真则程序继续运行,否则发送SIGABRT信号
参数 [in] expression:可以传入表达式,如果表达式为真,则程序继续运行,否则发送SIGABRT信号,默认情况将导致程序退出
返回

  实际上assert还是调用了abort,所以assert导致的SIGABRT信号其实也是abort发出的。

  3. 另外,如果使用malloc申请内存空间,多次使用free释放也会产生SIGABRT信号。测试例程如下:

 1 /**
 2  * filename: signal_6.c
 3  * author: Suzkfly
 4  * date: 2021-02-15
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     分别将MODE的值置为1,2,3,观察执行结果
 8  */
 9 #include <stdio.h>
10 #include <signal.h>
11 #include <stdlib.h>
12 #include <assert.h>
13 #include <string.h>
14 
15 #define MODE 3
16 
17 /* 自定义信号处理函数 */
18 void func(int sig)
19 {
20     switch (sig) {
21         case SIGABRT :
22             printf("SIGTRAP signal captured. value = %d\n", sig);
23             exit(0);
24         break;
25     }
26 }
27 
28 int main(int argc, const char *argv[])
29 {
30     void (*p)(int); /* 定义一个函数指针 */
31     char *p_malloc = NULL;
32     
33     p = signal(SIGABRT, func);
34     printf("p = %p\n", p);
35 
36     printf("MODE = %d\n", MODE);
37 #if (MODE == 1)
38     abort();
39 #elif (MODE == 2)
40     assert(0);
41 #elif (MODE == 3)
42     p_malloc = malloc(100);
43     free(p_malloc);
44     free(p_malloc);
45 #endif
46     while (1) {
47         sleep(1);
48     }
49     
50     return 0;
51 }

测试结果:

 7) SIGBUS

  一般来说导致SIGBUS是由于硬件错误导致的,但想办法让硬件出错还是有点难,不过想引发SIGBUS还有别的方法,比如将一个int型的指针赋值为不是4的整数倍,然后来操作该地址空间,具体代码如下:

 1 /**
 2  * filename: signal_7.c
 3  * author: Suzkfly
 4  * date: 2021-02-16
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     执行程序,经测试,该程序在i386架构和x86架构的平台上都不会产生SIGBUS信号。
 8  */
 9 #include <stdio.h>
10 #include <signal.h>
11 #include <stdlib.h>
12 
13 /* 自定义信号处理函数 */
14 void func(int sig)
15 {
16     switch (sig) {
17         case SIGBUS :
18             printf("SIGBUS signal captured. value = %d\n", sig);
19             exit(0);
20         break;
21     }
22 }
23 
24 int main(int argc, const char *argv[])
25 {
26     void (*p)(int); /* 定义一个函数指针 */
27     int  i[2] = { 0 };
28     int  *p_i = NULL;
29     
30     p = signal(SIGBUS, func);
31     printf("p = %p\n", p);
32 
33     printf("&i[0] = %p\n", i);
34     p_i = (int *)((char *)i + 1);
35 
36     printf("p_i = %p\n", p_i);
37     *p_i = 0x12345678;              /* 赋值(不按4字节对齐) */
38     printf("*p_i = %#x\n", *p_i);
39     printf("i[0] = %#x\n", i[0]);
40     printf("i[1] = %#x\n", i[1]);
41     
42     return 0;
43 }

测试结果:

 

  从测试结果看出来,p_i是一个int型的地址,其并非是4的整数倍,但是赋值行为并没有产生SIGBUS信号,我在网上看到很多人进行这样的操作是会产生SIGBUS信号的,原因应该是我们用的平台不一样,我在i386架构和x86架构的Ubuntu的平台上测试都没有产生SIGBUS信号。

  但是通过另外一种方法产生了SIGBUS信号,代码如下:

 1 /**
 2  * filename: signal_7.c
 3  * author: Suzkfly
 4  * date: 2021-02-16
 5  * platform: Ubuntu
 6  *     操作步骤:
 7  *     运行可执行程序。
 8  *         如果执行open时,1.txt不存在,则会引发SIGBUS信号;
 9  *         如果执行open时,1.txt存在,并且长度为0,也会引发SIGBUS信号;
10  *         如果执行open时,1.txt存在,但是长度不为0,则不会引发SIGBUS信号。
11  */
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <sys/mman.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <signal.h>
20 
21 /* 自定义信号处理函数 */
22 void func(int sig)
23 {
24     switch (sig) {
25         case SIGBUS :
26             printf("SIGBUS signal captured. value = %d\n", sig);
27             exit(0);
28         break;
29     }
30 }
31 
32 int main(int argc,char *argv[])
33 {
34     int fd = 0;
35     char *p = NULL;
36     int i = 0;
37 
38     signal(SIGBUS, func);
39 
40     //如果加入O_TRUNC,则一定会引发SIGBUS信号 */
41     //fd = open("1.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
42     fd = open("1.txt", O_RDWR | O_CREAT, 0666);
43     if (fd < 0) {
44         printf("open failed\n");
45         return 0;
46     }
47     
48     p = mmap(NULL,     /* 映射区的开始地址,为NULL表示由系统决定映射区的起始地址 */
49              5,        /* 映射区大小 */
50              PROT_READ | PROT_WRITE, /* 内存保护标志(可读可写) */
51              //MAP_PRIVATE,            /* 映射对象类型(不与其他进程共享) */
52              MAP_SHARED,            /* 映射对象类型(与其他进程共享) */
53              fd,                     /* 有效的文件描述符 */
54              0);                     /* 被映射内容的偏移量 */
55     if (p == MAP_FAILED) {
56         printf("mmap failed\n");
57         return 0;
58     }
59     printf("p = %p\n",p);
60 
61     *p = 'H';
62     //strcpy(p, "0123456789");
63 
64     munmap(p, 5);
65     close(fd);
66 
67     return 0;
68 }

运行结果:

 

 代码分析:

  该程序有2个关键点,第1是打开的1.txt必须是不存在或者是空的,第2是要通过mmap映射地址。我的理解是这样的,通过mmap从1.txt的文件描述符里映射出来一个地址,这个地址就相当于是1.txt这个文件,但是这个文件的大小又是0,因此实际上映射出来的这个地址可用空间大小就是0,就好像有人告诉你3号房间到5号房间都归你管,但这几个房间压根就是没有的,对它写数据就造成了SIGBUS信号。

题外话:

  在测试过程中发现了两个我认为奇怪的现象:

  1)1.txt这个文件能修改的部分取决于文件原本有多大,而与mmap传入的第2个参数无关。比如1.txt原来的长度就是8个字节,通过mmap分配的长度为5个字节,而使用strcpy写入10个字节,最后的结果是前8个字节可以写成功,最后文件大小仍为8个字节,而且这个操作并不会引发信号。

  2)如果mmap传入的第4个参数为MAP_PRIVATE,那么对p进行赋值并不会改变文件内容,也不会引发信号。

  由于本人对mmap的机制并不清楚,因此暂时无法解释这个现象,希望有大神可以解答。