API Hooking which takes effect across entire process - both EXE and DLLs - c++

I have an application consists of a single EXE and multiple DLLs. After reading Windows via C/C++, I try to perform hook on Sleep function in one of the DLL, and expecting the hook will work across both EXE and all DLLs. Note that, CAPIHook code is getting from Windows via C/C++'s sample code
In DLL Project
void WINAPI MySleep( DWORD dwMilliseconds );
CAPIHook g_Sleep("Kernel32.dll", "Sleep", (PROC)MySleep);
typedef void (WINAPI *Sleep_Type)( DWORD dwMilliseconds );
// Hook function.
void WINAPI MySleep( DWORD dwMilliseconds )
{
printf ("-------> In MySleep\n");
((Sleep_Type)(PROC)g_Sleep)(dwMilliseconds);
}
// This is an example of an exported function.
DLL_API int dll_function_which_is_going_to_call_sleep(void)
{
printf ("DLL function being called\n");
printf ("Call Sleep in DLL function\n");
Sleep(100);
return 42;
}
In EXE Project
void CexeDlg::OnBnClickedButton1()
{
// TODO: Add your control notification handler code here
printf ("Button being clicked\n");
printf ("Call Sleep in EXE function\n");
Sleep(100);
dll_function_which_is_going_to_call_sleep();
printf ("Call Sleep in EXE function\n");
Sleep(100);
dll_function_which_is_going_to_call_sleep();
}
This is the output I am getting
Button being clicked
Call Sleep in EXE function
-------> In MySleep
DLL function being called
Call Sleep in DLL function
Call Sleep in EXE function
-------> In MySleep
DLL function being called
Call Sleep in DLL function
What make me feel strange is that, I am expecting CAPIHook will take effect across entire single process. Since EXE and DLLs belong to a same process, both should be able to reach MySleep. However, my observation is that, only call from EXE will reach MySleep, but not DLL.
I locate sample code right here CAPIHook-doesnt-have-effect-in-entire-process.zip, it contains dll and exe projects.
I also once drop in replace CHookAPI with code in apihijack. Same problem still happen. The hooking effect will not spread across entire process.
Is there anything I had missed out? Please do not suggest me to use EasyHook, Detours, ..., as I just want to know why the above code won't work, and how I can fix it.

This is because the original CAPIHook does not replace local IAT (in your case, the DLL project which contains binaries for CAPIHook).
The reason behind this was to protect itself from infinite recursion which lead to stackoverflow (which the users will also post question in SO :D).
To ensure that any subsequent modules loaded will be importing the "correct" function,CAPIHook search and re-direct LoadLibrary and GetProcAddress upon construction.
However, these function are used by CAPIHook itself too, so changing local IAT to proxy function (CAPIHook::LoadLibrary or CAPIHook::GetProcAddress) will cause infinite recursion as the proxies unintentionally called itself while trying to call underlying OS API !
One way to solve this is by modifying CAPIHook to check whether it is alright to replace local IAT.
1.) New attribute m_bIncludeLocalIAT added to CAPIHook and ctor/dtor modified accordingly.
class CAPIHook
{
...
CAPIHook(PSTR pszCalleeModName, PSTR pszFuncName,
PROC pfnHook, BOOL bIncludeLocalIAT = TRUE);
...
BOOL m_bIncludeLocalIAT;
...
};
CAPIHook::CAPIHook( PSTR pszCalleeModName, PSTR pszFuncName,
PROC pfnHook, BOOL bIncludeLocalIAT) {
...
m_bIncludeLocalIAT = bIncludeLocalIAT;
...
ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnOrig, m_pfnHook, m_bIncludeLocalIAT);
}
CAPIHook::~CAPIHook() {
ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnHook, m_pfnOrig, m_bIncludeLocalIAT);
...
}
2.) New parameter added to the static function CAPIHook::ReplaceIATEntryInAllMods.
static void WINAPI ReplaceIATEntryInAllMods(PCSTR pszCalleeModName,
PROC pfnOrig, PROC pfnHook, BOOL bReplaceLocalIAT){
HMODULE hmodThisMod = ExcludeAPIHookMod
? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;
// Get the list of modules in this process
CToolhelp th(TH32CS_SNAPMODULE, GetCurrentProcessId());
MODULEENTRY32 me = { sizeof(me) };
for (BOOL bOk = th.ModuleFirst(&me); bOk; bOk = th.ModuleNext(&me)) {
if (bReplaceLocalIAT || (me.hModule != hmodThisMod)) {
// Hook this function in this module
ReplaceIATEntryInOneMod(
pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
}
}
}
3.) Update the static CAPIHook instances
CAPIHook CAPIHook::sm_LoadLibraryA ("Kernel32.dll", "LoadLibraryA",
(PROC) CAPIHook::LoadLibraryA, FALSE);
CAPIHook CAPIHook::sm_LoadLibraryW ("Kernel32.dll", "LoadLibraryW",
(PROC) CAPIHook::LoadLibraryW, FALSE);
CAPIHook CAPIHook::sm_LoadLibraryExA("Kernel32.dll", "LoadLibraryExA",
(PROC) CAPIHook::LoadLibraryExA, FALSE);
CAPIHook CAPIHook::sm_LoadLibraryExW("Kernel32.dll", "LoadLibraryExW",
(PROC) CAPIHook::LoadLibraryExW, FALSE);
CAPIHook CAPIHook::sm_GetProcAddress("Kernel32.dll", "GetProcAddress",
(PROC) CAPIHook::GetProcAddress, FALSE);

Related

global variable in dll inconsistent?

I am creating a (temporary) log file from a dll. But the global variable I defined seem to be inconsistent.
Here is how I define variables in dll's main cpp file.
char * g_bfr;
__declspec(dllexport) CMemFile memFile;
Then in DllMain function:
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
{
TRACE0("UTLADO.DLL Initializing!\n");
g_bfr = new char[1000]();
memFile.Attach((BYTE*)g_bfr, 1000 );
// Extension DLL one-time initialization
if (!AfxInitExtensionModule(AcnDll, hInstance))
return 0;
new CDynLinkLibrary(AcnDll);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
TRACE0("UTLADO.DLL Terminating!\n");
delete[] g_bfr;
// Terminate the library before destructors are called
AfxTermExtensionModule(AcnDll);
}
return 1; // ok
}
The problem is when I use the memFile in the dll to write log to memory, somewhere down the road, it becomes bad as if newly declared (uninitialized). See the where file positions/size are all reset.
What makes thing weirder is when I set breakpoint in DllMain, inside case DLL_PROCESS_ATTACH, it never breaks there (like never called) but the initialization does work! Breakpoint in case DLL_PROCESS_DETACH does work and is called only when I close application.
So, in a nutshell, it appears that memFile gets created another time during course of the application but should it? How can I make sure I only have one instance of the global variable in the dll?
After you have finished using the DLL, try using the FreeLibrary function.
However, this method has not been considered for concurrent use.
enter link description here

CreateThread inside another thread

I am having an issue creating a thread inside of another thread. Normally I would be able to do this, but the reason for this issue is because I've Incremented Reference Count of the DLL which starts these threads. I need to start multiple threads inside this DLL. How can I get around this and be able to issue multiple CreateThread()'s when needed in my project without experiencing problems because of the Incremented Reference Count in my DLL?
Here is the function I've written to Increment Reference Count in my DLL file:
BOOL IncrementReference( HMODULE hModule )
{
if ( hModule == NULL )
return FALSE;
TCHAR ModulePath[ MAX_PATH + 1 ];
if ( GetModuleFileName( hModule , ModulePath , MAX_PATH ) == 0 )
return FALSE;
if ( LoadLibrary( ModulePath ) == NULL )
return FALSE;
return TRUE;
}
As requested, here is a PoC program to recreate the issue I am facing. I am really hoping this will help you guys point me to a solution. Also, take note, the DLL is being unloading due to conditions in the application which I am targeting (hooks that are already set in that application), so Incrementing the Reference Count is required for my thread to run in the first place.
Also, I can't run more than one operation in the main thread as it has its own functionality to take care of and another thread is required on the side to take care of something else. They must also run simultaneously, hence I need to fix this issue of making more than one thread in an Incremented DLL.
// dllmain.cpp : Defines the entry point for the DLL application.
#pragma comment( linker , "/Entry:DllMain" )
#include <Windows.h>
#include <process.h>
UINT CALLBACK SecondThread( PVOID pParam )
{
MessageBox( NULL , __FUNCTION__ , "Which Thread?" , 0 );
return 0;
}
UINT CALLBACK FirstThread( PVOID pParam )
{
MessageBox( NULL , __FUNCTION__ , "Which Thread?" , 0 );
_beginthreadex(0, 0, &SecondThread, 0, 0, 0);
return 0;
}
BOOL IncrementReference( HMODULE hModule )
{
if ( hModule == NULL )
return FALSE;
TCHAR ModulePath[ MAX_PATH + 1 ];
if ( GetModuleFileName( hModule , ModulePath , MAX_PATH ) == 0 )
return FALSE;
if ( LoadLibrary( ModulePath ) == NULL )
return FALSE;
return TRUE;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
if (IncrementReference(0))
_beginthreadex(0, 0, &FirstThread, 0, 0, 0);
}
break;
}
return TRUE;
}
As you can see, the code never executes the SecondThread function. The question is, why? And what can be done to fix it?
#pragma comment( linker , "/Entry:DllMain" )
That was a very bad idea, the proper entrypoint for a DLL is not in fact DllMain(). You have to keep in mind that WinMain and DllMain are just place-holder names. A way for Microsoft to document the relevance of executable file entrypoints. By convention you use those same names in your program, everybody will understand what they do.
But there's a very important additional detail in a C or C++ program, the CRT (C runtime library) needs to be initialized first. Before you can run any code that might make CRT function calls. Like _beginthreadex().
In other words, the default /ENTRY linker option is not DllMain(). The real entrypoint of a DLL is _DllMainCRTStartup(). A function inside the CRT that takes care of the required initialization, then calls DllMain(). If you wrote one in your program then that's the one that runs. If you didn't then a dummy one in the CRT gets linked.
All bets are off when you make CRT function calls and the CRT wasn't initialized. You must remove that #pragma so the linker will use the correct entrypoint.
According to MSDN you schould neither call LoadLibrary nor CreateThread inside DllMain - your code does both!
The MCVE as posted has three problems:
The first is a simple mistake, you're calling IncrementReference(0) instead of IncrementReference(hModule).
The second is that there is no entry point for rundll32 to use; the entry point argument is mandatory, or rundll32 won't work (I don't think it even loads the DLL).
The third is the #pragma as pointed out by Hans.
After fixing the IncrementReference() call, removing the #pragma and adding an entry point:
extern "C" __declspec(dllexport) void __stdcall EntryPoint(HWND, HINSTANCE, LPSTR, INT)
{
MessageBoxA( NULL , __FUNCTION__ , "Which Thread?" , 0 );
}
You can then run the DLL like this:
rundll32 testdll.dll,_EntryPoint#16
This works on my machine; EntryPoint, FirstThread and SecondThread all generate message boxes. Make sure you do not dismiss the message box from EntryPoint prematurely, as that will cause the application to exit, taking the other threads with it.
The call to LoadLibrary is still improper, however it does not appear to have any side-effects in this scenario (probably because the library in question is guaranteed to already be loaded).
(Previous) Answer:
The MCVE can be fixed by simply moving the call to IncrementReference from DllMain to FirstThread. That is the only safe and correct way to resolve the problem.
Addendum: as Hans pointed out, you'll also need to remove the /Entry pragma.
(Redundant?) Commentary:
If the application that is loading the DLL is misbehaving to the extent where the DLL is being unloaded before FirstThread can run, and assuming for the sake of argument that you can't fix it, the only realistic option is to work around the problem - for example, DllMain could suspend all the other threads in the process so that they cannot unload the DLL, and resume them from FirstThread after the call to IncrementReference.
Or you could try hooking FreeLibrary, or reverse engineering the loader and messing with the reference count directly, or removing the hooks the application has placed, or loading a separate copy of the DLL by hand inside DllMain (with your own DLL loader rather than the one Windows provides) or starting a separate process and working from there or, oh, no doubt there's any number of other possibilities, but at that point I'm afraid the question really is too broad for Stack Overflow, particularly since you can't give us the real details of what the application is doing.

Calling a method in another process from DLL which is loaded by a process

Requirement:
A method in the Client application should be called when there is key press in the “On Screen Keyboard”
To get the above requirement done, we are creating a DLL and exporting a callback method.
extern "C"
{
void __declspec(dllexport) __stdcall onKeyPress(void);
}
This DLL will be loaded dynamically by the “On Screen Keyboard” and will call the callback method exported from the DLL.
Where I am stuck:
I have to call a method in the “Client application” from this exported callback function which is there in the DLL so that whenever there is a key press on keyboard, “Client Application” will get the notification.
I am unable to call the method in the client application.
Think that the On Screen Keyboard will load the DLL and will call the exported method as shown
[Sample code]
#pragma comment(lib,"..\\Debug\\SharedDll.lib")
__declspec(dllimport) void __stdcall calledByOnKeyPress(int scanCode);
int main(void)
{
char ch = getchar();
calledByOnKeyPress(ch);
return 0;
}
from the DLL, i am trying to call a method in the application something like this.
void __declspec(dllexport) __stdcall calledByOnKeyPress(int scanCode)
{
callBackFunction(scanCode);
}
I am not getting how to go ahead...
One of the possible solution involves the following.
SharedDll should define a common data segment that can be shared among multiple processes.
Create a separate (message) thread in the Client Application to receive keyboard messages. Then set the Thread ID of this thread to the common data segment of SharedDll by the means of an exported function.
Your On Screen Keyboard process loads the SharedDll and calls the onKeyPress() function as usual.
Inside the onKeyPress() function in the SharedDll, it should check for a valid thread id which is stored in the common dll data segment. If there is a valid thread id then simply post a thread message.
The 4th step above will deliver your keyboard message from the "On Screen Keyboard" process to the Thread running inside the second process "Client Application"!
Usage of Dll common-data-segment is the decisive technique here.
Inside the Client Application
DWORD WINAPI KeyboardMsgThread( LPVOID lpParam )
{
// Start the message thread
MSG stMsg;
while( GetMessage( &stMsg, 0, KEYBOARD_MSG_MIN, KEYBOARD_MSG_MAX ))
{
// Process the keyboard message here!
}
return TRUE;
}
bool CreateKeyboardMsgThread()
{
DWORD dwThreadID = 0;
CreateThread( 0, 0, KeyboardMsgThread, 0, 0, &dwThreadID );
Sleep( 100 );// Let the message queue be created.
SetKeyboardThread( dwThreadID );//Set the thread id to the common data area.
return true;
}
Inside the SharedDll
#pragma data_seg(".SHARED")
DWORD Dll_dwThreadID = 0;
#pragma data_seg()
#pragma comment(linker, "/section:.SHARED,RWS")
extern "C"
{
void __declspec(dllexport) __stdcall onKeyPress(void)
{
if( 0 != Dll_dwThreadID )
{
//When there is a valid thread id, simply post it to the thread.
//This thread can be inside any other process.
PostThreadMessage( Dll_dwThreadID, KEYBOARD_MSG_MIN, 0, 0 );
}
}
// Client Application will create the thread and calls this function to
// set the thread-id to the common-data segment so that it can be
// utilized by the instance of SharedDll which resides in the process
// space of On Screen Keyboard.
void __declspec(dllexport) __stdcall SetKeyboardThread(DWORD dwThreadID)
{
Dll_dwThreadID = dwThreadID;
}
}
Inside the On Screen Keyboard Application
bool RecieveKeyboardNotification()
{
onKeyPress();
}
Hope this help!
When you load a .dll it will have different instances on different processes (so to speak).
For example, if App1 uses myDll.dll and App2 also uses myDll.dll , if you make a call inside myDll.dll from App1 , App2 won't be able to see it .
Dlls are just providers of compiled code at runtime.
For intra-process comunication you need to use intra-process approaches, such as communicating through sockets , shared memory ,etc .
In your case, where from my understanding the keyboard is on a different process, you need to signal through a socket (for example) to the client App the keyboard change.
I asume, the client app loads the dll, and the dll shall call a function in the app.
So your app must register a function which should be called by the dll.
Therefore you need something like a (simplified):
void registerCallback(CallbackFunctionPointer callbackfunction){
//the app, or anyone else can call this to register a function which should be called
remember = callbackfunction;
}
and, if the the key is pressed, you call:
void something(char ch){
//call the previously registered callback
remember(ch);
}
the variable "remember" should be defined as static var, and must be declared, like:
typedef void (*CallbackFunctionPointer) (char ch);
static CallbackFunctionPointer remember;
hope, this helps

How to return value from asynchronous function in dll

My dll has asynchronous function which starts a thread and returns immediately. It accepts handle of event object (type HANDLE) which the thread signals when it is done. This works fine but how can I return result from the function that it passed and no error occurred? A simple bool type will do.
I am thinking of using GetLastError() kind of call to get result of last function but I am not really sold on this way. I also looked at std::future and std::async but I am not sure if I can use that in dll function!? Another option I thought about is to use GetOverlappedResultbut that works usually with file i/o and I don't know if I can use this for a custom function that I have written.
Chad is right callback is safe and easy way to do it
// DLL:
__declspec(dllexport) void (*callback_function)(DWORD ret)=NULL;
DWORD _stdcall thread_function(LPVOID p)
{
// do something ...
if (callback_function) callback_function(some_return_value);
}
// DLL usage
DWORD return_value1=0;
bool done1=false;
void callback_function1(DWORD ret)
{
return_value1=ret;
done1=true;
}
void main()
{
callback_function=callback_function1; // set callbak function for DLL
done1=false; // invalidate return value
// here call you DLL function
for (;!done1;) Sleep(1); // wait for valid result ... also can add some timeout to avoid hang-ups
// now in return_value1 is the valid return value
}
also you can use waitforsingleobject instead
http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx

Step execution of release code / post-mortem debugging (VS/C++)

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)