从一个程序coredump说起
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv[]) {
int len1 = 1;
int len2 = 2;
const char * msg = "AAAAAA";
unsigned char * buffer = malloc(20);
int out1;
int out2;
int i = 0;
memset(buffer, 0, 20);
for (i = 0; i < 20; i++) { printf("%02X ", buffer[i]); } printf("\n");
memcpy(buffer, &len1, sizeof(int));
memcpy(buffer+sizeof(int), msg, strlen(msg));
memcpy(buffer+sizeof(int)+strlen(msg), &len2, sizeof(int));
for (i = 0; i < 20; i++) { printf("%02X ", buffer[i]); } printf("\n");
printf("p1=%p,p2=%p\n", buffer, buffer+sizeof(int)+strlen(msg));
out1 = *(int *)buffer;
printf("out1=%d\n", out1);
out2 = *(int *)(buffer+sizeof(int)+strlen(msg));
printf("out1=%d\n", out2);
return 0;
}
这个例子的目的就是创造错误场景,有一段buffer先把一个整数len1存入,然后再存入6个字符msg,接着再存入一个整数len2;后面从buffer里面读出存入的两个整数。
在Solaris SPARC上编译运行:
bash-3.2$ uname -a
SunOS <hostname> 5.10 Generic_148888-02 sun4v sparc sun4v
bash-3.2$ gcc -g t.c
bash-3.2$ ./a.out
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 01 41 41 41 41 41 41 00 00 00 02 00 00 00 00 00 00
p1=20dc8,p2=20dd2
out1=1
Bus Error (core dumped)
bash-3.2$ echo $?
138
bash-3.2$
用GDB查看出错的位置:
bash-3.2$ gdb a.out core
...
#0 0x10aa4 in main (argc=1, argv=0xffbffaac) at t.c:25
25 out2 = *(int *)(buffer+sizeof(int)+strlen(msg));
(gdb)
很明显在第25行的时候出现了Bus Error问题;这个程序的退出代码是138,换算成最大127的返回值就是10,对应的signal就是10,即SIGBUS
而同样的程序在Linux 86平台是可以编译运行的
$ uname -a
Linux <hostname> 4.1.12-94.3.8.el7uek.x86_64 #2 SMP Fri Jun 30 10:40:13 PDT 2017 x86_64 x86_64 x86_64 GNU/Linux
$ gcc -g t.c
$ ./a.out
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 00 00 00 41 41 41 41 41 41 02 00 00 00 00 00 00 00 00 00
p1=0xcb6010,p2=0xcb601a
out1=1
out2=2
正常的打印出了out1和 out2。
究其原因因为SPARC是RISC类型处理器,当需要访问内存地址的时候需要满足对齐条件,具体来说,
- 如果要从一个内存地址读写2字节,例如short,那么这个地址必须是2字节对齐的,也就是说地址能被2整除。
- 如果要从一个内存地址读写4字节,例如int,那么这个地址必须是4字节对齐的,也就是说地址能被4整除,通俗的说地址末尾必须是0x0, 0x4, 0x8, 0xC。
- 如果要从一个内存地址读写8字节,例如long long,那么这个地址必须是8字节对齐的,也就是说地址能被8整除,通俗的说地址末尾必须是0x0, 0x8。
我们在例子中看到p1的值的末尾是0x0,这是一个4字节对齐的地址,所以可以读出来,赋给一个int类型out1,而p2的值的末尾是0xa,它不是一个4字节对齐的地址,所以它不能读出来付给int类型,但其实这是一个2字节对齐的地址,那么可以赋值给一个short。
$ vim t.c
//int out2;
short out2;
...
out2 = *(int *)(buffer+sizeof(int)+strlen(msg));
printf("out=%d\n", out2);
$ gcc t.c && ./a.out
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01 00 00 00 41 41 41 41 41 41 02 00 00 00 00 00 00 00 00 00
p1=0x15431010,p2=0x1543101a
out=1
out=2
此时我们看到在Solaris SPARC环境下整个程序编译运行正常。
那为什么在Linux x86环境下面没有问题,这主要是依赖于CISC处理器的特点,CISC和RISC处理器的特征差异,具体得看他们的文档了,我也解释不了,RISC为什么要这样限制。
参考文档
Runtime Checking Errors: https://docs.oracle.com/cd/E18659_01/html/821-1380/blaiu.html