I tried to make my program dump and save its stack trace when crashes. I installed my own win32 SE handler with _set_se_translator and tried to dump the stack trace with StackWalk64 and finally throw a C++ exception (which actually does the logging when caught).
The code looks like this:
...
_set_se_handlers(WIN32EXCEPTION::Win32ExceptionStuff);
...
void WIN32EXCEPTION::Win32ExceptionStuff(unsigned int Code, struct _EXCEPTION_POINTERS* Info) // static
{
STACKFRAME64 sf64;
sf64.AddrPC.Offset = Info->ContextRecord->Eip;
sf64.AddrStack.Offset = Info->ContextRecord->Esp;
sf64.AddrFrame.Offset = Info->ContextRecord->Ebp;
sf64.AddrPC.Mode= sf64.AddrStack.Mode= sf64.AddrFrame.Mode= AddrModeFlat;
while (StackWalk64(IMAGE_FILE_MACHINE_I386,GetCurrentProcess(),GetCurrentThread(),
&sf64,Info->ContextRecord,0,SymFunctionTableAccess64,SymGetModuleBase64,0))
{
//... Do something with the stack frames
}
throw WIN32EXCEPTION(/*...*/);
}
as I saw in some examples, but there is a problem: StackWalk64 always return true and that while loop becomes infinite. The StackWalk64 only repeats the same frame.
What is the problem, and how to fix?
This looks very similar to code I've got, and that does work. The only difference I can see is that my code uses ZeroMemory() to clear the STACKFRAME64 structure before populating any parts of it - this may be required.
Related
MAJOR EDIT: I now have a much shorter error description!
I have an MFCx64 c++ program where InitInstace() is calling a ReadXML function that uses pugixml (latest version 1.13). Pugixml was compiled by including the pugixml.cpp file into the project. The creation of a pugi::xml_parse_result object is then causing a stack corruption when exiting the ReadXML function.
Minimum code causing the error:
int CMyApp::ReadXML_debug()
{
pugi::xml_parse_result result1;
return 0; //program will throw exception when leaving the function
}
BOOL CMyApp::InitInstance()
{
int test = ReadXML_debug();
//rest of InitInstance() here...
}
When the code leaves the ReadXML() function I get this exception: Run-Time Check Failure #2 - Stack around the variable 'result1' was corrupted. Stack corruption happens in both release and debug (but some more code lines might be needed to avoid the result1 object to be optimized away in release).
What in this very minimalistic code could be causing a stack corruption???
It should be noted that, of course, this was originally part of a much bigger code base. Initially the problematic code line was status = the_params->load_file(file).status; but that was much harder to debug. I have now found that it was the creation of the intermediate xml_parse_result object returned by load_file() that was causing the stack corruption.
More details about the compiler settings causing this error:
Studio: VS2019
Platform: x64
Build: Both Debug and Release
Toolset: Visual Studio 2019 (v142)
MFC in a static library
Runtime library: /MT or /MTd
Struct alignment: Default
------------------ END OF MAJOR EDIT, remaining part of original post below -------------
int config_params::load(const char* file)
{
int status = -1;
//Writing the code like this does not throw the stack corruption exception, but I get stack corruption (corrupted data) in the calling function.
//status = the_params->load_file(file).status;
// Rewrote code like this in order to find error
pugi::xml_parse_result result = the_params->load_file(file);
pugi::xml_parse_status statusTmp = result.status;
status = (int)statusTmp;
return status;
} //stack corruption exception triggered when leaving this function
When running in debug mode I get the exception Run-Time Check Failure #2 - Stack around the variable 'result' was corrupted. When running in release mode I get a similar exception: Stack cookie instrumentation code detected a stack-based buffer overrun.
Additional information: config_params is a singleton class that keeps the pugixml doc object.
// Singleton class for the xml parameters
class config_params : public params
{
public:
~config_params();
static config_params& get_instance()
{
static config_params instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return instance;
}
virtual int init();
virtual int load(const char* xml_file);
private:
// Default constructor an xml abstraction layer for configuration parameter files
config_params();
static config_params* instance;
pugi::xml_document* the_params;
};
config_params::config_params()
{
the_params = new pugi::xml_document;
}
config_params::~config_params()
{
if (the_params)
{
delete (pugi::xml_document*)the_params;
}
}
int config_params::init()
{
int status = -1;
// This creates the file path (my code uses an actual xml file of course)
char filePath[500];
sprintf_s(filePath, "%s%s", "C:\\inserting_file_path", "\\here.xml");
//This gets the instance and loads the file
status = config_params::get_instance().load(filePath);
return status;
}
When my program starts, the InitInstance() function will call status = config_params::get_instance().init(); This call will first run the constructor, creating the new pugi::xml_document. Then init runs, calling load that reads the xml file. Finally, load function will exit and trigger the stack corruption exception. Alternatively, if the exception is not triggered, I will see stack corruption in InitInstance(), including a corrupted this pointer.
I have been debugging this a lot, changing around in the code. I have also tried to edit the load function, so that it does not read anything into the_params (the member pugi::xml_document). Instead the load function looked like this, still causing the stack corruption when leaving the function.
pugi::xml_document myDoc;
myDoc.load_file(file);
pugi::xml_parse_status statusTmp = result.status;
status = (int)statusTmp;
return status;
The xml documents are not especially big. They vary in content, but this would be a simple example:
<?xml version="1.0"?>
<PARAMETERS>
<Name>Register parameters</Name>
<VOLUME test="1" />
</PARAMETERS>
What could be triggering the stack corruption?
Final note: This is somewhat related to my previous question, in which pugixml was compiled as a very faulty dll file. Now I have thrown out that DLL and the problem is very different, so I am posting as a new question. (InitInstance(): pugixml corrupts my this pointer)
I am working with a 3rd party C++ DLL that is running __debugbreak() in some scenario, and is not checking IsDebuggerPresent() before doing so. This results in my application "crashing" when that scenario happens outside of a debugger (e.g. end user running the application). I would like to catch this and deal with it myself, or at least ignore it.
I actually have had an unhandled exception filter in place to translate SEH to C++ exceptions for a while, so it's a little strange that it's not working.
::SetUnhandledExceptionFilter(OnUnhandledException);
I've been doing some direct testing, and the standard __try/__except works, so I could wrap every call into the DLL with this as a fallback, but seems to be that if __try/__except works, then ::SetUnhandledExceptionFilter() should also work.
__try
{
__debugbreak();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("caught");
}
try/catch(...) does not work.
try
{
__debugbreak();
}
catch (...)
{
printf("caught");
}
_set_se_translator() isn't working either.
From the MSDN documentation at https://msdn.microsoft.com/en-us/library/ms679297(VS.85).aspx it states that it should function as a structured exception. I realize that is the documentation for DebugBreak() but I have tested with that as well and have the same problem, even with "catch(...)".
I am compiling with /EHa.
How can I catch the __debugbreak (asm INT 3), or at least change the behavior?
Breakpoints generate the EXCEPTION_BREAKPOINT structured exception. You cannot use try/catch to catch it because it doesn't get translated to a C++ exception, irrespective of the /EHa switch or _set_se_translator. EXCEPTION_BREAKPOINT is a special exception.
First, you should know that catch blocks and __except blocks execute only after unwinding the stack. This means that execution continues after the handler block, NOT after the call to __debugbreak(). So if you just want to skip EXCEPTION_BREAKPOINT while at the same time continue execution after the int 3 instruction. You should use a vectored exception handler. Here is an example:
// VEH is supported only on Windows XP+ and Windows Server 2003+
#define _WIN32_WINNT 0x05020000
#include <windows.h>
#include <stdio.h>
//AddVectoredExceptionHandler constants:
//CALL_FIRST means call this exception handler first;
//CALL_LAST means call this exception handler last
#define CALL_FIRST 1
#define CALL_LAST 0
LONG WINAPI
VectoredHandlerBreakPoint(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
{
/*
If a debugger is attached, this will never be executed.
*/
printf("BreakPoint at 0x%x skipped.\n", ExceptionInfo->ExceptionRecord->ExceptionAddress);
PCONTEXT Context = ExceptionInfo->ContextRecord;
// The breakpoint instruction is 0xCC (int 3), just one byte in size.
// Advance to the next instruction. Otherwise, this handler will just be called ad infinitum.
#ifdef _AMD64_
Context->Rip++;
#else
Context->Eip++;
#endif
// Continue execution from the instruction at Context->Rip/Eip.
return EXCEPTION_CONTINUE_EXECUTION;
}
// IT's not a break intruction. Continue searching for an exception handler.
return EXCEPTION_CONTINUE_SEARCH;
}
void main()
{
// Register the vectored exception handler once.
PVOID hVeh = AddVectoredExceptionHandler(CALL_FIRST, VectoredHandlerBreakPoint);
if (!hVeh)
{
// AddVectoredExceptionHandler failed.
// Practically, this never happens.
}
DebugBreak();
// Unregister the handler.
if (hVeh)
RemoveVectoredExceptionHandler(hVeh);
}
In this way, the breakpoint instruction int 3 will just be skipped and the next instruction will be executed. Also if a debugger is attached, it will handle EXCEPTION_BREAKPOINT for you.
However, if you really want to unwind the stack, you have to use __except(GetExceptionCode() == EXCEPTION_BREAKPOINT ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH).
I have done some searches on here, MSDN, and through some other forums via Google trying to find any sort of solution to this, but so far am stuck.
I have been looking for a week, trying to track down an access violation error in my C++ Program. I cant really post code here as it is under some IP Restrictions, but basically, it is a loop that is running roughly every 100ms reading bytes from a TCP Connection and placing them onto the back of a std::queue.
After I notice a particular byte sequence come through, I then remove x bytes from the queue and handle them as a message defined in an internal protocol.
What happens is, somewhere inside my application, the queue is becoming corrupted and crashing the application. So pair that with the fact that it is an access violation, it must be a dodgy pointer somewhere.
I have tried to use the VS2005 Debugger and Windbg to find it, I had call stacks to look at but it wasnt much help. All I could work out from it is that the cause is corruption of my internal queue. The reason it crashes is because the header of the message gets send to be parsed, but because it is corrupted everything falls over.
Then I tried Intel Thread Checker but that is far too slow to use in this application, as my program is part of a synchronous multi-threaded system.
Sometimes it will run for 300 reads... sometimes it can do 5000 reads... sometimes it can do 10000 reads before it crashes.
What are some other routes of diagnosis I can try? Am I missing something simple here that I should have checked already? From what I can see, anything being newed has a matching delete, and I am using Boost Librarys for Shared Pointers and Auto Pointers on long-living objects.
Use SEH(structured exception handling) to find out which part raises AV.
SEH in C++ example code from MSDN.
#include <stdio.h>
#include <windows.h>
#include <eh.h>
void SEFunc();
void trans_func( unsigned int, EXCEPTION_POINTERS* );
class SE_Exception
{
private:
unsigned int nSE;
public:
SE_Exception() {}
SE_Exception( unsigned int n ) : nSE( n ) {}
~SE_Exception() {}
unsigned int getSeNumber() { return nSE; }
};
int main( void )
{
try
{
_set_se_translator( trans_func );
SEFunc();
}
catch( SE_Exception e )
{
printf( "Caught a __try exception with SE_Exception.\n" );
}
}
void SEFunc()
{
__try
{
int x, y=0;
x = 5 / y;
}
__finally
{
printf( "In finally\n" );
}
}
void trans_func( unsigned int u, EXCEPTION_POINTERS* pExp )
{
printf( "In trans_func.\n" );
throw SE_Exception();
}
Random crash usually caused by heap corruption, it is hard to find. Past years I had deal with several heap corruption problems, as I remembered, one of the problems took me a whole weekend to track it down. Here're some suggestions:
Try app verifier first. details is in:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd371695(v=vs.85).aspx
.
Gflags:
http://msdn.microsoft.com/en-us/library/windows/hardware/ff549557(v=vs.85).aspx.
Use it to to enable Page heap verification.
The solution 1 and 2 are both using heap verification for your whole
program, so you may get many exceptions and slow down your program,
but some of them are not related to your problem. If you know which
part of code has errors, you can use window API _CrtSetDbgFlag to
enable heap verifciation, some thing like this:
`int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
_CrtSetDbgFlag(tmpFlag); // verify heap when alloc and dealloc
//you code here, if the heap is corrupt, exception will be thrown at next allocation.
tmpFlag |= ~_CRTDBG_CHECK_ALWAYS_DF;
_CrtSetDbgFlag(tmpFlag)// do not verify heap`
I have a weird problem:
On my Win32 C++ App, I have a function where the function returns after a call to another function.
void f()
{
//SECTION 1//
if( interactFrame )
{
psFrame->getWindow()->deactivate();
interactFrame = activeFrame = 0;
logFile << "PS deactive" << endl;
}
//SECTION 2//
}
void Window::deactivate()
{
SetLayeredWindowAttributes( handle_, 0, 0, LWA_ALPHA );
SetFocus( applicationWindow_ );
}
After I call f(), the function goes through Section 1, branches into the if statement, completes line 1 (psFrame->...) and returns after that without evaluating the remaining two lines and the Section 2 out of the branch. I had this happen to me when for instance I was calling a method of a variable which was NULL, in this case psFrame, and it would instead of breaking, just return. However it is a legitimate variable, its contents and the pointer returned from getWindow() exists. In fact I can trace until the completion of deactivate() however my breakpoint at the next line is never hit.
In fact this is the same code that runs on one computer, and doesn't on my laptop. Both running Win 7.
What do you think could be the cause of this?
It sounds like something (quite possibly the deactivate, or something it calls) is making a mess of the stack (e.g., overwriting the end of a buffer) and messing up the return address. Much more than that would be a pretty wild guess though.
Given your description, it still sounds like you are getting null dereference errors. Guard your code a bit and see what happens like this:
if( interactFrame )
{
if (psFrame)
{
if (psFrame->getWindow())
{
psFrame->getWindow()->deactivate();
}
// else log("getWindow == null")
}
// else log("psFrame == null")
interactFrame = activeFrame = 0;
logFile << "PS deactive" << endl;
}
Beyond that we'd need to see more code.
UPDATE: OK - you posted more code, and that's pretty odd, unless something very strange is happening like getWindow() is overrunning your stack and trashing the return address. Check any local variables (especially strings and arrays) you have in getWindow().
GMan also has a good point - if psFrame is returning a pointer to a deleted window in getWindow, that could also be a culprit (and you might see different behaviors depending on if the memory has been re-allocated or not yet)
I guess the line
psFrame->getWindow()->deactivate();
simply generates an exception. And your function does not return at all - it terminates with exception. To confirm that set a breakpoint after the call to f() function (part of which is the code you've posted) and if this breakpoint doesn't hit either then it is likely an exception (possibly invalid memory access or simply C++ exception thrown).
Stack corruption is also possible and it will also likely lead to an exception (unless you accidentally overwrite return address with a valid address to executable memory).
Also note that if psFrame happen to be 0 (or other invalid pointer) then exception is guaranteed if getWindow() access any non-static member of its object in any way. And you would see exactly the behaviour you described. The same situation is when psFrame->getWindow() returns 0 (or another invalid pointer) and deactivate() accesses non-static member.
UPD:
You may also follow stack contents changes when debugging.
Is there any sense to step-execute release code? I noticed that some lines of code are omitted, i.e. some method calls. Also variable preview doesn't show some variables and shows invalid (not real) values for some others, so it's all quite misleading.
I'm asking this question, because loading WinDbg crashdump file into Visual Studio brings the same stack and variables partial view as step-execution. Are there any way to improve crashdump analyze experience, except recompiling application without optimalizations?
Windows, Visual Studio 2005, unmanaged C++
Yes - if you have the .pdb for the build, and the .dmp file from the crash, then you can open the debugger on the exact point of failure, and examine the state of your app at that point.
As several have noted - some variables will be optimized away, but if you're mildly creative / inquisitive, you'll find ways to obtain those values.
You can build in a root crash handler for your code to generate a .dmp file automatically which works on all Windows flavors (assuming you are creating a Windows app) using something like the following:
// capture the unhandled exception hook - we will create a mini dump for ourselves
// NOTE: according to docs, if a debugger is present, this API won't succeed (ie. debug builds ignore this)
MiniDumper::Install(
true,
filename,
"Please send a copy of this file, along with a brief description of the problem, to [insert your email address here] so that we might fix this issue."
);
The above would require the MiniDumper class I wrote, below:
#pragma once
#include <dbghelp.h>
#include "DynamicLinkLibrary.h"
#include "FileName.h"
//////////////////////////////////////////////////////////////////////////
// MiniDumper
//
// Provides a mechanism whereby an application will generate its own mini dump file anytime
// it throws an unhandled exception (or at the client's request - see GenerateMiniDump, below).
//
// Warning: the C-runtime will NOT invoke our unhandled handler if you are running a debugger
// due to the way that the SetUnhandledExceptionFilter() API works (q.v.)
//
// To use this facility, simply call MiniDumper::Install - for example, during CWinApp initialization.
//
// Once this has been installed, all current and future threads in this process will be covered.
// This is unlike the StructuredException and CRTInvalidParameter classes, which must be installed for
// for each thread for which you wish to use their services.
//
class MiniDumper
{
public:
// install the mini dumper (and optionally, hook the unhandled exception filter chain)
// #param filename is the mini dump filename to use (please include a path)
// #return success or failure
// NOTE: we can be called more than once to change our options (unhook unhandled, change the filename)
static bool Install(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType = MiniDumpNormal)
{
return GetSingleton().Initialize(bHookUnhandledExceptionFilter, filenameMiniDump, strCustomizedMessage, dwMiniDumpType);
}
// returns true if we've been initialized (but doesn't indicate if we have hooked the unhandled exception filter or not)
static bool IsInitialized() { return g_bInstalled; }
// returns true if we've been setup to intercept unhandled exceptions
static bool IsUnhandledExceptionHooked() { return g_bInstalled && GetSingleton().m_bHookedUnhandledExceptionFilter; }
// returns the filename we've been configured to write to if we're requested to generate a mini dump
static CFilename GetMiniDumpFilename() { return g_bInstalled ? GetSingleton().m_filenameMiniDump : ""; }
// you may use this wherever you have a valid EXCEPTION_POINTERS in order to generate a mini dump of whatever exception just occurred
// use the GetExceptionInformation() intrinsic to obtain the EXCEPTION_POINTERS in an __except(filter) context
// returns success or failure
// DO NOT hand the result of GenerateMiniDump to your __except(filter) - instead use a proper disposition value (q.v. __except)
// NOTE: you *must* have already installed MiniDumper or this will only error
static bool GenerateMiniDump(EXCEPTION_POINTERS * pExceptionPointers);
private:
// based on dbghelp.h
typedef BOOL (WINAPI * MINIDUMPWRITEDUMP_FUNC_PTR)(
HANDLE hProcess,
DWORD dwPid,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
// data we need to pass to our mini dump thread
struct ExceptionThreadData
{
ExceptionThreadData(EXCEPTION_POINTERS * exceptionPointers, bool bUnhandled, DWORD threadID = ::GetCurrentThreadId())
: pExceptionPointers(exceptionPointers)
, dwThreadID(threadID)
, bUnhandledException(bUnhandled)
{
}
EXCEPTION_POINTERS * pExceptionPointers;
DWORD dwThreadID;
bool bUnhandledException;
};
// our unhandled exception filter (called automatically by the run time if we've been installed to do so)
static LONG CALLBACK UnhandledExceptionFilter(EXCEPTION_POINTERS * pExceptionPointers);
// creates a new thread in which to generate our mini dump (so we don't run out of stack)
static bool ExecuteMiniDumpThread(EXCEPTION_POINTERS * pExceptionPointers, bool bUnhandledException);
// thread entry point for generating a mini dump file
static DWORD WINAPI MiniDumpThreadProc(LPVOID lpParam);
// obtains the one and only instance
static MiniDumper & GetSingleton();
// flag to indicate if we're installed or not
static bool g_bInstalled;
// create us
MiniDumper()
: m_pPreviousFilter(NULL)
, m_pWriteMiniDumpFunction(NULL)
, m_bHookedUnhandledExceptionFilter(false)
{
}
// install our unhandled exception filter
bool Initialize(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType);
// generates a mini dump file
bool GenerateMiniDumpFile(ExceptionThreadData * pData);
// handle an unhandled exception
bool HandleUnhandledException(ExceptionThreadData * pData);
bool m_bHookedUnhandledExceptionFilter;
CFilename m_filenameMiniDump;
CString m_strCustomizedMessage;
DWORD m_dwMiniDumpType;
MINIDUMPWRITEDUMP_FUNC_PTR m_pWriteMiniDumpFunction;
LPTOP_LEVEL_EXCEPTION_FILTER m_pPreviousFilter;
};
And its implementation:
#include "StdAfx.h"
#include "MiniDumper.h"
using namespace Toolbox;
//////////////////////////////////////////////////////////////////////////
// Static Members
bool MiniDumper::g_bInstalled = false;
// returns true if we were able to create a mini dump for this exception
bool MiniDumper::GenerateMiniDump(EXCEPTION_POINTERS * pExceptionPointers)
{
// obtain the mini dump in a new thread context (which will have its own stack)
return ExecuteMiniDumpThread(pExceptionPointers, false);
}
// this is called from the run time if we were installed to hook the unhandled exception filter
LONG CALLBACK MiniDumper::UnhandledExceptionFilter(EXCEPTION_POINTERS * pExceptionPointers)
{
// attempt to generate the mini dump (use a separate thread to ensure this one is frozen & we have a fresh stack to work with)
ExecuteMiniDumpThread(pExceptionPointers, true);
// terminate this process, now
::TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF);
// carry on as normal (we should never get here due to TerminateProcess, above)
return EXCEPTION_CONTINUE_SEARCH;
}
bool MiniDumper::ExecuteMiniDumpThread(EXCEPTION_POINTERS * pExceptionPointers, bool bUnhandledException)
{
// because this may have been created by a stack overflow
// we may be very very low on stack space
// so we'll create a new, temporary stack to work with until we fix this situation
ExceptionThreadData data(pExceptionPointers, bUnhandledException);
DWORD dwScratch;
HANDLE hMiniDumpThread = ::CreateThread(NULL, 0, MiniDumpThreadProc, &data, 0, &dwScratch);
if (hMiniDumpThread)
{
VERIFY(::WaitForSingleObject(hMiniDumpThread, INFINITE) == WAIT_OBJECT_0);
VERIFY(::GetExitCodeThread(hMiniDumpThread, &dwScratch));
VERIFY(::CloseHandle(hMiniDumpThread));
return AsBool(dwScratch);
}
return false;
}
DWORD WINAPI MiniDumper::MiniDumpThreadProc(LPVOID lpParam)
{
// retrieve our exception context from our creator
ExceptionThreadData * pData = (ExceptionThreadData *)lpParam;
// generate the actual mini dump file in this thread context - with our own stack
if (pData->bUnhandledException)
return GetSingleton().HandleUnhandledException(pData);
else
return GetSingleton().GenerateMiniDumpFile(pData);
}
bool MiniDumper::HandleUnhandledException(ExceptionThreadData * pData)
{
// generate the actual mini dump file first - hopefully we get this even if the following errors
const bool bMiniDumpSucceeded = GenerateMiniDumpFile(pData);
// try to inform the user of what's happened
CString strMessage = FString("An Unhandled Exception occurred in %s\n\nUnfortunately, this requires that the application be terminated.", CFilename::GetModuleFilename());
// create the mini dump file
if (bMiniDumpSucceeded)
{
// let user know about the mini dump
strMessage.AppendFormat("\n\nOn a higher note, we have saved some diagnostic information in %s", m_filenameMiniDump.c_str());
}
// append any custom message(s)
if (!IsEmpty(m_strCustomizedMessage))
strMessage.AppendFormat("\n\n%s", m_strCustomizedMessage);
// cap it off with an apology
strMessage.Append("\n\nThis application must be terminated now. All unsaved data will be lost. We are deeply sorry for the inconvenience.");
// let the user know that things have gone terribly wrong
::MessageBox(GetAppWindow(), strMessage, "Internal Error - Unhandled Exception", MB_ICONERROR);
// indicate success or not
return bMiniDumpSucceeded;
}
//////////////////////////////////////////////////////////////////////////
// Instance Members
MiniDumper & MiniDumper::GetSingleton()
{
static std::auto_ptr<MiniDumper> g_pSingleton(new MiniDumper);
return *g_pSingleton.get();
}
bool MiniDumper::Initialize(bool bHookUnhandledExceptionFilter, const CFilename & filenameMiniDump, const CString & strCustomizedMessage, DWORD dwMiniDumpType)
{
// check if we need to link to the the mini dump function
if (!m_pWriteMiniDumpFunction)
{
try
{
// attempt to load the debug helper DLL
DynamicLinkLibrary dll("DBGHelp.dll", true);
// get the function address we need
m_pWriteMiniDumpFunction = (MINIDUMPWRITEDUMP_FUNC_PTR)dll.GetProcAddress("MiniDumpWriteDump", false);
}
catch (CCustomException &)
{
// we failed to load the dll, or the function didn't exist
// either way, m_pWriteMiniDumpFunction will be NULL
ASSERT(m_pWriteMiniDumpFunction == NULL);
// there is nothing functional about the mini dumper if we have no mini dump function pointer
return false;
}
}
// record the filename to write our mini dumps to (NOTE: we don't do error checking on the filename provided!)
if (!IsEmpty(filenameMiniDump))
m_filenameMiniDump = filenameMiniDump;
// record the custom message to tell the user on an unhandled exception
m_strCustomizedMessage = strCustomizedMessage;
// check if they're updating the unhandled filter chain
if (bHookUnhandledExceptionFilter && !m_bHookedUnhandledExceptionFilter)
{
// we need to hook the unhandled exception filter chain
m_pPreviousFilter = ::SetUnhandledExceptionFilter(&MiniDumper::UnhandledExceptionFilter);
}
else if (!bHookUnhandledExceptionFilter && m_bHookedUnhandledExceptionFilter)
{
// we need to un-hook the unhandled exception filter chain
VERIFY(&MiniDumper::UnhandledExceptionFilter == ::SetUnhandledExceptionFilter(m_pPreviousFilter));
}
// set type of mini dump to generate
m_dwMiniDumpType = dwMiniDumpType;
// record that we've been installed
g_bInstalled = true;
// if we got here, we must have been successful
return true;
}
bool MiniDumper::GenerateMiniDumpFile(ExceptionThreadData * pData)
{
// NOTE: we don't check this before now because this allows us to generate an exception in a different thread context (rather than an exception while processing an exception in the main thread)
ASSERT(g_bInstalled);
if (!g_bInstalled)
return false;
HANDLE hFile = ::CreateFile(m_filenameMiniDump.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
// indicate failure
return false;
}
else
{
// NOTE: don't use exception_info - its a #define!!!
Initialized<_MINIDUMP_EXCEPTION_INFORMATION> ex_info;
ex_info.ThreadId = pData->dwThreadID;
ex_info.ExceptionPointers = pData->pExceptionPointers;
// generate our mini dump
bool bStatus = FALSE != ((*m_pWriteMiniDumpFunction)(GetCurrentProcess(), GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)m_dwMiniDumpType, &ex_info, NULL, NULL));
// close the mini dump file
::CloseHandle(hFile);
return bStatus;
}
}
I apologize for the fact that this is not a drop-in solution. There are dependencies on other parts of my Toolbox library. But I think it would go a long way towards giving you the right idea as to how to build-in "capture a crash mini-dump" automatically from your code, which you can then combine with your .dsp files that you can make a normal part of your development cycle - so that when a .dmp comes in - you can fire up the debugger on it with your saved .pdb from your release build (which you don't distribute!) and you can debug the crash conditions quite easily.
The above code is an amalgam of many different sources - code snippets from debugging books, from MSDN documentation, etc., etc. If I have left out attribution I mean no harm. However, I do no believe that any of the above code is significantly created by anyone but myself.
Recompile just the file of interest without optimisations :)
In general:
Switch to interleaved disassembly mode. Single-stepping through the disassembly will enable you to step into function calls that would otherwise be skipped over, and make inlined code more evident.
Look for alternative ways of getting at values in variables the debugger is not able to directly show you. If they were passed in as arguments, look up the callstack - you will often find they are visible in the caller. If they were retrieved via getters from some object, examine that object; glance over the assembly generated by the code that calculates them to work out where they were stored; etc. If all else fails and disabling optimisations / adding a printf() distorts timings sufficiently to affect debugging, add a dummy global variable and set it to the value of interest on entry to the section of interest.
At least is not a IA64 dump...
There really isn't much you can do beyond having full dump and private symbols. Modern compilers have a field day with your code and is barely recognisable, specially if you add something like LTCG.
There are two things I found usefull:
Walk up the stack until you get a good anchor on what 'this' really points to. Most times when you are in an object method frame 'this' is unreliable because of registry optmizations. Usually several calls up the stack you get an object that has the correct address and you can navigate, member reference by member reference, until your crash point and have a correct value for 'this'
uf (Windbg's unassembly function command). This little helper can list a function dissasembly in a more manageable form than the normal dissasembly view. Because it follows jumps and code re-arranges, is easier to follow the logic of uf output.
The most important thing is to have the symbol files (*.pdb). You can generate them for release builds, by default they are not active.
Then you have to know that because of optimizations, code might get re-ordered, so debugging could look a bit jerky. Also some intermediate variables might have got optimized away. Generally speaking the behaviour and visibility of data might have some restrictions.
With Visual Studio C++ 2008 you can automatically debug the *.dmp files. I believe it also works for VS 2005. For older compilers I am afraid you´ll have to use WinDbg... (Also specify of course the *.pdb files for WinDbg, otherwise the info will be quite limited)