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

Kur*_*son 2 c++ debugging windbg exception

在 Visual Studio 中,通过“调试”>“异常...”对话框,您可以设置要中断或跳过的特定 C++ 异常类型。在 Windbg 中,打开 C++ 异常中断sxe eh是全部或全部。

有什么方法可以跳过特定 C++ 异常类型的中断吗?相反,有没有办法只打破特定类型?

Kur*_*son 5

注意:这个答案是针对 32 位的,因为我还没有进行太多的 64 位调试。我不知道多少适用于64位。

假设以下代码:

class foo_exception : public std::exception {};

void throw_foo()
{
    throw foo_exception();
}
Run Code Online (Sandbox Code Playgroud)

假设您已经打开了 C++ 异常的第一次机会异常中断:sxe eh

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

class foo_exception : public std::exception {};

void throw_foo()
{
    throw foo_exception();
}
Run Code Online (Sandbox Code Playgroud)

查看当前堆栈,您可以看到这些内容位于何处:

    0027f6c4 e06d7363
    0027f6c8 00000001
    0027f6cc 00000000
    0027f6d0 751dc42d 内核库!RaiseException+0x58
    0027f6d4 00000003
    0027f6d8 19930520 
    0027f6dc 0027f770
    0027f6e0 0122ada0 langD!_TI2?AVfoo_异常
    ...

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

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
Run Code Online (Sandbox Code Playgroud)

该命令表示:从 开始@esp+18转储一个指针大小的值,然后将在那里找到的值也作为指针取消引用,并写入与第二个地址匹配的任何符号的名称。在本例中,它找到了该类的虚函数表foo_exception。这告诉我们地址处的对象0027f770是 a foo_exception。我们可以使用该信息来创建条件断点的表达式。

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

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

由于后面的勾号和撇号,我们必须引用它。我们还需要提取所需的堆栈值:

0:000> dpp @esp+18 L1
0027f6dc  0027f770 01225ffc langD!foo_exception::`vftable'
Run Code Online (Sandbox Code Playgroud)

poi运算符获取一个地址并返回存储在那里的指针大小的值。第一次求值将堆栈地址转换为对象地址,第二次求值将对象地址转换为我们需要进行比较的虚函数表地址。整个情况是这样的:

@!"langD!foo_exception::`vftable'"
Run Code Online (Sandbox Code Playgroud)

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

poi(poi(@esp+18))
Run Code Online (Sandbox Code Playgroud)

翻译:

  • 在第一次出现 C++ 异常时中断并运行以下命令:
  • foo_exceptionvtable 地址与对象的 vtable 地址进行比较@esp+18
  • 如果它们相同,则发出该gc命令,如果到达此命令时调试器正在运行,该命令将继续运行
  • (不要忘记转义内部引号)

如果您只想中断a foo_exception,请将条件从 更改==!=

需要记住的是,有时异常会作为指针而不是值抛出,这意味着您将需要在表达式的部分poi()周围再抛出一个异常。@esp您将能够分辨出来,因为当您使用 转储异常记录时.exrType将会是class foo_expression *。这完全取决于引发异常的代码,而不是异常类型本身,因此您可能需要.if根据情况调整条件。

最后,如果您想中断或跳过几种异常类型,这是可行的。我建议使用链接的,命令编写一个脚本并将自动命令设置为. 在一行上执行大量 if 条件链接可能非常难以阅读和正确执行,尤其是在进行额外转义的情况下。脚本不需要额外的转义。这是一个小例子:.if.elsifsxe$$><path\to\script

@!"langD!foo_exception::`vftable'" == poi(poi(@esp+18))
Run Code Online (Sandbox Code Playgroud)

(注意:每当运行时,Windbg 都会抱怨脚本错误,因为它不喜欢命令gc后跟其他任何内容。但它仍然运行良好)