在Windbg中,我可以在抛出特定的C ++异常时跳过中断吗?

时间:2014-10-10 18:43:33

标签: c++ debugging exception exception-handling windbg

在Visual Studio中,通过Debug>中的对话框;异常......,您可以设置特定的C ++异常类型来打破或跳过。在Windbg中,使用sxe eh打开C ++异常是完全或全无。

有没有办法跳过特定的C ++异常类型?相反,有没有办法打破特定的类型?

1 个答案:

答案 0 :(得分:4)

注意:这个答案是32位特定的,因为我还没有做过多次64位调试。我不知道64位有多少适用。

假设以下代码:

class foo_exception : public std::exception {};

void throw_foo()
{
    throw foo_exception();
}

让我们假设你已经开始打破C ++异常的第一次机会异常:sxe eh

现在,当调试器中断时,您的异常记录将位于堆栈顶部。因此,如果您只想查看类型是什么,则可以显示异常记录信息:

0:000> .exr @esp
ExceptionAddress: 751dc42d (KERNELBASE!RaiseException+0x00000058)
   ExceptionCode: e06d7363 (C++ EH exception)
  ExceptionFlags: 00000001
NumberParameters: 3
   Parameter[0]: 19930520
   Parameter[1]: 0027f770
   Parameter[2]: 0122ada0
  pExceptionObject: 0027f770
  _s_ThrowInfo    : 0122ada0
  Type            : class foo_exception
  Type            : class std::exception

看看当前的堆栈,你可以看到这些东西在哪里:

    0027f6c4  e06d7363
    0027f6c8  00000001
    0027f6cc  00000000
    0027f6d0  751dc42d KERNELBASE!RaiseException+0x58
    0027f6d4  00000003
    0027f6d8  19930520
    0027f6dc  0027f770
    0027f6e0  0122ada0 langD!_TI2?AVfoo_exception
    ...

因此,在此示例中,异常本身位于0027f770,正如您可以从.exr旁边的pExceptionObject输出中看到的那样。你可以在0027f6dc看到堆栈上的值,或者从堆栈顶部偏移0x18,所以@esp+18。让我们看看调试器告诉我们该位置的内容。

0:000> dpp @esp+18 L1
0027f6dc  0027f770 01225ffc langD!foo_exception::`vftable'

此命令说:从@esp+18开始, d ump一个 p 整数大小的值,然后将那里找到的值deref为 p ointer,也写下与第二个地址匹配的任何符号的名称。在这种情况下,它找到了foo_exception类的vtable。这告诉我们地址0027f770的对象是foo_exception。我们可以使用该信息为条件断点创建表达式。

我们需要一种直接获取vtable地址的方法,如下所示:

@!"langD!foo_exception::`vftable'"

我们必须引用它,因为后退和背叛。我们还需要提取所需的堆栈值:

poi(poi(@esp+18))

poi运算符获取一个地址并返回存储在那里的指针大小的值。第一次评估将堆栈地址转换为对象地址,第二次评估将对象地址转换为vtable地址,我们需要进行比较。整个条件看起来像这样:

@!"langD!foo_exception::`vftable'" == poi(poi(@esp+18))

现在我们可以判断它是否是foo_exception,我们可以通过设置一个命令在调试器中断C ++异常时自动运行来跳过它们:

sxe -c".if ( @!\"langD!foo_exception::`vftable'\" == poi(poi(@esp+18)) ) {gc}" eh

翻译:

  • 打破C ++异常的第一次机会并运行以下命令:
  • foo_exception vtable地址与@esp+18
  • 处对象的vtable地址进行比较
  • 如果它们相同,则发出gc命令,如果在达到此命令时调试器正在运行,该命令将继续运行
  • (别忘了逃避内部报价)

如果您想要打破foo_exception ,请将条件从==更改为!=

要记住的一点是,有时会将异常作为指针而不是按值抛出,这意味着您需要在表达式的poi()部分周围再多@esp.exr。您可以告诉我,因为当您使用Type转储例外记录时,class foo_expression *将为.if。这完全取决于抛出异常的代码,而不是异常类型本身,因此您可能需要针对场景定制.if - 条件。

最后,如果你想要打破或跳过几个异常类型,它是可行的。我建议使用链式.elsifsxe命令writing a script并将$$><path\to\script自动命令设置为.if ( @!"langD!foo_exception::`vftable'" == poi(poi(@esp+0x18)) ) { $$ skip foo_exceptions gc } .elsif ( @!"langD!bar_exception::`vftable'" == poi(poi(@esp+0x18)) ) { $$ dump the exception to see the error message, then continue running dt poi(@esp+18) langD!bar_exception gc } .elsif ( @!"langD!baz_exception::`vftable'" == poi(poi(@esp+0x18)) ) { $$ show the top 10 frames of the stack and then break (because we don't `gc`) kc 10 } 。在一行上执行大量if条件链接可能非常难以阅读并且正确,尤其是在额外转义时。脚本不需要额外的转义。这是一个小例子:

gc

(注意:Windbg会在运行时抱怨脚本错误,因为它不像{{1}}命令后面跟着其他任何东西。但它仍然运行良好)