EXCEPTION_HIJACK(0xe0434f4e)异常的抛出过程
样例工程
在VS2013里新建一个C#控制台工程,写下如下代码:
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading;namespaceConsoleApplication1
{classProgram
{static void Main(string[] args)
{
Thread t1= new Thread(newThreadStart(TestMethod));
t1.Start();
t1.Join();
}public static voidTestMethod()
{while (true)
{
StringBuilder sb= new StringBuilder(1024*1024);
Thread.Sleep(0);
}
}
}
}
如何关闭/禁用.NET JIT调试对话框
当.NET程序有未处理的异常时,您可能会希望关闭出现的调试对话框。下面有两个选项:
1、启用JIT调试的注册表项
对于包含托管代码的应用程序,公共语言运行库将显示类似于JIT附加调试器的对话框。控制此选项的注册表项称为HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSetting。
- 如果值为0,则通过消息框提示用户。选择包括:“继续”---这将导致堆栈转储和进程终止。“附加调试器”---在这种情况下,运行时生成DbgManagedDebugger注册表项中列出的调试器。如果没有,则返回控件并终止进程。
- 如果值为1,则返回控件。这会导致堆栈转储,然后终止进程。(不再有对话)
- 如果值为2,则生成DbgManagedDebugger注册表项中列出的调试器。
2、如果要禁用“JIT调试”对话框,但仍需要错误对话框
Visual Studio.NET|Tools|Options|Debugging|Just-In-Time 下取消“"Common Language Runtime"”的选择,现在将显示“确定/取消”对话框,而不是“选择调试器”对话框。注意:上面选项1中的注册表项需要为0才能显示对话框。
windbg是如何搜索符号文件的?
来个样例
我的符号目录设置是:
用我们在windows下调试必须用到的ntdll.dll模块来讲下windbg加载符号文件的过程。windbg加载符号文件时,会首先根据配置的符号目录信息,在本地符号目录中查找对应的符号文件。一个典型的搜索过程如下:
F:\Debug_Symbol\Symbols32\
F:\Debug_Symbol\Symbols32\pingme.txt
F:\Debug_Symbol\Symbols32\flat.txt
F:\Debug_Symbol\Symbols32\index2.txt
F:\Debug_Symbol\Symbols32\ntdll.pdb\2505F15902821D2C6931BBFF1B941EBF1\ntdll.pdb
F:\Debug_Symbol\Symbols32\ntdll.pdb\2505F15902821D2C6931BBFF1B941EBF1\ntdll.pd_
F:\Debug_Symbol\Symbols32\ntdll.pdb\2505F15902821D2C6931BBFF1B941EBF1\file.ptr
首先解释一下路径中的那一串字母和数字混合的东西是什么玩意儿,这个字符串是编译器根据编译时的时间、版本、程序类型等信息生成的一个类似GUID一样的东西(VC6编译的符号文件其内部编号是编译时间的绝对秒,就是
time
函数返回的32位从1970年1月1日0点开始的秒数,后面加上程序的特征,例如目标机器的类型、程序类型等;VC7.0、7.1、8.0、9.0
编译的符号文件编号是一个
GUID,这可能是为了避免多线程同时编译相同特征的程序引发内部编号冲突),存储在PE文件的DebugDirecotry数据目录指向的数据中,暂且称之为pdb的索引串,对于每个编译出来的文件而言它是唯一的。同名文件的不同版本,它的这个索引串也不同。
过程详解
下面我来逐一解释下上面看到的这个搜索过程:
- 调试器先检查符号目录是否存在
- 检查符号目录下是否存在flat.txt、pingme.txt或index2.txt。这三个文件的存在与否,决定了搜索过程中的一些细节。
如果存在pingme.txt,说明该目录下存在自动下载的符号文件。那么windbg将按照自动下载时的存放路径来检查符号文件是否存在。若没有pingme.txt,将不会采用这种路径来搜索。具体搜索方式参考第3条。
如果存在flat.txt(即使同时也存在pingme.txt),将忽略上面这种采用pdb索引串的快捷搜索方式,只以文件名和文件类型等信息进行搜索。
如果存在index2.txt,将按照文件名称分组进行搜索。分组方式是:使用符号文件名称的前两个字母最为一级目录,符号文件的名称作为二级目录,符号文件的编号作为三级目录,如此可对大量的文件进行分级索引,避免Symbols 目录下的子目录过多。比如以下路径:
F:\Debug_Symbol\Symbols32\ke\kernel32.pdb\
F:\Debug_Symbol\Symbols32\nt\ntdll.pdb\
F:\Debug_Symbol\Symbols32\nt\ntkrnlpa.pdb\ - 按pdb索引搜索(要求pingme.txt存在)
对于windbg自动下载的符号文件,会以"符号目录+符号文件名+pdb索引串+符号文件名的方式"为路径存储符号文件,这样,在下次需要查找该符号时,可以直接从PE文件中取得pdb索引串,然后构造出这样一个路径来快速加载符号文件。这就是搜索路径"F:\Debug_Symbol\Symbols32\ntdll.pdb\2505F15902821D2C6931BBFF1B941EBF1\ntdll.pdb"的由来。当存在pingme.txt时,将优先采用这种方式搜索。当然,自动下载符号的目录一般会自动创建一个pingme.txt的。 - 检查是否存在压缩的符号文件
windbg从符号服务器下载的符号文件,有些可能是压缩形式(文件名以_结束,需要用expand.exe解压缩),所以windbg会检查ntdll.pd_的存在,若存在就会将其解压缩。file.ptr可能也是某种方式的临时文件,暂时我无法完全解释它。 - 以符号文件名作为文件夹名进行搜索
如果以上都没有找到,那么就检查符号目录下ntdll.pdb这个文件夹是否存在,注意这里是文件夹。如果该文件夹存在,就会继续查找F:\Debug_Symbol\Symbols32\ntdll.pdb\ntdll.pdb。若文件夹不存在,就会直接在符号目录下查找符号文件ntdll.pdb(注意是文件)。 - 以目标文件的类型作为分类搜索
如果仍然没有找到,那么将根据PE文件的类型(dll,exe,sys,ocx等)作为子目录进行查找(安装的符号文件一般是以这种路径形式存放的)。 - 以目标文件Debug信息中指定的符号路径进行搜索
对于我们自己编译的驱动,通常是包含了pdb文件的全路径的,随便用一个编辑器打开一个sys文件都可以看到文件中出现的pdb路径信息。 - 搜索windbg所在路径
- 到符号服务器查找符号
如果以上都没有找到的话,也就是说本地符号库中无法找到匹配的符号文件,如果符号设置中允许自动到符号服务器下载符号(比如出现了"SRV*F:\Debug_Symbol\Symbols32*http://msdl.microsoft.com/download/symbols"这样的配置),那么windbg就会根据PE文件的pdb索引串到符号服务器上查找是否有与该pdb索引串匹配的符号文件,若有,就将其下载到本地,若没有,那就是真的没有了,windbg将返回"ERROR: Symbol file could not be found."
设置微软符号服务器的又一方法
通过注册表设置:HKLM\SOFTWARE\Microsoft\Symbol Server Proxy\Web Directories\symbols下,设置SymbolPath,类型为REG_EXPAND_SZ。可以通过命令行直接添加:
reg add "HKLM\SOFTWARE\Microsoft\Symbol Server Proxy\Web
Directories\symbols" /v SymbolPath /t REG_EXPAND_SZ /f /d
c:\windows\symbols;SRV*d:\symbols*http://msdl.microsoft.com/download/symbols
Windbg Call Stack(调用堆栈)窗口的使用
调用堆栈是指向程序计数器当前位置的函数调用链。调用堆栈的顶部函数是当前函数,下一个函数是调用当前函数的函数,依此类推。显示的调用堆栈基于当前程序计数器,除非更改寄存器上下文。
在 WinDbg 中,可以通过输入命令或通过使用Call Stack窗口中查看调用堆栈。
Call Stack窗口的打开方式
- 通过菜单View--->Call Stack打开
- 通过快捷键Alt+6打开
- 通过工具栏按钮打开
Call Stack窗口
作为一种替代方法 k命令时,您可以调用窗口中查看调用堆栈。通过上面的方式打开call stack窗口
call stack窗口中的按钮可用于自定义调用堆栈的视图。要移动到源窗口或反汇编窗口中相应的调用位置,请双击调用堆栈中的一行,或选择一行并按回车键。此操作还将本地上下文更改为选定的堆栈帧。
调用窗口具有一个包含多个按钮和具有带其他命令的快捷菜单的工具栏。 若要访问此菜单中,右键单击标题栏或单击窗口 (在右上角附近的图标)。 工具栏和菜单包含以下按钮和命令:
Raw args 显示传递给函数的前三个参数。在基于x86的处理器上,此显示包括传递给函数的前三个参数(“Args to Child”)。
Func info 显示帧指针省略(FPO)数据和其他有关函数的内部信息。此命令仅在基于x86的处理器上可用。
Source 在函数名之后显示源模块名和行号(如果调试器具有此信息)。
Addrs 显示各种与帧相关的地址。在基于x86的处理器上,此显示包括堆栈帧的基指针(“ChildEBP”)和返回地址(“RetAddr”)。
Nonvolatile regs 显示寄存器上下文的非易失性部分。此命令仅在基于安腾的处理器上可用。
Frame nums 显示帧编号。帧总是连续编号的,从零开始。
Headings 显示列头Arg types 显示有关堆栈中的函数预期和接收的参数的详细信息。
Always floating 将使窗口停靠,即使仍拖到停靠位置。
Move with frame WinDbg帧移动时使窗口在移动,即使窗口已解除锁定。
其他说明
在用户模式下,堆栈跟踪基于当前线程的堆栈。在内核模式下,堆栈跟踪基于当前寄存器上下文。 可以设置寄存器上下文以匹配特定线程、 上下文记录或捕获帧。 如果Call Stack窗口打开时,使用~1s切换线程、.cxr切换上下文等指令事,Call Stack窗口里显示的堆栈内容会发生改变。