为什么Excel在这里崩溃?

时间:2018-10-07 00:56:07

标签: excel vba terminate

以下是什么原因导致excel 2016崩溃?

Option Explicit
Option Compare Text
Option Base 1

Sub p1()
    Dim t(1), w()
    t(1) = w
    If IsMissing(w) Then DoEvents
    If IsMissing(t(1)) Then DoEvents
End Sub

w是一个未初始化的数组,但是将w传递到ismissing(w)很好,但是当通过t(1)传递时,excel可再现地终止了... >

这是我使用vba设法生成的最短的代码,它只能杀死excel应用程序(即不使用createobjectshell之类的外部代码)。

还有其他这样的例子吗?

欢呼

1 个答案:

答案 0 :(得分:17)

tl; dr

您将IsMissing弄得一团糟,因为它认为未初始化的数组是格式错误的ParamArray,并在实现中遇到了错误。


根据Windows事件查看器,导致Excel崩溃的根本异常是vbe7.dll中的访问冲突:

我看到的第一个报告(在上面进行注释)是Rubberduck回调失败,因为Excel是敬酒的。

Faulting application name: EXCEL.EXE, version: 15.0.5067.1000, time stamp: 0x5b76360d
Faulting module name: VBE7.DLL, version: 7.1.10.68, time stamp: 0x58def301
Exception code: 0xc0000005
Fault offset: 0x00000000001cd38c
Faulting process id: 0xac4
Faulting application start time: 0x01d45de81ef072ed
Faulting application path: C:\Program Files\Microsoft Office\Office15\EXCEL.EXE
Faulting module path: C:\PROGRA~1\COMMON~1\MICROS~1\VBA\VBA7.1\VBE7.DLL

访问冲突是由于VB运行时试图取消引用空指针而引起的。这是调试器在死亡时降落的地方:

00007FFF9CDDD383  mov         rax,qword ptr [rsp+30h]  
00007FFF9CDDD388  mov         rax,qword ptr [rax+8]  
00007FFF9CDDD38C  movzx       eax,word ptr [rax]    <--fails, rax is 0.
00007FFF9CDDD38F  cmp         eax,1  
00007FFF9CDDD392  jne         00007FFF9CD7B8A4

因此,让我们看一下IsMissing function正在检查什么。 VBA本质上是COM的野兽,被调用的函数以Variant的形式接收未传递的可选参数,该参数根据RPC约定具有VT_ERROR的类型。它不会作为NullEmpty或任何其他VB构造传递。以下代码演示:

Sub Foo()
    Bar
End Sub

Sub Bar(Optional x)
    Debug.Print VarPtr(x)   'This has a valid pointer.
    Debug.Print IsError(x)  'True
    Debug.Print VarType(x)  '10 (vbError)
End Sub

您可以阅读技术性更强的说明on Raymond Chen's blog


IsMissing意向传递的传入参数,因此它还必须注意ParamArray s(在幕后,ParamArray是只是Variant())。虽然,如果您不给它任何参数,过程仍然会得到一个数组-它的UBoundLBound低:

Sub Calling()
    Called
End Sub

Sub Called(ParamArray params())
    Debug.Print LBound(params)   '<-- 0
    Debug.Print UBound(params)   '<-- -1
End Sub

在这里变得很有趣。同一过程签名中不能同时包含ParamArray和可选参数-它可以是一个或另一个。当您查看函数签名时,会看到它需要指向Variant的指针(也就是说,它需要一个ByRef参数):

[entry(0x60000007), helpcontext(0x000f69b5)]
VARIANT_BOOL _stdcall IsMissing([in] VARIANT* ArgName);

但是这样的代码并不能提供预期的功能:

Sub DieExcelDie()
    Dim x(1), y()
    x(1) = y
    x(1) = IsMissing(x(1))
End Sub

x(1)必须编组为ByRef Variant,才能作为参数传递给IsMissing。因此,它 看到 的是一个Variant数组,其LBoundUBound值均为零({{3} }深入探讨了未初始化的数组在内存中的外观。该参数尚未通过RPC机制进行处理,因此与包含一个传递参数的ParamArray完全 无关。它将检查变体是否键入为VT_ERROR,看是否它是一个数组,然后尝试以这种方式进行处理。由于它不符合空参数列表的规范,因此它可能通过取消引用数据区域来检查第一个传递的参数是什么。 SAFEARRAY结构看起来像这样 1

this answer

返回反汇编,而不是完全解开vbe7.dll的麻烦,上一条指令中的rax+8最有可能加载了pvData指针(一个8字节Excel的64位版本),您可以看到它为零。


这只是IsMissing实现中的错误-应该检查空指针,而不是盲目地取消引用它。我怀疑,尽管无法验证,但它是与VB5一起引入的,并且所做的更改允许非{Variant可选参数和默认值。


1 图像是来自Codeguru上SAFEARRAY in memory的屏幕截图。