1
0
Fork 0
mirror of synced 2025-03-07 03:53:26 +01:00
wine/dlls/dbghelp/tests/minidump.c
Eric Pouech 819d8692c6 dbghelp: No longer embed unwind information in minidump (x86_64).
This can generate very long time when saving the minidump, but
also when reloading it.
Unwind information is expected to be gotten from an image matching
the modules listed in the minidump for Normal mode.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=55798
Signed-off-by: Eric Pouech <epouech@codeweavers.com>
2024-03-15 15:06:38 +01:00

864 lines
34 KiB
C

/*
* Copyright 2024 Eric Pouech for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <assert.h>
#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windows.h"
#include "dbghelp.h"
#include "wine/test.h"
#include "winternl.h"
#include "winnt.h"
#include "wine/test.h"
static unsigned popcount32(ULONG val)
{
val -= val >> 1 & 0x55555555;
val = (val & 0x33333333) + (val >> 2 & 0x33333333);
return ((val + (val >> 4)) & 0x0f0f0f0f) * 0x01010101 >> 24;
}
static inline unsigned popcount64(ULONG64 val)
{
return popcount32(val >> 32) + popcount32(val);
}
#define minidump_open_for_read(a) _minidump_open_for_read(__LINE__, (a))
static MINIDUMP_HEADER *_minidump_open_for_read(unsigned line, const char *filename)
{
MINIDUMP_HEADER *hdr;
HANDLE file, map;
BOOL ret;
file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
ok_(__FILE__, line)(file != INVALID_HANDLE_VALUE, "Couldn't reopen file\n");
map = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL);
ok_(__FILE__, line)(map != 0, "Couldn't map file\n");
hdr = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
ok(hdr != NULL, "Couldn't map file\n");
ok(hdr && hdr->Signature == MINIDUMP_SIGNATURE, "Unexpected signature\n");
ret = CloseHandle(map);
ok_(__FILE__, line)(ret, "Couldn't unmap file\n");
ret = CloseHandle(file);
ok_(__FILE__, line)(ret, "Couldn't close file\n");
return hdr;
}
#define minidump_close_for_read(a) _minidump_close_for_read(__LINE__, (a))
static void _minidump_close_for_read(unsigned line, MINIDUMP_HEADER *data)
{
BOOL ret;
ret = UnmapViewOfFile(data);
ok_(__FILE__, line)(ret, "Couldn't unmap file\n");
}
static BOOL minidump_write(HANDLE proc, const WCHAR *filename, MINIDUMP_TYPE type, BOOL windows_can_fail,
MINIDUMP_EXCEPTION_INFORMATION *mei, MINIDUMP_CALLBACK_INFORMATION *cbi)
{
HANDLE file;
BOOL ret, ret2;
file = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
ok(file != INVALID_HANDLE_VALUE, "Failed to create minidump %ls\n", filename);
ret = MiniDumpWriteDump(proc, GetProcessId(proc), file, type, mei, NULL, cbi);
/* using new features that are not supported on old dbghelp versions */
ok(ret || broken(windows_can_fail), "Couldn't write minidump content\n");
ret2 = CloseHandle(file);
ok(ret2, "Couldn't close file\n");
return ret;
}
typedef DWORD64 stream_mask_t;
#define STREAM2MASK(st) (((stream_mask_t)1) << (st))
#define BASIC_STREAM_MASK \
(STREAM2MASK(ThreadListStream) | STREAM2MASK(ModuleListStream) | STREAM2MASK(MemoryListStream) | \
STREAM2MASK(SystemInfoStream) | STREAM2MASK(MiscInfoStream) | STREAM2MASK(SystemMemoryInfoStream) | \
STREAM2MASK(ProcessVmCountersStream))
/* streams added in Win8 & Win10... */
#define BASIC_STREAM_BROKEN_MASK (STREAM2MASK(SystemMemoryInfoStream) | STREAM2MASK(ProcessVmCountersStream))
#define BASIC_STREAM_TODO_MASK (STREAM2MASK(SystemMemoryInfoStream) | STREAM2MASK(ProcessVmCountersStream))
static void test_minidump_contents(void)
{
static const struct minidump_streams
{
MINIDUMP_TYPE type;
stream_mask_t streams_mask;
stream_mask_t todo_wine_mask;
}
streams_table[] =
{
/* 0 */ {MiniDumpNormal, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK},
{MiniDumpWithDataSegs, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK},
{MiniDumpWithProcessThreadData, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK},
{MiniDumpWithThreadInfo, BASIC_STREAM_MASK | STREAM2MASK(ThreadInfoListStream), BASIC_STREAM_TODO_MASK | STREAM2MASK(ThreadInfoListStream)},
{MiniDumpWithCodeSegs, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK},
/* 5 */ {MiniDumpWithTokenInformation, BASIC_STREAM_MASK | STREAM2MASK(TokenStream), BASIC_STREAM_TODO_MASK | STREAM2MASK(TokenStream)},
{MiniDumpWithModuleHeaders, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK}, /* requires win8 at least */
{MiniDumpWithAvxXStateContext, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK}, /* requires win10 at least */
{MiniDumpWithIptTrace, BASIC_STREAM_MASK, BASIC_STREAM_TODO_MASK},
};
MINIDUMP_HEADER *hdr;
void *where;
ULONG size;
BOOL ret;
int i, j;
for (i = 0; i < ARRAY_SIZE(streams_table); i++)
{
winetest_push_context("streams_table[%d]", i);
/* too old for dbghelp in Win7 & 8 */
if (minidump_write(GetCurrentProcess(), L"foo.mdmp", streams_table[i].type, i >= 6, NULL, NULL))
{
hdr = minidump_open_for_read("foo.mdmp");
/* native keeps (likely padding) some unused streams at the end of directory, but lists them here */
ok(hdr->NumberOfStreams >= popcount64(streams_table[i].streams_mask), "Unexpected number of streams %u <> %u\n",
hdr->NumberOfStreams, popcount64(streams_table[i].streams_mask));
ok(hdr->Flags == streams_table[i].type, "Unexpected flags\n");
/* start at 3, 0=unused (cf above), 1,2=reserved shall be skipped */
for (j = 3; j < 25 /* last documented stream */; j++)
{
ret = MiniDumpReadDumpStream(hdr, j, NULL, &where, &size);
todo_wine_if(streams_table[i].todo_wine_mask & STREAM2MASK(j))
if (streams_table[i].streams_mask & STREAM2MASK(j))
ok((ret && where) || broken(BASIC_STREAM_BROKEN_MASK & STREAM2MASK(j)), "Expecting stream %d to be present\n", j);
else
ok(!ret, "Not expecting stream %d to be present\n", j);
}
minidump_close_for_read(hdr);
ret = DeleteFileA("foo.mdmp");
ok(ret, "Couldn't delete file\n");
}
else win_skip("Skipping not supported feature (too old dbghelp version)\n");
winetest_pop_context();
}
}
static void minidump_check_nostream(void *data, MINIDUMP_STREAM_TYPE stream_type)
{
void *stream;
BOOL ret;
ret = MiniDumpReadDumpStream(data, stream_type, NULL, (void**)&stream, NULL);
ok(!ret, "Unexpected stream %u\n", stream_type);
}
static void minidump_check_pid(void *data, DWORD pid)
{
MINIDUMP_MISC_INFO *misc_info;
BOOL ret;
ret = MiniDumpReadDumpStream(data, MiscInfoStream, NULL, (void**)&misc_info, NULL);
ok(ret && misc_info, "Couldn't find misc-info stream\n");
ok(misc_info->Flags1 & misc_info->Flags1 & MINIDUMP_MISC1_PROCESS_ID, "No process-id in misc_info\n");
ok(pid == misc_info->ProcessId, "Unexpected process id\n");
}
static unsigned minidump_get_number_of_threads(void *data)
{
MINIDUMP_THREAD_LIST *thread_list;
BOOL ret;
ret = MiniDumpReadDumpStream(data, ThreadListStream, NULL, (void**)&thread_list, NULL);
ok(ret && thread_list, "Couldn't find thread-list stream\n");
return thread_list->NumberOfThreads;
}
static void minidump_check_threads(void *data)
{
MINIDUMP_THREAD_LIST *thread_list;
ULONG stream_size;
int i;
BOOL ret;
ret = MiniDumpReadDumpStream(data, ThreadListStream, NULL, (void**)&thread_list, &stream_size);
ok(ret && thread_list, "Couldn't find thread-list stream\n");
ok(stream_size == sizeof(thread_list->NumberOfThreads) + thread_list->NumberOfThreads * sizeof(thread_list->Threads[0]),
"Unexpected size\n");
for (i = 0; i < thread_list->NumberOfThreads; i++)
{
const MINIDUMP_THREAD *thread = &thread_list->Threads[i];
const CONTEXT *ctx;
ok(thread->SuspendCount == 0, "Unexpected value\n");
ok(thread->Stack.StartOfMemoryRange, "Unexpected value %I64x\n", thread->Stack.StartOfMemoryRange);
ok(thread->Stack.Memory.DataSize, "Unexpected value %x\n", thread->Stack.Memory.DataSize);
ok(thread->Teb, "Unexpected value\n");
ok(thread->ThreadContext.DataSize >= sizeof(CONTEXT), "Unexpected value\n");
ctx = RVA_TO_ADDR(data, thread->ThreadContext.Rva);
ok((ctx->ContextFlags & CONTEXT_ALL) == CONTEXT_ALL, "Unexpected value\n");
}
}
static void minidump_check_module(void *data, const WCHAR *name, DWORD64 base)
{
MINIDUMP_MODULE_LIST *module_list;
size_t namelen = wcslen(name);
int i;
BOOL ret;
ret = MiniDumpReadDumpStream(data, ModuleListStream, NULL, (void**)&module_list, NULL);
ok(ret && module_list, "Couldn't find module-list stream\n");
ok(module_list->NumberOfModules > 3, "Unexpected number of modules\n");
for (i = 0; i < module_list->NumberOfModules; i++)
{
MINIDUMP_MODULE *module = &module_list->Modules[i];
WCHAR *ptr;
MINIDUMP_STRING *string = RVA_TO_ADDR(data, module->ModuleNameRva);
for (ptr = string->Buffer + string->Length / sizeof(WCHAR) - 1; ptr >= string->Buffer && *ptr != L'\\'; ptr--) {}
ptr++;
if (ptr + namelen == string->Buffer + string->Length / sizeof(WCHAR) &&
!wcsnicmp(name, ptr, namelen) &&
module->BaseOfImage == base) break;
}
ok(i < module_list->NumberOfModules, "Couldn't find module %ls in minidump\n", name);
}
struct memory_description
{
/* MD_SECTION, MD_DIRECTORY can be present at the same time */
/* MD_UNMAPPED: some DLLs are present when creating the minidump, but are unloaded afterwards (native) */
enum {MD_NONE = 0, MD_UNMAPPED = 1, MD_PE_HEADER = 2, MD_STACK = 3, MD_SECTION = 4, MD_DIRECTORY = 8, MD_UNWIND = 16} kind;
unsigned id; /* MD_STACK: thread index
* MD_DIRECTORY: directory index
* MD_UNWIND: function index in function table
*/
const char* name; /* MD_SECTION: section name */
};
static struct memory_description minidump_get_memory_description(void *data, DWORD64 addr)
{
const BYTE *addr_ptr = (void*)(ULONG_PTR)addr;
MINIDUMP_MODULE_LIST *module_list;
MINIDUMP_THREAD_LIST *thread_list;
struct memory_description md = {.kind = MD_NONE};
BOOL ret;
int i, j, dir;
ret = MiniDumpReadDumpStream(data, ModuleListStream, NULL, (void**)&module_list, NULL);
ok(ret && module_list, "Couldn't find module-list stream\n");
for (i = 0; i < module_list->NumberOfModules; i++)
{
MINIDUMP_MODULE *module = &module_list->Modules[i];
MINIDUMP_STRING *string = RVA_TO_ADDR(data, module->ModuleNameRva);
if (module->BaseOfImage <= addr && addr < module->BaseOfImage + module->SizeOfImage)
{
HMODULE module_handle;
WCHAR *module_name;
size_t module_name_len = string->Length / sizeof(WCHAR);
IMAGE_NT_HEADERS *nthdr;
module_name = malloc((module_name_len + 1) * sizeof(WCHAR));
if (!module_name) continue;
memcpy(module_name, string->Buffer, module_name_len * sizeof(WCHAR));
module_name[module_name_len] = L'\0';
module_handle = GetModuleHandleW(module_name);
if ((nthdr = RtlImageNtHeader(module_handle)))
{
for (dir = 0; dir < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; dir++)
{
ULONG dir_size;
const BYTE *dir_start = RtlImageDirectoryEntryToData(module_handle, TRUE, dir, &dir_size);
if (dir_start && dir_start <= addr_ptr && addr_ptr < dir_start + dir_size)
{
md.kind |= MD_DIRECTORY;
md.id = dir;
}
switch (dir)
{
case IMAGE_DIRECTORY_ENTRY_EXCEPTION:
if (nthdr->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
{
const IMAGE_AMD64_RUNTIME_FUNCTION_ENTRY *func;
for (func = (const void*)dir_start; func < (const IMAGE_AMD64_RUNTIME_FUNCTION_ENTRY *)(dir_start + dir_size); func++)
{
if (RtlImageRvaToVa(nthdr, module_handle, func->UnwindData, NULL) == addr_ptr)
{
md.kind = MD_UNWIND;
md.id = func - (const IMAGE_AMD64_RUNTIME_FUNCTION_ENTRY *)dir_start;
}
}
}
break;
/* FIXME handle more areas: import/export tables... */
}
}
if (addr < (DWORD_PTR)(IMAGE_FIRST_SECTION(nthdr) + nthdr->FileHeader.NumberOfSections))
{
md.kind = MD_PE_HEADER;
}
for (j = 0; j < nthdr->FileHeader.NumberOfSections; j++)
{
IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nthdr) + j;
const BYTE *section_start = (BYTE*)module_handle + section->VirtualAddress;
if (section_start <= addr_ptr && addr_ptr < section_start + section->Misc.VirtualSize)
{
md.kind |= MD_SECTION;
md.name = (const char*)section->Name;
}
}
}
else md.kind = MD_UNMAPPED;
free(module_name);
return md;
}
}
ret = MiniDumpReadDumpStream(data, ThreadListStream, NULL, (void**)&thread_list, NULL);
ok(ret && thread_list, "Couldn't find thread-list stream\n");
for (i = 0; i < thread_list->NumberOfThreads; i++)
{
MINIDUMP_THREAD *thread = &thread_list->Threads[i];
if (thread->Stack.StartOfMemoryRange <= addr &&
addr < thread->Stack.StartOfMemoryRange + thread->Stack.Memory.DataSize)
{
md.kind = MD_STACK;
md.id = i;
return md;
}
}
return md;
}
/* modules could be load/unloaded when generating the minidump, so count the number of available modules */
static unsigned minidump_get_number_of_available_modules(void *data)
{
MINIDUMP_MODULE_LIST *module_list;
unsigned num_modules = 0;
BOOL ret;
int i;
ret = MiniDumpReadDumpStream(data, ModuleListStream, NULL, (void**)&module_list, NULL);
ok(ret && module_list, "Couldn't find module-list stream\n");
for (i = 0; i < module_list->NumberOfModules; i++)
{
struct memory_description md;
md = minidump_get_memory_description(data, module_list->Modules[i].BaseOfImage);
if (md.kind != MD_UNMAPPED)
num_modules++;
}
return num_modules;
}
struct memory_walker
{
unsigned num_unknown; /* number of unknown memory locations */
unsigned num_thread_stack; /* number of locations inside a thread stack */
unsigned num_directories[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; /* number of locations in side the directories' content */
unsigned num_text; /* number of locations inside .text section */
unsigned num_unwind_info;
};
static void minidump_walk_memory(void *data, struct memory_walker *walker)
{
MINIDUMP_MEMORY_LIST *memory_list;
BOOL ret;
int i;
ret = MiniDumpReadDumpStream(data, MemoryListStream, NULL, (void**)&memory_list, NULL);
ok(ret && memory_list, "Couldn't find memory-list stream\n");
for (i = 0; i < memory_list->NumberOfMemoryRanges; i++)
{
MINIDUMP_MEMORY_DESCRIPTOR *desc = &memory_list->MemoryRanges[i];
struct memory_description md;
md = minidump_get_memory_description(data, desc->StartOfMemoryRange);
switch ((int)md.kind)
{
case MD_NONE:
walker->num_unknown++;
break;
case MD_UNMAPPED:
/* nothing we can do here */
break;
case MD_STACK:
walker->num_thread_stack++;
break;
case MD_SECTION | MD_UNWIND:
walker->num_unwind_info++;
break;
case MD_PE_HEADER:
/* FIXME may change with MiniDumpWithModuleHeaders */
ok(0, "Unexpected memory block in PE header\n");
break;
case MD_SECTION:
case MD_SECTION | MD_DIRECTORY:
if (!strcmp(md.name, ".text"))
walker->num_text++;
if (md.kind & MD_DIRECTORY)
{
ok(md.id < ARRAY_SIZE(walker->num_directories), "Out of bounds index\n");
walker->num_directories[md.id]++;
}
break;
default:
ok(0, "Unexpected memory description kind: %x\n", md.kind);
break;
}
}
}
static void test_current_process(void)
{
static const struct
{
MINIDUMP_TYPE dump_type;
}
process_tests[] =
{
{ MiniDumpNormal /* = 0 */ },
{ MiniDumpWithCodeSegs },
{ MiniDumpWithDataSegs },
{ MiniDumpWithThreadInfo },
/* requires more work
{ MiniDumpWithModuleHeaders },
{ MiniDumpWithProcessThreadData },
*/
};
struct memory_walker walker;
struct memory_description md, md2;
unsigned num_available_modules, num_threads;
void *data;
BOOL ret;
int i;
for (i = 0; i < ARRAY_SIZE(process_tests); i++)
{
winetest_push_context("process_tests[%d]", i);
minidump_write(GetCurrentProcess(), L"foo.mdmp", process_tests[i].dump_type, FALSE, NULL, NULL);
data = minidump_open_for_read("foo.mdmp");
num_threads = minidump_get_number_of_threads(data);
ok(num_threads > 0, "Unexpected number of threads\n");
minidump_check_threads(data);
md = minidump_get_memory_description(data, (DWORD_PTR)&i);
ok(md.kind == MD_STACK, "Couldn't find automatic variable\n");
md2 = minidump_get_memory_description(data, (DWORD_PTR)NtCurrentTeb()->Tib.StackBase - sizeof(void*));
ok(md2.kind == MD_STACK, "Couldn't find stack bottom\n");
ok(md.id == md2.id, "Should be on same stack\n");
minidump_check_pid(data, GetCurrentProcessId());
num_available_modules = minidump_get_number_of_available_modules(data);
#define CHECK_MODULE(s) minidump_check_module(data, s, (DWORD64)(DWORD_PTR)GetModuleHandleW(s))
CHECK_MODULE(L"ntdll.dll");
CHECK_MODULE(L"kernelbase.dll");
CHECK_MODULE(L"kernel32.dll");
CHECK_MODULE(L"dbghelp.dll");
#undef CHECK_MODULE
memset(&walker, 0, sizeof(walker));
minidump_walk_memory(data, &walker);
ok(walker.num_unknown == 0, "unexpected unknown memory locations\n");
ok(walker.num_thread_stack == num_threads, "Unexpected number of stacks\n");
if (sizeof(void*) > 4 && (process_tests[i].dump_type & MiniDumpWithModuleHeaders))
ok(walker.num_directories[IMAGE_DIRECTORY_ENTRY_EXCEPTION] > 0, "expected unwind information\n");
else
ok(walker.num_directories[IMAGE_DIRECTORY_ENTRY_EXCEPTION] == 0, "unexpected unwind information\n");
if (process_tests[i].dump_type & MiniDumpWithCodeSegs)
{
todo_wine
ok(walker.num_text >= num_available_modules ||
/* win7 & 8 report one less */
broken(walker.num_text + 1 >= num_available_modules), "expected code segments %u %u\n", walker.num_text, num_available_modules);
}
else
/* native embeds some elements in code segment from ntdll */
ok(walker.num_text < 5, "unexpected code segments %u %u\n", walker.num_text, num_available_modules);
ok(walker.num_unwind_info == 0, "unexpected unwind info %u\n", walker.num_unwind_info);
minidump_check_nostream(data, ExceptionStream);
minidump_close_for_read(data);
ret = DeleteFileA("foo.mdmp");
ok(ret, "Couldn't delete file\n");
winetest_pop_context();
}
}
struct cb_info
{
/* input */
unsigned module_flags;
unsigned thread_flags;
/* output */
DWORD64 mask_types;
unsigned num_modules;
unsigned num_threads;
unsigned num_include_modules;
unsigned num_include_threads;
};
static BOOL CALLBACK test_callback_cb(void *pmt, MINIDUMP_CALLBACK_INPUT *input, MINIDUMP_CALLBACK_OUTPUT *output)
{
struct cb_info *cb = pmt;
ok(input->CallbackType < sizeof(cb->mask_types) * 8, "Too small mask\n");
cb->mask_types |= (DWORD64)1u << input->CallbackType;
if (input->CallbackType == WriteKernelMinidumpCallback || input->CallbackType == IoStartCallback)
{
ok(input->ProcessId == 0, "Unexpected pid %lu %lu\n", input->ProcessId, input->CallbackType);
ok(input->ProcessHandle == NULL, "Unexpected process handle %p %lu\n", input->ProcessHandle, input->CallbackType);
}
else if (input->CallbackType == IsProcessSnapshotCallback || input->CallbackType == VmStartCallback)
{
ok(input->ProcessId == 0, "Unexpected pid %lu %lu\n", input->ProcessId, input->CallbackType);
ok(input->ProcessHandle == GetCurrentProcess(), "Unexpected process handle %p %lu\n", input->ProcessHandle, input->CallbackType);
}
else
{
ok(input->ProcessId == GetCurrentProcessId(), "Unexpected pid %lu %lu\n", input->ProcessId, input->CallbackType);
ok(input->ProcessHandle == GetCurrentProcess(), "Unexpected process handle %p %lu\n", input->ProcessHandle, input->CallbackType);
}
switch (input->CallbackType)
{
case ModuleCallback:
ok(output->ModuleWriteFlags == cb->module_flags, "Unexpected module flags %lx\n", output->ModuleWriteFlags);
cb->num_modules++;
break;
case IncludeModuleCallback:
ok(output->ModuleWriteFlags == cb->module_flags, "Unexpected module flags %lx\n", output->ModuleWriteFlags);
cb->num_include_modules++;
break;
case ThreadCallback:
case ThreadExCallback:
ok(output->ThreadWriteFlags == cb->thread_flags, "Unexpected thread flags %lx\n", output->ThreadWriteFlags);
cb->num_threads++;
break;
case IncludeThreadCallback:
ok(output->ThreadWriteFlags == cb->thread_flags, "Unexpected thread flags %lx\n", output->ThreadWriteFlags);
cb->num_include_threads++;
break;
case MemoryCallback:
case RemoveMemoryCallback:
ok(output->MemoryBase == 0, "Unexpected memory info\n");
ok(output->MemorySize == 0, "Unexpected memory info\n");
break;
case CancelCallback:
ok(!output->Cancel, "Unexpected value\n");
ok(!output->CheckCancel, "Unexpected value\n");
break;
case WriteKernelMinidumpCallback:
ok(output->Handle == NULL, "Unexpected value\n");
break;
/* case KernelMinidumpStatusCallback: */
/* case IncludeVmRegionCallback: */
case IoStartCallback:
ok(output->Status == E_NOTIMPL, "Unexpected value %lx\n", output->Status);
/* TODO check the output->Vm* fields */
break;
/* case IoWriteAllCallback:
* case IoFinishCallback:
*/
case ReadMemoryFailureCallback:
/* TODO check the rest */
break;
case SecondaryFlagsCallback:
ok(input->SecondaryFlags == 0x00, "Unexpected value %lx\n", input->SecondaryFlags);
ok(input->SecondaryFlags == output->SecondaryFlags, "Unexpected value %lx\n", output->SecondaryFlags);
break;
case IsProcessSnapshotCallback:
break;
case VmStartCallback:
break;
/*
case VmQueryCallback:
case VmPreReadCallback:
case VmPostReadCallback:
*/
default:
ok(0, "Unexpected callback type %lu\n", input->CallbackType);
break;
}
return TRUE;
}
static void test_callback(void)
{
static const struct
{
MINIDUMP_TYPE dump_type;
unsigned module_flags;
unsigned thread_flags;
}
callback_tests[] =
{
{
MiniDumpNormal /* = 0 */,
ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteCvRecord,
ThreadWriteThread | ThreadWriteStack | ThreadWriteContext | ThreadWriteInstructionWindow,
},
{
MiniDumpWithCodeSegs,
ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteCvRecord | ModuleWriteCodeSegs,
ThreadWriteThread | ThreadWriteStack | ThreadWriteContext | ThreadWriteInstructionWindow,
},
{
MiniDumpWithDataSegs,
ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteCvRecord | ModuleWriteDataSeg,
ThreadWriteThread | ThreadWriteStack | ThreadWriteContext | ThreadWriteInstructionWindow,
},
{
MiniDumpWithThreadInfo,
ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteCvRecord,
ThreadWriteThread | ThreadWriteStack | ThreadWriteContext | ThreadWriteInstructionWindow | ThreadWriteThreadInfo,
},
};
#define X(a) ((stream_mask_t)1u << (a))
static const stream_mask_t mask_types =
X(ModuleCallback) | X(ThreadCallback) | /* ThreadExCallback */ X(IncludeThreadCallback) |
X(IncludeModuleCallback) | X(MemoryCallback) | X(CancelCallback) | X(WriteKernelMinidumpCallback) |
/* KernelMinidumpStatusCallback) */ X(RemoveMemoryCallback) | /* IncludeVmRegionCallback */ X(IoStartCallback) |
/* IoWriteAllCallback IoFinishCallback ReadMemoryFailureCallback */ X(SecondaryFlagsCallback) |
X(IsProcessSnapshotCallback) | X(VmStartCallback) /* VmQueryCallback VmPreReadCallback */
/* VmPostReadCallback */;
static const stream_mask_t mask_types_too_old_dbghelp = X(IsProcessSnapshotCallback) | X(VmStartCallback);
#undef X
struct cb_info cb_info;
MINIDUMP_CALLBACK_INFORMATION cbi = {.CallbackRoutine = test_callback_cb, .CallbackParam = &cb_info};
BOOL ret;
int i;
for (i = 0; i < ARRAY_SIZE(callback_tests); i++)
{
winetest_push_context("callback_tests[%d]", i);
memset(&cb_info, 0, sizeof(cb_info));
cb_info.module_flags = callback_tests[i].module_flags;
cb_info.thread_flags = callback_tests[i].thread_flags;
minidump_write(GetCurrentProcess(), L"foo.mdmp", callback_tests[i].dump_type, FALSE, NULL, &cbi);
todo_wine
ok(cb_info.mask_types == mask_types ||
broken(cb_info.mask_types == (mask_types & ~mask_types_too_old_dbghelp)),
"Unexpected mask for callback types %I64x (%I64x)\n", cb_info.mask_types, mask_types);
ok(cb_info.num_modules > 5, "Unexpected number of modules\n");
/* native reports several threads... */
ok(cb_info.num_threads >= 1, "Unexpected number of threads\n");
todo_wine
ok(cb_info.num_modules == cb_info.num_include_modules, "Unexpected number of include modules\n");
todo_wine
ok(cb_info.num_threads == cb_info.num_include_threads, "Unexpected number of include threads\n");
ret = DeleteFileA("foo.mdmp");
ok(ret, "Couldn't delete file\n");
winetest_pop_context();
}
}
static void test_exception(void)
{
static const struct
{
unsigned exception_code;
unsigned exception_flags;
unsigned num_args;
BOOL with_child;
}
exception_tests[] =
{
{ 0x1234, 0, 0, FALSE },
{ 0x1234, 0, 0, TRUE },
{ 0x1234, 0, 5, FALSE },
{ 0x1234, 0, 5, TRUE },
{ EXCEPTION_BREAKPOINT, 0, 1, TRUE },
{ EXCEPTION_ACCESS_VIOLATION, 0, 2, TRUE },
};
ULONG_PTR args[EXCEPTION_MAXIMUM_PARAMETERS];
MINIDUMP_EXCEPTION_STREAM *except_info;
ULONG size;
void *data;
BOOL ret;
int i, j;
for (i = 0; i < ARRAY_SIZE(args); i++) args[i] = 0x666000 + i;
for (i = 0; i < ARRAY_SIZE(exception_tests); i++)
{
PROCESS_INFORMATION pi;
MINIDUMP_EXCEPTION_INFORMATION mei;
EXCEPTION_POINTERS ep;
DEBUG_EVENT ev;
/* for local access */
EXCEPTION_RECORD er;
CONTEXT ctx;
CONTEXT *mctx;
winetest_push_context("test_exceptions[%d]", i);
if (exception_tests[i].with_child)
{
BOOL first_exception = TRUE;
STARTUPINFOA si;
char buffer[MAX_PATH];
char **argv;
winetest_get_mainargs(&argv);
snprintf(buffer, ARRAY_SIZE(buffer), "%s minidump exception %x;%x;%u",
argv[0], exception_tests[i].exception_code, exception_tests[i].exception_flags,
exception_tests[i].num_args);
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi);
ok(ret, "CreateProcess failed, last error %#lx.\n", GetLastError());
while ((ret = WaitForDebugEvent(&ev, 2000)))
{
if (!first_exception && ev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) break;
if (first_exception && ev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
ev.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
first_exception = FALSE;
ret = ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE);
};
ok(ret, "Couldn't get debug event\n");
ok(ev.dwThreadId == pi.dwThreadId, "Unexpected value\n");
mei.ThreadId = ev.dwThreadId;
mei.ExceptionPointers = &ep;
mei.ClientPointers = FALSE;
ep.ExceptionRecord = &ev.u.Exception.ExceptionRecord;
ep.ContextRecord = &ctx;
ctx.ContextFlags = CONTEXT_FULL;
ret = GetThreadContext(pi.hThread, &ctx);
ok(ret, "Couldn't get thread context\n");
CloseHandle(pi.hThread);
}
else
{
mei.ThreadId = GetCurrentThreadId();
mei.ExceptionPointers = &ep;
mei.ClientPointers = FALSE;
ep.ExceptionRecord = &er;
ep.ContextRecord = &ctx;
memset(&ctx, 0xA5, sizeof(ctx));
ctx.ContextFlags = CONTEXT_FULL;
er.ExceptionCode = exception_tests[i].exception_code;
er.ExceptionFlags = exception_tests[i].exception_flags;
er.ExceptionAddress = (void *)(DWORD_PTR)0xdeadbeef;
er.NumberParameters = exception_tests[i].num_args;
for (j = 0; j < exception_tests[i].num_args; j++)
er.ExceptionInformation[j] = args[j];
pi.hProcess = GetCurrentProcess();
}
minidump_write(pi.hProcess, L"foo.mdmp", MiniDumpNormal, FALSE, &mei, NULL);
data = minidump_open_for_read("foo.mdmp");
ret = MiniDumpReadDumpStream(data, ExceptionStream, NULL, (void *)&except_info, &size);
ok(ret, "Couldn't find exception stream\n");
ok(except_info->ThreadId == mei.ThreadId, "Unexpected value\n");
ok(except_info->ExceptionRecord.ExceptionCode == exception_tests[i].exception_code, "Unexpected value %x %x\n", except_info->ExceptionRecord.ExceptionCode, exception_tests[i].exception_code);
/* windows 11 starts adding EXCEPTION_SOFTWARE_ORIGINATE flag */
ok((except_info->ExceptionRecord.ExceptionFlags & ~EXCEPTION_SOFTWARE_ORIGINATE) == exception_tests[i].exception_flags, "Unexpected value\n");
/* yes native does a signed conversion to DWORD64 when running on 32bit... */
ok(except_info->ExceptionRecord.ExceptionAddress == (DWORD_PTR)ep.ExceptionRecord->ExceptionAddress
|| broken(except_info->ExceptionRecord.ExceptionAddress == (LONG_PTR)ep.ExceptionRecord->ExceptionAddress), "Unexpected value\n");
if (except_info->ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
{
/* number of parameters depend on machine, wow64... */
ok(except_info->ExceptionRecord.NumberParameters, "Unexpected value %x\n", except_info->ExceptionRecord.NumberParameters);
}
else if (except_info->ExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
ok(except_info->ExceptionRecord.NumberParameters == exception_tests[i].num_args, "Unexpected value\n");
}
else
{
ok(except_info->ExceptionRecord.NumberParameters == exception_tests[i].num_args, "Unexpected value\n");
for (j = 0; j < exception_tests[i].num_args; j++)
ok(except_info->ExceptionRecord.ExceptionInformation[j] == args[j], "Unexpected value\n");
}
ok(except_info->ThreadContext.Rva, "Unexpected value\n");
mctx = RVA_TO_ADDR(data, except_info->ThreadContext.Rva);
ok(!memcmp(mctx, &ctx, sizeof(ctx)), "Unexpected value\n");
minidump_check_threads(data);
minidump_close_for_read(data);
DeleteFileA("foo.mdmp");
winetest_pop_context();
if (exception_tests[i].with_child)
{
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
}
}
}
static void generate_child_exception(const char *arg)
{
DWORD code, flags;
unsigned num_args;
if (sscanf(arg, "%lx;%lx;%u", &code, &flags, &num_args) == 3)
{
switch (code)
{
case EXCEPTION_BREAKPOINT:
DbgBreakPoint();
break;
case EXCEPTION_ACCESS_VIOLATION:
{
/* volatile to silence gcc warning */
char * volatile crashme = (char *)(DWORD_PTR)0x12;
*crashme = 2;
}
break;
default:
{
DWORD_PTR my_args[EXCEPTION_MAXIMUM_PARAMETERS];
int i;
for (i = 0; i < ARRAY_SIZE(my_args); i++)
my_args[i] = 0x666000 + i;
RaiseException(code, flags, num_args, my_args);
}
break;
}
}
}
START_TEST(minidump)
{
int argc;
char **argv;
argc = winetest_get_mainargs(&argv);
if (argc == 4 && !strcmp(argv[2], "exception"))
{
generate_child_exception(argv[3]);
return;
}
test_minidump_contents();
test_current_process();
test_callback();
test_exception();
}