Почему программа аварийно завершает работу даже при перехвате глобального исключения?
Сводка проблемы: какой-то код в 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.
Все, что я делаю, чтобы активировать ловушку, - это добавлять LogExceptions в список interface uses в Main.pas.
А теперь пошаговое описание сбоя:
Выполнение входит UartComm.OnGetIdRES()
ERangeError возникает, когда я пытаюсь установить Length динамического массива на -7:
SetLength(InfoBytes, InfoLength);
Входим 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 .... }
Как только мы выходим из HookGlobalException, отладчик выдает диалог:
поднял класс исключения ERangeError с сообщением "Ошибка проверки диапазона"
Если я нажму «Продолжить», программа все равно остановится. Без отладчика программа также зависает на этом этапе.
Если я нажму «Прервать» и продолжу работать с отладчиком, выполнение упадет через стек до VaComm.TVaCommEventThread.DoEvent и выполнит строку:
Application.HandleException(Self);
После чего он ничего не делает (я вошел в эту процедуру с отладчиком, и программа "работает" вечно).
Даже если я не использую библиотеку JCL для ловушки, а вместо этого указываю Application.OnException на какую-то пустую процедуру, происходит то же самое.
Почему исключение перехватывается обработчиком, а затем повторно возникает, когда перехватчик возвращается? Как я могу подавить исключение, чтобы программа не аварийно завершила работу, а продолжала работать?
ОБНОВЛЕНИЕ. Я сделал 3 великих открытия:
Ловушка JCL фактически перехватывала ВСЕ исключения, как обработанные, так и необработанные. Вот почему GlobalExceptHook() до того, как исключение выпадет из стека вызовов.
Application.OnException был назначен повторно назначен где-то еще в коде.
Application.HandleException выполнил OnException (но отладчик не показал мне этого, когда я попытался войти внутрь), и там была строка, которая пыталась закрыть COM-порт. ЭТО строка, из-за которой графический интерфейс моей программы просто завис.
Почему вы вообще пытаетесь установить для длины динамического массива отрицательное значение? NewLength должен быть либо 0, либо положительным числом.
- personSilverWarior  schedule17.06.2015
comment
@SilverWarior Да, я нашел эту ошибку и исправил ее. Но проблема в том, что таких ошибок больше, и мне нужен глобальный перехватчик, чтобы эти ошибки не привели к сбою моей программы.
- personDBedrenko  schedule17.06.2015
comment
Я знаю, что это не ответ на ваш вопрос, но, вероятно, лучше дать ему вылететь, чем позволить вашему приложению делать неожиданные вещи. Однако наблюдается ли у вас такое же поведение при использовании Application.OnException?
- personStefan Wanitzek  schedule17.06.2015
comment
@NewWorld Основная цель исключений - сообщить программисту, что что-то пошло не так и что это нужно исправить. Да, иногда у вас нет времени на исправление всех этих ошибок, поэтому вы бы предпочли просто скрыть их от конечных пользователей. Но это плохая практика, потому что пользователи все равно будут сталкиваться с этими ошибками, и единственная разница в том, что они не узнают об этом. Результатом будут ваши пользователи, переписывающие всевозможные странные ошибки, о которых вы даже не сможете догадаться, откуда они возникли.
- personSilverWarior  schedule17.06.2015
comment
@NewWorld Поэтому я настоятельно рекомендую вам найти и исправить все эти ошибки. Особенно те, которые вызывают исключения ERangeError. Почему? ERangeError означает, что ваша программа попыталась получить доступ к некоторой памяти, которая может принадлежать какому-то другому компоненту, классу и т. Д. Это может привести к серьезным проблемам и даже потере данных, что определенно не является тем, что вы хотите в вашей программе.
- personSilverWarior  schedule17.06.2015
comment
@viertausend Я думаю, что в продакшене лучше быть стабильным и не падать при такой икоте. Я пробовал установить Application.OnException на пустой метод. Но программа все равно вылетает. Он проходит через стек вызовов, который я разместил в VaComm.TVaCommEventThread.DoEvent, и выполняет строку Application.HandleException(Self);. После чего ничего не делает (зашёл с отладчиком, программа запущена).
- personDBedrenko  schedule17.06.2015
comment
@SilverWarior Я думаю, что в продакшене лучше быть стабильным. Если есть ошибка, в диалоговом окне появится сообщение об этом пользователю, и пользователь может отправить мне трассировку стека, если захочет. Я не могу исправить все ошибки; это невозможно ни для одного проекта.
- personDBedrenko  schedule17.06.2015
comment
Насколько я могу судить, функция только добавляет функцию в цепочку исключений, что означает, что она вызывается при каждом исключении, но затем снова вызывается нормальный программный поток - это означает, что после завершения глобального хука выполняется обычная обработка исключений. Посмотрите код в JclHookExcept.pas - он просто вызывает уведомители, а затем продолжает работу.
- personmrabat  schedule17.06.2015
comment
@mrabat Тогда как можно объяснить, что то же самое происходит, если я использую Application.OnException вместо хука JCL? Я добавил Шаг 5 в пост с дополнительной информацией.
- personDBedrenko  schedule17.06.2015
comment
@NewWorld Я не использую для обработки исключений с помощью хуков, но я верю, что ваша проблема зависит от того факта, что ваш хук не отмечает исключения как обрабатываемые, а только обнаруживает их. Это похоже на то, что отладчик Delphi не отмечает какие-либо исключения, которые он обнаруживает, как обработанные, а только приостанавливает выполнение программы. Но если вы возобновите выполнение кода, исключение все равно будет вызвано таким же образом, как если бы оно возникло без подключения отладчика к вашей программе.
- personSilverWarior  schedule17.06.2015
comment
@SilverWarior Понятно, вы знаете, как отмечать исключения как обработанные?
- personDBedrenko  schedule17.06.2015
comment
@NewWorld К сожалению, нет. если бы я хотел, я бы уже дал полный ответ. Но, честно говоря, я только предполагаю, что исключение не помечается как обработанное, потому что это кажется мне единственной логической возможностью, основанной на доступной информации.
- personSilverWarior  schedule17.06.2015
comment
Если ваша проблема не имеет зависимости от JCL, то почему вы включаете весь этот несвязанный код и информацию?
- personSertac Akyuz  schedule17.06.2015
comment
@SertacAkyuz Я понял, что только через 2 часа после публикации вопроса, когда кто-то предложил мне попробовать использовать Application.OnException.
- personDBedrenko  schedule17.06.2015
Я не совсем понимаю, что вы имеете в виду под словом «продолжать». Если вы имеете в виду продолжить с того места, где было возбуждено исключение, это не имеет смысла. Это точка исключений, чтобы фильтровать стек до тех пор, пока вы не достигнете точки, в которой вы можете встроить механизм восстановления и безопасно возобновить работу. Это означает, что вам нужно найти разумную точку или точки, чтобы разрешить возобновление вашей программы, и в этот момент использовать блок try ... except ... end. Как правило, исключения ведут себя так, как вы хотите, за исключением того, что журнал не записывается, и именно здесь появляется Application.OnException, и сообщение не то, что вы хотели бы, и здесь вам потребуется соответствующая попытка ... кроме .. .end блоки. Если ваша программа действительно дает сбой, то есть завершается или зависает, это больше связано с характером того, что вызвало исключение, чем с обработкой исключений как таковой, и никакие подделки не скроют этого.
personDsmschedule18.06.2015
comment
Под продолжением я имею в виду, что я перехожу к Application.HandleException(Self); на шаге 5, и он никуда не переходит, но программа возобновляет выполнение. Графический интерфейс завис, но программа работает. Я даже пробовал определить Application.OnException, но он так и не попал.
- personDBedrenko; 18.06.2015
comment
Тогда, как я уже сказал, это больше связано с характером проблемы, чем с обработкой исключений. В этом случае, вероятно, поврежден сам TApplication. Обычно такое случается, когда, например, вы удаляете уже удаленный объект. Попробуйте тот же эксперимент, но с использованием простого приложения (а не вашего настоящего) и просто используйте оператор «поднять», чтобы где-нибудь создать исключение. Вы увидите, что графический интерфейс не зависает и будет введено исключение OnException.
- personDsm; 19.06.2015
comment
Я так и сделал, добился прогресса в решении своей проблемы и по большей части решил ее. Когда я полностью это выясню, я отправлю ответ. Спасибо за помощь :)
- personDBedrenko; 19.06.2015
Проблема заключалась в том, что UartComm.TfrmUartComm.UartVaComm1RxChar был инициирован событием. Когда в этой подпрограмме возникло необработанное исключение, выполнение прервалось через стек вызовов, пока не достигло Application.OnException.
Внутри OnException пробовал закрыть COM-порт с помощью VaComm1.Close(). Частью Close() был призыв остановить поток VaComm1 и WaitFor() поток завершить. Но помните, что UartVaComm1RxChar так и не вернулся! Никогда не закончился! Так что этот WaitFor() ждет вечно.
Решением было включить TTimer внутри OnException и переместить подпрограмму VaComm1.Close() внутрь этого таймера. Программа завершила обработку возникшего исключения и вернулась к выполнению «основного» цикла с завершением события. Теперь TTimer запускает и закрывает COM-порт.
Application.OnException
на пустой метод. Но программа все равно вылетает. Он проходит через стек вызовов, который я разместил вVaComm.TVaCommEventThread.DoEvent
, и выполняет строкуApplication.HandleException(Self);
. После чего ничего не делает (зашёл с отладчиком, программа запущена). - person DBedrenko   schedule 17.06.2015Application.OnException
вместо хука JCL? Я добавил Шаг 5 в пост с дополнительной информацией. - person DBedrenko   schedule 17.06.2015Application.OnException
. - person DBedrenko   schedule 17.06.2015