11 мая 2022 г.

Почему рекомендуется перезапускать приложение после вылета

К нам обратился клиент с жалобой на то, что EurekaLog скрывает его приложение.

В частности, клиент пытался сделать так, чтобы диалог об ошибке показывался бы только для некоторых исключений. Делать он это пытался с помощью такого кода:
if AShowDlg then
  EI.Options.ExceptionDialogType := edtEurekaLog
else
  EI.Options.ExceptionDialogType := edtNone;
а проверял - на таком:
procedure TForm1.Button1Click(Sender: TObject);
var
  List: TStringList;
begin
  try
    List.Add('Test');
  except
    on E: Exception do
      HandleError(E);
  end;
end;
Где HandleError - это код клиента по обработке исключения.

При этом клиент утверждал, что "без EurekaLog всё работает верно", а "с EurekaLog после обработки тестогового исключения возбуждается (ещё один) Access Violation и приложение пропадает с монитора, но всё ещё показывается в диспетчере задач".

Видите ли вы проблему в указанном коде?

Проблема состоит в том, что тестовый пример использует неинициализированную переменную List. Это означает, что в ней может быть любое значение (равное произвольному мусору на стеке, оставшемуся после выполнения предыдущего кода).

Так уж получилось (читай: "повезло"), что в приложении без EurekaLog этот мусор указывал на недопустимую область памяти, поэтому попытка вызова List.Add выбрасывала исключение Access Violation сразу же, при попытке вызова метода. Сообщение при этом выглядело следующим образом: 'Access violation at address 00000001 in module 'Project1.exe'. Read of address 00000001'. Заметьте, что оба адреса совпадают и являются недопустимыми.

Однако когда в приложение была добавлена EurekaLog, ситуация изменилась, и на стеке оказалось иное мусорное значение. Так уж получилось (читай: "не повезло"), что этот мусор указывал на область памяти, в которой был записан адрес метода. Мы не знаем, какового было это значение на машине клиента, но при проверке у нас значение в памяти указывало на метод TCustomForm.Create. Поэтому вызов List.Add на самом деле вызывал TCustomForm.Create - с мусорным Self (указывающим на главную форму) и мусорными аргументами. Разумеется, корректно отработать такое не может. Код VCL начинал выполнение, использовал мусор из аргументов, и в итоге вылетал при попытке зарегистрировать созданную форму в несуществующем Owner. Сообщение при этом выглядело следующим образом: 'Access violation at address 004092F9 in module 'Project1.exe'. Read of address E8C78BD6'. Заметьте, что адреса различны; и первый адрес - корректен, а второй - нет.

EurekaLog ловила это исключение (которое клиент ошибочно принимал за исключение при попытке вызова List.Add) и обрабатывала его корректно. Однако VCL при этом уже была в испорченном состоянии. Соответсвенно, при показе диалога EurekaLog (или после его закрытия) цикл обработки сообщений обращался к сохранённым мусорным данным, что и приводило к второму ("неправильному") исключению Access Violation (которое EurekaLog также ловила).

Приложение же "пропадало" по той причине, что главная форма "затиралась", когда она передавалась как Self в TCustomForm.Create.

Именно поэтому, если вы хотите протестировать выброс исключений, то вам следует делать так:
List := nil; // - добавлено
List.Add('Test');

Этот пример также отлично показывает почему вам следует перезапускать или закрывать приложение сразу после обработки исключения: ваше приложение может находится в испорченном состоянии. Действительно, если бы оно находилось в корректном состоянии, то непредвиденного исключения не произошло бы. А раз непредвиденное произошло, то приложение уже не находится в ожидаемом состоянии. Это означает, что его дальнейшее поведение не определено. Попытка продолжить выполнение может привести к порче данных, выбросу других, крайне сложно диагностируемых исключений, и так далее.

Конечно, при этом крайне важно отделять действительно "неожиданные" исключения от "штатных". К сожалению, это обширная тема, которая выходит за рамки этой заметки.

Комментариев нет :

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.