Added MiniDumpWriteDump overload and fixed test (thanks @NN)

pull/211/head
dahall 2021-02-03 14:49:31 -07:00
parent c57e42274d
commit d1e856c448
2 changed files with 123 additions and 43 deletions

View File

@ -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);
/// <summary>Writes user-mode minidump information to the specified file.</summary>
/// <param name="hProcess">
/// <para>A handle to the process for which the information is to be generated.</para>
/// <para>
/// This handle must have <c>PROCESS_QUERY_INFORMATION</c> and <c>PROCESS_VM_READ</c> access to the process. If handle information
/// is to be collected then <c>PROCESS_DUP_HANDLE</c> access is also required. For more information, see Process Security and Access
/// Rights. The caller must also be able to get <c>THREAD_ALL_ACCESS</c> access to the threads in the process. For more information,
/// see Thread Security and Access Rights.
/// </para>
/// </param>
/// <param name="ProcessId">The identifier of the process for which the information is to be generated.</param>
/// <param name="hFile">A handle to the file in which the information is to be written.</param>
/// <param name="DumpType">
/// The type of information to be generated. This parameter can be one or more of the values from the MINIDUMP_TYPE enumeration.
/// </param>
/// <param name="ExceptionParam">
/// 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 <c>NULL</c>, no exception information is included in the minidump file.
/// </param>
/// <param name="UserStreamParam">
/// A pointer to a MINIDUMP_USER_STREAM_INFORMATION structure. If the value of this parameter is <c>NULL</c>, no user-defined
/// information is included in the minidump file.
/// </param>
/// <param name="CallbackParam">
/// 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 <c>NULL</c>, no callbacks are performed.
/// </param>
/// <returns>
/// <para>
/// If the function succeeds, the return value is <c>TRUE</c>; otherwise, the return value is <c>FALSE</c>. To retrieve extended
/// error information, call GetLastError. Note that the last error will be an <c>HRESULT</c> value.
/// </para>
/// <para>If the operation is canceled, the last error code is
/// <code>HRESULT_FROM_WIN32(ERROR_CANCELLED)</code>
/// .
/// </para>
/// </returns>
/// <remarks>
/// <para>
/// The MiniDumpCallback function receives extended minidump information from <c>MiniDumpWriteDump</c>. 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.
/// </para>
/// <para>
/// <c>MiniDumpWriteDump</c> 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 <c>MiniDumpWriteDump</c> from within the target process.
/// </para>
/// <para>
/// <c>MiniDumpWriteDump</c> 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 <c>MiniDumpWriteDump</c> and use it as the ExceptionParam parameter. One
/// way to do this is to force an exception inside a <c>__try</c>/ <c>__except</c> 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
// 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);
/// <summary>Writes user-mode minidump information to the specified file.</summary>
/// <param name="hProcess">
/// <para>A handle to the process for which the information is to be generated.</para>

View File

@ -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;
}
}
}
}