究竟是什么导致了进程崩溃?

本文探讨了导致进程崩溃的各种原因,包括但不限于堆栈溢出、内存溢出、未处理的COM异常、本地堆冲突等问题,并提供了相应的调试技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文出自:http://blogs.msdn.com/tess/archive/2005/11/28/497386.aspx

版权归原文作者所有,转载请注明出处

究竟是什么导致了进程崩溃?


不知道为什么,事件查看器中有数以千计的w3wp.exe意外停止的日志记录,或者进程以某种怪异而不明确的方式退出.

当进程崩溃或者一个叫做EPR(Exit PRocess)的特殊事件被触发,可以使用调试器如windbg附加到进程,等待EPR被抛出然后进行内存转储.安装the debugging tools for windows后,会得到一个叫做adplus的vbs脚本,它会自动帮你做上述工作并且打印出转储操作中发生的大部分异常.

调试技巧:打开crash模式下得到的内存转储,调试器将自动定位到崩溃发生时的活动线程(也是最有嫌疑的线程).要在故障线程和其他线程间切换,输入 "~" 来列出所有线程,故障线程用"."来标记.

如果转储仅显示一个活动线程且这个线程是进程的主线程,这个进程很可能被从外部中止(系统监视器,系统可用内存低,使用iisreset等)

稍后的日志中我将会对一些场景作深入讨论,但我是一个思维严谨的人,希望从一些常见的情况开始讨论,这样当发生托管进程退出的时候,你就知道该查看转储的哪些部分.


下面是常见的导致进程崩溃的原因:


堆栈溢出异常

分配给线程堆栈的内存耗尽时就会发生内存溢出异常.默认情况下是1MB,调用栈可以非常深,所以,发生这个异常基本是因为无限递归,也就是函数A调用函数B,函数B反复调用自身,无法终止.

不幸的是,误用错误处理程序块经常导致隐晦的无限递归.想象以下的场景:程序发生异常,异常处理程序处理然后将其记录到日志文件.由于某种原因(比如拒绝访问),记录的时候再次发生异常,此时再次利用异常处理来处理刚发生的异常.处理一个,抛出另一个,无限递归循环... 故事的寓意?不要用异常处理程序来处理异常处理过程中发生的异常 :)

调试技巧:运行"kb 2000"(查看那本地栈)和"!clrstack"(sos.dll提供,查看托管栈)


内存溢出异常

大多数情况下内存溢出是由设计问题导致的,要么是cache或者session使用了过多的内存.使用正确的话,cache能带来极大的性能提升,也就是说,缓存那些访问最频繁的数据,不需要时尽快移除.在旧时代的asp保存对象到session不大会出现问题,因为开发人员仅保存最需要的条目.和减少并发用户数来解决内存占用过高一样,保存大dataset到session,都是效率杀手,你可能在耗时的垃圾搜集和检索cache与需要的时候再数据库检索数据的比较上做过头了.


没有特定的标准来决定什么时候该保存东西到session/cache,什么时候不该.最好在早期决定有多少用户需要处理,在此基础上决定每个用户允许保存多少多少数据.然后以多于最大可处理用户的数值做压力测试,以此确定能处理多少.更好的是,在不使用session的情况下来查看性能.区别在于不同的用户数量.

生产环境下的内存问题非常难于修正,因为通常需要重新设计,早期多付出一点可以避免后期付出更多.

调试技巧:运行!dumpheap -type System.Web.Caching.Cache来获取缓存的根,然后运行!objsize 根地址 来得知缓存了多少数据.(注意:InProc 模式下的session也保存在cache中

)


关于Out of Memory更深入的讨论,请查看我之前的日志


COM组件中未处理的异常

要是应用程序调用了本地COM组件,如果发生了未处理的异常会导致崩溃.举个例子:引用了已经释放的内存或相似的问题.

调试技巧:kb 2000 可以展示堆栈上的com组件,这样可以缩小范围.


本地堆冲突

和GC漏洞一样,本地堆冲突排错是最恶心的.写入了错误内存地址就会发生本地堆冲突.随之而来的问题在于:写入错误的内存地址不会发生任何错误,而当其他程序用正确方式访问这个地址的时候才会出错.换句话说,有贼偷取了内存数据.写入的内存地址可能在堆上,更恶劣的情况是:这个地址是用来保存指令的(指令将被改写)或者是堆栈,以至于代码调用到一半就无处可去.这个在写入buffer越界或者同样效果的操作时,发生的最频繁.

关于堆冲突的介绍请看Geoff Gray的文章.当由于堆冲突崩溃时,故障栈通常位于ntdll的堆分配函数,解决这个问题,需要使用GFlags和PageHeap抓到这个"贼".然而,为什么难于抓到这个"贼",原因在于:这个问题出现的非常随机并且非常难以重现.


托管堆冲突

托管堆冲突也是发生在托管堆上的堆冲突.同样的,在发生后很久才会显现出来.托管堆冲突是由于托管堆某一块被强行覆盖导致的.一般而言托管代码中,缓冲区是不会溢出的。如

果对一个byte[]越界写入,会报IndexOutOfRange 一类的异常.导致托管堆冲突最常见的原因是:代码调用用PInvoke传递一个过大的buffer进来。PInvoke函数越界写入buffer并覆

盖了紧挨着buffer的对象使用的内存。稍后垃圾搜集器开始遍历托管堆,问题就出来了,进程崩溃。
如果在当前活动栈的顶部有GC函数,就需要在代码中寻找是否有传递了buffer的PInvokes调用。


致命的执行引擎异常

这种异常非常少见,如果出现了,一般都是程序bug.一般是由于执行了一段CLR认为不应该执行的代码,因为无法从该点恢复,所以CLR抛出致命的执行引擎异常并结束。在事件日

志中会记录准确的地址。要是遇到了这种情况,最好联系技术支持,可能的话附上崩溃时的内存转储文件,它对问题的解决有极大帮助。

GC空洞

这也非常少见。CLR的非托管部分引用托管内存并“忘记”告诉GC这部分被引用,GC不知道应该保留被引用的内容(如果这部分没有被其他对象引用)而进行了回收,那么这个指针可

能指向意一个地址,最终导致严重问题.Yun Jin讨论了这个问题,地址:http://blogs.msdn.com/yunjin/archive/2004/02/08/69906.aspx

进程可用内存将为0

如果在进程死亡的同时从性能监视器观察到 物理内存/可用数(Mb) 大幅下降至0,这能导致进程崩溃。下面的任务就是找出原因

外部进程 杀死/回收 了asp.net进程

有无数次调试的时候,进程被外部程序杀死而不是进程自己崩溃的经历。一般来说,这种情况一般不是进程崩溃的真正原因。但在调试过程中,最好检查一下事件日志确保没有外部因素(如iisreset)杀死进程。

健康监视设置

健康监视设置也会导致进程回首,通常会设置每24小时或者服务器空闲20分钟回收进程。在开始调试之前最好检查一下应用程序池或者machine.config中的健康监视设置,不要因为这些设置误导调试工作.
<think>嗯,用户问为什么解引用NULL指针会导致程序崩溃。首先,我得回忆一下指针的基本概念。NULL指针在C语言中通常被定义为指向地址0的指针,而这个地址在大多数操作系统中是受保护的,不允许用户程序访问。 那解引用NULL指针具体发生了什么?当程序试图读取或写入一个NULL指针指向的内存时,处理器会触发一个访问违规的信号。操作系统捕获到这个信号后,通常会终止程序,导致崩溃。比如,在Linux下会产生SIGSEGV信号,而Windows下则是访问冲突异常。 需要解释一下内存分页和虚拟地址空间的概念。操作系统为每个进程分配独立的虚拟地址空间,其中低地址区域(如0x0到0x1000)通常未被映射,任何访问都会导致页错误。内核处理这个错误时发现是非法访问,于是终止进程。 另外,可能用户会混淆未初始化指针和NULL指针的区别,需要说明NULL是显式赋值为空,而未初始化指针可能指向任意地址,解引用同样危险但表现可能不同。 还要注意,C标准规定解引用空指针是未定义行为,所以不同系统可能有不同表现,但崩溃是最常见的。某些嵌入式系统可能没有内存保护,可能导致不可预测的结果,比如覆盖关键内存区域。 最后,给出避免的方法,比如初始化指针、检查NULL、使用静态分析工具等。这样用户不仅知道原因,还能知道如何预防。</think><< 解引用 NULL 指针会导致程序崩溃的原因是:NULL 指针本质上指向内存地址 0,而该地址在大多数操作系统中被明确标记为「不可访问区域」。以下是详细解释: 1. **操作系统内存保护机制** 现代操作系统会为每个进程分配独立的虚拟地址空间,其中低地址(如 0x00000000 到 0x0000FFFF)通常被标记为未映射区域。当程序尝试访问这些地址时,CPU 会触发一个硬件异常(例如页错误),操作系统随后会强制终止程序以防止非法内存操作。 2. **C 语言标准的定义** 根据 C 语言标准,解引用空指针(NULL)属于「未定义行为」(Undefined Behavior)。这意味着编译器不需要为此提供任何保护措施,程序可能直接崩溃、产生错误数据,甚至出现不可预测的行为。 3. **实际场景示例** ```c int *ptr = NULL; *ptr = 42; // 尝试向地址 0 写入数据,触发崩溃 ``` 上述代码会尝试通过空指针写入数据,操作系统检测到非法内存访问后,会向进程发送 SIGSEGV 信号(Linux/Unix)或触发访问冲突异常(Windows)。 4. **调试工具反馈** 在调试模式下(如使用 gdb),崩溃时会看到类似错误提示: `Program received signal SIGSEGV, Segmentation fault.` 这表明程序试图访问受保护的内存区域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值