1

Problem Summary: Some code in UartComm.OnGetIdRES() raises an ERangeError, which crashes my program. This bug isn't the problem, what matters is why my application-global exception hook catches the exception, but my program still crashes. I expect the hook to catch all unhandled exceptions and suppress them; the program should keep running.

Here is the unit responsible for the global exception hook:

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.

(If it's helpful here's a link to JclDebug.pas and JclHookExcept.pas)

All I do to activate the hook is to add LogExceptions to the interface uses list in Main.pas.

Now here is a step-by-step of the crash:

  1. Execution enters UartComm.OnGetIdRES()
  2. ERangeError is raised when I try to set the Length of a dynamic array to -7:

    SetLength(InfoBytes, InfoLength);

  3. We enter LogExceptions.HookGlobalException(). The call stack shown in the IDE at this moment is this (I left out memory addresses):

    ->  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. As soon we come out of HookGlobalException the debugger throws a dialog:

    raised exception class ERangeError with message 'Range check error'

If I press "Continue" program is still frozen work. Without the debugger the program also freezes at this point.

  1. If I click "Break" and keep stepping with the debugger, execution falls through the stack all the way into VaComm.TVaCommEventThread.DoEvent and executes the line:

    Application.HandleException(Self);
    

After which it does nothing (I stepped into this routine with the debugger and program is "running" forever).

Even if I don't use the JCL library for the hook, and instead point Application.OnException to some empty routine, the exact same thing happens.

Why is the exception caught by the hook and then re-raised when the hook returns? How can I suppress the exception so that the program doesn't crash but keeps running?

UPDATE: I made 3 great discoveries:

  • The JCL hook was actually catching ALL exceptions, whether handled or unhandled. That's why GlobalExceptHook() before the exception falls throurgh the call stack.
  • Application.OnException was assigned re-assigned somewhere else in the code.
  • Application.HandleException executed OnException (but the debugger didn't show me that when I tried to step inside) and there was a line there that tried to close a COM port. THIS is the line that made my program's GUI just freeze.

I'll write an answer when I figure everything out.

DBedrenko
  • 4,871
  • 4
  • 38
  • 73
  • Why are you even trying to set the length of a dynamic array to negative value in the first place? NewLength should be either 0 or a positive number. – SilverWarior Jun 17 '15 at 13:20
  • @SilverWarior Yes I found this bug and fixed it. But the issue is that there are more bugs like this out there and I need tho global hook preventing these errors from crashing my program. – DBedrenko Jun 17 '15 at 13:24
  • I know tht's not the answer to your question but it's propably better to let it crash than to let your application do unexpected things. However, do you have the same behaviour if you use Application.OnException ? – Stefan Wanitzek Jun 17 '15 at 13:33
  • @NewWorld The main purpose of the exceptions is to tell the programmer that something went wrong and that it needs to be fixed. Yes sometimes you don't have time to fix all those bugs so you would rather just hide them from your end users. But doing so is a bad practice becouse the users Will still encounter those bugs and the only diference is that they won't know about it. result Will be your users repowrting all sorts of wierd bugs that you won't be able to even suspect where they originated. – SilverWarior Jun 17 '15 at 13:42
  • @NewWorld So I strongly recomend you take the time to find and fix all those bugs. Especially the ones that are causing the ERangeError exceptions. Why? ERangeError means that your program tried to acces some memory that might belong to some other component, class, etc. This could lead to serious problems and even loss of data which is definitly not something you want in your prgoram. – SilverWarior Jun 17 '15 at 13:46
  • @viertausend I think in production it's better to be stable and not crash on a hiccup like this. I have tried setting `Application.OnException` to an empty method. But the program still crashes. It falls through the callstack I posted all the way into `VaComm.TVaCommEventThread.DoEvent` and executes the line `Application.HandleException(Self);`. After which it does nothing (I stepped into it with the debugger and program is "running"). – DBedrenko Jun 17 '15 at 13:52
  • @SilverWarior I think in production it's better to be stable. If there's an error the dialog will show saying this to the user, and the user can send me the stack trace if they want. I can't fix all bugs; it's not feasible on any project. – DBedrenko Jun 17 '15 at 13:54
  • 1
    As far as I can see the function only adds the function into the exception chain which means that it's called on every exception but then the normal program thread is called again - meaning that after the global hook finished the normal exception handling is executed. Check out the code in JclHookExcept.pas - it just calls the notifiers and then proceeds. – mrabat Jun 17 '15 at 14:10
  • 1
    @mrabat Then how can it be explained that the exact same thing happens if I use `Application.OnException` instead of the JCL hook? I added "Step 5" in the post with more info. – DBedrenko Jun 17 '15 at 14:14
  • @NewWorld I'm not used in handling exceptions using hooks but I belive your problem relies in the fact that your hook does not mark exceptions as being handled but only detects it. That is similar as Delphi debuger does not marks any exception it detects as handled but only pauses the execution of the program. But if you resume the code excecution the exception will still be reaised the same way is if it would be reaised without debugger being atached to your progarm. – SilverWarior Jun 17 '15 at 14:43
  • @SilverWarior I see, do you know how to mark exceptions as "handled"? – DBedrenko Jun 17 '15 at 14:48
  • @NewWorld Unfortunately not. if I would I would already provide a full answer. But to be honest I'm only guessing that the Exception is not getting marked as handled becouse this seems as the only logical posibility to me based on available information. – SilverWarior Jun 17 '15 at 14:54
  • If your problem does not have and JCL dependency, then why do you include all that unrelated code and information? – Sertac Akyuz Jun 17 '15 at 16:08
  • @SertacAkyuz I realised that only 2 hours after posting the question after someone suggested I try using `Application.OnException`. – DBedrenko Jun 17 '15 at 17:49

2 Answers2

0

I don't understand exactly what you mean by 'carry on'. If you mean carry on from where the exception was raised that makes no sense. That is the point of exceptions, to filter up the stack until you reach a point where you can build in a recovery mechanism and safely resume. This means you need to find a sensible point or points to enable resumption of your program and at that point use a try...except...end block. Generally exceptions behave just the way you want except that the the log is not written, which is where Application.OnException comes in, and the message is not what you would like, which is where you would need appropriate try... except...end blocks. If your program really crashes, i.e. terminates or freezes, that is more to do with the nature of what caused the exception than exception handling per se and no amount of fudging will hide this.

Dsm
  • 5,870
  • 20
  • 24
  • By carry on I mean I step into `Application.HandleException(Self);` in Step 5 and it doesn't step anywhere but the program resumes execution. The GUI is frozen, but the program is running. I even tried defining `Application.OnException` but it never gets entered. – DBedrenko Jun 18 '15 at 14:22
  • 1
    Then as I say this is more to do with the nature of the problem than with exception handling handling. In this case probably TApplication itself has become corrupted. Typically this kind of thing happens when, for example, you delete an already deleted object. Try this same experiment but using a simple application (rather than your real one) and just use a 'raise' statement to create an exception somewhere. You will see that the GUI does not freeze and OnException will be entered. – Dsm Jun 19 '15 at 13:11
  • I did just that and made progress with my problem and solved it for the most part. When I figure it out completely I will post an answer. Thanks your help :) – DBedrenko Jun 19 '15 at 13:19
0

The problem was that UartComm.TfrmUartComm.UartVaComm1RxChar was event triggered. When an unhandled exception was raised in this routine, execution fell through the call stack until it reached Application.OnException.

Inside OnException I tried to close the COM port with VaComm1.Close(). Part of Close() was a call to stop the VaComm1 thread and WaitFor() the thread to finish. But remember that the UartVaComm1RxChar never returned! The never finished! So this WaitFor() is waiting forever.

The solution was to enable a TTimer inside OnException and move the VaComm1.Close() routine inside this Timer. The program finished handling the raised exception and returned back to executing the "main" loop, with the event finished. Now the TTimer fires and closes the COM port.

More details here.

DBedrenko
  • 4,871
  • 4
  • 38
  • 73