diff --git a/PInvoke/DbgHelp/MiniDump.cs b/PInvoke/DbgHelp/MiniDump.cs index c50c4069..c3aa89a4 100644 --- a/PInvoke/DbgHelp/MiniDump.cs +++ b/PInvoke/DbgHelp/MiniDump.cs @@ -732,6 +732,77 @@ namespace Vanara.PInvoke [In, Optional] in MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, [In, Optional] in MINIDUMP_USER_STREAM_INFORMATION UserStreamParam, [In, Optional] in MINIDUMP_CALLBACK_INFORMATION CallbackParam); + /// Writes user-mode minidump information to the specified file. + /// + /// A handle to the process for which the information is to be generated. + /// + /// This handle must have PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access to the process. If handle information + /// is to be collected then PROCESS_DUP_HANDLE access is also required. For more information, see Process Security and Access + /// Rights. The caller must also be able to get THREAD_ALL_ACCESS access to the threads in the process. For more information, + /// see Thread Security and Access Rights. + /// + /// + /// The identifier of the process for which the information is to be generated. + /// A handle to the file in which the information is to be written. + /// + /// The type of information to be generated. This parameter can be one or more of the values from the MINIDUMP_TYPE enumeration. + /// + /// + /// A pointer to a MINIDUMP_EXCEPTION_INFORMATION structure describing the client exception that caused the minidump to be + /// generated. If the value of this parameter is NULL, no exception information is included in the minidump file. + /// + /// + /// A pointer to a MINIDUMP_USER_STREAM_INFORMATION structure. If the value of this parameter is NULL, no user-defined + /// information is included in the minidump file. + /// + /// + /// A pointer to a MINIDUMP_CALLBACK_INFORMATION structure that specifies a callback routine which is to receive extended minidump + /// information. If the value of this parameter is NULL, no callbacks are performed. + /// + /// + /// + /// If the function succeeds, the return value is TRUE; otherwise, the return value is FALSE. To retrieve extended + /// error information, call GetLastError. Note that the last error will be an HRESULT value. + /// + /// If the operation is canceled, the last error code is + /// HRESULT_FROM_WIN32(ERROR_CANCELLED) + /// . + /// + /// + /// + /// + /// The MiniDumpCallback function receives extended minidump information from MiniDumpWriteDump. It also provides a way for + /// the caller to determine the granularity of information written to the minidump file, as the callback function can filter the + /// default information. + /// + /// + /// MiniDumpWriteDump should be called from a separate process if at all possible, rather than from within the target process + /// being dumped. This is especially true when the target process is already not stable. For example, if it just crashed. A loader + /// deadlock is one of many potential side effects of calling MiniDumpWriteDump from within the target process. + /// + /// + /// MiniDumpWriteDump may not produce a valid stack trace for the calling thread. To work around this problem, you must + /// capture the state of the calling thread before calling MiniDumpWriteDump and use it as the ExceptionParam parameter. One + /// way to do this is to force an exception inside a __try/ __except block and use the EXCEPTION_POINTERS information + /// provided by GetExceptionInformation. Alternatively, you can call the function from a new worker thread and filter this worker + /// thread from the dump. + /// + /// + /// All DbgHelp functions, such as this one, are single threaded. Therefore, calls from more than one thread to this function will + /// likely result in unexpected behavior or memory corruption. To avoid this, you must synchronize all concurrent calls from more + /// than one thread to this function. + /// + /// + // https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump BOOL MiniDumpWriteDump( + // HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + // PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); + [DllImport(Lib_DbgHelp, SetLastError = true, ExactSpelling = true)] + [PInvokeData("minidumpapiset.h", MSDNShortId = "NF:minidumpapiset.MiniDumpWriteDump")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern unsafe bool MiniDumpWriteDump(HPROCESS hProcess, uint ProcessId, HFILE hFile, MINIDUMP_TYPE DumpType, + [In, Optional] in MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, [In, Optional] IntPtr UserStreamParam, + [In, Optional] IntPtr CallbackParam); + /// Writes user-mode minidump information to the specified file. /// /// A handle to the process for which the information is to be generated. diff --git a/UnitTests/PInvoke/DbgHelp/DbgHelpTests.cs b/UnitTests/PInvoke/DbgHelp/DbgHelpTests.cs index 3f02023a..0ec1cb27 100644 --- a/UnitTests/PInvoke/DbgHelp/DbgHelpTests.cs +++ b/UnitTests/PInvoke/DbgHelp/DbgHelpTests.cs @@ -192,53 +192,62 @@ namespace Vanara.PInvoke.Tests [Test] public void MiniDumpCallbackOrderTest() { - var memCallbackCalled = false; - using var hFile = CreateFile("CallbackOrder.dmp", Kernel32.FileAccess.GENERIC_READ | Kernel32.FileAccess.GENERIC_WRITE, 0, default, FileMode.Create, FileFlagsAndAttributes.FILE_ATTRIBUTE_NORMAL); - if (!hFile.IsInvalid) + try { - var mdei = new MINIDUMP_EXCEPTION_INFORMATION - { - ThreadId = GetCurrentThreadId(), - ExceptionPointers = Marshal.GetExceptionPointers() - }; - - var mci = new MINIDUMP_CALLBACK_INFORMATION { CallbackRoutine = MyMiniDumpCallback }; - - Assert.That(MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MINIDUMP_TYPE.MiniDumpNormal, mdei, default, mci), ResultIs.Successful); + // Load an exception into the system tables + throw new InvalidOperationException(); } - - bool MyMiniDumpCallback([In, Out] IntPtr CallbackParam, in MINIDUMP_CALLBACK_INPUT CallbackInput, ref MINIDUMP_CALLBACK_OUTPUT CallbackOutput) + catch { - TestContext.Write($"{CallbackInput.CallbackType} "); - switch (CallbackInput.CallbackType) + // Test for debug exception info + var memCallbackCalled = false; + using var hFile = CreateFile("CallbackOrder.dmp", Kernel32.FileAccess.GENERIC_READ | Kernel32.FileAccess.GENERIC_WRITE, 0, default, FileMode.Create, FileFlagsAndAttributes.FILE_ATTRIBUTE_NORMAL); + if (!hFile.IsInvalid) { - case MINIDUMP_CALLBACK_TYPE.ModuleCallback: - TestContext.WriteLine($"(module: {CallbackInput.Module.FullPath})"); - return true; - case MINIDUMP_CALLBACK_TYPE.ThreadCallback: - TestContext.WriteLine($"(thread: {CallbackInput.Thread.ThreadId:X})"); - return true; - case MINIDUMP_CALLBACK_TYPE.ThreadExCallback: - TestContext.WriteLine($"(thread: {CallbackInput.ThreadEx.ThreadId:X})"); - return true; - case MINIDUMP_CALLBACK_TYPE.IncludeThreadCallback: - TestContext.WriteLine($"(thread: {CallbackInput.IncludeThread.ThreadId:X})"); - return true; - case MINIDUMP_CALLBACK_TYPE.IncludeModuleCallback: - TestContext.WriteLine($"(module: {CallbackInput.IncludeModule.BaseOfImage:X})"); - return true; - case MINIDUMP_CALLBACK_TYPE.MemoryCallback: - memCallbackCalled = true; - TestContext.WriteLine(""); - return false; - case MINIDUMP_CALLBACK_TYPE.CancelCallback: - CallbackOutput.Cancel = false; - CallbackOutput.CheckCancel = !memCallbackCalled; - TestContext.WriteLine(""); - return true; - default: - TestContext.WriteLine(""); - return false; + var mdei = new MINIDUMP_EXCEPTION_INFORMATION + { + ThreadId = GetCurrentThreadId(), + ExceptionPointers = Marshal.GetExceptionPointers() + }; + + var mci = new MINIDUMP_CALLBACK_INFORMATION { CallbackRoutine = MyMiniDumpCallback }; + + Assert.That(MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MINIDUMP_TYPE.MiniDumpNormal, mdei, default, mci), ResultIs.Successful); + } + + bool MyMiniDumpCallback([In, Out] IntPtr CallbackParam, in MINIDUMP_CALLBACK_INPUT CallbackInput, ref MINIDUMP_CALLBACK_OUTPUT CallbackOutput) + { + TestContext.Write($"{CallbackInput.CallbackType} "); + switch (CallbackInput.CallbackType) + { + case MINIDUMP_CALLBACK_TYPE.ModuleCallback: + TestContext.WriteLine($"(module: {CallbackInput.Module.FullPath})"); + return true; + case MINIDUMP_CALLBACK_TYPE.ThreadCallback: + TestContext.WriteLine($"(thread: {CallbackInput.Thread.ThreadId:X})"); + return true; + case MINIDUMP_CALLBACK_TYPE.ThreadExCallback: + TestContext.WriteLine($"(thread: {CallbackInput.ThreadEx.ThreadId:X})"); + return true; + case MINIDUMP_CALLBACK_TYPE.IncludeThreadCallback: + TestContext.WriteLine($"(thread: {CallbackInput.IncludeThread.ThreadId:X})"); + return true; + case MINIDUMP_CALLBACK_TYPE.IncludeModuleCallback: + TestContext.WriteLine($"(module: {CallbackInput.IncludeModule.BaseOfImage:X})"); + return true; + case MINIDUMP_CALLBACK_TYPE.MemoryCallback: + memCallbackCalled = true; + TestContext.WriteLine(""); + return false; + case MINIDUMP_CALLBACK_TYPE.CancelCallback: + CallbackOutput.Cancel = false; + CallbackOutput.CheckCancel = !memCallbackCalled; + TestContext.WriteLine(""); + return true; + default: + TestContext.WriteLine(""); + return false; + } } } }