Chinaunix

标题: 月经结贴 -- 《Segmentation Fault in Linux》 [打印本页]

作者: zx_wing    时间: 2009-12-21 17:23
标题: 月经结贴 -- 《Segmentation Fault in Linux》
写在前面的话
    最近CU(chinaunix)出现了很多问segmentation fault的帖子,其实这也是个“月经贴”了,泡CU几年,每个月都有人问。为了减少重复回帖,笔者结合自己的经验,总结了SIGSEGV在Linux中产生的机理,并用实际例子概括哪些编程错误容易引发SIGSEGV。由于本人经验有限,文中难免有疏漏和错误,请发现的朋友发信到xing5820@163.com指正,笔者好即使修改。

内容提要
  本文简单介绍了Segmentation fault发生的原因,结合实际例子描述了内核向用户态程序发送SIGSEGV信号的流程。文中以实例回答了常见的一些SIGSEGV问题,例如“为什么函数返回了栈还可以访问?”、“为什么free()后的内存仍然可以使用”、“为什么我遇到的是SIGSEGV而不是SIGILL信号”等。最后笔者结合自己的经验,列举了一些预防SIGSEGV的编程习惯,供大家参考。SIGSEGV严格依赖操作系统、编译器、硬件平台,本文基于Linux、GCC、32bit IA32架构,但对其他平台操作系统也有借鉴意义。


大家在阅读的过程中发现什么错误,或我没有讲到的segfault情况,请及时指出,我好更正。

Segmentation fault in Linux.pdf

309.26 KB, 下载次数: 4418


作者: unistd    时间: 2009-12-21 17:28
下来看看。。。
作者: OwnWaterloo    时间: 2009-12-21 17:28
标题: 回复 #1 zx_wing 的帖子
沙发居然被 …… 抢了……
留名
作者: OwnWaterloo    时间: 2009-12-21 17:39
标题: 回复 #1 zx_wing 的帖子
看了一下……  看来那帖子我是白费口水了……
表面上说接受, 实际上依然是在鼓励大家编写不可移植代码 —— 而且, 并不是为了得到什么实际的好处。
作者: cookis    时间: 2009-12-21 17:58
当你的一个需求,标准的方法不能满足时,只有两种可能:
1.从一开始的设计就错了,才会导致错误的需求;
2.你读过的代码太少,不知道业界解决该问题的标准方法是什么。
作者: zx_wing    时间: 2009-12-21 18:04
原帖由 OwnWaterloo 于 2009-12-21 17:39 发表
看了一下……  看来那帖子我是白费口水了……
表面上说接受, 实际上依然是在鼓励大家编写不可移植代码 —— 而且, 并不是为了得到什么实际的好处。

哈哈哈,我是接受你看问题的角度,其它的我持保留意见。
至于这篇文章嘛,已经不在标准范畴了,我一开始也说,这是非常平台相关的
作者: albcamus    时间: 2009-12-21 18:13
楼主应该写书啊, 做独立技术写作也不错…
作者: OwnWaterloo    时间: 2009-12-21 18:18
标题: 回复 #6 zx_wing 的帖子
char a[] = "hello";
char* p;

for ( p = a+sizeof(a)-2; p>=a; p-- )
      printf("%c\n", *p );

与:

for ( p = a+sizeof(a)-2; p!=a; p-- )
      printf("%c\n", *p );


效果是一样的: 如果真出现那帖里的情况, 不会越界,不会引发signal, 而是死循环。



上面2个版本与下面一个版本相比:

for ( p = a+sizeof(a)-1; p!=a /* or p>=a */; ) {
      --p;
      printf("%c\n", *p );
}

得到的好处就是 : 少写一行代码。  就为了这么点好处值得将代码变得不可移植么
如果推荐下面一种写法, 什么烦恼都没有了, 比如不需要补充这么一句:
笔者无法100%保证,所以大家在实践中还是各自斟酌吧。



当然,会出现回绕的机器只是假象出来的, 也许下面一种写法的好处永远得不到。
我只是反感这么一种风气: 将自己的实践、 不经过严格的论证, 就上升到理论高度, 认为那是普遍真理
我真的不是在说你,  你的文章里至少还有那么一句补充说明, 以说明这种经验的适用范围
我其实说的是林锐…… 以及和林锐有相同倾向的一帮人……


算了, 别人写的代码是否可移植关我鸟事 ……   大家爱怎么写就怎么写好了……

[ 本帖最后由 OwnWaterloo 于 2009-12-21 18:20 编辑 ]
作者: zx_wing    时间: 2009-12-21 18:35
原帖由 albcamus 于 2009-12-21 18:13 发表
楼主应该写书啊, 做独立技术写作也不错…

哈哈哈,哪儿有那功夫,平时忙的要死,泡个论坛都是忙里偷闲了。
上次不是chen xu过来提到说我泡CU,我差不多都要忘了有好久没上过了。
写书真是吃力不讨好的事情,就那本在内核版打广告的书,我虽然只写了一章加一节,但被老板当较字工人让去review全书,那真是一个字一个字得看,一个字一个字的改啊,惨的不行。发现错误还要去请教那些大牛原意是什么的,然后帮他们重写。还是写点这种小文好了,全凭兴趣,想怎么写就怎么写
中国人搞技术啊,真是为了讨生活,除非像老外衣食无忧纯粹的"working for fun"差不多。
作者: lenky0401    时间: 2009-12-21 18:45
先顶贴再看文章是lenky的一贯作风。
作者: zx_wing    时间: 2009-12-21 18:53
原帖由 OwnWaterloo 于 2009-12-21 18:18 发表
char a[] = "hello";
char* p;

for ( p = a+sizeof(a)-2; p>=a; p-- )
      printf("%c\n", *p );

与:

for ( p = a+sizeof(a)-2; p!=a; p-- )
      printf("%c\n", *p );


效果是一样的: 如 ...

嘿嘿,我理解你的意思,觉得标准都规定了好了,为什么就不好好按标准写呢。
这里面一是有历史原因,一是不方便。
比如你这里这个例子
for ( p = a+sizeof(a)-1; p!=a /* or p>=a */; ) {
      --p;
      printf("%c\n", *p );
}
先--p就把空字符去掉了,很可能别人原意就要打印这个空字符。p!=a,又少打一个h,所以我在写这个例子时都是跳出循环后再打一次。
标准规定的是完美情况,但现实中要想写出完美的代码时很难的,所以我说有一些既成事实的标准。比如说按标准写:
int a = 70000;
严格来说这样写是不对的,因为不知道这个int的长度,如果是2字节,它就溢出了。怎么办呢,写成
int32_t a = 70000;
是保险的。这意味着我们就不随便使用int、short、long之类的变量的,一定要#include <stdint.h>。但那么多的教材、那么多的书籍都在这样用,
从一学C语言就开始这样用,突然就告诉说这样不对,不严格,在某些情况下会出错。但实际上99%的时间我们写不是跨平台程序,要把所有细节都严格按照标准,
基本上就寸步难行了。所以对于这种情况,人们大都follow的那些即成事实的标准。也正是因为有这些既成事实的标准了,所以几乎也不会出现你说那种特殊的编译器,出现了也不会被市场接受。

历史原因也有很多,比如大家都知道宏不好,定义常量会污染名字空间,应该用enum。写函数没有类型检查,应该用inline函数。但它就用了这么久了,那么多知名项目都在用,都这样用,所以也有很多人follow。

OwnWaterloo你可能是个追求完美的人,但现实中大部分时候都不是完美的。我倒不是鼓励大家不遵守标准,我只是从我的观点陈述一种事实,即它可能不符合标准,但它99%的时间不会出错,至于如何抉择,就看个人了。

>>算了, 别人写的代码是否可移植关我鸟事 ……   大家爱怎么写就怎么写好了……
至于这个嘛,你也别急,现实中和别人意见相左的情况很多,其实我们只要了解了别人的观点,理解他的出发点,然后在心中坚持自己的理念即可。至于别人怎么做怎么想,那真是管我们鸟事
作者: OwnWaterloo    时间: 2009-12-21 19:15
标题: 回复 #11 zx_wing 的帖子
我是故意把空字符去掉的……   上面2份代码是-2 ……
要输出空字符的话:
for ( p = a+sizeof(a)/*-1 把这个-1去掉*/; p!=a /* or p>=a */; ) {
      --p;
      printf("%c\n", *p );
}

那么, 在逆向遍历这个问题上, 遵守标准的写法, 还会造成什么不方便么?
如果还有, 请指出, 再讨论有没有可以遵守标准也能写出的方法。

其实, 多出的那一行也是可以去掉的……  只是……   我写了你就明白只是什么了……
for (T* p = a + n; p!=a; )
      --p, process( *p );


事实标准确实重要。 现实生活中其实依赖的是事实标准。 我的观点是: 不应该没有任何理由地、 无故地违反标准
所以遇见这种问题, 我一般都先问case。 而不是说"一定不能那样做"。
如果标准能解决这个case, 那还有什么理由去依赖未定义行为呢?
如果标准不能, 或者不够高效(也算是不能), 这种情况我自己都会去违反标准……



关于那个帖里22楼的那种yin 荡的、会主动crash程序的C实现, 我又想了想, 应该只会出现在想象中, 而不会实际出现。
因为这不符合C的逻辑 —— C语言是不会做多余的事情以提供保姆功能的。
但underflow确实是有可能的。 这依然是因为C语言的逻辑 —— 不会做多余的事情以提供保姆功能 —— 检查offset的溢出并相应的修改segment就是一种多余的保姆功能。

所以, 逆向遍历的case, 这么写也应该没问题 —— 在正常的C编译器上:
for (T* p = a + n; p-- != a; )
      process( *p );

有可能溢出, 但正常的C编译器不会提供检测溢出并且crash程序的功能。
熟悉msvc的同学说话前先仔细看清楚了: 这里的溢出仅仅是算术运算的溢出, 并没有到溢出的单元。/RTCs检查的不是这种情况。
如果真有检查算术溢出的C编译器 ……  我相信没人会去用的。
作者: zx_wing    时间: 2009-12-21 19:25
原帖由 OwnWaterloo 于 2009-12-21 19:15 发表
我是故意把空字符去掉的……   上面2份代码是-2 ……
要输出空字符的话:
for ( p = a+sizeof(a)/*-1 把这个-1去掉*/; p!=a /* or p>=a */; ) {
      --p;
      printf("%c\n", *p );
}

那么, 在逆 ...

是我看错了,你把 --p提出来了。

这样是很好,但一般人不会相到这样写。就像你后来说了,人们不会接受指针算术溢出会导致错误。

[ 本帖最后由 zx_wing 于 2009-12-21 19:29 编辑 ]
作者: OwnWaterloo    时间: 2009-12-21 19:29
标题: 回复 #13 zx_wing 的帖子
当真?
作者: zx_wing    时间: 2009-12-21 19:30
原帖由 OwnWaterloo 于 2009-12-21 19:29 发表
当真?

你回的真快,我刚改你就回了。见上
作者: OwnWaterloo    时间: 2009-12-21 19:35
标题: 回复 #13 zx_wing 的帖子
原帖由 zx_wing 于 2009-12-21 19:25 发表
就像你后来说了,人们不会接受指针算术溢出会导致错误。

人们不会接受的是编译器主动检查溢出而导致错误。

因为编译器没有检测溢出而导致的语意错误, 是程序员的责任。
作者: OwnWaterloo    时间: 2009-12-21 19:42
标题: 回复 #13 zx_wing 的帖子
原帖由 zx_wing 于 2009-12-21 19:25 发表
但一般人不会相到这样写


用idx逆向遍历 —— 自从某次错误之后 —— 我都是这么写的
for (size_t idx = size; idx-- ; )
      process( a[idex] );


用prt逆向遍历……  也是在那个帖里才头一次考虑这个问题, 以后我会选择:
for (T* ptr = a + size; ptr != a; ) {
      --ptr;
      process( *ptr );
}

for (T* ptr = a + size; ptr-- != a; )
      process( *ptr );

for (T* ptr = a + size; ptr != a; )
      --ptr, process( *ptr );


之一
作者: happyzlz    时间: 2009-12-21 22:54
原帖由 zx_wing 于 2009-12-21 18:53 发表

嘿嘿,我理解你的意思,觉得标准都规定了好了,为什么就不好好按标准写呢。
这里面一是有历史原因,一是不方便。
比如你这里这个例子
for ( p = a+sizeof(a)-1; p!=a /* or p>=a */; ) {
      --p;
    ...


我做嵌入式开发比较多,为了能够在16位机和32位机之间更好的移植,代码中的 int 都会写成 int32_t 这样子的。
作者: happyzlz    时间: 2009-12-21 22:57
标题: 回复 #1 zx_wing 的帖子
最近我也出了一系列段错误,其中一个比较深刻,大意如下:???标示不用关注。
...
struct ??? * x = malloc( ??? );
char * p = x->data;
x = realloc( x , ??? );
??? = *p;

[ 本帖最后由 happyzlz 于 2009-12-21 23:08 编辑 ]
作者: axlrose    时间: 2009-12-22 10:58
好文章,收藏,传阅
作者: file3    时间: 2009-12-22 11:07
超级喜欢短错误
作者: gofortime    时间: 2009-12-22 11:46
来看zx_wing和OwnWaterloo辩论,
作者: OwnWaterloo    时间: 2009-12-22 14:39
标题: 回复 #19 happyzlz 的帖子
这个错误可以归类

hanlde何时失效

0. happyzlz列举的p

1. vector,
vector<T> v = ...
T* p = &v[ 0 ];
vector<T>::iterator it = v.begin();
v.push_back( xxx );  

2. string
string s = ...
const char* p = s.c_str();
s = xxx;   

3. ctime  asctime
char* s = ctime( time(0) );
ctime( time(0) );
作者: zhyclt    时间: 2009-12-22 16:01
原帖由 zx_wing 于 2009-12-21 18:35 发表

哈哈哈,哪儿有那功夫,平时忙的要死,泡个论坛都是忙里偷闲了。
上次不是chen xu过来提到说我泡CU,我差不多都要忘了有好久没上过了。
写书真是吃力不讨好的事情,就那本在内核版打广告的书,我虽然只写了 ...

最后一句说的好啊,中国人做技术,纯属讨生活
作者: koolcoy    时间: 2009-12-22 16:12
其实sf的原因简单的归结一下就是:
读了,写了或者执行了 不该读, 不该写或者不该执行的内存。
作者: to407    时间: 2009-12-22 18:17
下了不看,继续加班
作者: mik    时间: 2009-12-22 20:24
zx_wing 花了不少心思啊

zx_wing 把你写过的专集再添加、整理、修饰、校对一下,可以出书了。

至少比市面上某些作者好多了。
作者: zx_wing    时间: 2009-12-22 21:38
原帖由 mik 于 2009-12-22 20:24 发表
zx_wing 花了不少心思啊

zx_wing 把你写过的专集再添加、整理、修饰、校对一下,可以出书了。

至少比市面上某些作者好多了。

老大过奖了
写过的东西还不多。反正凭兴趣,写点小东西,集多了也是件有成就感的事情
作者: leiing    时间: 2009-12-23 08:53
好文章,学习中
作者: 登楼望月    时间: 2009-12-23 10:11
学习,谢谢lz
作者: coneagoe    时间: 2009-12-23 12:29
看节选更像是写的论文.mark
作者: deargodzw    时间: 2009-12-23 13:41
经典分析总结,看完了,受益匪浅
作者: rainballdh    时间: 2009-12-23 17:19
拜读以下,谢谢
作者: it-rocket    时间: 2009-12-23 22:12
好东西,楼主辛苦了!
作者: 灰菲湮灭    时间: 2009-12-24 09:47
楼主辛苦,下下来看看~~
作者: skybyte    时间: 2009-12-24 10:12
标题:
c = (char*)&c – 8192 *2;
这句话是什么意思
优先级是不是这样
( (char*)&c) – 8192 *2;
感觉这个很怪样,把指针地址当成c的地址值再减去8192*2

c=c-8192*2; 直接这样也会出错啊

[ 本帖最后由 skybyte 于 2009-12-24 10:34 编辑 ]
作者: skybyte    时间: 2009-12-24 10:31
标题: warning
a = malloc (sizeof(int));
会出现
tree.c(5) : warning C4133: '=' : incompatible types - from 'char *' to 'int *'
没加类型
a = (int*) malloc (sizeof(int));
作者: waily    时间: 2009-12-24 11:41
好文 谢谢!
作者: silentfish    时间: 2009-12-24 13:08
印证了以前我不确认的观点,学习了一些新的东西,纠正了一些以前的认识错误
非常感谢!

[ 本帖最后由 silentfish 于 2009-12-24 13:26 编辑 ]
作者: qiang1012    时间: 2009-12-24 17:26
收藏了,谢谢楼主~~~
作者: towel    时间: 2009-12-24 23:19
大概看了一边,如果说是对于初学者来说,有点不适合.
作者: nice90    时间: 2009-12-25 11:15
不错,我转发给了同事们。
segmentation fault经常出现在C++中,大家实在是对临时对象和生成对象管理不严格。
作者: kenshinxf    时间: 2009-12-26 15:57
前一段日子还和同事一起总结了呢
呵呵
作者: angle4    时间: 2009-12-26 16:27
还没看内容,但楼主的精神首先很赞
作者: Tim_tsang    时间: 2009-12-26 16:43
标题: 回复 #1 zx_wing 的帖子
佩服,热血沸腾。。。
作者: tianxiaogang12    时间: 2009-12-27 16:16
在下水平有限,请问
在build_ill_func函数里面的if ( buf == 0xffffffe8 )语句中的
          0xffffffe8是指什么, 是文章中说的call指令吗? 具体讲讲贝
作者: gpp9837    时间: 2009-12-28 18:01
支持楼主工作!!!!
作者: ppyyf    时间: 2010-01-15 10:41
这些东西直接在帖子里写写不就好了吗?用得着要放到PDF作为附件下载吗?
作者: thyman    时间: 2010-01-18 16:33
不错不错!!
作者: kofshower    时间: 2010-05-18 10:51
下来看看,这只是内存错误的一种,呵呵,希望有系列文档
作者: gigabyte    时间: 2010-05-18 14:25
支持……
作者: BMW_325i    时间: 2010-05-23 15:11
看样子应该是好东西,先收藏了
作者: rain_fish    时间: 2010-05-23 19:57
lz好样的。。。
作者: zhuqing_739    时间: 2010-09-19 12:44
这是一个曾今遇到过的问题!!!!!!
作者: aaron_xueli    时间: 2010-09-24 16:12
新手学习中……,分享,谢谢楼主!
作者: whuhujin    时间: 2010-12-26 13:10
回复 1# zx_wing


    呵呵,多谢分享哦
作者: nju_chenbin    时间: 2011-05-23 10:55
多谢多谢了饿啊
作者: zhuyoong    时间: 2011-07-01 14:51
先顶再看。
作者: emwoody    时间: 2012-10-26 15:26
下来看看,先顶了!
作者: ly20119    时间: 2012-11-05 19:03
好东西
作者: zeorro    时间: 2012-11-18 19:10
mark,最近对 segmentation fault 怕的。。
作者: flfromhlp    时间: 2014-08-22 12:26
下来看看有哪些原因会产生这个错误




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2