crashdump.c

text/plain

Filename: crashdump.c
Type: text/plain
Part: 0
Message: Re: Re: Proposed Windows-specific change: Enable crash dumps (like core files)
/*-------------------------------------------------------------------------
 *
 * crashdump.c
 *	  Automatic crash dump creation for PostgreSQL on Windows
 *
 * The crashdump module traps unhandled exceptions produced by the backend
 * the module is loaded in, and tries to produce a Windows MiniDump crash
 * dump for later debugging and analysis. The machine performing the dump
 * doesn't need any special debugging tools; the user only needs to send
 * the dump to somebody who has the same version of PostgreSQL and has debugging
 * tools.
 *
 * crashdump module originally by Craig Ringer <ringerc@ringerc.id.au>
 *
 * LIMITATIONS:
 * ============
 * This *won't* work in hard OOM situations or stack overflows.
 * 
 * For those, it'd be necessary to take a much more complicated approach where
 * the handler switches to a new stack (if it can) and forks a helper process
 * to debug its self. That's in the too hard basket as far as I'm concerned;
 * this approach will get 90% of the results with 10% of the work.
 *
 * POSSIBLE FUTURE WORK:
 * =====================
 * For bonus points, the crash dump format permits embedding of user-supplied data.
 * If there's anything else (postgresql.conf? Last few lines of a log file?) that
 * should always be supplied with a crash dump, it could potentially be added,
 * though at the cost of a greater chance of the crash dump failing. Again, 
 * I'm not going to tackle that, but thought it worth mentioning in case
 * someone wants it down the track.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"
#include "fmgr.h"

#define VC_EXTRALEAN
#include <windows.h>
#include <string.h>
#include <dbghelp.h>

PG_MODULE_MAGIC;

#if !(defined(_WIN32) || defined(_WIN64))
/* Don't add lots of ifdefs here - build different files for different platforms instead,
 * if adding crash dump support for additional platforms. */
#error crashdump_win32.c is only supported on MS Windows
#endif

/* 
 * Much of the following code is based on CodeProject and MSDN examples,
 * particularly http://www.codeproject.com/KB/debug/postmortemdebug_standalone1.aspx
 *
 * Useful MSDN articles:
 *
 * http://msdn.microsoft.com/en-us/library/ff805116(v=VS.85).aspx
 * http://msdn.microsoft.com/en-us/library/ms679294(VS.85).aspx
 * http://msdn.microsoft.com/en-us/library/ms679291(VS.85).aspx
 * http://msdn.microsoft.com/en-us/library/ms680519(v=VS.85).aspx
 * http://msdn.microsoft.com/en-us/library/ms680360(VS.85).aspx
 * 
 * Other useful articles on working with minidumps:
 * http://www.debuginfo.com/articles/effminidumps.html
 */

typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(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
									);

/*
 * To perform a crash dump, we need to load dbghelp.dll and find the
 * MiniDumpWriteDump function. If possible, the copy of dbghelp.dll
 * shipped with PostgreSQL is loaded; failing that, the system copy will
 * be used.
 *
 * Called in crash handler context.
 *
 * If NULL is returned, loading failed and the crash dump handler should
 * not try to continue.
 */
static MINIDUMPWRITEDUMP
loadDbgHelp()
{
	// Use the dbghelp.dll shipped with Pg if we can find and load
	// it, otherwise fall back to the copy installed in Windows.
	HMODULE hDll = NULL;
	MINIDUMPWRITEDUMP pDump = NULL;
	char dbgHelpPath[_MAX_PATH];
	if (GetModuleFileName( NULL, dbgHelpPath, _MAX_PATH ))
	{
		char *slash = strrchr( dbgHelpPath, '\\' );
		if (slash)
		{
			strcpy( slash+1, "DBGHELP.DLL" );
			hDll = LoadLibrary( dbgHelpPath );
		}
	}
	if (hDll==NULL)
	{
		// Load any version we can
		hDll = LoadLibrary( "DBGHELP.DLL" );
	}
	if (hDll!=NULL)
	{
		pDump = (MINIDUMPWRITEDUMP)GetProcAddress( hDll, "MiniDumpWriteDump" );
	}
	return pDump;
}

/*
 * This function is the exception handler passed to SetUnhandledExceptionFilter. It's invoked
 * only if there's an unhandled exception. The handler will use dbghelp.dll to generate a crash
 * dump, then resume the normal unhandled exception process, which will generally exit with a
 * an error message from the runtime.
 *
 * This function is run under the unhandled exception handler, effectively
 * in a crash context, so it should be careful with memory and avoid using
 * any PostgreSQL API or other functions that use PostgreSQL API.
 * 
 */
static LONG WINAPI
crashDumpHandler(struct _EXCEPTION_POINTERS *pExceptionInfo)
{
	MINIDUMPWRITEDUMP pDump = NULL;
	char dumpPath[_MAX_PATH];
	
	// Dump pretty much everything except shared memory, code segments, and memory mapped files
	MINIDUMP_TYPE dumpType = MiniDumpNormal|\
				 MiniDumpWithIndirectlyReferencedMemory|\
				 MiniDumpWithHandleData|\
				 MiniDumpWithThreadInfo|\
				 MiniDumpWithPrivateReadWriteMemory|
				 MiniDumpWithDataSegs;

	HANDLE selfProcHandle = GetCurrentProcess();
	DWORD selfPid = GetProcessId(selfProcHandle);
	HANDLE dumpFile;

	struct _MINIDUMP_EXCEPTION_INFORMATION ExInfo;
	ExInfo.ThreadId = GetCurrentThreadId();
	ExInfo.ExceptionPointers = pExceptionInfo;
	ExInfo.ClientPointers = FALSE;

	pDump = loadDbgHelp();
	if (pDump==NULL)
		return EXCEPTION_CONTINUE_SEARCH;

	snprintf(&dumpPath[0], _MAX_PATH, "crashdumps\\postgres-%i.mdmp", selfPid);
	dumpPath[_MAX_PATH-1] = '\0';
	dumpFile = CreateFile( dumpPath, GENERIC_WRITE, FILE_SHARE_WRITE,\
		       	NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
	if (dumpFile==INVALID_HANDLE_VALUE)
	{
		elog(WARNING, "crashdump: Unable to open dump file %s for writing (win32 error %i)",
				&dumpPath[0], GetLastError());
		elog(DEBUG1, "crashdump: is there a 'crashdump' directory within the data dir, and is it writable by the postgres user?");
		return EXCEPTION_CONTINUE_SEARCH;
	}

	if( (*pDump)( selfProcHandle, selfPid, dumpFile, dumpType, &ExInfo, NULL, NULL ) )
	{
		elog(WARNING,"crashdump: wrote crash dump to %s", &dumpPath[0]);
	}
	else
	{
		elog(WARNING,"crashdump: failed to write dump file to %s (win32 error %i)",
				&dumpPath[0], GetLastError());
	}
	CloseHandle(dumpFile);
	return EXCEPTION_CONTINUE_SEARCH;
}

extern Datum crashdump_crashme(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(crashdump_crashme);

Datum
crashdump_crashme(PG_FUNCTION_ARGS)
{
	int * ptr = NULL;
	*ptr = 1;
	return NULL;
}

void
_PG_init()
{
	// http://msdn.microsoft.com/en-us/library/ms680634(VS.85).aspx
	SetUnhandledExceptionFilter( crashDumpHandler );
}