原文链接地址:http://blog.csdn.net/Donjuan/article/details/3859160

在现在C++、Java、.Net代码大行其道的时候,很多代码错误(Bug)都是通过异常的形式表现出来的。由于工期紧或者种种原因,很多程序员在碰到程序发生未处理的异常的第一反应就是try … catch (Exception e) { … }。然而代码开发到后期的时候,这种简单粗暴的解决代码错误(Bug)的方式就会在其他不相干的地方表现出来,有的时候甚至导致程序随机的不稳定,而且很难调试。比如执行下面的代码你会发现输出的值是12345.6789:

using System;public class Class1{public static void Main(){Console.WriteLine(Calculate("12345.6789 + 987654321l"));}private static double Calculate(string expression){string[] numbers = expression.Split('+');return RedundantParseForDemoOnly(numbers[0].Trim()) +RedundantParseForDemoOnly(numbers[1].Trim());}private static double RedundantParseForDemoOnly(string number){try{return double.Parse(number);}catch{return 0;}}}

当然啦,你可以说只要强迫项目组所有程序员不要catch通用异常就可以了,但是老虎总是有打盹的时候,而且大部分项目组因为人才梯队建设的问题都会有那么几个新人……

我在工作中发现很多程序员都不理解,或者说根本就没有去注意过表2里面的输出,浪费了很多好的调试机会。因此我看到很多优秀的程序员都是非常辛苦的单步调试,一步步地跟踪代码。这个过程不仅辛苦,而且非常容易出错,因为单步跟踪代码容易让人犯晕。实际上你可以利用Windows提供的结构化异常处理(SEH)来帮助你快速找到问题所在。

在程序运行的时候,特别是程序里面发生异常的时候,如果你使用windbg.exe、cdb.exe等命令行调试器的时候,或者如果你留心Visual Studio的输出( output)窗口里的文本输出的时候,你将会看到类似下面的输出:

First-chance exception at 0x004116a9 in 异常处理.exe: 0xC0000005: Access violation writing location 0x00000000.……First-chance exception at 0x7c812aeb in 异常处理.exe: Microsoft C++ exception: char at memory location 0x0012fd88..

异常机制简介

当CPU运行到一些非法的指令,例如除零错误,访问内存页失败等指令,CPU会生成一个硬件异常,不同的异常有固定的异常代码作为标识符,异常产生以后CPU暂时不能继续执行后续的指令—因为后续的指令有可能也是无效的。当然不能让整个计算机系统就这么当掉,因此CPU内置了一个异常处理表—这个异常处理表只有运行在内核模式的代码才能访问,操作系统在启动的时候初始化这个异常处理表,为每一个异常注册一个异常处理程序,因此这个表看起来就像:

CPU产生异常以后,根据异常代码在异常处理表里面查找对应的异常处理程序,通过调用异常处理程序执行了合适的处理之后再继续执行其他的代码。

处理CPU自定义的硬件异常以外,异常处理表里面还有一些空的表项,这样操作系统可以通过添加自定义的异常代码和相应的异常处理程序实现软件异常。在Windows操作系统中,程序可以通过调用Win32的RaiseException函数来触发软件异常。因此实际上C++和.NET里面的异常都是通过调用RaiseException来实现的,然而异常处理表不是很大,只能保存不超过256个异常处理程序信息,因此Windows只定义了几个通用的异常码来表示C++ 和.NET异常。

对于CPU来说,硬件异常、软件异常、C++ 异常和.NET异常的触发和处理的方式都是一样的,这种方式在Windows中叫做结构化异常处理(SEH)。

异常处理

1. 当程序里面有一个异常发生以后;

2. SEH首先通过回溯堆栈内容,查找程序内部是否有一个异常处理块(就是我们通常知道的catch 块);如果找到一个异常处理块可以处理这个异常,那么Windows将异常处理块所在的函数下面的堆栈释放,并且执行这个异常处理块里面的代码。

3. 如果Windows没有发现任何一个异常处理块处理掉这个异常的话,也就是到程序入口(main)函数也没有找到一个合适的异常处理块的话,Windows会使用它自带的异常处理块处理这个异常;

4. Windows自带的异常块会检查你的程序是否附加了一个调试器,如果是的话,Windows中断程序并将控制权交给调试器。

5. 如果没有调试器附加到你的程序上,Windows启动注册在注册表里面的默认验尸调试器,一般情况下,Windows的默认调试器是Dr Watson,这个调试器的工作就是弹出著名的“调试还是终止,这个问题”对话框(或者是“是否将错误报告发送给微软?”对话框):

在上面的处理步骤中,第一步通过RaiseException触发异常的时候,Windows会先检查你的程序是否正在被调试,如果有一个调试正附加在你的程序上,Windows向调试发送一个调试消息,通知调试器程序里面有一个异常发生,询问调试器是否要中断程序执,默认情况下调试器会忽略这条消息—因为我们纯洁的调试器总是相信程序员都能写出优良的代码出来。这个步骤在调试术语中叫做第一次机会(First Chance)--第一次在异常发生的现场观察触发异常的环境。

而第3步以后,操作系统自己来处理这个异常的时候,在调试术语中叫做第二次机会(Second Chance),由于这个时候程序实际上已经挂了(不会有任何生命活动了)—不是病危,所以这个时候即使你将调试器附加上去来检查触发异常的环境时,就像法医在检查人非正常死亡的原因一样—验尸调试。

设置调试器不要忽略first chance异常

在程序开发的时候,我们当然是希望在程序病危(first chance异常)的时候检查代码错误(Bug)的原因啦,因此如果程序发生一些莫名其妙的Bug时,在调试程序的时候,最好通知调试器不要忽略first chance异常。因为如果异常被程序内部的代码catch掉的话,很有可能会导致我们忽略重要的线索。

在Windbg里面,使用下面的命令来通知调试器不要忽略first chance异常:

sxe 异常代码

注意sxe后面有一个空格,例如在调试.NET程序的时候,可以使用命令sxe 0xE0434f4D来使调试器在catch块执行之前中断程序的执行。

可以使用命令

sxd 异常代码

来启用忽略first chance异常的功能。

在Visual Studio里面,请参考我的另外一篇文章学习如何设置:CLR Debugger - 在程序抛出异常的时候中断程序的执行

如何知道调试器正在忽略first chance异常?

通过查看调试器里面的输出信息,你可以了解调试器是否启用了在first chance异常触发时中断程序执行的功能。

在Visual Studio里面,可以查看“输出”窗口里面的文本输出:

这个输出就说明调试器忽略了first chance异常

在Windbg里面,类似下面的输出表示调试器忽略了first chance异常:

附录

你可以使用下面的代码去尝试一下上面讲解的概念:

// 异常处理.cpp : Defines the entry point for the console application.//

#include "stdafx.h"#include <iostream>#include <windows.h>#include <excpt.h>using namespace std;int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep){if ( code == EXCEPTION_ACCESS_VIOLATION )return EXCEPTION_EXECUTE_HANDLER;elsereturn EXCEPTION_CONTINUE_SEARCH;}void CppTestFunction(){try{throw "Test Cpp Exception";}catch ( char * ){cout << "char * exception caught" << endl;}}int _tmain(int argc, _TCHAR* argv[]){int *p = NULL;__try{*p = 1;}__except ( ExceptionFilter(GetExceptionCode(), GetExceptionInformation())){cout << "ACCESS VIOLATION CAUGHT" << endl;}CppTestFunction();return 0;}

转载于:https://www.cnblogs.com/wuyuan2011woaini/p/5178970.html

理解First Chance和Second Chance避免单步调试相关推荐

  1. 单步调试理解webpack里通过require加载nodejs原生模块实现原理

    在webpack和nodejs里,我们经常使用require函数加载原生模块或者开发人员自定义的模块. 原生模块的加载,比如: const path = require("path" ...

  2. 二、Neo4j源码研究系列 - 单步调试

    二.Neo4j源码研究系列 - 单步调试 一.背景介绍 上一篇我们已经把了neo4j的源码准备以及打包流程完成了,本篇将讲解如何对neo4j进行单步调试.对于不了解如何编译打包neo4j的读者,请阅读 ...

  3. linux下gdb单步调试

    用 GDB调试程序 GDB 概述 ---- GDB 是 GNU开源组织发布的一个强大的 UNIX下的程序调试工具.或许,各位比较喜欢那种图形界面方式的,像 VC. BCB等 IDE的调试,但如果你是在 ...

  4. android平台从froyo 2.2开始支持jni单步调试了

    一.写在前面的话(可以理解为废话) 前段时间从网上看了google的io大会,亲眼看到了google在it技术发展和创新领域所做的工作(在此也问候一下apple公司的美术家门,你们辛苦了).虽然goo ...

  5. x86_64平台SingleStep单步调试原理和示例

    先看一个程序: // simple.c int value = 0; int main(int argc, char **argv) {value ++;value ++;value ++;value ...

  6. GDB调试利器-单步调试与查看变量

    前言 前面两篇已经对Linux下如何使用gdb启动调试,GDB调试利器-断点设置与查看源码我们已经了解了GDB基本的启动调试,设置断点,查看源码等,如果这些内容你还不知道,建议先回顾一下前面的内容. ...

  7. Windows上VS2017单步调试FFmpeg源码的方法

    之前在https://blog.csdn.net/fengbingchun/article/details/90114411 介绍过如何在Windows7/10上通过MinGW方式编译FFmpeg 4 ...

  8. 单步调试_keil for arm 调试时无法单步运行及打断点

    在keil调试的过程中,会出现部分函数断点打不上去.单步调试step over功能无法使用的情况.造成该情况的主要原因可能为编译时使用的优化等级过高,导致其调试信息失真的问题. 解决办法,打开魔术棒- ...

  9. Python如何实现单步调试

    Python如何实现单步调试 https://www.cnblogs.com/jing1617/p/9396617.html https://www.cnblogs.com/xiaohai2003ly ...

最新文章

  1. java8学习:新的日期使用
  2. aspose.words for java操作文档doc,设置一级二级三级标题以及段落表格等详情
  3. how is SAP UI5 configuration.js loaded in the runtime
  4. Android 第五课 常用控件的使用方法(TextView、Button、EditView、 ImageView、 ProgressBar、 ProgressDialog等)
  5. javascript权威指南(2)
  6. vue 转换信息为二进制 并实现下载
  7. 选择器、像素和百分比、颜色单位、文档流-css基础
  8. 【UOJ#177】欧拉回路
  9. 经典神经网络 -- GoogLeNet / Inception : 设计原理与pytorch实现
  10. C语言编译器(C语言编程软件)完全攻略
  11. 【第十届“泰迪杯”数据挖掘挑战赛】C题:疫情背景下的周边游需求图谱分析 问题一方案及Python实现
  12. keyword can not be an expression
  13. 内窥镜胶囊(胶囊内镜)硬件方案(二)
  14. java web play_玩转Java Web应用开发:Play框架
  15. 百度seo引流怎么做?一个免费分享知识的平台
  16. 10万存银行,几年能翻一番?
  17. (十一:2020.08.28)CVPR 2017 追踪之论文纲要(译)
  18. access表达式的用法
  19. Appium 常用初始化启动参数 Capability详解
  20. html 图片自动滚动播放,JS实现图片自动滚动(图片横向滚动)

热门文章

  1. mac mysql本地连接数_如何用command line链接mysql
  2. 网络品牌推广浅析网站标题该如何进行SEO优化?
  3. 良品铺子如何通过网站推广打造国内休闲食品连锁零售企业?
  4. 浅析如何让企业网站实现精准营销?
  5. 虚拟主机众多用途你心仪哪一项?
  6. H1标签对网站优化有什么作用?
  7. java面试浦发_记一次凉凉的浦发面试
  8. Java基础-我所理解的泛型
  9. 开发日记-20190914 关键词 汇编语言王爽版 第四章
  10. DGA短域名(360样本) mark下 下次分析可以参考