One of the valuable techniques to investigate crashes in C is generating stack trace and dumps when errors happen. Unfortunately, it is not a built-in feature of the C language. On the contrary, other languages, such as C# (with its CLR runtime) or Java (with its JVM runtime), provide auto-generated stack traces. Therefore, It is easier for the C# or Java developers to eliminate bugs and investigate problems when an exception is raised, or an error occurs.
This article shows how to investigate crashes in C in the Windows platform. We see how to generate stack trace in C and to auto-generate dumps of programs written in C/C++ language when an error occurs.
Structured Exception Handling (SEH)
First, Let’s see what are Structured Exception Handling (SEH) and __try __except construct, a specific extension of Microsoft C/C++ compiler to the C/C++ language.
Structured Exception Handling (SEH) is a technique that allows the programmer to gain control when fatal errors occurred when the program is running before the operating system gain control and brutally terminates the execution of the program. Those fatal errors require the execution of code outside the normal flow of the program and are therefore called exceptions.
Structured Exception Handling (SEH) allows the developer to terminate the program gracefully. Using this feature, The programmer can verify that resources (such as memory and files) are released. The programmer can also log why the program has been terminated and generate a dump file and stack trace. The dump file can be loaded later by the debugger to investigate further the bug that causes the crash.
There are 2 types of exceptions:
Software exceptions
are exceptions that are initiated by the program or the operating system. Those exceptions are initiated by calling functions such asRaiseException
Hardware exceptions
are exceptions that are initiated by the CPU. The CPU will initiate exceptions when the program try to execute invalid instruction. Examples of hardware exceptions can be trying to access an invalid memory ( i.e dereferencing a NULL pointer ) or attempting to divide by zero.
The __try __except Construct
The __try __except allows to handle the exception
__try __except construct
__try {
// guarded-code
} __except ( execution-expression ) {
// exception-handler-code
}
The code that will be executed when exception occurs in guarded-code
depends on the value of the execution-expression
:
- If the value is
EXCEPTION_CONTINUE_EXECUTION
then execution will continue from the point at which the exception occurred in theguarded-code
. Please note that some exceptions are noncontinuable – for them ,EXCEPTION_NONCONTINUABLE_EXCEPTION
exception will be raised. - If the value is
EXCEPTION_EXECUTE_HANDLER
thenexception-handler-code
will be executed. - If the value is
EXCEPTION_CONTINUE_SEARCH
then the program will continue to search for enclosing_try __except
construct in the current function or upward the call stack. The search ends when it find construct whoseexecution-expression
is one of the above values.
Find information about the exception
We can find information about the exception using the following macros:
GetExceptionInformation()
– Retrieves a computer-independent description of an exception, and information about the computer state that exists for the thread when the exception occurs. It can be called insideexecution-expression
only.GetExceptionCode()
– The code which identifies the exception. It can be called insideexecution-expression
orexception-handler-code
Sample of __try __except construct
The following program denonstrate the __try __except
construct.
Sample of __try __except construct
int main(int argc, char* argv[]) {
__try {
int *zz = NULL;
*zz += 1; // Raise hardware exception since we dereferencing a NULL pointer
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
printf("Exception 1: 0x%08x\n", GetExceptionCode() );
}
__try {
int zz = 0;
int xx = 10 / zz; // Raise hardware exception since we divide by zero
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
printf("Exception 2: 0x%08x\n", GetExceptionCode() );
}
__try {
RaiseException( 0x1 , 0, 0 , NULL ); // Raise software exception with code 0x1
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
printf("Exception 3: 0x%08x\n", GetExceptionCode() );
}
__try {
GenerateException();
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
printf("Exception 5: 0x%08x\n", GetExceptionCode());
}
return 0;
}
void GenerateException() {
__try {
RaiseException( 0x1000 , 0, 0 , NULL ); // Raise software exception with 0x1000 code
printf("After Exception 4\n");// This will be displayed since
//the execution-expression is EXCEPTION_CONTINUE_EXECUTION for exception with 0x1000 code
RaiseException( 0x2000 , 0, 0 , NULL ); // Raise software exception with 0x2000 code.
//Since execution-expression is EXCEPTION_CONTINUE_SEARCH for exception with 0x2000,
//it will search enclosing handler in current function or upward the call stack
printf("After Exception 5\n"); // This will not be displayed
} __except ( GetExceptionCode() == 0x1000 ?
EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH ) {
}
}
The output of the program
Exception 1: 0xc0000005
Exception 2: 0xc0000094
Exception 3: 0x00000001
After Exception 4
Exception 5: 0x00002000
Minidump and Stack Trace with DumpManager
We will use DumpManager
class which uses SEH to create minidump and stack trace text file when errors occurred. The stack trace text file can be viewed in the text editor while the minidump file can be loaded later by the debugger for further investigation of the bug.
Generate stack trace in C/C++ with DumpManager
We can use the following DumpManager class to generate stack trace and dumps
DumpManager.h
class DumpManager {
public:
DumpManager(char* buildId = "????", char* prefix = "");
DWORD Add(LPEXCEPTION_POINTERS exception_pointers);
protected:
virtual bool Validate();
virtual DWORD OnException();
void WriteMinidump (LPEXCEPTION_POINTERS exceptionPointers); // write dump file
void WriteStackTrace(LPEXCEPTION_POINTERS exceptionPointers); // write stack trace
virtual void FindNameStackTrace(char* outname, int size);
virtual void FindNameDump(char* outname, int size);
void FindName(char* outname, int size, char* extension);
char* _buildId; // unique string which identifies the current version of the compiled program.
char* _prefix; // the prefix to save the stack trace and dumps
// exception properties
HANDLE _hProcess; // The process handle where the exception occured
DWORD _processId; // The process id where the exception occured
DWORD _threadId ; // The thread id where the exception occured
SYSTEMTIME _time; // the time the exception occoured
DWORD _code; // the code of the exception
};
Using DumpManager.Add
The Add
method is responsible for creating the files and updating the exception properties (_time
,_code
, … ).
DumpManager::Add
DWORD DumpManager::Add(LPEXCEPTION_POINTERS exceptionPointers) {
_hProcess = GetCurrentProcess();
_processId = GetProcessId(_hProcess);
_threadId = GetCurrentThreadId();
_code = exceptionPointers->ExceptionRecord->ExceptionCode;
GetSystemTime(&_time);
if ( Validate() ) {
WriteMinidump(exceptionPointers);
WriteStackTrace(exceptionPointers);
}
return OnException();
}
It should be execution-expression
of _try __except
construct and should be called with GetExceptionInformation()
macro.
DumpManager usage
DumpManager dumpManager("buildId", "prefix");
__try {
// guarded-code
} __except ( dumpManager.Add( GetExceptionInformation() ) ) {
// exception-handler-code
}
When exception occurred in above guarded-code
the dumpManager.Add
will be called: The created files have the following format:
__--_-_-_.dmp
__--_-_-__stack-trace.txt
By default prefix
is empty string and therefore the files will be created in the current working directory. You can change the location of the created files by setting prefix
in the constructor. The buildId
provided in the constructor should be unique string which identify the current build of the program. This will allow to associate the stack traces text files and the minidump files to binary which generate them.
Guidelines for building and distributing the program
The program should be linked with /DEBUG flag. This flag will generate the program database file (or PDB file) which contains debugging symbols which are needed by the debugger.
The source files, the binaries including the generated PDB file should be archived so they can be loaded by the debugger later.
The PDB file should be distributed with the EXE file if you want that the stack trace text files will contain the function names, source files, and line numbers. You can emit PDB file from the distribution if only the required file is minidump file.
You can open the stack trace text file in a text editor or use the debugger to load the minidump files:
- Move the minidump file to directory where the binary file was compiled.
- Click on the minidump, The debugger should be opened.
- Use the debugger to investigate the problem.
In the following example, The program generates an exception for dereferencing a NULL pointer as we can see in the minidump file summary (The thread tried to read from or write to a virtual address for which it does not have the appropriate access).


The dump file is loaded and the c stack trace text file is displayed. Also call stack window and source code editor are opend.
We can also see that this exception happened at f3
fuction in call stack debugger window. clicking the f3
function should open the main.cpp
at the position where the f3
function was defined (line 5). we can also find this infomation by viewing the stack trace text file in the text editor.
You can add actions that should be done when exception occurred by overriding the OnException
method. Those actions can be logging the exception to the system log, sending email or uploading the dump file to remote server. The function should return one of execution-expression
values. The default implemention returns EXCEPTION_EXECUTE_HANDLER
which will make the program execute the exception-handler-code
.
You can decide which exceptions should generate stack trace and dump files by overriding Validate
and return true
only for exceptions which desired _code
values.
Investigate crashes in C
We learn about Structured Exception Handling(SEH) and how to use it with the __try and __except construct. Next, we see how we can generate a stack trace and dump when fatal errors occur in your program before the operating system gains control and brutally terminates the program’s execution. Next, we see how to build the program to support generating this helpful information. Finally, we see how to use the stack trace files and the dump files to investigate crashes in C in the Windows platform.
Further Reading
In the above DumpManager
. WriteStackTrace
and DumpManager.WriteMinidump
, we used functions located in dbghelp dll
- In the
WriteStackTrace
is usingStackWalk64
which allows to obtaining a stack trace in portable way. - The
DumpManager.WriteMinidump
use the MiniDumpWriteDump function. You can adjust the parameters of this function to control the amount of data that should be saved in the dump ( i.e , Including all accessible memory in the process or specific module).
Faq
What Structured Exception Handling (SEH)?
Structured Exception Handling (SEH) is a technique that allows the programmer to gain control when fatal errors occurred when the program is running before the operating system gain control and brutally terminates the execution of the program. Read more
What is __try __except construct in C/C++ Language?
The __try __except construct allows the developer to gain control when fatal errors occurred when the program is running before the operating system gain control and brutally terminates the execution of the program. Read more