Download source files - 60 Kb
So you have written a great application and rolled it out to all your users and a week later you get the dreaded email:
Downloaded your program and I love it but it crashed. Can you please fix it.
You didn't expect details ... did you? So now you go through an extended exchange with the user trying to tie down the bug and eventually you give up and add it to the too hard basket in case someone else experiences the problem.
Of course you could ask the user to install Dr Watson or another tool which captures information when an application experiences an exception but what if the problem is a hang or if you would like internal application state information when the problem occured.
There is a better way. Using the debug toolkit outlined in this article you can get the users to send to you a file which provides:
- Trace Output
Trace output can be captured even in release builds. The trace output can be filtered to allow selective tracing output. Trace output is kept in a file which ensures it is not lost even when your program hangs or has an exception and its size is constrained so it won't fill up the users hard disk.
- Command Line
The full command line used to run the program including any command line options.
- Program Options
You need to do some work here but the you can save the current program options in effect. This can be done both in a description form and a more machine readable form.
- Check Files
Check and dump details about any DLLs or files your program is dependent upon. You just need to tell it which files to check.
- Check COM
Check and dump details about any COM objects your program is dependent upon. Once again just let it know which ones to check.
Dump the settings for the current default printer to help you diagnose printing related problems.
Dump details about the CPU in the PC.
- Current Directory
Debug toolkit will dump the name of the current working directory.
- Windows Version
Dump the version details of windows.
- User definable
Of course if there are other things you would like to dump you can do that as well.
The trace log not only contains statements you have traced out. When an error occurs such as an exception, a hang or an assert fails the following details are dumped to the trace log so you can get a picture of what was happening:
- Call Stack
The current call stack so you can trace back the error to an exact line of code.
- Register Dump
A dump of all processor registers and the 16 bytes of memory that the value in the register points to.
- Task List
A list of active tasks on the PC.
- Loaded modules
A list of DLLs and other modules loaded into your processes address space.
- Windows Resources
Available windows GDI and USER resources.
- Program Stack
A dump of the contents of the programs stack.
These are produced based on the following events:
When your code calls the debug toolkit to assert a condition that is false the toolkit will dump the programs state.
When windows traps an exception such as invalid memory access or divide by zero the toolkit will dump the programs state.
- Automatically Detected Hangs
If the thread in which you create the debug toolkit object does not process any windows messages for a given period then the toolkit will dump the programs state and kill the program.
- User Detected Hangs
If the user presses and holds <CTRL><F11><F12> for a given period then the toolkit will assume a user hang and dump the programs state and kill the program.
To get all of this you actually need do very little coding and can easily enhance or limit the functionality without the need to modify directly the provided code.
One of the design goals here was to make this class very easy to incorporate into an existing or new application. The main class is
CDebugToolkit. It also has some helper classes but you should never need to access them.
CDebugToolkit has been designed to form the base class for your own debug toolkit class in which you will override numerous methods and occasionally call methods on the base class. If you find yourself changing
CDebugToolkit then either you have found a bug (in which case let us know) or you are providing a generic enhancement which could be used by others (in which case also let us know). In all other circumstances I would hope the functionality could live in the derived class.
By sticking with this approach you should be able to easily incorportate enhancements if/when they are made.
BOOL AddFile(const CString& sFile, const BOOL fTestDLL = FALSE, const BOOL fTestFile = FALSE);
Adds a file to the list of files that will be dumped should a log be required.
Name of the file to add. If the file must live in a specific directory then include the full path.
If true then the file will be treated as a DLL and the program will verify it can be loaded at startup.
If true then the file will be tested to see if it can be reached at startup.
Returns FALSE if the file was to be tested and the test failed.
BOOL AddCOM(const CString& sFile, const CLSID clsid, const BOOL fTest = FALSE);
Adds a COM objects to the list of COM objects that will be dumped should a log be required.
Name of the COM object to add.
CLSID of the COM object to add.
If true the the COM object will be tested to see if it can be loaded at startup.
Returns FALSE if the COM object was to be tested and it failed.
virtual void Assert(DWORD dwLine, const CString& sFile, BOOL fCondition, const CString& sCondition, const CString& sDescription = "");
fCondition. If it is FALSE then it writes trace output with details of the line, the condition and an optional description. See the DT_ASSERT* macros for an easy way to use this function. This is a virtual function so you can always replace it in your derived class.
The source line number containing the assert call.
The source file containing the assert call.
The condition which is being evaluated.
The condition in text format.
A description of what was being tested.
virtual void Trace(const CString& sOut, const DWORD dwFilterFlag = 0xFFFFFFFF, const BOOL fForce = FALSE);
Writes out a message to the trace log. See the DT_TRACE* macros for an easy way to use this function. This is a virtual function so you can always replace it in your derived class.
Text to trace out.
A 32 bit field which will be and'd with the filter to see if this should be traced out.
If true then the text is written to the trace log regardless of any filters of the result of the
virtual BOOL DumpToFile(const CString& sFile, const CString& sPre = "");<BR>virtual void DumpToFile(CFile& file, const CString& sPre = "");
Writes all debug information to a log file. These are virtual functions so you can always replace them in your derived class.
Name of the log file to create.
An already open file to write the log contents into.
A text string to insert at the beginning of the file.
Returns FALSE if the file could not be created.
Deletes the cicular log file used to track trace output.
Calling this function causes the debug toolkit to reload the debug options. Use this function if you want to turn on/off debug capabilities at runtime.
void SetTraceFilter(const DWORD dwFilter)
This function sets the trace filter 32 bit flag which gets and'd with each trace request.
32 bit flag filter.
void SetSuspendHang(const BOOL fSuspend)
This function suspends and re-activates the automated hang detection routine. Typically you would suspend this routine when performing particularly long tasks which do not allow the thread to process messages.
TRUE to suspend. FALSE to re-activate.
static CDebugToolkit* GetDebugToolkit(void)
This function is a static function used to access the single instance of the debug toolkit. This function is used by the DT_ macros to allow you to easily write code to call instance methods.
It is essential you call the base class constructor when constructing your derived class. You should also set any of the Debug Toolkit member variables that you don't like the default values for:
Name of your application. This is used as a filename so careful what you call it.
Version of your application.
True if we are to trap exceptions
True if we are to handle asserts
True if we are to automatically detect main thread hangs
True if we are to look for user nominated hangs
True if we are to process trace output
True if we are to dump program options
True if we are to test and dump DLL and files. NOTE: For lots of files this can be slow.
True if we are to dump the trace log
True if we are to dump details about the default printer. NOTE: This is slow.
True if we are to dump details about the machines CPU
True if we are to dump current directory details
True if we are to dump windows version details
True if we are to dump command line details.
True if we are to test and dump com object details. NOTE: For lots of com object this can be slow.
True if we are to dump machine loadable (but text) options NOTE: This would typically be the options in INI file format
Key1 the user has to press when reporting a user detected hang
Key2 the user has to press when reporting a user detected hang
Maximum milliseconds the user has to press the keys for the user detected hang to be acknowledged. Smaller settings increase CPU load.
Amount of time to wait before automatically detecting a hang
True if we are to prefix 2nd & subsequent lines in a multiline trace output.
You would also typically add any files and COM objects that you want checking or dumping.
This method should be called from your derived classes constructor just before it returns. It is critical that you have set all options prior to calling this function.
Returns FALSE if an error occured in initialisation.
DT_TRACE[0..3](s, p1, p2, p3)
These macros are shortcuts for calling the
Trace method. Using this macro prevents using filtering. This macro is used in an identical manner to MFCs
DT_TRACEF[0..3](f, s, p1, p2, p3)
These macros are identical to
DT_TRACE[0..3] except the additional f parameter allows you to pass a 32 bit flag field which will be and'd with the trace filter flag and only traced if at least 1 set bit matches.
This macro is a shortcut for calling the
Assert method. This macro is used in an identical manner to MFCs
This macro is a shortcut for calling the
Assert method. It extends the
DT_ASSERT macro in that it allows you to describe what you are asserting.
These methods have been specifically designed to be overridden in your class that derives from
CDebugToolkit. Please do not change the methods directly.
virtual BOOL OnAssert(void);
Called to see if you want the assert handled. If you want the assert handled return TRUE.
virtual void OnEndAssert(void);
Called once the assert has been accepted and handled. Allows you to do some post assert processing such as maybe saving a log file and suggesting the user mail it to you.
virtual BOOL OnException(struct _EXCEPTION_POINTERS *pExceptionInfo);
Called to see if you want to handle the exception. If you want the exception handled return TRUE.
virtual void OnEndException(void);
Called once the exception has been accepted and handled. You need to be very careful what you do here as exceptions usually mean something is quite wrong.
virtual BOOL OnHang(void);
Called to see if you want to handle the hang automatically detected. If you want the hang handled return TRUE. You may want to ask the user if the program appears to have hung ... just in case.
virtual void OnEndHang(void);
Called once the hang has been accepted and handled. Allows you to do some final processing before the task is killed. BEWARE when this method is called you are not running in the main thread.
virtual BOOL OnUserHang(void);
Called to see if you want to handle the user reported hang. If you want the hang handled return TRUE.
virtual void OnEndUserHang(void);
Called once the user reported hang has been accepted and handled. Allows you to do some final processing before the task is killed. BEWARE when this method is called you are not running in the main thread.
virtual BOOL OnTrace(const DWORD dwFilterFlag);
Called to see if you want the trace processed. If you want the trace handled return TRUE.
virtual void OnReset(void);
Called to allow you to change debug toolkit processing options before debugging is restarted.
virtual BOOL OnDelete(void);
Called to allow you to do some processing before the delete request is handled. Return FALSE to cancel the delete.
virtual void OnAbnormalExit(void);
Called to allow you to do some processing when we detect the program did not exit normally last time it was run. This function is usually run prior to completion of the application constructor which limits some of the MFC functionality you can use.
virtual void OnTestDLLFail(const CString& sFile, DWORD dwError);
Called when a DLL could not be loaded. Override this if you want to tell the user that a critical DLL is missing.
virtual void OnTestCOMFail(const CLSID clsid, const CString& sFile, DWORD dwError);
Called when a COM file could not be loaded. Override this if you want to tell the user that a critical COM object is missing.
virtual void OnTestFileFail(const CString& sFile, DWORD dwError);
Called when a file could not be found. Override this if you want to tell the user that a critical file is missing.
virtual CString GetDumpOtherAtEvent(void);
Allows you to provide additional data to write to the trace log when a hang, exception or assert event occurs.
virtual CString GetDumpOtherAtSave(void);
Allows you to provide additional data to write to the log file when it is being written to disk.
virtual void GetOptionDescriptions(CString& sOptionDescriptions);
Allows you to dump in human readable form the options currently in effect.
virtual void ExportOptions(CFile& file);
Allows you to dump in machine readable (but text) form to options currently in effect.
Adding Debug Toolkit To Your Project
- Add these files to your project.
- DebugToolkit.cpp // main debug toolkit class - Do Not Change
- DebugToolkit.h // main debug toolkit class - Do Not Change
- FastLogger.cpp - Do Not Change
- FastLogger.h - Do Not Change
- FileInfo.cpp - Do Not Change
- FileInfo.h - Do Not Change
- K32exp.cpp - Do Not Change
- K32exp.h - Do Not Change
- MyDebugToolkit.cpp // shell for your overriden debug toolkit
- MyDebugToolkit.h // shell for your overriden debug toolkit
- ProcessorInfo.h - Do Not Change
- ProgressWnd.cpp - Do Not Change
- ProgressWnd.h - Do Not Change
- WindowsVersion.h - Do Not Change
VERSION.LIB to your link options.
- Select the
Generate mapfile option in your projects link options.
- In C++ Listing Files options select
Assembly with Source Code.
- Edit your
CApplication derived class to add a new public member variable
#include "MyDebugToolkit.h" to the top of you
CApplication derived class header file. You may need to do the same in other c++ files which will call the debug toolkit.
- Customise your new
- Now replace all
TRACE[0..3] calls with the
- When you produce your final release build don't forget to create a zip file containing your projects executables along with the .MAP and all the .ASM files. You will need these to decode the call stack to the original source code line.
CAUTION: When running your code under the debugger this class will not trap exception events. This is because the debugger takes priority and traps it first.
There is obviously a lot of functionality here but there are also lots of possible enhancements. Should you make any or have any suggestions please let me know.
Here are some possible enhancements I have thought of but either don't have the time or equipment to do.
- Fix issue with the program not correctly dumping COM object registration details on NT.
- Add task listing for NT.
- 28 March 2000 : Fixed toolhelp APIs so they do not cause runtime errors on Windows NT.
NOTE: I do post fixes to the code project site but sometimes it can take a few days for them to get there. The latest source is always available here.