Почему Excel падает здесь? - PullRequest
       11

Почему Excel падает здесь?

0 голосов
/ 07 октября 2018

Есть ли какая-то причина, по которой приведенные ниже сбои в 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 (т.е. не использует внешний код, такой как createobject или shell).

Есть ли другие подобные примеры?

cheers

1 Ответ

0 голосов
/ 07 октября 2018

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 .VBA - это, по сути, зверь COM, и необязательный параметр, который не передается, принимается вызываемой функцией как Variant с типом VT_ERROR для соглашения RPC.Он не передается как Null, Empty или любая другая конструкция 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

Вы можете прочитать чуть более техническое объяснение в блоге Раймонда Чена .


IsMissing предназначен для передачи входящего параметра, поэтому он также должен знать о ParamArray с (под капотом ParamArray - это просто Variant()).Хотя, если вы не дадите ей никаких параметров, процедура все равно получит массив - он просто на UBound ниже, чем LBound:

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);

Но такой код не 't дает функции то, что она ожидает увидеть:

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

x(1) необходимо упорядочить в ByRef Variant для передачи в качестве параметра в IsMissing.Итак, что он видит - это массив Variant, который имеет значение LBound и UBound, равное нулю ( этот ответ идет немного вкак выглядит неинициализированный массив в памяти).Параметр еще не прошел механизм RPC, поэтому на данный момент он совершенно неотличим от ParamArray, содержащего один переданный параметр.Он проверит, был ли введен вариант VT_ERROR, что это массив, и попытался бы обработать его таким образом.Поскольку он не соответствует спецификации для пустого списка параметров, он, скорее всего, проверяет, что было первым переданным параметром ... путем разыменования области данных.Структура SAFEARRAY выглядит следующим образом 1 :

SAFEARRAY in memory

Возвращаясь к разборке и не спускаясь полностью вниз по кроличьей норедизассемблирования vbe7.dll, rax+8 в предыдущей инструкции, скорее всего, загружает указатель pvData (это 8 байт в 64-битной версии Excel), который, как вы видите, равен нулю.


Это просто ошибка в реализации IsMissing - она ​​должна проверять нулевой указатель, а не разыменовывать его вслепую.Я подозреваю, хотя не могу проверить, что это было введено в VB5 вместе с изменениями, которые позволили как необязательные параметры, так и не Variant и значения по умолчанию.


1 Изображение было взято как скриншот с Как Visual Basic 6 хранит данные в codeguru.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...