博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SIGSEGV 和 SIGBUS & gdb看汇编
阅读量:5877 次
发布时间:2019-06-19

本文共 3314 字,大约阅读时间需要 11 分钟。

参考这篇文章:

 

SIGBUS和SIGSEGV也许是我们在平时遇到的次数最多的两个内存错误信号。内存问题一直是最令我们头疼的事情,弄清楚两个信号的发生缘由对我们很好的理解程序的运行是大有裨益的。

 

我们来看两段程序:

//testsigsegv.c
int main() {
        char *pc = (char*)0x00001111;
        *pc = 17;
}
//testsigbus.c
int main() {
        int *pi = (int*)0x00001111;
        *pi = 17;
}

 

上面的代码那么的相似,我们也同样用gcc编译(加上-g选项,便于gdb调试;平台Solaris Sparc),执行结果也都是dump core。但通过GDB对core进行观察,你会发现细微的不同。

第一个例子出的core原因是:Program terminated with signal 11, Segmentation fault.

而第二个例子的core则提示:Program terminated with signal 10, Bus error. 两者有什么不同呢?这两段代码的共同点都是将一个非法地址赋值给指针变量,然后试图写数据到这个地址。

 

如果要说清楚这个问题,我们就要结合汇编码和一些计算机的体系结构的知识来共同分析了。

 

先来看testsigsegv.c的汇编码:

... ...
main:
        !#PROLOGUE# 0
        save    %sp, -120, %sp
        !#PROLOGUE# 1
        sethi   %hi(4096), %i0
        or      %i0, 273, %i0
        st      %i0, [%fp-20]
        ld      [%fp-20], %i1
        mov     17, %i0
        stb     %i0, [%i1]
        nop
        ret
        restore
... ...

 

我们关注的是这句:stb     %i0, [%i1]

从计算机底层的执行角度来说,过程是如何的呢?%i0寄存器里存储的是立即数17,我们要将之存储到寄存器%i1的值指向的内存地址。这一过程对于CPU来说其指挥执行的正常过程是:将寄存器%i0中的值送上数据总线,将寄存器%i1的值送到地址总线,然后使能控制总线上的写信号完成这一向内存写1 byte数据的过程。

 

我们再看testsigbus.c的汇编码:

... ...
main:
        !#PROLOGUE# 0
        save    %sp, -120, %sp
        !#PROLOGUE# 1
        sethi   %hi(4096), %i0
        or      %i0, 273, %i0
        st      %i0, [%fp-20]
        ld      [%fp-20], %i1
        mov     17, %i0
        st      %i0, [%i1]
        nop
        ret
        restore
... ...
同样最后一句:st      %i0, [%i1],CPU执行的过程与testsigsegv.c中的一致(只是要存储数据长度是4字节),那为什么产生错误的原因不同呢?一个是SIGSEGV,而另一个是SIGBUS。这里涉及到的就是对内存地址的校验的问题了,包括对内存地址是否对齐的校验以及该内存地址是否合法的校验。

 

注:gdb看汇编代码,

这可以通过disassemble命令或x命令或类似的命令:

[root@localhost test]# gdb ./a.out -q(gdb) list1   #include
2 #include
34 int callee(int a, int b, int c, int d, int e)5 {6 return 1;7 }89 int main(){10 callee(1,2,3,4,5);(gdb) disassemble mainDump of assembler code for function main:0x0000000000400463
: push %rbp0x0000000000400464
: mov %rsp,%rbp0x0000000000400467
: mov $0x5,%r8d0x000000000040046d
: mov $0x4,%ecx0x0000000000400472
: mov $0x3,%edx0x0000000000400477
: mov $0x2,%esi0x000000000040047c
: mov $0x1,%edi0x0000000000400481
: callq 0x400448
0x0000000000400486
: mov $0x2,%eax0x000000000040048b
: leaveq0x000000000040048c
: retqEnd of assembler dump.(gdb) x/10i main0x400463
: push %rbp0x400464
: mov %rsp,%rbp0x400467
: mov $0x5,%r8d0x40046d
: mov $0x4,%ecx0x400472
: mov $0x3,%edx0x400477
: mov $0x2,%esi0x40047c
: mov $0x1,%edi0x400481
: callq 0x400448
0x400486
: mov $0x2,%eax0x40048b
: leaveq(gdb)

 

回到SIGSEGV和SIGBUS

我们假设如果首先进行的内存地址是否合法的校验(是否归属于用户进程的地址空间),那么我们回顾一下,这两个程序中的地址0x00001111显然都不合法,按照这种流程,两个程序都应该是SIGSEGV导致的core才对,但是事实并非如此。那难道是先校验内存地址的对齐?我们再看这种思路是否合理?

 

testsigsegv.c中,0x00001111这个地址值被赋给了char *pc;也就是告诉CPU通过这个地址我们要存取一个字节的值,对于一个字节长度的数据,无所谓对齐,所以该地址通过对齐校验;

并被放到地址总线上了。而在testsigbus.c里,0x00001111这个地址值被赋给了int *pi;也就是告诉CPU通过这个地址我们要存取一个起码4个字节的值,那么对于长度4个字节的对象,其存放地址起码要被4整除才可以,而0x00001111这个值显然不能满足要求,也就不能通过内存对齐的校验(也就是说地址也要被4整除,否则不对齐)。也就是说SIGBUS这个信号在地址被放到地址总线之后被检查出来的不符合对齐的错误;而SIGSEGV则是在地址已经放到地址总线上后,由后续流程中的某个设施检查出来的内存违法访问错误。

 

一般我们平时遇到SIGBUS时总是因为地址未对齐导致的,而SIGSEGV则是由于内存地址不合法造成的

1) SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。
2) SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对应该地址。
Linux的mmap(2)手册页
使用映射可能涉及到如下信号
SIGSEGV    试图对只读映射区域进行写操作
SIGBUS     试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以前有文件内容对应,现在为另一进程截断过的内存区域。
 
调试方法:
gcc -g 编译 
ulimit -c 20000 
之后运行程序,等core dump 
最后gdb -c core <exec file> 
 
 

 

转载于:https://www.cnblogs.com/charlesblc/p/6262783.html

你可能感兴趣的文章
js replace,正则截取字符串内容
查看>>
socket
查看>>
Highcharts使用表格数据绘制图表
查看>>
Thinkphp5笔记三:创建基类
查看>>
hdu5373
查看>>
4.单链表的创建和建立
查看>>
Android 好看的搜索界面,大赞Animation
查看>>
查询反模式 - GroupBy、HAVING的理解
查看>>
上班族的坐姿
查看>>
ubuntu 12.04 下面安装vmware workstation 8.0.4
查看>>
[原创]FineUI秘密花园(二十三) — 树控件概述
查看>>
【Java学习笔记】如何写一个简单的Web Service
查看>>
如何解决This system is not registered with RHN.
查看>>
Cocos2d-x学习笔记(两)Cocos2d-x总体框架
查看>>
拆解探索MagSafe电源接口结构和指示灯变颜色原理
查看>>
Android中EditText,Button等控件的设置
查看>>
lintcode:Remove Nth Node From End of Lis 删除链表中倒数第n个节点
查看>>
POJ 1915-Knight Moves (单向BFS &amp;&amp; 双向BFS 比)
查看>>
linux编译安装LAMP
查看>>
php中的continue用法
查看>>