I'm calling Windows APIs from C++ code and I have a helper method to do the FormatMessage stuff and throw an exception for error handling. The signature of the function is
void throw_system_error(const std::string& msg_prefix, DWORD messageid)
I've noticed something strange. This code does not work properly:
handle = ...;
if (handle == NULL) {
throw_system_error("something bad happened", GetLastError());
}
The error code that is passed to throw_system_error is always zero.
Where as this works just fine:
handle = ...;
if (handle == NULL) {
DWORD error = GetLastError();
throw_system_error("something bad happened", error);
}
Some more investigation showed that this version has the same problem:
handle = ...;
if (handle == NULL) {
std::string msg("something bad happened");
DWORD error = GetLastError();
throw_system_error(msg, error);
}
It looks for all the world as if the constructor of std::string is resetting the error code.
My guess would be that std::string is allocating memory internally which causes some system call that then sets the last error back to zero.
Anyone knows what is actually going on here?
Visual C++ 2015, 64bit.
Let's see the GetLastError documentation:
Most functions that set the thread's last-error code set it when they
fail. However, some functions also set the last-error code when they
succeed.
You should call the GetLastError function immediately when a
function's return value indicates that such a call will return useful
data. That is because some functions call SetLastError with a zero
when they succeed, wiping out the error code set by the most recently
failed function.
So there is one function calling SetLastError, very likely one that allocates memory:
When you construct a string, new is called to allocate memory.
Now, let's see new's implementation in vc++. There is a very good answer to that question in Stack Overflow : https://softwareengineering.stackexchange.com/a/293209
It depends if you are in debug or release mode. In release mode, there is HeapAlloc/HeapFree which are kernel functions,
while in debug mode (with visual studio) there is a hand written
version of free and malloc (to which new/delete are re-directed) with
thread locks and more exceptions detection, so that you can detect
more easily when you did some mistakes with you heap pointers when
running your code in debug mode.
So in release mode, the function called is HeapAlloc, which does NOT call SetLastError. From the documentation:
If the function fails, it does not call SetLastError
So the code should work properly in release mode.
However, in the debug implementation, the function FlsGetValue is called, and that function calls SetLastError when succeeded.
It's very easy to check this,
#include <iostream>
#include <Windows.h>
int main() {
DWORD t = FlsAlloc(nullptr);
SetLastError(23); //Set error to 23
DWORD error1 = GetLastError(); //store error
FlsGetValue(t); //If success, it is going to set error to 0
DWORD error2 = GetLastError(); //store second error code
std::cout << error1 << std::endl;
std::cout << error2 << std::endl;
system("PAUSE");
return 0;
}
It outputs the following:
23
0
So FlsGetValue has called SetLastError(). To prove that it is called only on debug we can do the following test:
#include <iostream>
#include <Windows.h>
int main() {
DWORD t = FlsAlloc(nullptr);
SetLastError(23); //Set error to 23
DWORD error1 = GetLastError(); //store error
int* test = new int; //allocate int
DWORD error2 = GetLastError(); //store second error code
std::cout << error1 << std::endl; //output errors
std::cout << error2 << std::endl;
delete test; //free allocated memory
system("PAUSE");
return 0;
}
If you run it in debug mode, it will give you, because it calls FlsGetValue:
23
0
However, if you run it in release mode, it produces, because it calls HeapAlloc:
23
23
Per the documentation for GetLastError
The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.
At some point during the construction of std::string, SetLastError is called. The standard library on windows uses Win32 calls as part of its implementation.
Your second method (that works) is the correct way to use GetLastError
You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call SetLastError with a zero when they succeed, wiping out the error code set by the most recently failed function.
This is normal - the "last error" can be set indirectly through any function call.
Some functions set it to "no error" on success, so if you want to use it reliably you need to store it immediately before you do anything else.
If you've ever encountered an "A serious error occurred: The operation completed successfully" dialogue, this is probably the reason.
Related
I have some function call like below, and want to print success after success call, but got failure, even the function actually behave correctly.
int myfunction() {
// does some linux sys call for example
int error = run_cmd ("ifconfig usb10 up");
int syserrorno = errno;
strerror(syserrorno);
return error;
}
int main(){
int error =1;
int retry = 0;
do {
error = myfunction();
retry++;
}
while ( error !=-1 && retry <3);
return 0;
}
Basically I tried to:
Run a syscal via myFunction, return error = 1 if fail or 0 if success.
The return error in myFunction is the same as in syscal.
The syscal is a posix spawn command that I reuse from library.
If there is error, print error, redo 3 times.
So I have 1st run of syscall unsuccessfully; it returns error and print out "unavailabe resources". It is expected.
The second time is successful as I check the usb10 and it is up. But it still prints out the same error instead of success.
Is there a way to print it correctly ?
When using errno, always set errno=0; before calling the function(s) whose status you want to check. C library and POSIX functions will set errno to a non-zero value if they encounter an error, but they do not reset it to zero if they succeed.
(The reason they work this way: When a function reporting via errno is actually implemented in terms of other functions, you don't want a later success to make errno forget about an earlier failure. This also makes it possible for user code to set errno=0;, call a number of closely-related library functions, and just check for overall success or failure after all of those calls.)
I am trying to create and execute a thread using Windows CreateThread API. am seeing that running the program gives non-deterministic behavior. I am seeing from the output of the program that sometimes the thread method "my" gets executed and sometimes not. What might be the reason for this? The program is very simple as shown below. Also what should dwThreadID be initialized with. Should it be 0 or any integer value?
PS:Compiling in Visual Studio.
#include "stdafx.h"
#include <iostream>
#include <windows.h>
using namespace std;
// DWORD WINAPI
DWORD WINAPI my (LPVOID lpParam ) {
for (int i = 0; i < 5;i++){
cout << "I am ";
}
return 0;
}
int main()
{
DWORD dwThreadID = 0;
DWORD dwThrdParam = 0;
HANDLE h = NULL;
h = CreateThread(
NULL, // default security
0, // default stack size
my, // name of the thread function
0, // no thread parameters
0, // default startup flags
&dwThreadID);
if (h == NULL){
cout <<"It is null";
}
cout << " BBBB" << endl ;
CloseHandle(h);
cout << "BBBB ";
return 0;
}
I am seeing from the output of the program that sometimes the thread
method "my" gets executed and sometimes not. What might be the reason
for this?
Probably your main thread exits before your second thread is even executed. Place breakpoint after call to CreateThread.
Or even more cleaner wait, just wait for second thread using WaitForSingleObject.
This effectively make your main thread waiting until "my" thread is done.
If CreateThread fails for some reason, you can call GetLastError right after CreateThread, it will give you ultimate answer.
Also what should dwThreadID be initialized with.
Quick look into MSDN yields following:
A pointer to a variable that receives the thread identifier.
It is indeterminate whether or not your thread executes. The main thread creates it and then terminates the program. The following may happen:
The thread may execute before the main thread terminates.
The program may terminate before the thread executes.
The thread may partially execute and be terminated when the program terminates.
You would need to add synchronization in order to be sure that the thread reaches completion. Pass the thread handle to WaitForSingleObject.
Your program does not check for errors. CreateThread returns NULL when it fails. If that happens, and only if that happens, call GetLastError for information as to why the function failed. If you add a call to WaitForSingleObject then you should check its return value for errors as described by the documentation.
What should dwThreadID be initialized with?
It does not need to be initialized because CreateThread guarantees it will be assigned if CreateThread succeeds.
Firstly do not use CreateThread it is intended for library providers. Use beginthreadex or std::thread
The likely behavior, is that the thread always runs, but it's output is being overwritten. This is because there is no synchronisation.
Check the return code and GetLastError
The value of dwThreadid does not matter
I know the GetLastError are to be called at once when error occured. I wrote the function:
void PrintErrorMsg() {
DWORD errCode = GetLastError();
LPTSTR msg = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errCode, 0, (LPTSTR)&msg, 0, NULL);
wcerr << msg << endl;
HeapFree(GetProcessHeap(), 0, msg);
}
Now I use it in my code:
LPCTSTR boundaryName = L"BushmanBoundary";
HANDLE hBoundary = CreateBoundaryDescriptor(boundaryName, 0);
if (NULL == hBoundary) {
PrintErrorMsg();
}
So I called GetLastError in the first code row inside of the PrintErrorMsg function. Whether such variant of using is admissible?
That code is fine. No Windows API functions are called between CreateBoundaryDescriptor returning, and your call to GetLastError.
Functions executed by the calling thread set this value by calling the SetLastError function. You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call SetLastError with a zero when they succeed, wiping out the error code set by the most recently failed function.
Your code is fine because you are not calling a function that may call SetLastError() in between failure and your call to GetLastError() (e.g. Win32 API Calls.)
Keep in mind that the insertion operator may call SetLastError().
std::cout << "Win32 function failed with error: " << GetLastError() << std::endl;
I've been caught out by something like this in the past where GetLastError() would not return the correct error code because it had been set/reset by the insertion operator.
Yes, that's allowed. I've done something similar, except that I pass the error code in as an argument, but make the default value for the argument the result of GetLastError(), like this:
// in header file
void PrintErrorMsg( DWORD errCode = GetLastError() );
// in implementation file
void PrintErrorMsg( DWORD errCode )
{
// ...
}
It's more flexible, and worked very well for me.
I find that the best way to deal with these low-level details of the Win32 API (or any C style API), is to immediately hoist that C style API into C++ practices as soon as possible. One approach I used was to wrap C-style API calls in a macro that validates the return value and then extracts the appropriate error code into an exception if the return value indicated failure.
Chapter 1 of my book "The Direct3D Graphics Pipeline" outlines such an approach for COM HRESULT return values. A macro THR throws if the HRESULT indicates failure. In an HRESULT, the failure status and error code are contained in the same integral value, so the exception can be computed directly from the provided HRESULT. The macro decorates its argument with __FILE__ and __LINE__ before passing the value to be checked onto a helper function that checks for failure. If failure was detected, all the information is gathered up into an exception that is thrown. If the HRESULT indicates success, the HRESULT value is returned.
I also provided similar variants for Win32 API status with TWS. This does the necessary validation checks for the Win32 API (usually some version of comparing against FALSE or 0) and on failure immediately calls ::GetLastError() to obtain the error code for the exception.
In both cases, I attempt to use ::FormatMessage to obtain a meaningful error message string when building the exception value that will be thrown.
These macros are intended to be used in situations where you don't expect the API call to fail and failure is indeed truly exceptional.
In my Windows API wrapper, I can choose to have a message box come up when there's an error. I have one that I can't really pin down though.
Here's my main function:
int main()
{
Window win; //create default window with default class (name changes each new instance)
return messageLoop(); //the familiar GetMessage() while loop, returns msg.wParam
}
This all works fine, but when I close my window (just tested via X button), I get the following message (this is what I get when I copy the message box):
---------------------------
Error
---------------------------
File: "G:\programming\v2\wwbasewindow.h"
Function: _fakeWndProc
Line: 61
Error Code: 1410
Error: Class already exists.
---------------------------
OK
---------------------------
Now it's crystal clear where this error is coming from, but not exactly why. Here's the _fakeWndProc function. The whole wrap (function, args) syntax checks GetLastError() after that function is called. This is why you don't see error checking.
LRESULT CALLBACK BaseWindow::_fakeWndProc (msgfillparams) //trick procedure (taken from someone's gui wrapper guide)
{
BaseWindow * destinationWindowPtr = 0; //for which window message goes to
//PROBLEM IN THE FOLLOWING LINE (gets a pointer to the window, set when it's created)
destinationWindowPtr = (BaseWindow *)wrap (GetWindowLongPtr, hwnd, GWLP_USERDATA);
if (msg == WM_COMMAND && lParam != 0) //if control message, set destination to that window
destinationWindowPtr = (BaseWindow *)wrap (GetWindowLongPtr, (hwin)lParam, GWLP_USERDATA);
if (destinationWindowPtr) //check if pointer is valid
return destinationWindowPtr->_WndProc (hwnd, msg, wParam, lParam); //call window's procedure
else
return wrap (DefWindowProc, hwnd, msg, wParam, lParam); //call default procedure
}
I'm just wondering why this call is (trying to create a class?) That aside, I tried checking the error codes from the time a WM_CLOSE message comes along. I output the code before the line, and after. This is what comes up:
Before: 0
After: 0
--->Before: 0
--->Before: 1410
After: 1410
Before: 1410
After: 1410
...
This puts the topping on my confusion, as this implies that the function calls SendMessage somewhere inside. But why wouldn't it do the same for any others?
The error itself doesn't make much of a difference, as the program ends right after, but I don't want it hanging around. How can I deal with it?
Note:
I just tried not calling PostQuitMessage (0); when WM_DESTROY came up, and created 2 windows. Both of them gave the same error when closing, so it's not necessarily the end of the program anyways.
Also, each one gave an error 1400 (Invalid window handle) too, but only when I didn't call PostQuitMessage. This error originated from the call to DefWindowProc in those windows' respective window procedures. Any ideas on that one either?
Edit:
Due to request, here is the code for wrap:
// pass along useful error information (useless constants within error function)
#define wrap(...) Wrap (__FILE__, __FUNCTION__, __LINE__, __VA_ARGS__)
// cstr == char *
// con == const
// sdword == int (signed dword)
// version if return value of API function is not void
template<typename TRet, typename... TArgs>
typename std::enable_if<!std::is_void<TRet>::value, TRet>::type
Wrap(con cstr file, const char * const func, con sdword line, TRet(*WINAPI api)(TArgs...), TArgs... args)
{
TRet result = api(std::forward<TArgs>(args)...); //call API function
if (GetLastError()) __wwError.set (GetLastError(), file, func, line); //set variables and create message box
return result; // pass back return value
}
// version if return value is void
template<typename... TArgs>
void Wrap(con cstr file, const char * const func, con sdword line, void(*WINAPI api)(TArgs...), TArgs... args)
{
api(std::forward<TArgs>(args)...);
if (GetLastError()) __wwError.set (GetLastError(), file, func, line);
}
I'm 100% sure this and __wwError.set() work though. All other functions wrapped with this give appropriate message boxes.
Your calls to GetLastError are simply incorrect. You cannot indiscriminately call GetLastError like that. You should only call it when the API call documentation says that it is valid to do so. Usually this will be if the API call reports failure.
The calls to DefWindowProc are a fine illustration of how this can go wrong. The documentation for DefWindowProc does not make any mention of a way for the function to report failure. And it makes no mention of calling GetLastError. Thus your calls to GetLastError should not be made and are returning undefined, meaningless values.
Since there is no single common mechanism for a Win32 function to report failure, your attempt to wrap all Win32 API calls with a single common error handling routine is doomed to failure.
What you need to do is to treat each API call on its own merits, and write error checking appropriate for that API call. Since you are using C++ I would recommend you make use of exceptions here. Write a function, ThrowLastWin32Error say, that you call whenever an API function reports failure. The implementation of ThrowLastWin32Error would call GetLastError and then call FormatMessage to obtain a textual description before throwing a suitably descriptive exception. You would use it like this:
if (!CallSomeWin32Function())
ThrowLastWin32Error();
But the main point is that you do need case-by-case checking of function success since different Win32 functions report failure in different ways.
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)