1 /*-------------------------------------------------------------------------
2  *
3  * crashdump.c
4  *		   Automatic crash dump creation for PostgreSQL on Windows
5  *
6  * The crashdump feature traps unhandled win32 exceptions produced by the
7  * backend, and tries to produce a Windows MiniDump crash
8  * dump for later debugging and analysis. The machine performing the dump
9  * doesn't need any special debugging tools; the user only needs to send
10  * the dump to somebody who has the same version of PostgreSQL and has debugging
11  * tools.
12  *
13  * crashdump module originally by Craig Ringer <ringerc@ringerc.id.au>
14  *
15  * LIMITATIONS
16  * ===========
17  * This *won't* work in hard OOM situations or stack overflows.
18  *
19  * For those, it'd be necessary to take a much more complicated approach where
20  * the handler switches to a new stack (if it can) and forks a helper process
21  * to debug it self.
22  *
23  * POSSIBLE FUTURE WORK
24  * ====================
25  * For bonus points, the crash dump format permits embedding of user-supplied
26  * data. If there's anything else that should always be supplied with a crash
27  * dump (postgresql.conf? Last few lines of a log file?), it could potentially
28  * be added, though at the cost of a greater chance of the crash dump failing.
29  *
30  *
31  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
32  *
33  * IDENTIFICATION
34  *	  src/backend/port/win32/crashdump.c
35  *
36  *-------------------------------------------------------------------------
37  */
38 
39 #include "postgres.h"
40 
41 #define WIN32_LEAN_AND_MEAN
42 
43 /*
44  * Some versions of the MS SDK contain "typedef enum { ... } ;" which the MS
45  * compiler quite sanely complains about. Well done, Microsoft.
46  * This pragma disables the warning just while we include the header.
47  * The pragma is known to work with all (as at the time of writing) supported
48  * versions of MSVC.
49  */
50 #ifdef _MSC_VER
51 #pragma warning(push)
52 #pragma warning(disable : 4091)
53 #endif
54 #include <dbghelp.h>
55 #ifdef _MSC_VER
56 #pragma warning(pop)
57 #endif
58 
59 /*
60  * Much of the following code is based on CodeProject and MSDN examples,
61  * particularly
62  * http://www.codeproject.com/KB/debug/postmortemdebug_standalone1.aspx
63  *
64  * Useful MSDN articles:
65  *
66  * http://msdn.microsoft.com/en-us/library/ff805116(v=VS.85).aspx
67  * http://msdn.microsoft.com/en-us/library/ms679294(VS.85).aspx
68  *
69  * Other useful articles on working with minidumps:
70  * http://www.debuginfo.com/articles/effminidumps.html
71  */
72 
73 typedef BOOL (WINAPI * MINIDUMPWRITEDUMP) (HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
74 										   CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
75 										   CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
76 										   CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
77 );
78 
79 
80 /*
81  * This function is the exception handler passed to SetUnhandledExceptionFilter.
82  * It's invoked only if there's an unhandled exception. The handler will use
83  * dbghelp.dll to generate a crash dump, then resume the normal unhandled
84  * exception process, which will generally exit with an error message from
85  * the runtime.
86  *
87  * This function is run under the unhandled exception handler, effectively
88  * in a crash context, so it should be careful with memory and avoid using
89  * any PostgreSQL functions.
90  */
91 static LONG WINAPI
crashDumpHandler(struct _EXCEPTION_POINTERS * pExceptionInfo)92 crashDumpHandler(struct _EXCEPTION_POINTERS *pExceptionInfo)
93 {
94 	/*
95 	 * We only write crash dumps if the "crashdumps" directory within the
96 	 * postgres data directory exists.
97 	 */
98 	DWORD		attribs = GetFileAttributesA("crashdumps");
99 
100 	if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY))
101 	{
102 		/* 'crashdumps' exists and is a directory. Try to write a dump' */
103 		HMODULE		hDll = NULL;
104 		MINIDUMPWRITEDUMP pDump = NULL;
105 		MINIDUMP_TYPE dumpType;
106 		char		dumpPath[_MAX_PATH];
107 		HANDLE		selfProcHandle = GetCurrentProcess();
108 		DWORD		selfPid = GetProcessId(selfProcHandle);
109 		HANDLE		dumpFile;
110 		DWORD		systemTicks;
111 		struct _MINIDUMP_EXCEPTION_INFORMATION ExInfo;
112 
113 		ExInfo.ThreadId = GetCurrentThreadId();
114 		ExInfo.ExceptionPointers = pExceptionInfo;
115 		ExInfo.ClientPointers = FALSE;
116 
117 		/* Load the dbghelp.dll library and functions */
118 		hDll = LoadLibrary("dbghelp.dll");
119 		if (hDll == NULL)
120 		{
121 			write_stderr("could not load dbghelp.dll, cannot write crash dump\n");
122 			return EXCEPTION_CONTINUE_SEARCH;
123 		}
124 
125 		pDump = (MINIDUMPWRITEDUMP) (pg_funcptr_t) GetProcAddress(hDll, "MiniDumpWriteDump");
126 
127 		if (pDump == NULL)
128 		{
129 			write_stderr("could not load required functions in dbghelp.dll, cannot write crash dump\n");
130 			return EXCEPTION_CONTINUE_SEARCH;
131 		}
132 
133 		/*
134 		 * Dump as much as we can, except shared memory, code segments, and
135 		 * memory mapped files. Exactly what we can dump depends on the
136 		 * version of dbghelp.dll, see:
137 		 * http://msdn.microsoft.com/en-us/library/ms680519(v=VS.85).aspx
138 		 */
139 		dumpType = MiniDumpNormal | MiniDumpWithHandleData |
140 			MiniDumpWithDataSegs;
141 
142 		if (GetProcAddress(hDll, "EnumDirTree") != NULL)
143 		{
144 			/* If this function exists, we have version 5.2 or newer */
145 			dumpType |= MiniDumpWithIndirectlyReferencedMemory |
146 				MiniDumpWithPrivateReadWriteMemory;
147 		}
148 
149 		systemTicks = GetTickCount();
150 		snprintf(dumpPath, _MAX_PATH,
151 				 "crashdumps\\postgres-pid%0i-%0i.mdmp",
152 				 (int) selfPid, (int) systemTicks);
153 		dumpPath[_MAX_PATH - 1] = '\0';
154 
155 		dumpFile = CreateFile(dumpPath, GENERIC_WRITE, FILE_SHARE_WRITE,
156 							  NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
157 							  NULL);
158 		if (dumpFile == INVALID_HANDLE_VALUE)
159 		{
160 			write_stderr("could not open crash dump file \"%s\" for writing: error code %lu\n",
161 						 dumpPath, GetLastError());
162 			return EXCEPTION_CONTINUE_SEARCH;
163 		}
164 
165 		if ((*pDump) (selfProcHandle, selfPid, dumpFile, dumpType, &ExInfo,
166 					  NULL, NULL))
167 			write_stderr("wrote crash dump to file \"%s\"\n", dumpPath);
168 		else
169 			write_stderr("could not write crash dump to file \"%s\": error code %lu\n",
170 						 dumpPath, GetLastError());
171 
172 		CloseHandle(dumpFile);
173 	}
174 
175 	return EXCEPTION_CONTINUE_SEARCH;
176 }
177 
178 
179 void
pgwin32_install_crashdump_handler(void)180 pgwin32_install_crashdump_handler(void)
181 {
182 	SetUnhandledExceptionFilter(crashDumpHandler);
183 }
184