madExcept Unit

The unit "madExcept" implements all the exception handling stuff. A full list of what is contained in this unit can be found in the madExcept Unit Reference.

When an exception occurs, madExcept creates an interface named "IMEException", which not only contains all the information about the exception, but which inherits the madExcept settings from the module, in which the exception occurred. In your own exception callbacks you can then change the settings for the current exception only, if you like, by accessing the properties which the "IMEException" interface inherited from the IMESettings interface.

  IMEException = interface (IMESettings) ['{5EE7E49B-571F-4504-B9E6-1EEF6587C51F}'];

The "IMEException" interface contains information about the current state of the exception:


  // in what phase of exception handling are we right now?
  TExceptPhase = (epNotHandledYet,   // for manually created exception only
                                     // see NewException
                  epQuickFiltering,  // called before bugReport/screenShot are created
                                     // main purpose: fast exception filtering
                  epMainPhase,       // you can do everything here, e.g. showing your
                                     // own exception box, filtering, whatever
                  epPostProcessing,  // you can't filter exceptions (= abort exception
                                     // handling) during this phase, anymore
                  epCompleteReport   // gets called when the rendering of the bug
                                     // report is complete

property IMEException.Phase       : TExceptPhase;  // see type definition above
property IMEException.CanContinue : boolean;       // can the program continue after this exception?

There are lots of properties which give you more information about the exception itself and about the circumstances in which it occurred:


  // what kind of exception is this?
  TExceptType = (etNormal, etFrozen, etHidden);

  // who initiated madExcept exception handling?
  TExceptSource = (esManual,                         // you called HandleException manually
                   esAntiFreezeCheck,                // the anti freeze check gave alarm
                   esBcbAbnormalProgramTermination,  // BCB: abnormal program termination detected
                   esSysUtilsShowException,          // SysUtils.ShowException was called
                   esApplicationShowException,       // Application.ShowException was called
                   esApplicationHandleException,     // Application.HandleException was called
                   esCGI,                            // TCGIApplication.HandleException was called
                   esISAPI,                          // TISAPIApplication.HandleException was called
                   esHttpExtension,                  // exception inside of our HttpExtensionProc
                   esIntraweb,                       // exception inside of IntraWeb client session
                   esTThreadSynchronize,             // exception inside of TThread.Synchronize
                   esRuntimeError,                   // runtime error detected
                   esInitializePackage,              // init. of dynamically loaded package crashed
                   esExceptProc,                     // System.ExceptProc was called
                   esSystemExceptionHandler,         // System.ExceptionHandler caught exception
                   esHidden,                         // hidden exception was detected
                   esThreadFrame,                    // win32 thread function crashed
                   esTThreadExecute,                 // TThread.Execute crashed
                   esUnhandledFilter);               // unhandled exception filter was called

property IMEException.ExceptType        : TExceptType;       // what kind of exception is this?
property IMEException.ExceptObject      : TObject;           // return the "ExceptObject" that was raised
property IMEException.ExceptClass       : string;            // which class does the "ExceptObject" have?
property IMEException.ExceptMessage     : string;            // which message does the "ExceptObject" contain?
property IMEException.ExceptAddr        : pointer;           // at which code address was the exception raised?
property IMEException.CrashedThreadId   : dword;             // which thread crashed?
property IMEException.Context           : PContext;          // which context did the thread have at crash time?
property IMEException.ExceptionRecord   : TExceptionRecord;  // which exception record belongs to the exception?
property IMEException.Source            : TExceptSource;     // where was the exception caught by madExcept?
property IMEException.RelatedObject     : TObject;           // can e.g. be the TThread object of the crashing thread
property IMEException.Package           : dword;             // the package which crashed during "InitializePackage"
property IMEException.BugReportHeader   : IMEFields;         // access to the bug report header
property IMEException.BugReportSections : IMEFields;         // access to the bug report sections
property IMEException.ScreenShot        : INVBitmap;         // access to the screen shot

property IMEException.ThreadIds  [index: integer] : dword;         // id of the specified thread index (enum until 0)
property IMEException.Callstacks [index: integer] : TStackTrace;   // callstack of the specific thread index

There are 2 ways to ask the composed bug report text. You can either ask the "IMEException.BugReport" property. Or you can call the "IMEException.GetBugReport" method. If you want to understand the difference, you first need to understand, that madExcept calculates the bug report in a background thread, while already calling your registered exception handlers at the same time. As long as you behave nicely, this logic works quite fine and results in that the exception box can be shown without delay, although the calculation of the full bug report might not be fully done yet.

However, if you ask "IMEException.BugReport" or if you call "IMEException.GetBugReport(true)", then you force madExcept to complete the full bug report composition, before it can return to you. When doing this in your registered exception handler you might delay the showing of the exception box and you might see a progress bar window instead. You can get around that by calling "IMEException.GetBugReport(false)" instead. In that case you'll get a bug report without delaying anything. However, the bug report may not be fully rendered yet.

Generally you should avoid asking "IMEException.BugReport" or calling "IMEException.GetBugReport(true)" in your exception handler. If you absolutely do need access to the complete bug report text, you should when calling RegisterExceptionHandler explicitly ask for being called when the exception phase is "epCompleteReport". This way you don't delay anything, however, the exception box is likely to be shown before your exception callback is called. Hey - you can't have it all. But at least you have the choice.

function IMEException.GetBugReport (mustBeComplete: boolean = true) : string;
property IMEException.   BugReport                                  : string;

In some situations you might want to check whether the current exception is already stored somewhere. Comparing exceptions is not too easy, though. Especially comparing callstacks is tough. So madExcept offers you some help by adding a "CallstackCrc" property to the "IMEException" interface. It consists of 3 dwords. The first dword designates the exception location only. The second dword is a crc of the callstack of the crashed thread. The third dword is a crc of the callstack of all delphi threads. Please note that this information is only available after all the callstacks were fully calculated. So asking this property before the "epCompleteReport" phase doesn't make too much sense.

property IMEException.CallstackCrc [index: integer] : dword;

As already explained, the composition of the bug report is done in background. If you want to be notified about any progress the composition makes, you can register a callback function. The "critical" parameter states if your callback absolutely does need to get the complete bug report. If not, and if the user aborts the exception box before the rendering of the bug report is complete, the rendering can be aborted to save processing time.

  TBugReportCallback   = procedure (complete: boolean; bugReport: string; exceptIntf: IMEException);
  TBugReportCallbackOO = procedure (complete: boolean; bugReport: string; exceptIntf: IMEException) of object;

procedure IMEException.  RegisterBugReportCallback (bugReportCallback: TBugReportCallback;   critical: boolean); overload;
procedure IMEException.  RegisterBugReportCallback (bugReportCallback: TBugReportCallbackOO; critical: boolean); overload;
procedure IMEException.UnregisterBugReportCallback (bugReportCallback: TBugReportCallback  ); overload;
procedure IMEException.UnregisterBugReportCallback (bugReportCallback: TBugReportCallbackOO); overload;

Everytime you change the bug report header or the bug report sections, callbacks registered through IMEException.RegisterBugReportCallback are called, which can result in follow up actions. If you are doing multiple changes to the bug report header and/or sections, you should call "IMEException.BeginUpdate" before and "IMEException.EndUpdate" after doing your changes. This will make sure that the callbacks are fired only once.

procedure IMEException.BeginUpdate;
procedure IMEException.EndUpdate;

There are some settings stored in the "IMEException" interface, which you can change, which however only have effect during the "epQuickFiltering" phase:

property IMEException.CreateBugReport  : boolean;  // shall a bug report be created for this exception?
property IMEException.CreateScreenShot : boolean;  // shall a screen shot be created for this exception?
property IMEException.CallHandlers     : boolean;  // shall event handlers get called for this exception?

The following settings can be changed and show effect during all phases of exception handling:

property IMEException.AppendScreenShot : boolean;         // append the screen shot when sending the bug report?
property IMEException.ShowSetting      : TMEShowSetting;  // what shall be shown for this exception?
property IMEException.ShowAssistant    : string;          // which assistant shall be shown for this exception?

You can force an exception box to be shown by calling "IMEException.Show". But actually this makes sense only, if you've manually created the exception by calling NewException. Otherwise you usually shouldn't call this method.

procedure IMEException.Show;  // show the exception box

Calling one of the following methods has the same effect as the end user pressing one of the buttons in the exception box. Calling "ShowBugReport" only has any effect if the exception box is actually currently visible. The other methods work even if the exception box is not currently visible. If you leave the "parentWindow" parameter zero, madExcept will use its own exception box main window handle as the parent window.

procedure IMEException. ShowBugReport (parentWindow: dword = 0);  // show the bug report details
procedure IMEException. SendBugReport (parentWindow: dword = 0);
procedure IMEException. SaveBugReport (parentWindow: dword = 0);
procedure IMEException.PrintBugReport (parentWindow: dword = 0);

procedure IMEException.ContinueApplication (parentWindow: dword = 0);
procedure IMEException. RestartApplication (parentWindow: dword = 0);
procedure IMEException.   CloseApplication (parentWindow: dword = 0);

You can manually create an "IMEException" interface. You can do that if you want to get a bug report, or if you want to show an exception box. You can ignore most parameters. If you just want to have a snapshot of the current situation (makes sense e.g. if the application seems to be frozen), just leave all parameters empty.

function NewException (exceptType      : TExceptType       = etFrozen;
                       exceptObject    : TObject           = nil;
                       exceptAddr      : pointer           = nil;
                       canContinue     : boolean           = true;
                       crashedThreadId : dword             = 0;
                       currentEsp      : dword             = 0;
                       currentEbp      : dword             = 0;
                       context         : PContext          = nil;
                       settings        : IMEModuleSettings = nil;
                       source          : TExceptSource     = esManual;
                       relatedObject   : TObject           = nil;
                       package         : dword             = 0;
                       preparedStack   : pointer           = nil;
                       dontCompleteYet : boolean           = false   ) : IMEException;

If you got a bug report from somewhere in text form, you can turn it into an "IMEException" interface by calling "ImportBugReport". Please note that many properties won't work properly, e.g. IMEException.ExceptObject. The main purpose of this function is to show the bug report with the standard madExcept dialog.

function ImportBugReport (const bugReport: UnicodeString) : IMEException;

// Example:

The function "HandleException" does all the stuff like firing the registered exception handlers and showing the exception box. If you want your dlls to handle their own exceptions, you should put a "try..except" frame around every exported function and around every message handler.

procedure HandleException (exceptType    : TExceptType   = etNormal;
                           exceptObject  : TObject       = nil;
                           exceptAddr    : pointer       = nil;
                           canContinue   : boolean       = true;
                           currentEsp    : dword         = 0;
                           currentEbp    : dword         = 0;
                           context       : PContext      = nil;
                           source        : TExceptSource = esManual;
                           relatedObject : TObject       = nil;
                           package       : dword         = 0;
                           showReport    : TPString      = nil     );

// Example:
library blabla;

uses madExcept, SysUtils;

procedure DllExportedFunction;
    // real code here

exports DllExportedFunction;


When using the option "pause running delphi/bcb threads", madExcept automatically suspends all delphi/bcb threads in the moment when they call GetMessage or PeekMessage. If you have a thread which you want to be paused during exception handling, but which doesn't call GetMessage nor PeekMessage, you can call "PauseMeEventually" in the thread's main loop. Calling this function doesn't harm anywhere. It just checks whether the current thread should be suspended and in that case suspends it. You can add it to your threads without danger.

procedure PauseMeEventually;

// Example:
function SomeThread(param: pointer) : integer; stdcall;
// this thread gets suspended during exception handling thanks to the
// "PauseMeEventually" call. Place the call at the location where you want
// the thread to be suspended (during exception handling)
  while ReadFile(...) do begin
    // do some stuff
    // do some other stuff

You can register exception handlers, which then get called for every exception that occurs thereafter. There's no limit, you can register as many exception handlers as you like. Each one is called in a row, until one sets "handled" to true. For each exception handler you can choose whether you want madExcept to try to call your handler in the context of the main thread - and what shall happen if the main thread doesn't respond (see "TSyncType"). Furthermore you can choose in what phase of exception handling you want your handler to be called (see "TExceptPhase").

  // stDontSync:             Don't synchronize the handler, instead call it in the context
  //                         of madExcept's exception handling thread.
  //                         + It doesn't matter what the main thread is doing.
  //                         - You must not use the VCL, cause it's not thread safe.
  // stTrySyncCallOnSuccess: Try to call the handler in the context of the main thread.
  //                         If the main thread doesn't react, don't call this handler.
  //                         + You may use the VCL, cause the handler is only ever called
  //                           in the context of the main thread.
  //                         - If synchronzation fails, your handler isn't called.
  // stTrySyncCallAlways:    Try to call the handler in the context of the main thread.
  //                         If the main thread doesn't react, call the handler nevertheless.
  //                         + The handler is always called - and if possible in the context
  //                           of the main thread. In the latter case you may use the VCL.
  //                         - You may use the VCL only if you're in the main thread context.
  //                           (if GetCurrentThreadID = MainThreadID then)
  TSyncType = (stDontSync, stTrySyncCallOnSuccess, stTrySyncCallAlways);
  // types for RegisterExceptionHandler
  TExceptEvent   = procedure (const exceptIntf : IMEException;
                              var handled      : boolean);
  TExceptEventOO = procedure (const exceptIntf : IMEException;
                              var handled      : boolean) of object;

procedure  RegisterExceptionHandler (exceptHandler: TExceptEvent;
                                     sync         : TSyncType;
                                     phase        : TExceptPhase = epMainPhase); overload;
procedure  RegisterExceptionHandler (exceptHandler: TExceptEventOO;
                                     sync         : TSyncType;
                                     phase        : TExceptPhase = epMainPhase); overload;

function UnregisterExceptionHandler (exceptHandler: TExceptEvent  ) : boolean; overload;
function UnregisterExceptionHandler (exceptHandler: TExceptEventOO) : boolean; overload;

The following "exception action handlers" get called whenever action happens as a result of an exception. You can register as many exception action handlers as you like. Each one is called in a row. If the action is "eaSendBugReport", "eaSaveBugReport" or "eaPrintBugReport" and a handler sets the parameter "handled" (for all other actions this parameter has no meaning) to true, madExcept doesn't send/save/print the bug report anymore, it's your duty then. Furthermore in that case the handler calling row is canceled.

  TExceptAction = (eaSendBugReport,        // a bug report is about to be sent    - assistant not shown yet
                   eaSaveBugReport,        // a bug report is about to be saved   - assistant not shown yet
                   eaPrintBugReport,       // a bug report is about to be printed - assistant not shown yet
                   eaSendBugReport2,       // a bug report is about to be sent    - assistant completed
                   eaSaveBugReport2,       // a bug report is about to be saved   - assistant completed
                   eaPrintBugReport2,      // a bug report is about to be printed - assistant completed
                   eaSendBugReport3,       // a bug report was sent
                   eaSaveBugReport3,       // a bug report was saved
                   eaPrintBugReport3,      // a bug report was printed
                   eaShowBugReport,        // the user pressed the "show bug report" button
                   eaHelp,                 // the user pressed F1
                   eaContinueApplication,  // the application is about to be continued
                   eaRestartApplication,   // the application is about to be restarted
                   eaCloseApplication);    // the application is about to be closed

  TExceptActionEvent   = procedure (action           : TExceptAction;
                                    const exceptIntf : IMEException;
                                    var handled      : boolean      );
  TExceptActionEventOO = procedure (action           : TExceptAction;
                                    const exceptIntf : IMEException;
                                    var handled      : boolean      ) of object;

procedure  RegisterExceptActionHandler (actionHandler: TExceptActionEvent;
                                        sync         : TSyncType           ); overload;
procedure  RegisterExceptActionHandler (actionHandler: TExceptActionEventOO;
                                        sync         : TSyncType           ); overload;

function UnregisterExceptActionHandler (actionHandler: TExceptActionEvent  ) : boolean; overload;
function UnregisterExceptActionHandler (actionHandler: TExceptActionEventOO) : boolean; overload;

Sometimes you want to be notified even about exceptions, which are handled by a try..except statement somewhere. Normally such exceptions are hidden from you and the user. But in a kind of debug mode there might be situations where you want to know about such exceptions, too. With the following functions you can tell madExcept to catch such "hidden exceptions" for you and notify you about them. Please note, however, that this makes sense only for debugging. You should not enable this feature by default in a shipping product, because otherwise this option does cost time during the normal program flow, while all the other features of madExcept cost time only when a "real" exception occurs.

When a hidden exception occurs, your handler will be called with "handled" initialized to "true". That means if you don't do anything, you're only notified about the exception, but nothing more happens. If you want the exception to behave like normal exceptions, just set "handled" to "false". You can ask IMEException.BugReport, but doing so in the hidden exception handler will result in a delay, because the bug report will be calculated in the very moment you access the "BugReport" property.

procedure  RegisterHiddenExceptionHandler (hiddenHandler: TExceptEvent;
                                           sync         : TSyncType     ); overload;
procedure  RegisterHiddenExceptionHandler (hiddenHandler: TExceptEventOO;
                                           sync         : TSyncType     ); overload;

function UnregisterHiddenExceptionHandler (hiddenHandler: TExceptEvent  ) : boolean; overload;
function UnregisterHiddenExceptionHandler (hiddenHandler: TExceptEventOO) : boolean; overload;

The madExcept exception box is always on top. Sometimes inside of an exception handler you may want to show other windows or start other programs. In such a situation it might make sense to temporarily remove the topmost state of the exception box. The following two functions help you do that.

// get the handle of the madExcept exception box
// returns 0, if the box is not currently visible
function GetExceptBoxHandle : dword;

// sets or unsets the topmost state of a window
// the return value is the old topmost state
function SetTopmost (window: dword; topmost: boolean) : boolean;

You can pause madExcept's exception catching powers, if you like. But please don't call this from multiple threads at the same time. Also please don't turn it on and off again all the time. It should work ok, but it's not fully thread safe, so be careful. Each "PauseMadExcept(true)" must have its own "PauseMadExcept(false)" call.

// pause madExcept's exception catching powers
procedure PauseMadExcept (pause: boolean = true);

With the following function you can influence freeze checking. "PauseFreeCheck" manually pauses & resumes freeze checking. You should use it for code blocks where the main thread is doing time consuming tasks without handling messages. Otherwise you're in danger of getting false freezing alarms. Also you can call "ImNotFrozen" at any time to reset the freeze check timer. Furthermore you can change the timeout value by calling "SetFreezeTimeout".

// pause/restart freeze checking
// each PauseFreezeCheck(true) should get its own PauseFreezeCheck(false)
procedure PauseFreezeCheck (pause: boolean = true);

// reset the freeze detection timer
procedure ImNotFrozen;

// set a new freeze timeout time (in seconds)
procedure SetFreezeTimeout (newTimeout: dword);

Leak checking is normally turned on/off globally in the madExcept settings dialog. However, you can also activate it manually, even for specific threads, only, if you prefer it that way. Furthermore you can also explicitly tell madExcept to not report a specific handle or pointer as a leak. Doing so makes sense if you have an "intentional" leak somewhere or madExcept gets confused about a specific handle or pointer for some strange reason.

// You can start/stop leak checking at runtime.
// The file "madExcept32.dll" must be available for leak checking to work.
// You can call StartLeakChecking multiple times, from the same thread or
// different threads, but for every StartLeakChecking call you need to call
// StopLeakChecking with matching parameters to turn leak checking off again.
procedure StartLeakChecking (limitedToCurrentThread: boolean = true);
procedure  StopLeakChecking (limitedToCurrentThread: boolean = true);

// By default leaks are reported when the exe closes down, or the dll is
// unloaded. If you want to have a leak report earlier than that, you can
// manually invoke it. Doing so will start the madExceptViewer (if it is
// installed on the PC). The reported leaks are cleared.
procedure ReportLeaksNow (limitedToCurrentThread: boolean = true);

// You can call "GetLeakReport" at any time to get a leak report in string
// form. You can decide if you want the leak report to contain callstacks and
// if you want child leaks to be hidden. The reported leaks are *not* cleared.
function GetLeakReport (limitedToCurrentThread: boolean = true; includeCallstacks: boolean = true; hideChildLeaks: boolean = true) : UnicodeString;

// If you call "GetLeakReport" with "includeCallstacks = false", you can
// request the callstack of a specific leak (identified by the allocation
// number).
function GetLeakCallstack (allocationNumber: int64) : UnicodeString;

// This clears all currently known leaks.
procedure ClearLeaks (limitedToCurrentThread: boolean = true; rememberLeakParents: boolean = true);

// Calling "ShowLeakReport" will open the madExceptViewer (if available).
function ShowLeakReport (const leakReport: UnicodeString) : boolean;

// you can call these functions to hide something from leak detection
function HideLeak (memory: pointer) : boolean; overload;
function HideLeak (handle: THandle) : boolean; overload;

// Call this API to hide expected object leaks.
// If more objects are leaked than expected, all will be shown.
function HideLeak (someClass: TClass; count: integer) : boolean; overload;

// This API allows you to hide leaks based on their callstack.
// e.g. 'MakeObjectInstance|TApplication.Create'
function HideLeak (const callstack: UnicodeString) : boolean; overload;

// This function hides all leaks in unit "initialization" sections.
// It's ok to use this for EXEs, but not recommended for DLLs.
function HideInitializationLeaks : boolean;

// force leak reporting to save the report to a specific file
// the report will always be saved and not displayed in the viewer
procedure SetLeakReportFile (const leakReportFile: UnicodeString);

// do you want the "Creating leak report..." progress window to be shown?
procedure ShowLeakProgressWindow (show: boolean);

// do you want the "Well done - no leaks found!" info window to be shown?
procedure ShowNoLeaksWindow (show: boolean);

// Do you want child leaks to be filtered (can be very slow)?
procedure SetChildLeakFiltering (doFiltering: boolean);

// Sets the alignment of the debug memory manager (default 4).
procedure SetDebugMmAlignment (alignment: integer = 4);

// Should leak reporting wait until ExitProcess is called with a finalized EXE (default true)?
procedure WaitForCleanExitProcess (wait: boolean);

If your project uses multiple threads, bug reports usually contain the callstacks of all threads. The callstack of the main thread shows up under the name "main thread", of course. But the callstacks of the other threads are named only with either "thread $[id]" (pure win32 threads created by CreateThread or BeginThread) or with their TThread class name. As a result it's often difficult to find out, which callstack belongs to which of your secondary threads. To make the bug report better understandable you can give all your threads a name with the function "NameThread". If you want to exclude specific threads from the bug report, call "NameThread" with the name "-".

// to make the bug report more readable, you can name your secondardy threads
procedure NameThread (threadID: dword; threadName: string);

// ask the name of a specific thread (0 = current thread)
// if the thread has no name, "thread %id%" is returned
function GetThreadName (threadID: dword = 0) : string;

When writing NT/2k/XP services, you will notice that the anti freeze detection doesn't work. In fact it brings false alarms all the time. The reason is that in NT/2k/XP services the main thread is sleeping for the most part and doesn't handle messages at all. The main work is done in a seperate thread.

In case you want anti freeze detection for that worker thread, you can't use the madExcept settings dialog to turn on anti freeze detection, instead turn it off there and call "InitAntiFreeze" at the beginning of your worker thread.

procedure  InitAntiFreeze;
procedure CloseAntiFreeze;

Sometimes there are situations where you might want to catch exceptions in a try..except statement. Nevertheless you might want to get a full bug report in the except..end block. That's possible by manually calling the function "CreateBugReport", which creates a full bug report according to your madExcept settings.

function CreateBugReport (exceptType        : TExceptType       = etFrozen;
                          exceptObject      : TObject           = nil;
                          exceptAddr        : pointer           = nil;
                          callingThreadId   : dword             = 0;
                          currentEsp        : dword             = 0;
                          currentEbp        : dword             = 0;
                          context           : PContext          = nil;
                          showPleaseWaitBox : boolean           = false;
                          settings          : IMEModuleSettings = nil;
                          source            : TExceptSource     = esManual;
                          relatedObject     : TObject           = nil;
                          package           : dword             = 0;
                          preparedStack     : pointer           = nil     ) : string;

If you need a stack trace for one specific thread, only, you can call one of the following two functions. Generally you can leave all parameters unchanged, unless you have special needs:

// get the crash stack trace for the current thread
// the current thread must be in a try..except block for this to work
function GetCrashStackTrace  (hideUglyItems       : boolean           = false;
                              showRelativeAddrs   : boolean           = false;
                              showRelativeLines   : boolean           = false;
                              stackTrace          : TPStackTrace      = nil;
                              const progressAlert : IProgressAlert    = nil;
                              dumbTrace           : boolean           = false;
                              pCrc1               : TPCardinal        = nil;
                              pCrc2               : TPCardinal        = nil;
                              pDelphiThread       : TPBoolean         = nil;
                              pMinDebugInfos      : TPDAUnicodeString = nil  ) : string;

// get a stack trace for any running thread
// neither the calling thread nor the target thread have to be in a try..except block
function GetThreadStackTrace (threadId            : dword             = 0;
                              hideUglyItems       : boolean           = false;
                              showRelativeAddrs   : boolean           = false;
                              showRelativeLines   : boolean           = false;
                              stackTrace          : TPStackTrace      = nil;
                              const progressAlert : IProgressAlert    = nil;
                              dumbTrace           : boolean           = false;
                              pCrc1               : TPCardinal        = nil;
                              pCrc2               : TPCardinal        = nil;
                              pDelphiThread       : TPBoolean         = nil;
                              pMinDebugInfos      : TPDAUnicodeString = nil  ) : string;

In the bug report you'll find a whole collection of useful system information. If you want to use the same information in your own functions, you can do so by calling the following functions:

function IsUserAdmin          : boolean;  // does the current user have admin rights?
function GetTSClientName      : string;   // client session name (terminal server)
function GetOsVersionString   : string;   // e.g. "Windows XP Service Pack 1 build 2600"
function GetOsLanguageString  : string;   // e.g. "English"
function GetSystemUpTime      : string;   // e.g. "9 hours 52 minutes"
function GetProgramUpTime     : string;   // e.g. "15 seconds"
function GetCpuName           : string;   // e.g. "Intel(R) Pentium(R) 4 CPU 2.80GHz"
function GetCpuCount          : integer;  // number of installed CPUs (HT-CPUs count as 2)
function GetMemoryStatus      : string;   // e.g. "327/511 MB (free/total)"
function Get9xResourceReport  : string;   // e.g. "99/97 (gdi/user)"
function GetDisplayModeString : string;   // e.g. "1024x768, 32 bit"
function GetAllocatedMemory   : string;   // e.g. "3.52 MB"
function GetLargestFreeBlock  : string;   // e.g. "1.46 GB"

Finally you can call all the action functions, which are executed, when a user hits one of the exception box buttons. This way you can easily create your own exception box without having to reimplement all the functionality like mailing and printing etc...

// if you have a window, enter it as the parentWindow, otherwise leave it 0
function  SendBugReport (bugReport    : string;
                         screenShot   : INVBitmap;
                         parentWindow : dword       = 0;
                         settings     : IMESettings = nil) : TExtBool;
function  SaveBugReport (bugReport    : string;
                         parentWindow : dword       = 0;
                         settings     : IMESettings = nil) : boolean;
function PrintBugReport (bugReport    : string;
                         parentWindow : dword       = 0;
                         settings     : IMESettings = nil) : boolean;

procedure RestartApplication;
procedure CloseApplication;

// this is for secretly saving/sending the bug report (without UI)
function AutoSendBugReport (bugReport  : string;
                            screenShot : INVBitmap;
                            settings   : IMESettings = nil) : TExtBool;
function AutoSaveBugReport (bugReport  : string;
                            settings   : IMESettings = nil) : boolean;