如果程序出现Segment fault错误,该如何排错?谈SIGBUS和SIGSEGV

news/2024/5/19 2:16:06 标签: signal, 汇编, solaris, 存储, 面试, byte
 

2010年网易有道的一道考基础知识的面试题,如果程序出现Segment fault错误,该如何排错?那么,什么是Segment fault?这里笔者引用一篇文章,说的很清楚。

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。这里涉及到的就是对内存地址的校验的问题了,包括对内存地址是否对齐的校验以及该内存地址是否合法的校验。

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

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

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

原文地址:http://bigwhite.blogbus.com/logs/12296535.html


http://www.niftyadmin.cn/n/1535448.html

相关文章

Qt从零开始制作串口调试助手-(第八章、添加串口配置程序)-Creator_Ly

第八章、添加串口配置程序 说明:在Qt中并没有关于串口的控件,现在大部分都是使用第三方写的qextsrialport类。 官方下载地址:http://sourceforge.net/projects/qextserialport/files/ 个人网盘下载地址: 1、创建文件夹E:\Qt_W…

分词:词性标注北大标准

汉语词性对照表[北大标准/中科院标准] 词性编码词性名称注 解 Ag 形语素 形容词性语素。形容词代码为 a,语素代码g前面置以A。 a 形容词 取英语形容词 adjective的第1个字母。 ad 副形词 直接作状语的形容词。形容词代码 a和副词代码d并在一起。…

Atl com 支持MFC没有DLLMian()函数的替代解决办法

在建立atl com接口DLL时,如果加入了Mfc支持,系统就会自动建立DLLMain函数,而且函数的位置特殊,可参见:http://www.cnblogs.com/helloboyang/p/5237648.html, 如果程序需要在DLLMain()函数中实现处理过程怎…

Qt从零开始制作串口调试助手-(第九章、串口读数据实现)-Creator_Ly

第九章、串口读数据实现 说明: 在上一章节的添加的串口数据读取连接语句中,有提到ReadMyCom()为串口读函数,这里将实现串口读函数的内容。 1、在ComDialog.h中添加读取串口数据函数声明 2、在ComDialog.c中添加读取串口数据函数 3、在实现读…

sqlserver 处理百万级以上的数据处理与优化

一处理百万级以上的数据提高查询速度的方法&#xff1a; 1.应尽量避免在 where 子句中使用!或<>操作符&#xff0c;否则将引擎放弃使用索引而进行全表扫描。 2.对查询进行优化&#xff0c;应尽量避免全表扫描&#xff0c;首先应考虑在 where 及 order by 涉及的列上建立索…

隐马尔可夫模型及其在分词中的简单应用

隐马尔可夫模型是一个五元组<S,O,A,B,π>&#xff1a; S&#xff1a;状态集合&#xff1a;即所有可能的状态s1,s2,…,sn所组成的集合。 O&#xff1a;观察序列&#xff1a;即实际存在的一个状态的有向序列&#xff0c;如状态o1,o2,…,on&#xff0c;注意状态是存在顺序…

在ATL项目中添加WTL界面

之前在Atl项目中添加过mfc的dialog对话框&#xff0c;只要在建立atl项目时勾选mfc支持就可以。 现在想在AtL的动态链接库中调用Wtl框架&#xff0c;拿到之后&#xff0c;就犯迷糊了。网上查了一些资料&#xff0c;参考了&#xff1a;http://blog.csdn.net/dragoo1/article/det…

Qt从零开始制作串口调试助手-(第十章、串口接收数据处理)-Creator_Ly

第十章、串口接收数据处理 说明&#xff1a;上一章已经把串口接收调试成功&#xff0c;但我们还不能对接收到的数据进行一些处理&#xff0c;这一章节将通过下面四个方面来进行讲解。 1、十六进制显示 ⑴在上一章节我们观察到&#xff0c;在接收框显示的数据就是为16进制的&…