Почему программа аварийно завершает работу даже при перехвате глобального исключения?

Сводка проблемы: какой-то код в UartComm.OnGetIdRES() вызывает ERangeError, что приводит к сбою моей программы. Проблема не в этой ошибке, важно то, почему мой обработчик глобального исключения приложения перехватывает исключение, но моя программа по-прежнему дает сбой. Я ожидаю, что ловушка перехватит все необработанные исключения и подавит их; программа должна продолжать работать.

Вот модуль, отвечающий за ловушку глобального исключения:

unit LogExceptions;

interface
uses
  Windows, SysUtils, Classes, JclDebug, JclHookExcept;

procedure AppendToLog(Msg: String; const LogFileLevel: TLogFileLevel);

implementation
uses Main;

procedure HookGlobalException(ExceptObj: TObject; ExceptAddr: Pointer;
                              OSException: Boolean);
var
  Trace: TStringList;
  DlgErrMsg: String;
begin
  { Write stack trace to `error.log`. }
  Trace := TStringList.Create;
  try
    Trace.Add(
        Format('{ Original Exception - %s }', [Exception(ExceptObj).Message]));
    JclLastExceptStackListToStrings(Trace, False, True, True, False);
    Trace.Add('{ _______End of the exception stact trace block_______ }');
    Trace.Add(' ');

    Trace.LineBreak := sLineBreak;
    LogExceptions.AppendToLog(Trace.Text, lflError);

    { Show an dialog to the user to let them know an error occured. }
    DlgErrMsg := Trace[0] + sLineBreak +
      Trace[1] + sLineBreak +
      sLineBreak +
      'An error has occured. Please check "error.log" for the full stack trace.';
    frmMain.ShowErrDlg(DlgErrMsg);
  finally
    Trace.Free;
  end;
end;

procedure AppendToLog(Msg: String; const LogFileLevel: TLogFileLevel);
{ .... irrelevant code ....}

initialization
  Include(JclStackTrackingOptions, stTraceAllExceptions);
  Include(JclStackTrackingOptions, stRawMode);

  // Initialize Exception tracking
  JclStartExceptionTracking;

  JclAddExceptNotifier(HookGlobalException, npFirstChain);
  JclHookExceptions;

finalization
  JclUnhookExceptions;
  JclStopExceptionTracking;

end.

(Если это полезно, вот ссылка на _ofol. a> и

Все, что я делаю, чтобы активировать ловушку, - это добавлять LogExceptions в список interface uses в Main.pas.

А теперь пошаговое описание сбоя:

  1. Выполнение входит UartComm.OnGetIdRES()
  2. ERangeError возникает, когда я пытаюсь установить Length динамического массива на -7:

    SetLength(InfoBytes, InfoLength);

  3. Входим LogExceptions.HookGlobalException(). Стек вызовов, показанный на данный момент в среде IDE, выглядит следующим образом (я не учел адреса памяти):

    ->  LogExceptions.HookGlobalException
        :TNotifierItem.DoNotify
        :DoExceptNotify
        :HookedRaiseException
        :DynArraySetLength
        :DynArraySetLength
        :@DynArraySetLength
        UartComm.TfrmUartComm.OnSpecificRES // This method runs `OnGetIdRES()`
        UartComm.TfrmUartComm.OnSpecificPktRX
        UartComm.TfrmUartComm.DisplayUartFrame
        UartComm.TfrmUartComm.UartVaComm1RxChar
        VaComm.TVaCustommComm.HandleDataEvent
        VaComm.TVaCommEventThread.DoEvent
        { ... }
        { ... Some low-level calls here .... }
    
  4. Как только мы выходим из HookGlobalException, отладчик выдает диалог:

    поднял класс исключения ERangeError с сообщением "Ошибка проверки диапазона"

Если я нажму «Продолжить», программа все равно остановится. Без отладчика программа также зависает на этом этапе.

  1. Если я нажму «Прервать» и продолжу работать с отладчиком, выполнение упадет через стек до VaComm.TVaCommEventThread.DoEvent и выполнит строку:

    Application.HandleException(Self);
    

После чего он ничего не делает (я вошел в эту процедуру с отладчиком, и программа "работает" вечно).

Даже если я не использую библиотеку JCL для ловушки, а вместо этого указываю Application.OnException на какую-то пустую процедуру, происходит то же самое.

Почему исключение перехватывается обработчиком, а затем повторно возникает, когда перехватчик возвращается? Как я могу подавить исключение, чтобы программа не аварийно завершила работу, а продолжала работать?

ОБНОВЛЕНИЕ. Я сделал 3 великих открытия:

  • Ловушка JCL фактически перехватывала ВСЕ исключения, как обработанные, так и необработанные. Вот почему GlobalExceptHook() до того, как исключение выпадет из стека вызовов.
  • Application.OnException был назначен повторно назначен где-то еще в коде.
  • Application.HandleException выполнил OnException (но отладчик не показал мне этого, когда я попытался войти внутрь), и там была строка, которая пыталась закрыть COM-порт. ЭТО строка, из-за которой графический интерфейс моей программы просто завис.

Напишу ответ, когда во всем разберусь.


person DBedrenko    schedule 17.06.2015    source источник
comment
Почему вы вообще пытаетесь установить для длины динамического массива отрицательное значение? NewLength должен быть либо 0, либо положительным числом.   -  person SilverWarior    schedule 17.06.2015
comment
@SilverWarior Да, я нашел эту ошибку и исправил ее. Но проблема в том, что таких ошибок больше, и мне нужен глобальный перехватчик, чтобы эти ошибки не привели к сбою моей программы.   -  person DBedrenko    schedule 17.06.2015
comment
Я знаю, что это не ответ на ваш вопрос, но, вероятно, лучше дать ему вылететь, чем позволить вашему приложению делать неожиданные вещи. Однако наблюдается ли у вас такое же поведение при использовании Application.OnException?   -  person Stefan Wanitzek    schedule 17.06.2015
comment
@NewWorld Основная цель исключений - сообщить программисту, что что-то пошло не так и что это нужно исправить. Да, иногда у вас нет времени на исправление всех этих ошибок, поэтому вы бы предпочли просто скрыть их от конечных пользователей. Но это плохая практика, потому что пользователи все равно будут сталкиваться с этими ошибками, и единственная разница в том, что они не узнают об этом. Результатом будут ваши пользователи, переписывающие всевозможные странные ошибки, о которых вы даже не сможете догадаться, откуда они возникли.   -  person SilverWarior    schedule 17.06.2015
comment
@NewWorld Поэтому я настоятельно рекомендую вам найти и исправить все эти ошибки. Особенно те, которые вызывают исключения ERangeError. Почему? ERangeError означает, что ваша программа попыталась получить доступ к некоторой памяти, которая может принадлежать какому-то другому компоненту, классу и т. Д. Это может привести к серьезным проблемам и даже потере данных, что определенно не является тем, что вы хотите в вашей программе.   -  person SilverWarior    schedule 17.06.2015
comment
@viertausend Я думаю, что в продакшене лучше быть стабильным и не падать при такой икоте. Я пробовал установить Application.OnException на пустой метод. Но программа все равно вылетает. Он проходит через стек вызовов, который я разместил в VaComm.TVaCommEventThread.DoEvent, и выполняет строку Application.HandleException(Self);. После чего ничего не делает (зашёл с отладчиком, программа запущена).   -  person DBedrenko    schedule 17.06.2015
comment
@SilverWarior Я думаю, что в продакшене лучше быть стабильным. Если есть ошибка, в диалоговом окне появится сообщение об этом пользователю, и пользователь может отправить мне трассировку стека, если захочет. Я не могу исправить все ошибки; это невозможно ни для одного проекта.   -  person DBedrenko    schedule 17.06.2015
comment
Насколько я могу судить, функция только добавляет функцию в цепочку исключений, что означает, что она вызывается при каждом исключении, но затем снова вызывается нормальный программный поток - это означает, что после завершения глобального хука выполняется обычная обработка исключений. Посмотрите код в JclHookExcept.pas - он просто вызывает уведомители, а затем продолжает работу.   -  person mrabat    schedule 17.06.2015
comment
@mrabat Тогда как можно объяснить, что то же самое происходит, если я использую Application.OnException вместо хука JCL? Я добавил Шаг 5 в пост с дополнительной информацией.   -  person DBedrenko    schedule 17.06.2015
comment
@NewWorld Я не использую для обработки исключений с помощью хуков, но я верю, что ваша проблема зависит от того факта, что ваш хук не отмечает исключения как обрабатываемые, а только обнаруживает их. Это похоже на то, что отладчик Delphi не отмечает какие-либо исключения, которые он обнаруживает, как обработанные, а только приостанавливает выполнение программы. Но если вы возобновите выполнение кода, исключение все равно будет вызвано таким же образом, как если бы оно возникло без подключения отладчика к вашей программе.   -  person SilverWarior    schedule 17.06.2015
comment
@SilverWarior Понятно, вы знаете, как отмечать исключения как обработанные?   -  person DBedrenko    schedule 17.06.2015
comment
@NewWorld К сожалению, нет. если бы я хотел, я бы уже дал полный ответ. Но, честно говоря, я только предполагаю, что исключение не помечается как обработанное, потому что это кажется мне единственной логической возможностью, основанной на доступной информации.   -  person SilverWarior    schedule 17.06.2015
comment
Если ваша проблема не имеет зависимости от JCL, то почему вы включаете весь этот несвязанный код и информацию?   -  person Sertac Akyuz    schedule 17.06.2015
comment
@SertacAkyuz Я понял, что только через 2 часа после публикации вопроса, когда кто-то предложил мне попробовать использовать Application.OnException.   -  person DBedrenko    schedule 17.06.2015


Ответы (2)


Я не совсем понимаю, что вы имеете в виду под словом «продолжать». Если вы имеете в виду продолжить с того места, где было возбуждено исключение, это не имеет смысла. Это точка исключений, чтобы фильтровать стек до тех пор, пока вы не достигнете точки, в которой вы можете встроить механизм восстановления и безопасно возобновить работу. Это означает, что вам нужно найти разумную точку или точки, чтобы разрешить возобновление вашей программы, и в этот момент использовать блок try ... except ... end. Как правило, исключения ведут себя так, как вы хотите, за исключением того, что журнал не записывается, и именно здесь появляется Application.OnException, и сообщение не то, что вы хотели бы, и здесь вам потребуется соответствующая попытка ... кроме .. .end блоки. Если ваша программа действительно дает сбой, то есть завершается или зависает, это больше связано с характером того, что вызвало исключение, чем с обработкой исключений как таковой, и никакие подделки не скроют этого.

person Dsm    schedule 18.06.2015
comment
Под продолжением я имею в виду, что я перехожу к Application.HandleException(Self); на шаге 5, и он никуда не переходит, но программа возобновляет выполнение. Графический интерфейс завис, но программа работает. Я даже пробовал определить Application.OnException, но он так и не попал. - person DBedrenko; 18.06.2015
comment
Тогда, как я уже сказал, это больше связано с характером проблемы, чем с обработкой исключений. В этом случае, вероятно, поврежден сам TApplication. Обычно такое случается, когда, например, вы удаляете уже удаленный объект. Попробуйте тот же эксперимент, но с использованием простого приложения (а не вашего настоящего) и просто используйте оператор «поднять», чтобы где-нибудь создать исключение. Вы увидите, что графический интерфейс не зависает и будет введено исключение OnException. - person Dsm; 19.06.2015
comment
Я так и сделал, добился прогресса в решении своей проблемы и по большей части решил ее. Когда я полностью это выясню, я отправлю ответ. Спасибо за помощь :) - person DBedrenko; 19.06.2015

Проблема заключалась в том, что UartComm.TfrmUartComm.UartVaComm1RxChar был инициирован событием. Когда в этой подпрограмме возникло необработанное исключение, выполнение прервалось через стек вызовов, пока не достигло Application.OnException.

Внутри OnException пробовал закрыть COM-порт с помощью VaComm1.Close(). Частью Close() был призыв остановить поток VaComm1 и WaitFor() поток завершить. Но помните, что UartVaComm1RxChar так и не вернулся! Никогда не закончился! Так что этот WaitFor() ждет вечно.

Решением было включить TTimer внутри OnException и переместить подпрограмму VaComm1.Close() внутрь этого таймера. Программа завершила обработку возникшего исключения и вернулась к выполнению «основного» цикла с завершением события. Теперь TTimer запускает и закрывает COM-порт.

Подробнее здесь.

person DBedrenko    schedule 15.07.2015