windows-nt/Source/XPSP1/NT/sdktools/debuggers/minidump/ntx.c

912 lines
26 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright(c) 1999-2002 Microsoft Corporation
Module Name:
ntx.c
Abstract:
Minidump user-mode crashdump NT specific functions. These routines work on
NT-based operating systems from NT5 on.
Author:
Matthew D Hendel (math) 20-Aug-1999
--*/
#include "pch.h"
#include "impl.h"
PINTERNAL_MODULE
NtxAllocateModuleObject(
IN PINTERNAL_PROCESS Process,
IN HANDLE ProcessHandle,
IN ULONG_PTR BaseOfModule,
IN ULONG DumpType,
IN ULONG WriteFlags,
IN PWSTR ModuleName OPTIONAL
)
{
WCHAR FullPath [ MAX_PATH + 10 ];
//
// The basic LdrQueryProcessModule API that toolhelp uses
// always returns ANSI strings for module paths. This
// means that even if you use the wide toolhelp calls
// you still lose Unicode information because the original
// Unicode path was converted to ANSI and then back to Unicode.
// To avoid this problem, always try and look up the true
// Unicode path first. This doesn't work for 32-bit modules
// in WOW64, though, so if there's a failure just use the
// incoming string.
//
if (GetModuleFileNameExW(ProcessHandle,
(HMODULE) BaseOfModule,
FullPath,
sizeof (FullPath))) {
ModuleName = FullPath;
} else if (!ModuleName) {
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
return NULL;
}
//
// Translate funky \??\... module name.
//
return GenAllocateModuleObject (Process, ModuleName, BaseOfModule,
DumpType, WriteFlags);
}
typedef
PLIST_ENTRY
(*FN_RtlGetFunctionTableListHead) (
VOID
);
BOOL
NtxGetFunctionTables(
IN HANDLE hProcess,
IN PINTERNAL_PROCESS Process,
IN ULONG DumpType
)
{
#ifdef _WIN32_WCE
return FALSE;
#else
HMODULE NtDll;
FN_RtlGetFunctionTableListHead GetHead = NULL;
PLIST_ENTRY HeadAddr;
LIST_ENTRY Head;
PVOID Next;
SIZE_T Done;
DYNAMIC_FUNCTION_TABLE Table;
//
// On systems that support dynamic function tables
// ntdll exports a function called RtlGetFunctionTableListHead
// to retrieve the head of a process's function table list.
// Currently this is always a global LIST_ENTRY in ntdll
// and so is at the same address in all processes since ntdll
// is mapped at the same address in every process. This
// means we can call it in our process and get a pointer
// that's valid in the process being dumped.
//
// We also use the presence of RGFTLH as a signal of
// whether dynamic function tables are supported or not.
//
NtDll = GetModuleHandle("ntdll");
if (NtDll) {
GetHead = (FN_RtlGetFunctionTableListHead)
GetProcAddress(NtDll, "RtlGetFunctionTableListHead");
}
if (!GetHead) {
// Dynamic function tables are not supported.
return TRUE;
}
HeadAddr = GetHead();
if (!ReadProcessMemory(hProcess, HeadAddr, &Head, sizeof(Head),
&Done) || Done != sizeof(Head)) {
GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY);
return FALSE;
}
Next = Head.Flink;
while (Next && Next != HeadAddr) {
PINTERNAL_FUNCTION_TABLE IntTable;
PVOID HeapEntries;
PVOID TableAddr;
ULONG EntryCount;
TableAddr = Next;
if (!ReadProcessMemory(hProcess, TableAddr, &Table, sizeof(Table),
&Done) || Done != sizeof(Table)) {
GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY);
return FALSE;
}
#ifdef _AMD64_
Next = Table.ListEntry.Flink;
#else
Next = Table.Links.Flink;
#endif
HeapEntries = NULL;
#if defined(_AMD64_) || defined(_IA64_)
//
// AMD64 and IA64 support a type of function table
// where the data is retrieved via a callback rather
// than being is a plain data table. In order to
// get at the data from out-of-process the table
// must have an out-of-process access DLL registered.
//
if (Table.Type == RF_CALLBACK) {
WCHAR DllName[MAX_PATH];
HMODULE OopDll;
POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK OopCb;
if (!Table.OutOfProcessCallbackDll) {
// No out-of-process access is possible.
continue;
}
if (!ReadProcessMemory(hProcess, Table.OutOfProcessCallbackDll,
DllName, sizeof(DllName) - sizeof(WCHAR),
&Done)) {
GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY);
return FALSE;
}
DllName[Done / sizeof(WCHAR)] = 0;
OopDll = LoadLibraryW(DllName);
if (!OopDll) {
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
return FALSE;
}
OopCb = (POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK)GetProcAddress
(OopDll, OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK_EXPORT_NAME);
if (OopCb == NULL) {
FreeLibrary(OopDll);
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
return FALSE;
}
if (!NT_SUCCESS(OopCb(hProcess, TableAddr,
&EntryCount,
(PRUNTIME_FUNCTION*)&HeapEntries))) {
FreeLibrary(OopDll);
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
return FALSE;
}
FreeLibrary(OopDll);
} else {
EntryCount = Table.EntryCount;
}
#else
EntryCount = Table.EntryCount;
#endif
IntTable = GenAllocateFunctionTableObject(Table.MinimumAddress,
Table.MaximumAddress,
#ifdef _ALPHA_
Table.MinimumAddress,
#else
Table.BaseAddress,
#endif
EntryCount,
&Table);
if (IntTable) {
#if defined(_AMD64_) || defined(_IA64_)
if (Table.Type == RF_CALLBACK) {
memcpy(IntTable->RawEntries, HeapEntries,
EntryCount * sizeof(RUNTIME_FUNCTION));
} else
#endif
{
if (!ReadProcessMemory(hProcess, Table.FunctionTable,
IntTable->RawEntries,
EntryCount * sizeof(RUNTIME_FUNCTION),
&Done) ||
Done != EntryCount * sizeof(RUNTIME_FUNCTION)) {
GenFreeFunctionTableObject(IntTable);
IntTable = NULL;
}
}
}
if (HeapEntries) {
RtlFreeHeap(RtlProcessHeap(), 0, HeapEntries);
}
if (!IntTable) {
return FALSE;
}
if (!GenIncludeUnwindInfoMemory(hProcess, DumpType, IntTable)) {
return FALSE;
}
Process->NumberOfFunctionTables++;
InsertTailList(&Process->FunctionTableList, &IntTable->TableLink);
}
return TRUE;
#endif // _WIN32_WCE
}
#ifdef RTL_UNLOAD_EVENT_TRACE_NUMBER
typedef
PRTL_UNLOAD_EVENT_TRACE
(*FN_RtlGetUnloadEventTrace) (
VOID
);
#endif
BOOL
NtxGetUnloadedModules(
IN HANDLE hProcess,
IN PINTERNAL_PROCESS Process,
IN ULONG DumpType
)
{
#if defined(_WIN32_WCE) || !defined(RTL_UNLOAD_EVENT_TRACE_NUMBER)
return FALSE;
#else
HMODULE NtDll;
FN_RtlGetUnloadEventTrace GetTrace = NULL;
PRTL_UNLOAD_EVENT_TRACE TraceAddr;
PRTL_UNLOAD_EVENT_TRACE TraceArray;
SIZE_T Done;
ULONG Entries;
PRTL_UNLOAD_EVENT_TRACE Oldest;
ULONG i;
PINTERNAL_UNLOADED_MODULE IntModule;
if (!(DumpType & MiniDumpWithUnloadedModules)) {
// No unloaded module info requested.
return TRUE;
}
//
// On systems that support unload traces
// ntdll exports a function called RtlGetUnloadEventTrace
// to retrieve the base of an unload trace array.
// Currently this is always a global in ntdll
// and so is at the same address in all processes since ntdll
// is mapped at the same address in every process. This
// means we can call it in our process and get a pointer
// that's valid in the process being dumped.
//
// We also use the presence of RGUET as a signal of
// whether unload traces are supported or not.
//
NtDll = GetModuleHandle("ntdll");
if (NtDll) {
GetTrace = (FN_RtlGetUnloadEventTrace)
GetProcAddress(NtDll, "RtlGetUnloadEventTrace");
}
if (!GetTrace) {
// Unload traces are not supported.
return TRUE;
}
TraceAddr = GetTrace();
// Currently there are always 16 entries.
Entries = 16;
TraceArray = (PRTL_UNLOAD_EVENT_TRACE)
AllocMemory(sizeof(*TraceArray) * Entries);
if (!TraceArray) {
return FALSE;
}
if (!ReadProcessMemory(hProcess, TraceAddr,
TraceArray, sizeof(*TraceArray) * Entries,
&Done) ||
Done != sizeof(*TraceArray) * Entries) {
GenAccumulateStatus(MDSTATUS_UNABLE_TO_READ_MEMORY);
return FALSE;
}
//
// Find the true number of entries in use and sort.
// The sequence numbers of the trace records increase with
// time and we want to have the head of the list be the
// most recent record, so sort by decreasing sequence number.
// We know that the array is a circular buffer, so things
// are already in order except there may be a transition
// of sequence after the newest record. Find that transition
// and sorting becomes trivial.
//
Oldest = TraceArray;
for (i = 0; i < Entries; i++) {
if (!TraceArray[i].BaseAddress || !TraceArray[i].SizeOfImage) {
// Unused entry, no need to continue.
Entries = i;
break;
}
if (TraceArray[i].Sequence < Oldest->Sequence) {
Oldest = TraceArray + i;
}
}
//
// Now push the entries on from the oldest to the youngest.
//
for (i = 0; i < Entries; i++) {
IntModule =
GenAllocateUnloadedModuleObject(Oldest->ImageName,
(ULONG_PTR)Oldest->BaseAddress,
(ULONG)Oldest->SizeOfImage,
Oldest->CheckSum,
Oldest->TimeDateStamp);
if (!IntModule) {
return FALSE;
}
Process->NumberOfUnloadedModules++;
InsertHeadList(&Process->UnloadedModuleList, &IntModule->ModulesLink);
if (Oldest == TraceArray + (Entries - 1)) {
Oldest = TraceArray;
} else {
Oldest++;
}
}
return TRUE;
#endif // _WIN32_WCE || !RTL_UNLOAD_EVENT_TRACE_NUMBER
}
BOOL
NtxGetProcessInfo(
IN HANDLE hProcess,
IN ULONG ProcessId,
IN ULONG DumpType,
IN MINIDUMP_CALLBACK_ROUTINE CallbackRoutine,
IN PVOID CallbackParam,
OUT PINTERNAL_PROCESS * ProcessRet
)
/*++
Routine Description:
Using toolhelp, obtain the process information for this process.
Return Values:
TRUE - Success.
FALSE - Failure:
Environment:
Win9x/Win2k+ only.
--*/
{
BOOL Succ;
ULONG i;
BOOL MoreThreads;
HANDLE Snapshot, ModuleSnapshot = INVALID_HANDLE_VALUE;
THREADENTRY32 ThreadInfo;
PINTERNAL_THREAD Thread;
PINTERNAL_PROCESS Process;
PINTERNAL_MODULE Module;
HMODULE Modules [ 512 ];
ULONG ModulesSize;
ULONG NumberOfModules;
ULONG BuildNumber;
ASSERT ( hProcess );
ASSERT ( ProcessId != 0 );
ASSERT ( ProcessRet );
Process = NULL;
Thread = NULL;
Module = NULL;
Snapshot = NULL;
ThreadInfo.dwSize = sizeof (THREADENTRY32);
Process = GenAllocateProcessObject ( hProcess, ProcessId );
if ( Process == NULL ) {
return FALSE;
}
Snapshot = CreateToolhelp32Snapshot (
TH32CS_SNAPTHREAD,
ProcessId
);
if ( Snapshot == INVALID_HANDLE_VALUE ) {
Succ = FALSE;
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
goto Exit;
}
//
// Walk thread list, suspending all threads and getting thread info.
//
for (MoreThreads = ProcessThread32First (Snapshot, ProcessId, &ThreadInfo );
MoreThreads;
MoreThreads = ProcessThread32Next ( Snapshot, ProcessId, &ThreadInfo ) ) {
HRESULT Status;
ULONG WriteFlags;
if (!GenExecuteIncludeThreadCallback(hProcess,
ProcessId,
DumpType,
ThreadInfo.th32ThreadID,
CallbackRoutine,
CallbackParam,
&WriteFlags) ||
IsFlagClear(WriteFlags, ThreadWriteThread)) {
continue;
}
Status = GenAllocateThreadObject (
Process,
hProcess,
ThreadInfo.th32ThreadID,
DumpType,
WriteFlags,
&Thread
);
if ( FAILED(Status) ) {
Succ = FALSE;
goto Exit;
}
// If Status is S_FALSE it means that the thread
// couldn't be opened and probably exited before
// we got to it. Just continue on.
if (Status == S_OK) {
Process->NumberOfThreads++;
InsertTailList (&Process->ThreadList, &Thread->ThreadsLink);
}
}
GenGetSystemType (NULL, NULL, NULL, NULL, &BuildNumber);
if (BuildNumber > 2468) {
//
// toolhelp had been changed to perform noninvasive
// module enumeration
//
MODULEENTRY32W ModuleEntry;
BOOL ModuleFound;
NumberOfModules = 0;
Succ = TRUE;
ModuleSnapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32,
ProcessId
);
if (ModuleSnapshot == INVALID_HANDLE_VALUE) {
Succ = FALSE;
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
goto Exit;
}
ZeroMemory(&ModuleEntry, sizeof(ModuleEntry));
ModuleEntry.dwSize = sizeof(ModuleEntry);
ModuleFound = Module32FirstW(ModuleSnapshot, &ModuleEntry);
while (ModuleFound) {
ULONG WriteFlags;
if (GenExecuteIncludeModuleCallback(hProcess,
ProcessId,
DumpType,
(LONG_PTR)ModuleEntry.modBaseAddr,
CallbackRoutine,
CallbackParam,
&WriteFlags) &&
IsFlagSet(WriteFlags, ModuleWriteModule)) {
Module = NtxAllocateModuleObject (Process,
Process->ProcessHandle,
(LONG_PTR)ModuleEntry.modBaseAddr,
DumpType,
WriteFlags,
ModuleEntry.szExePath);
if ( Module == NULL ) {
Succ = FALSE;
goto Exit;
}
InsertTailList (&Process->ModuleList, &Module->ModulesLink);
++NumberOfModules;
}
ModuleFound = Module32NextW(ModuleSnapshot, &ModuleEntry);
}
}
else {
//
// Walk module list, getting module information. Use PSAPI instead of
// toolhelp since it it does not exhibit the deadlock issues with
// the loader lock. ( on old versions of os )
//
ModulesSize = 0;
Succ = EnumProcessModules (
Process->ProcessHandle,
Modules,
sizeof (Modules),
&ModulesSize
);
if ( !Succ ) {
GenAccumulateStatus(MDSTATUS_CALL_FAILED);
goto Exit;
}
NumberOfModules = ModulesSize / sizeof (HMODULE);
for (i = 0; i < NumberOfModules; i++) {
ULONG WriteFlags;
if (!GenExecuteIncludeModuleCallback(hProcess,
ProcessId,
DumpType,
(LONG_PTR)Modules[i],
CallbackRoutine,
CallbackParam,
&WriteFlags) ||
IsFlagClear(WriteFlags, ModuleWriteModule)) {
continue;
}
Module = NtxAllocateModuleObject (
Process,
Process->ProcessHandle,
(LONG_PTR) Modules [ i ],
DumpType,
WriteFlags,
NULL
);
if ( Module == NULL ) {
Succ = FALSE;
goto Exit;
}
InsertTailList (&Process->ModuleList, &Module->ModulesLink);
}
}
Process->NumberOfModules = NumberOfModules;
Succ = NtxGetFunctionTables(hProcess, Process, DumpType);
// If we can't get unloaded modules that's not a critical problem.
NtxGetUnloadedModules(hProcess, Process, DumpType);
Exit:
if ( Snapshot && (Snapshot != INVALID_HANDLE_VALUE) ) {
CloseHandle ( Snapshot );
Snapshot = NULL;
}
if ( ModuleSnapshot && (ModuleSnapshot != INVALID_HANDLE_VALUE) ) {
CloseHandle ( ModuleSnapshot );
ModuleSnapshot = NULL;
}
if ( !Succ && Process != NULL ) {
GenFreeProcessObject ( Process );
Process = NULL;
}
*ProcessRet = Process;
return Succ;
}
LPVOID
NtxGetTebAddress(
IN HANDLE Thread,
OUT PULONG SizeOfTeb
)
{
#ifdef _WIN32_WCE
*SizeOfTeb = 0;
return NULL;
#else
THREAD_BASIC_INFORMATION ThreadInformation;
NTSTATUS NtStatus;
NtStatus = NtQueryInformationThread(Thread,
ThreadBasicInformation,
&ThreadInformation,
sizeof(ThreadInformation),
NULL);
if (NT_SUCCESS(NtStatus)) {
// The TEB is a little smaller than a page but
// save the entire page so that adjacent TEB
// pages get coalesced into a single region.
// As TEBs are normally adjacent this is a common case.
*SizeOfTeb = PAGE_SIZE;
return ThreadInformation.TebBaseAddress;
} else {
*SizeOfTeb = 0;
return NULL;
}
#endif
}
HRESULT
TibGetThreadInfo(
IN HANDLE Process,
IN LPVOID TibBase,
OUT PULONG64 StackBase,
OUT PULONG64 StackLimit,
OUT PULONG64 StoreBase,
OUT PULONG64 StoreLimit
)
{
#ifdef _WIN32_WCE
return E_NOTIMPL;
#else
TEB Teb;
HRESULT Succ;
SIZE_T BytesRead;
#if defined (DUMP_BACKING_STORE)
Succ = ReadProcessMemory(Process,
TibBase,
&Teb,
sizeof (Teb),
&BytesRead) ? S_OK : E_FAIL;
if ( Succ != S_OK || BytesRead != sizeof (Teb) ) {
return E_FAIL;
}
*StoreBase = SIGN_EXTEND(BSTORE_BASE(&Teb));
*StoreLimit = SIGN_EXTEND(BSTORE_LIMIT(&Teb));
#else
Succ = ReadProcessMemory(Process,
TibBase,
&Teb,
sizeof (Teb.NtTib),
&BytesRead) ? S_OK : E_FAIL;
if ( Succ != S_OK || BytesRead != sizeof (Teb.NtTib) ) {
return E_FAIL;
}
*StoreBase = 0;
*StoreLimit = 0;
#endif
*StackBase = SIGN_EXTEND((LONG_PTR)Teb.NtTib.StackBase);
*StackLimit = SIGN_EXTEND((LONG_PTR)Teb.NtTib.StackLimit);
return S_OK;
#endif // #ifdef _WIN32_WCE
}
LPVOID
NtxGetPebAddress(
IN HANDLE Process,
OUT PULONG SizeOfPeb
)
{
#ifdef _WIN32_WCE
*SizeOfPeb = 0;
return NULL;
#else
PROCESS_BASIC_INFORMATION Information;
NTSTATUS NtStatus;
NtStatus = NtQueryInformationProcess(Process,
ProcessBasicInformation,
&Information,
sizeof(Information),
NULL);
if (NT_SUCCESS(NtStatus)) {
*SizeOfPeb = sizeof(PEB);
return Information.PebBaseAddress;
} else {
*SizeOfPeb = 0;
return NULL;
}
#endif
}
BOOL
NtxWriteHandleData(
IN HANDLE ProcessHandle,
IN HANDLE hFile,
IN struct _MINIDUMP_STREAM_INFO * StreamInfo
)
{
#ifdef _WIN32_WCE
return FALSE;
#else
NTSTATUS NtStatus;
ULONG HandleCount;
ULONG Hits;
ULONG Handle;
ULONG64 Buffer[1024 / sizeof(ULONG64)];
POBJECT_TYPE_INFORMATION TypeInfo =
(POBJECT_TYPE_INFORMATION)Buffer;
POBJECT_NAME_INFORMATION NameInfo =
(POBJECT_NAME_INFORMATION)Buffer;
OBJECT_BASIC_INFORMATION BasicInfo;
HANDLE Dup;
PMINIDUMP_HANDLE_DESCRIPTOR Descs, Desc;
RVA Rva;
ULONG32 Len;
ULONG Done;
MINIDUMP_HANDLE_DATA_STREAM DataStream;
BOOL Succ;
NtStatus = NtQueryInformationProcess(ProcessHandle,
ProcessHandleCount,
&HandleCount,
sizeof(HandleCount),
NULL);
if (!NT_SUCCESS(NtStatus)) {
return FALSE;
}
Descs = AllocMemory(HandleCount * sizeof(*Desc));
if (Descs == NULL) {
return FALSE;
}
Hits = 0;
Handle = 0;
Desc = Descs;
Rva = StreamInfo->RvaOfHandleData;
while (Hits < HandleCount && Handle < (1 << 24)) {
if (!DuplicateHandle(ProcessHandle, (HANDLE)(ULONG_PTR)Handle,
GetCurrentProcess(), &Dup,
0, FALSE, DUPLICATE_SAME_ACCESS)) {
Handle += 4;
continue;
}
// Successfully got a handle, so consider this a hit.
Hits++;
if (!NT_SUCCESS(NtQueryObject(Dup, ObjectBasicInformation,
&BasicInfo, sizeof(BasicInfo), NULL)) ||
!NT_SUCCESS(NtQueryObject(Dup, ObjectTypeInformation,
TypeInfo, sizeof(Buffer), NULL))) {
// If we can't get the basic info and type there isn't much
// point in writing anything out so skip the handle.
goto CloseDup;
}
Len = TypeInfo->TypeName.Length;
TypeInfo->TypeName.Buffer[Len / sizeof(WCHAR)] = 0;
Desc->TypeNameRva = Rva;
if (!WriteFile(hFile, &Len, sizeof(Len), &Done, NULL) ||
Done != sizeof(Len)) {
goto ExitCloseDup;
}
Len += sizeof(WCHAR);
if (!WriteFile(hFile, TypeInfo->TypeName.Buffer, Len, &Done, NULL) ||
Done != Len) {
goto ExitCloseDup;
}
Rva += Len + sizeof(Len);
// Don't get the name of file objects as it
// can cause deadlocks. If we fail getting the
// name just leave it out and don't consider it fatal.
if (lstrcmpW(TypeInfo->TypeName.Buffer, L"File") &&
NT_SUCCESS(NtQueryObject(Dup, ObjectNameInformation,
NameInfo, sizeof(Buffer), NULL)) &&
NameInfo->Name.Buffer != NULL) {
Len = NameInfo->Name.Length;
NameInfo->Name.Buffer[Len / sizeof(WCHAR)] = 0;
Desc->ObjectNameRva = Rva;
if (!WriteFile(hFile, &Len, sizeof(Len), &Done, NULL) ||
Done != sizeof(Len)) {
goto ExitCloseDup;
}
Len += sizeof(WCHAR);
if (!WriteFile(hFile, NameInfo->Name.Buffer, Len, &Done, NULL) ||
Done != Len) {
goto ExitCloseDup;
}
Rva += Len + sizeof(Len);
} else {
Desc->ObjectNameRva = 0;
}
Desc->Handle = Handle;
Desc->Attributes = BasicInfo.Attributes;
Desc->GrantedAccess = BasicInfo.GrantedAccess;
Desc->HandleCount = BasicInfo.HandleCount;
Desc->PointerCount = BasicInfo.PointerCount;
Desc++;
CloseDup:
CloseHandle(Dup);
Handle += 4;
}
DataStream.SizeOfHeader = sizeof(DataStream);
DataStream.SizeOfDescriptor = sizeof(*Descs);
DataStream.NumberOfDescriptors = (ULONG)(Desc - Descs);
DataStream.Reserved = 0;
StreamInfo->RvaOfHandleData = Rva;
StreamInfo->SizeOfHandleData = sizeof(DataStream) +
DataStream.NumberOfDescriptors * sizeof(*Descs);
Succ =
WriteFile(hFile, &DataStream, sizeof(DataStream), &Done, NULL) &&
Done == sizeof(DataStream) &&
WriteFile(hFile, Descs, DataStream.NumberOfDescriptors *
sizeof(*Descs), &Done, NULL) &&
Done == DataStream.NumberOfDescriptors * sizeof(*Descs);
FreeMemory(Descs);
return Succ;
ExitCloseDup:
CloseHandle(Dup);
FreeMemory(Descs);
return FALSE;
#endif // #ifdef _WIN32_WCE
}