On 4/10/2010 8:27 PM, Heikki Linnakangas wrote:
On 04.10.2010 15:21, Craig Ringer wrote:
Thinking about it, a possibly better alternative is to ship it as a
library as is done with the pl/pgsql debugger, and use (I think)
shared_preload_libraries to load it when desired. That way there's zero
cost if disabled, though a somewhat higher cost if enabled.

Hmm. That'll let me put a test version together that'll work with 9.0 as
well, so that seems like a no-brainer really, it's just a better way to
do it. Time for a Pg-on-windows build, yay.

If it's not a lot of code, it's better to have it built-in always.
Loading extra libraries in debug-mode could lead to heisenbugs.

It turns out that the basic minidumps are small (21kb here) and very fast to generate. It may well be worth leaving it enabled all the time after all. I just need to time how long the handler registration takes - though as LOAD of the current handler implemented as a contrib module takes 2.1ms, and LOAD of an module with an empty _PG_init() also takes 2.1ms, I'm guessing "not long".

I've attached an early version for people to play with if anyone's interested. It currently contains a bunch of elog() messages to report on its progress - though I'm not at all sure elog() should be used in the final version given that Pg is crashing at the time the handler is called and there's no guarantee elog() is safe to call. It also doesn't offer any way to set the dump type yet, it's always the minimal dump with exception info, registers and stack only. Finally, a "crashme" function is included to trigger a reliable unhandled exception on demand, for initial testing.

Usage with Pg built from source on Windows:

- Drop crashdump.c and the Makefile in contrib/crashdump
- Run build.bat and install.bat
- Create a "crashdumps" directory inside your datadir, and make
  sure the server has read/write/create permission on it.
- add 'crashdump' to shared_preload_libraries in postgresql.conf
- Start the server as normal
- Start psql and issue:
-- CREATE OR REPLACE FUNCTION crashdump_crashme() RETURNS void
   AS '$libdir/crashdump','crashdump_crashme' LANGUAGE C;
-- SELECT crashdump_crashme();

Your backend should crash. In the error log (and, depending on how the buffering works out, maybe in psql) you should see:

WARNING:  loading dll
WARNING:  loading dll try 1, 00000000
WARNING:  loading dll try 2, 73880000
WARNING:  pdump: 738C70D8
WARNING:  Generating dumppath
WARNING:  Generated dumppath
WARNING:  dumping to path: crashdumps\backend.dmp
WARNING:  outfile: 000000B0
WARNING:  about to dump
NOTICE:  crashdump: wrote crash dump to crashdumps\postgres-4341.mdmp


Once you have the dump file, you can fire up windbg from the Debugging Tools for Windows and use File->Open Crash Dump to open it. The .excr command will report what the exception that caused the crash was, producing output like this:

0:000> .ecxr
eax=00000000 ebx=00000000 ecx=02a1e290 edx=73831014 esi=02a1e188 edi=00c3f798
eip=738313b2 esp=00c3f724 ebp=02a1e280 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
crashdump!crashdump_crashme+0x2:
738313b2 c70001000000    mov     dword ptr [eax],1    ds:0023:00000000=????????

and if possible opening the postgresql source file the crash happened in to the appropriate line:

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


If you're using Visual Studio Professional (not the free Express edition, unfortunately) you should also be able to debug the crash dump that way. I don't have it to test with.


--
Craig Ringer

Tech-related writing at http://soapyfrogs.blogspot.com/
/*-------------------------------------------------------------------------
 *
 * crashdump.c
 *        Automatic crash dump creation for PostgreSQL on Windows
 *
 *-------------------------------------------------------------------------
 */

#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/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
 */

// FIXME/TODO: elog is probably unsafe when we're crashing out. Should we just 
write to stderr?

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];
        elog(WARNING, "loading dll"); //XXX
        if (GetModuleFileName( NULL, dbgHelpPath, _MAX_PATH ))
        {
                char *slash = strrchr( dbgHelpPath, '\\' );
                if (slash)
                {
                        strcpy( slash+1, "DBGHELP.DLL" );
                        hDll = LoadLibrary( dbgHelpPath );
                }
        }
        elog(WARNING, "loading dll try 1, %p", (void*)hDll); //XXX
        if (hDll==NULL)
        {
                // Load any version we can
                hDll = LoadLibrary( "DBGHELP.DLL" );
        }
        elog(WARNING, "loading dll try 2, %p", (void*)hDll); //XXX
        if (hDll==NULL)
        {
                elog(WARNING, "crashdump: unable to load dbghelp.dll");
        }
        else
        {
                pDump = (MINIDUMPWRITEDUMP)GetProcAddress( hDll, 
"MiniDumpWriteDump" );
                if (pDump==NULL)
                {
                        elog(WARNING, "crashdump: unable to get 
MiniDumpWriteDump from dbghelp.dll");
                }
        }
        elog(WARNING, "pdump: %p", (void*)pDump); //XXX
        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];
        MINIDUMP_TYPE dumpType = MiniDumpNormal;
        HANDLE selfProcHandle = GetCurrentProcess();
        DWORD selfPid = GetProcessId(selfProcHandle);
        HANDLE dumpFile;

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

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


        elog(WARNING, "Generating dumppath"); //XXX
        // TODO: generate a proper dump path
        snprintf(&dumpPath[0], _MAX_PATH, "crashdumps\\postgres-%i.mdmp", 
selfPid);
        dumpPath[_MAX_PATH-1] = '\0';
        elog(WARNING, "Generated dumppath"); //XXX
        elog(WARNING, "dumping to path: %s", &dumpPath[0]); //XXX
        dumpFile = CreateFile( dumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, 
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
        elog(WARNING, "outfile: %p", (void*)dumpFile); //XXX
        if (dumpFile==INVALID_HANDLE_VALUE)
        {
                elog(WARNING, "crashdump: Unable to open dump file %s for 
writing", &dumpPath[0]);
                return EXCEPTION_CONTINUE_SEARCH;
        }

        elog(WARNING, "about to dump");
        if( (*pDump)( selfProcHandle, selfPid, dumpFile, dumpType, &ExInfo, 
NULL, NULL ) )
        {
                elog(NOTICE,"crashdump: wrote crash dump to %s", &dumpPath[0]);
        }
        else
        {
                elog(WARNING,"crashdump: failed to write dump file");
        }
        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 );
}
MODULE_big = crashdump
OBJS = crashdump.o

ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/crashdump
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to