wenmo8 发布的文章

VMMap是一个很好的系统内部工具,它可以可视化特定进程的虚拟内存,并帮助理解内存的用途。它有线程堆栈、映像、Win32堆和GC堆的特定报告。有时,VMMap会报告不可用的虚拟内存,这与可用内存不同。下面是32位进程(总共有2GB虚拟内存)的VMMap报告示例:

 

 

这种“不可用”的内存从何而来,为什么不能使用?Windows虚拟内存管理器具有64KB的分配粒度。当直接用VirtualAlloc分配内存并要求小于64KB(比如16KB)时,VirtualAlloc返回64KB边界上的地址。然后分配前四页(16KB),其余48KB标记为未使用。无法通过执行另一个分配来获取此内存,因为VirtualAlloc将始终返回驻留在64KB边界上的地址。那么,这个内存是不可用的。
VMMap中的fragmentation视图使问题更加明显。在下面的屏幕截图中,黄点是4KB区域,可以分配和使用,而灰色矩形是60KB区域,不能使用。当整个地址空间都是这些不可用的区域时,你得到的虚拟内存就没有你想象的那么多了。

 

 

幸运的是,很容易找到违规分配的来源。本质上,我们正在寻找分配大小小于64KB(或者更好:不能被64KB平均整除)的VirtualAlloc调用。您可以使用VMMap本身(它具有跟踪模式)跟踪这些分配,也可以附加WinDbg并设置断点:

0:000> bm kernelbase!VirtualAlloc* "r $t0 = poi(@esp+8); .if (@$t0 % 0x10000 != 0) { .printf \"Unusable memory will emerge after allocating %d bytes\", @$t0; kb 4 } .else { gc }"
  1: 76c03e8a          @!"KERNELBASE!VirtualAllocExNuma"
  2: 76bcd532          @!"KERNELBASE!VirtualAlloc"
  3: 76c03e66          @!"KERNELBASE!VirtualAllocEx"
0:000> g
Unusable memory will emerge after allocating 4096 bytes
ChildEBP RetAddr  Args to Child              
00defd48 010a4e34 00000000 00001000 00003000 KERNELBASE!VirtualAlloc
00defe2c 010a5f25 00000000 00000000 7eaa7000 FourKBLeak!allocate_small+0x34
00deff18 010a6989 00000001 012ba870 012bae98 FourKBLeak!main+0x35
00deff68 010a6b7d 00deff7c 7529919f 7eaa7000 FourKBLeak!__tmainCRTStartup+0x199

此断点确保传递给VirtualAlloc的分配大小可被64KB整除。否则,断点将停止并打印出有问题的分配和调用堆栈;否则,它将继续执行。这将使捕获小分配的源变得非常容易,并有望修复它们。

真是个不可思议的巧合。仅隔几天,我就要解决两个与嵌套异常处理程序有关的问题。具体来说,导致堆栈溢出的嵌套异常的无限循环。这是一个非常致命的组合。堆栈溢出对于调试来说是一个极其严重的错误;嵌套异常意味着异常处理程序遇到了一个异常,这是不可能的;更糟糕的是,堆栈损坏也在幕后发生。请继续阅读以了解诊断嵌套异常的技巧,以及首先可能导致这些异常的原因。

案例1:VC异常过滤器中的读取错误

客户机有多个转储文件供我查看,它们都显示了一个非常疯狂的模式。应用程序中会发生异常,这是完全可以预料和处理的异常。但是,它不会被正常处理,而是会导致无限的嵌套异常级联,最终导致进程崩溃,出现堆栈溢出。为了简洁起见,这里有一个简短的图片:

0:000> kcn 10000
... <repeated hundreds more times>
19e9 <Unloaded_Helper.dll>
19ea NestedExceptions1!exception_filter
19eb NestedExceptions1!trigger_exception
19ec MSVCR120D!_EH4_CallFilterFunc
19ed MSVCR120D!_except_handler4_common
19ee NestedExceptions1!_except_handler4
19ef ntdll!ExecuteHandler2
19f0 ntdll!ExecuteHandler
19f1 ntdll!KiUserExceptionDispatcher
19f2 <Unloaded_Helper.dll>
19f3 NestedExceptions1!exception_filter
19f4 NestedExceptions1!trigger_exception
19f5 MSVCR120D!_EH4_CallFilterFunc
19f6 MSVCR120D!_except_handler4_common
19f7 NestedExceptions1!_except_handler4
19f8 ntdll!ExecuteHandler2
19f9 ntdll!ExecuteHandler
19fa ntdll!KiUserExceptionDispatcher
19fb <Unloaded_Helper.dll>
19fc NestedExceptions1!exception_filter
19fd NestedExceptions1!trigger_exception
19fe MSVCR120D!_EH4_CallFilterFunc
19ff MSVCR120D!_except_handler4_common
1a00 NestedExceptions1!_except_handler4
1a01 ntdll!ExecuteHandler2
1a02 ntdll!ExecuteHandler
1a03 ntdll!KiUserExceptionDispatcher
1a04 NestedExceptions1!trigger_exception
1a05 NestedExceptions1!main
1a06 NestedExceptions1!__tmainCRTStartup
1a07 NestedExceptions1!mainCRTStartup
1a08 kernel32!BaseThreadInitThunk
1a09 ntdll!__RtlUserThreadStart
1a0a ntdll!_RtlUserThreadStart

在前面的调用堆栈中,很明显exception_filter试图调用卸载的DLL(Helper.DLL)中的函数。这又会导致一个异常(很可能是访问冲突),该异常会将控制权转移到异常过滤器,而我们在嵌套的异常循环中处于非常深的位置。顺便说一下,如果您知道要查找什么,那么很容易跟踪异常链。以下是几帧的kb输出:

006deb54 00a85706 00000000 00000000 00000000 <Unloaded_Helper.dll>+0x6666665e
006dec28 00a85eab 006dec4c 0f4e3924 00000000 NestedExceptions1!exception_filter+0x26
006dec30 0f4e3924 00000000 00000000 00000000 NestedExceptions1!trigger_exception+0x6b
006dec44 0f4e9268 006deda0 006dedf0 00000001 MSVCR120D!_EH4_CallFilterFunc+0x12
006dec7c 00a866d2 00a90000 00a81041 006deda0 MSVCR120D!_except_handler4_common+0xb8
006dec9c 7794c881 006deda0 006df734 006dedf0 NestedExceptions1!_except_handler4+0x22
006decc0 7794c853 006deda0 006df734 006dedf0 ntdll!ExecuteHandler2+0x26
006ded88 7794c6bb 006deda0 006dedf0006deda0 ntdll!ExecuteHandler+0x24
006ded88 58b3115e 006deda0 006dedf0 006deda0 ntdll!KiUserExceptionDispatcher+0xf
006df0d4 00a85706 00000000 00000000 00000000 <Unloaded_Helper.dll>+0x6666665e
006df1a8 00a85eab 006df1cc 0f4e3924 00000000 NestedExceptions1!exception_filter+0x26
006df1b0 0f4e3924 00000000 00000000 00000000 NestedExceptions1!trigger_exception+0x6b
006df1c4 0f4e9268 006df324 006df374 00000001 MSVCR120D!_EH4_CallFilterFunc+0x12
006df1fc 00a866d2 00a90000 00a81041 006df324 MSVCR120D!_except_handler4_common+0xb8
006df21c 7794c881 006df324 006df734 006df374 NestedExceptions1!_except_handler4+0x22
006df240 7794c853 006df324 006df734 006df374 ntdll!ExecuteHandler2+0x26
006df30c 7794c6bb 006df324 006df374 006df324 ntdll!ExecuteHandler+0x24
006df30c 00a85e8f 006df324 006df374 006df324 ntdll!KiUserExceptionDispatcher+0xf
006df744 00a86068 00000000 00000000 7ebab000 NestedExceptions1!trigger_exception+0x4f
006df818 00a86a79 00000001 00c37b88 00c35178 NestedExceptions1!main+0x28
006df868 00a86c6d 006df87c 76dc919f 7ebab000 NestedExceptions1!__tmainCRTStartup+0x199
006df870 76dc919f 7ebab000 006df8c0 77960bbb NestedExceptions1!mainCRTStartup+0xd
006df87c 77960bbb 7ebab000 35ed4a97 00000000 kernel32!BaseThreadInitThunk+0xe
006df8c0 77960b91 ffffffff 7794c9d2 00000000 ntdll!__RtlUserThreadStart+0x20
006df8d0 00000000 00a812d5 7ebab000 00000000 ntdll!_RtlUserThreadStart+0x1b

 突出显示的值(ntdll!ExecuteHandler的第二个参数)是上下文记录,前面的值是异常记录。您可以使用.cxr和.exr命令在WinDbg中检查它们:

 0:000> .exr 006df324 E

xceptionAddress: 00a85e8f (NestedExceptions1!trigger_exception+0x0000004f)

ExceptionCode: c0000005 (Access violation)

ExceptionFlags: 00000000

NumberParameters: 2

Parameter[0]: 00000001

Parameter[1]: 00000000

Attempt to write to address 00000000

0:000> .cxr 006dedf0

eax=cccccccc ebx=00000000 ecx=00000000 edx=00000000 esi=006df0dc edi=006df1a8 eip=58b3115e esp=006df0d8 ebp=006df1a8 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206

<Unloaded_Helper.dll>+0x6666665e: 58b3115e ?? ???

0:000> .exr 006deda0

ExceptionAddress: 58b3115e (<Unloaded_Helper.dll>+0x0006666665e)

ExceptionCode: c0000005 (Access violation)

ExceptionFlags: 00000010

NumberParameters: 2

Parameter[0]: 00000008

Parameter[1]: 58b3115e

Attempt to execute non-executable address 58b3115e

这表明原始异常是trigger_exception函数中的访问冲突,但它被另一个访问冲突所掩盖,该访问冲突是由从卸载的DLL执行代码引起的。

但最初的问题比卸载的DLL更微妙。在实际应用中,exception_filter实际上是由VisualC++处理的异常过滤器,用于处理C++异常:msvcr*!__InternalCxxFrameHandler。不知何故,它会触发嵌套异常,异常代码是0xc0000060xc0000006 (IN_PAGE_ERROR: in-page I/O error) 和 I/O error code 0xc000020c (STATUS_CONNECTION_DISCONNECTED)。该嵌套异常会将控制权再次传输到__InternalCxxFrameHandler,并会一次又一次地命中相同的嵌套错误。

有了这些信息,我们开始调查。导致嵌套异常的实际内存访问是一个数据结构传递给了 __InternalCxxFrameHandler处理程序,它存储在二进制文件中,并包含由C++编译器发出的异常处理信息。此信息不经常访问-仅在发生异常时才需要。

最后一个难题是应用程序是从一个网络驱动器运行的,这个驱动器被大量的机器访问,给服务器的网络连接带来了很大的负载。因此,工作流程如下:

  1. 应用程序的初始化路径早期发生异常
  2. 还发生连接错误,导致应用程序二进制文件所在的网络驱动器暂时断开连接
  3. VisualC++异常过滤器需要访问存储在应用程序二进制文件中的数据结构,但数据结构尚未从网络驱动器缓存在RAM中,因为只有在异常发生时才访问该数据结构。
  4. 异常筛选器随后失败,并出现I/O错误,该错误再次将控制权传递给异常筛选器-导致异常的嵌套循环

通过编写访问大型只读全局数据结构(只读全局数据也存储在应用程序二进制文件中)的异常过滤器,并从网络驱动器运行示例应用程序,我能够重建此问题。当我在导致异常之前禁用了网络适配器时,我们得到了上面描述的症状。

我们是怎么解决这个问题的?事实证明,VisualC++有一个名为/SWAPRUN的链接器设置(具体地说,/SWAPRUN:NET),它可以用来指示系统在执行之前将应用程序二进制加载到内存中。这意味着应用程序不可能开始运行,但由于连接问题,二进制文件的某些部分会突然变得不可用。显然,首先最好避免连接问题,但是考虑到网络本身是不可靠的。

案例2:导致堆栈溢出的堆栈损坏

第二个案例展示了导致堆栈溢出的嵌套异常链。唯一的区别是堆栈也已损坏。下面是堆栈底部的一些帧:

0:000> kn
... <repeated hundreds more times>
f04 002be8c4 7794c881 0xcccccccc
f05 002be8e8 7794c853 ntdll!ExecuteHandler2+0x26
f06 002be9b0 7794c6bb ntdll!ExecuteHandler+0x24
f07 002be9b0 cccccccc ntdll!KiUserExceptionDispatcher+0xf
f08 002becfc 7794c881 0xcccccccc
f09 002bed20 7794c853 ntdll!ExecuteHandler2+0x26
f0a 002bede8 7794c6bb ntdll!ExecuteHandler+0x24
f0b 002bede8 cccccccc ntdll!KiUserExceptionDispatcher+0xf
f0c 002bf134 7794c881 0xcccccccc
f0d 002bf158 7794c853 ntdll!ExecuteHandler2+0x26
f0e 002bf224 7794c6bb ntdll!ExecuteHandler+0x24
f0f 002bf224 010b51d5 ntdll!KiUserExceptionDispatcher+0xf
f10 002bf674 cccccccc NestedExceptions2!trigger_exception+0x65
f11 002bf748 010b5cd9 0xcccccccc
f12 002bf798 010b5ecd NestedExceptions2!__tmainCRTStartup+0x199
f13 002bf7a0 76dc919f NestedExceptions2!mainCRTStartup+0xd
f14 002bf7ac 77960bbb kernel32!BaseThreadInitThunk+0xe
f15 002bf7f0 77960b91 ntdll!__RtlUserThreadStart+0x20
f16 002bf800 00000000 ntdll!_RtlUserThreadStart+0x1b

堆栈上的0xCCCCCC地址看起来像是堆栈损坏的必然征兆。实际上,您可能已经有了一个有效的假设:堆栈已被0xcccccccc损坏,ntdll中的异常处理代码以某种方式尝试从地址0xcccccccc执行代码。这会导致嵌套异常,我们的情况与case#1相同。如果我们查看异常记录,我们可以确认。(第一个异常记录是根本原因,第二个异常记录是由于无效的异常处理程序造成的):

0:000> .exr 002bf23c 
ExceptionAddress: 010b51d5 (NestedExceptions2!trigger_exception+0x00000065)
 ExceptionCode: c0000005 (Access violation)
 ExceptionFlags: 00000000
NumberParameters: 2
 Parameter[0]: 00000001
 Parameter[1]: 00000000
Attempt to write to address 00000000
0:000> .exr 002bee00 
ExceptionAddress: cccccccc
 ExceptionCode: c0000005 (Access violation)
 ExceptionFlags: 00000010
NumberParameters: 2
 Parameter[0]: 00000000
 Parameter[1]: cccccccc
Attempt to read from address cccccccc

但也有一些微妙之处。为什么ntdll试图从无效地址0xcccccccc执行代码?答案是,在32位Windows应用程序中,异常过滤器的地址存储在堆栈中,作为名为异常注册记录的数据结构的一部分。如果该结构已损坏,则ntdll中的异常处理代码可能会尝试执行无效地址,认为它是指向异常筛选器的指针。
但这实际上是一个相当严重的安全漏洞!如果攻击者可以覆盖异常注册记录,然后触发异常,则可以执行任意代码。事实上,开发异常注册记录是克服Visual C++ 2003(/GS标志)中引入的堆栈防御的方法之一。
幸运的是,Windows有一些技巧。首先,Visual C++ 2003引入了一个链接器标志,称为。当此标志可用时,链接器在二进制文件中嵌入所有有效异常处理程序的目录。在运行时,Windows可以检查即将运行的异常处理程序是否在有效异常处理程序列表中。如果不是,它将直接拒绝执行无效的异常处理程序,并停止进程-这比尝试执行未知的异常处理程序要安全得多。

其次,Windows Vista SP1和Windows Server 2008引入了另一种系统范围的保护机制SEHOP(结构化异常处理覆盖保护)。默认情况下,在客户端操作系统(Windows Vista、Windows 7、Windows 8)上禁用SEHOP,在服务器操作系统(Windows server 2008和Windows server 2012)上启用SEHOP。SEHOP是一个相当简单的防御:当它打开时,每个线程在链的开头都有一个虚拟的异常注册记录。当发生异常时,ntdll中的异常处理代码将验证当前异常处理程序链是否以该虚拟异常注册记录终止。如果没有,则系统假设异常处理程序链已被篡改,并终止进程。

当这些防御打开时,上述情况根本不可能发生。将阻止将控件传输到0xCCCCCC,就像它是异常处理程序一样,并且该进程将突然终止。因此,我们可以得出结论,发生此崩溃的系统不符合现代安全指南:应用程序应该使用/SAFESEH编译,并且系统应该启用SEHOP。顺便说一下,从Windows7开始,您可以使用ImageFileExecutionOptions注册表项为单个进程配置SEHOP,而不会影响系统的其余部分。
例如,下面是使用/SAFESEH编译二进制文件时发生的情况(注意,启用软件DEP的/NXCOMPAT标志也是必需的):

:000> kn
 # ChildEBP RetAddr 
00 008aee54 779c625f ntdll!NtWaitForMultipleObjects+0xc
01 008af2b8 779c5e38 ntdll!RtlReportExceptionEx+0x3eb
02 008af314 779e81bf ntdll!RtlReportException+0x9b
03 008af394 7798b2e3 ntdll!RtlInvalidHandlerDetected+0x4e04 008af3ec 7797734a ntdll!RtlIsValidHandler+0x13f1a
05 008af484 7794c6bb ntdll!RtlDispatchException+0xfc
06 008af484 01284ef5 ntdll!KiUserExceptionDispatcher+0xf
07 008af8d4 cccccccc NestedExceptions2!trigger_exception+0x65
WARNING: Frame IP not in any known module. Following frames may be wrong.
08 008af9a8 012858c9 0xcccccccc
09 008af9f8 01285a0d NestedExceptions2!__tmainCRTStartup+0x199
0a 008afa00 76dc919f NestedExceptions2!mainCRTStartup+0xd
0b 008afa0c 77960bbb kernel32!BaseThreadInitThunk+0xe
0c 008afa50 77960b91 ntdll!__RtlUserThreadStart+0x20
0d 008afa60 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> !analyze -v
... <removed for brevity>
DEFAULT_BUCKET_ID: APPLICATION_FAULT
PROCESS_NAME: NestedExceptions2.exe
ERROR_CODE: (NTSTATUS) 0xc00001a5 - An invalid exception handler routine has been detected.
EXCEPTION_CODE: (NTSTATUS) 0xc00001a5 - An invalid exception handler routine has been detected.
... <removed for brevity>

重要的是,不存在嵌套异常的无限循环。进程立即终止,并调用Windows错误报告。另一方面,当为特定进程启用SEHOP时,它会阻止无效的异常处理程序执行。在一个典型的WER转储文件中,结果显示为原始异常(本应正常处理)最终未处理:

0:000> !analyze -v

... FAULTING_IP: NestedExceptions2!trigger_exception+65 002c51d5 c705000000002a000000 mov dword ptr ds:[0],2Ah

EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff)

ExceptionAddress: 002c51d5 (NestedExceptions2!trigger_exception+0x00000065)

ExceptionCode: c0000005 (Access violation)

ExceptionFlags: 00000008

NumberParameters: 2

Parameter[0]: 00000001

Parameter[1]: 00000000 Attempt to write to address 00000000 ...

BUGCHECK_STR: APPLICATION_FAULT_NULL_POINTER_WRITE_SEHOP

PRIMARY_PROBLEM_CLASS: NULL_POINTER_WRITE_SEHOP

DEFAULT_BUCKET_ID: NULL_POINTER_WRITE_SEHOP ...

FAILURE_BUCKET_ID: NULL_POINTER_WRITE_SEHOP_c0000005_NestedExceptions2.exe!trigger_exception

BUCKET_ID: APPLICATION_FAULT_NULL_POINTER_WRITE_SEHOP_nestedexceptions2!trigger_exception+65

ANALYSIS_SOURCE: UM FAILURE_ID_HASH_STRING: um:null_pointer_write_sehop_c0000005_nestedexceptions2.exe!trigger_exception ...

但有一个很好的暗示是SEHOP参与了其中——分析中“SEHOP”这个词出现了多次。我不确定是否有更好的方法来确定SEHOP的参与,但这对我来说已经足够了

结论

首先,在处理嵌套异常的无限循环时,不要惊慌。您需要标识启动链的原始异常,然后查找重复模式。链中某个点上的异常筛选器/处理程序必须已失败,并且一系列控制传输将返回到同一个异常筛选器。
其次,结构化异常处理是一种易受攻击的机制,特别是在32位应用程序中。确保使用编译器和操作系统提供的所有保护:SafeSEH、DEP和SEHOP。

我知道你现在在想什么。断点?真的吗?关于断点有什么需要掌握的?你按F9然后停在代码行。如果你是这么想的,这篇文章是给你的:继续读

 

Visual Studio提供了一组相当丰富的断点类型和操作,可以使您的调试体验更加舒适和高效。我们将在这篇文章中讨论四种断点。

条件断点

定义断点时,可以将其与条件关联(右键单击断点并选择何时命中)。在这种情况下,您几乎可以使用任何可以在即时窗口中计算的C++/C#语句,包括复杂表达式。调试器将在每次到达断点时计算表达式的值,并且仅当其计算结果为true时才会中断。

 

请注意,尽管断点可能只偶尔中断,但它仍然会减慢应用程序的速度,因为调试器必须在应用程序暂停时计算表达式,然后才能恢复它

追踪点---Tracepoints

断点不必中断应用程序的执行。在许多情况下,您只需要检查某些变量或表达式的当前值,然后立即继续执行。这不需要更改代码以包含跟踪语句:您只需将断点与跟踪操作相关联,跟踪操作将向Visual Studio输出窗口显示您想要的任何消息。同样,您可以显示复杂的表达式。

 

函数断点

即使没有函数的源代码,也可以在函数中设置断点。只需转到Debug>New Breakpoint>Break at函数并指定函数名。这也可以从“调用堆栈”窗口中获得-只需单击一个帧并按F9在该函数上放置一个断点。

 

数据断点

修改特定内存位置时,可以使用数据断点停止调试器。这个超级强大的特性对于多线程应用程序和各种内存损坏场景非常有用,在这些场景中,内存位置会以意外的方式修改。与源断点不同,您不会将断点放在代码行上。相反,您可以使用Debug>New Breakpoint>newdatabreakpoint菜单项指定内存位置。

 

不幸的是,这个特性只适用于C/C++应用程序。当托管对象在几代之间升级或发生堆压缩时,它们在堆中移动,使它们无法跟踪。

总结

如您所见,通过组合条件断点和跟踪点,您可以获得应用程序执行的跟踪,而无需向代码中添加任何跟踪语句或重新启动调试会话。通过添加函数断点和数据断点,可以进一步提高调试工作的灵活性。享受您的Visual Studio断点!

DML是一种非常简单的标记语言,它帮助基于现有命令的输出发现和执行新命令。许多WinDbg命令(以及扩展命令)都支持DML。例如,下面是lm D命令,它显示DML输出:

 

在上面的命令输出中,当我单击“SillyThreadPool”链接时,调试器为我执行了另一个命令lmDvmSillyThreadPool,它显示模块信息。同样,还有一个链接缺陷可以帮助我探索该模块中的符号和函数。

第一件事。您不必记住一堆奇怪的命令后缀就可以得到DML输出。有一个全局开关可以打开,.prefer_dml 1,这会导致许多内置命令和扩展命令显示dml输出。例如,下面是打开该开关后kn命令的输出:

 

单击时,此处的链接将切换到该帧并显示源代码和本地变量(执行的命令是.frame 0nN;dv/t/v)。
调试器脚本和扩展也可以生成DML输出。在调试器脚本中,只需使用.printf/D命令,并在其中嵌入DML链接。在调试器扩展中,可以使用IDebugClient::ControlledOutput函数输出DML。例如,下面显示单击时执行另一个命令的链接:

.printf /D "<exec cmd=\"lm m ntdll\">lm ntdll</exec>\n\n"

 

我敢打赌你不知道。应用程序本身可以向调试器输出DML命令!只需要使用outputDebugStringAPI,并在其中嵌入神奇的字符串<?dml?>. 该魔法令牌之后的所有内容都将被解释为DML字符串,并相应地显示在调试器中。例如,假设我们的应用程序中有以下代码:

OutputDebugString(L"Entered thread pool demo app.\n<?dml?><exec cmd=\"!uniqstack\">Dump unique stacks</exec>\n");

然后,调试器遇到此调试输出时将显示命令链接:

 

下一个命令是.dml_flow。此命令旨在通过将反汇编函数拆分为代码块并帮助使用DML链接在块之间导航,使其更易于读取。你自己试验这个命令比我用语言解释要容易得多,但总的来说,你提供了两个地址——一个开始地址和一个目标地址——这个命令帮助你理解从开始地址可以到达目标的代码路径。

 

前一个屏幕截图中的链接指向进入和退出屏幕上显示的基本代码块的跳转路径。

最近在调试几个问题时,发现跟abort函数有关,以前只是简单使用,现在却发现不简单,就多留意了下。

简介

abort中止当前进程并返回错误代码。异常终止一个进程。中止当前进程,返回一个错误代码。错误代码的缺省值是3。

代码

/***
*abort.c - abort a program by raising SIGABRT
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* defines abort() - print a message and raise SIGABRT.
*
******************************************************************************
*/#include<cruntime.h>#include<stdlib.h>#include<internal.h>#include<awint.h>#include<rterr.h>#include<signal.h>#include<oscalls.h>#include<mtdll.h>#include<dbgint.h>#ifdef _DEBUG#define _INIT_ABORT_BEHAVIOR _WRITE_ABORT_MSG #else /* _DEBUG */ #define _INIT_ABORT_BEHAVIOR _CALL_REPORTFAULT #endif /* _DEBUG */unsignedint __abort_behavior =_INIT_ABORT_BEHAVIOR;/***
*void abort() - abort the current program by raising SIGABRT
*
*Purpose:
* print out an abort message and raise the SIGABRT signal. If the user
* hasn't defined an abort handler routine, terminate the program
* with exit status of 3 without cleaning up.
*
* Multi-thread version does not raise SIGABRT -- this isn't supported
* under multi-thread.
*
*Entry:
* None.
*
*Exit:
* Does not return.
*
*Uses:
*
*Exceptions:
*
******************************************************************************
*/ void__cdecl abort (void)
{
_PHNDLR sigabrt_act
=SIG_DFL;

#ifdef _DEBUG
if (__abort_behavior &_WRITE_ABORT_MSG)
{
/*write the abort message*/_NMSG_WRITE(_RT_ABORT);
}
#endif /* _DEBUG */ /*Check if the user installed a handler for SIGABRT.
* We need to read the user handler atomically in the case
* another thread is aborting while we change the signal
* handler.
*/sigabrt_act=__get_sigabrt();if (sigabrt_act !=SIG_DFL)
{
raise(SIGABRT);
}
/*If there is no user handler for SIGABRT or if the user
* handler returns, then exit from the program anyway
*/ if (__abort_behavior &_CALL_REPORTFAULT)
{
#if defined (_M_ARM) || defined (_CRT_APP)__fastfail(FAST_FAIL_FATAL_APP_EXIT);#else /* defined (_M_ARM) || defined (_CRT_APP) */ if(IsProcessorFeaturePresent(PF_FASTFAIL_AVAILABLE))
__fastfail(FAST_FAIL_FATAL_APP_EXIT);

_call_reportfault(_CRT_DEBUGGER_ABORT, STATUS_FATAL_APP_EXIT, EXCEPTION_NONCONTINUABLE);
#endif /* defined (_M_ARM) || defined (_CRT_APP) */}/*If we don't want to call ReportFault, then we call _exit(3), which is the
* same as invoking the default handler for SIGABRT
*/_exit(3);
}
/***
*unsigned int _set_abort_behavior(unsigned int, unsigned int) - set the behavior on abort
*
*Purpose:
*
*Entry:
* unsigned int flags - the flags we want to set
* unsigned int mask - mask the flag values
*
*Exit:
* Return the old behavior flags
*
*Exceptions:
* None
*
******************************************************************************
*/unsignedint __cdecl _set_abort_behavior(unsigned int flags, unsigned intmask)
{
unsigned
int oldflags =__abort_behavior;
__abort_behavior
= oldflags & (~mask) | flags &mask;returnoldflags;
}