【问题标题】:Why does excel crash here?为什么excel在这里崩溃?
【发布时间】:2019-03-12 01:57:12
【问题描述】:

下面的崩溃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 这样的外部代码)。

还有其他这样的例子吗?

干杯

【问题讨论】:

  • 根据事件查看器,这是 combase.dll 中的访问冲突。在没有附加调试器的情况下,我猜它试图在双重包装的Variant 的数据区域中释放一个空指针。 DoEvents 调用是一个红鲱鱼,Option 声明也是如此。如果您正在寻找“最短”,您可以使用x = IsMissing(t(1)) 之类的作业打出至少 81 个字符。那是没有触及空白。 :-P
  • Public Sub Boom() : Dim a(1), b() : a(1) = b : a(1) = IsMissing(a(1)) : End Sub (chat transcript)
  • 'Sub plop(): Dim b(): IsMissing Array(b)(1): End Sub`????
  • 不,plop() 不会导致任何崩溃。
  • 啊,我指定了选项基数 1....尝试:Sub plop(): Dim b(): IsMissing Array(b)(0): End Sub

标签: excel vba terminate


【解决方案1】:

tl;博士

你把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 打算传递一个传入参数,因此它还必须注意ParamArrays(在后台,ParamArray 只是一个Variant()) .虽然,如果你不给它任何参数,那么过程仍然会得到一个数组——它只是有一个比LBound低的UBound

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 的值都为零(this answer 有点像未初始化的数组看起来像在内存中)。该参数尚未通过 RPC 机制,因此这一点与包含一个传递参数的ParamArray 完全无法区分。它将检查变量是否被键入为 VT_ERROR,查看它是否是一个数组,并尝试以这种方式处理它。由于它不符合空参数列表的规范,它可能会通过取消引用数据区域来检查第一个传递的参数是什么。 SAFEARRAY 结构如下所示1:

回到反汇编并没有完全进入反汇编 vbe7.dll 的兔子洞,上一条指令中的 rax+8 很可能加载了 pvData 指针(在 64 位版本的Excel),你可以看到是零。


这只是IsMissing 实现中的一个错误——它应该检查空指针而不是盲目地取消引用它。我怀疑,虽然无法验证,但它是在 VB5 中引入的,以及允许非Variant 可选参数和默认值的更改。


1图片是codeguru上How Visual Basic 6 Stores Data的截图。

【讨论】:

  • 很好,这是一个非常全面的答案,基于深入的错误分析,使用工具我当然没有技能使用。谢谢!有关信息,我在尝试取消嵌套参数数组时遇到了这个问题。
  • @Comintern ...我很敬畏。欣赏答案......它很有启发性。
  • @TechnoDabbler 谢谢,那是written on a dare。 :-D
  • 太棒了!技术之旅!
猜你喜欢
  • 2010-11-29
  • 2011-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 2018-10-28
  • 2020-10-30
相关资源
最近更新 更多