From f8fe689a81f906d1b91bb3220acde2a4ecb14c5b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 6 May 2024 05:01:46 +0200 Subject: Adding upstream version 6.0.4-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Debugger/DBGPlugInWinNt.cpp | 1093 ++++++++++++++++++++++++++++++++++ 1 file changed, 1093 insertions(+) create mode 100644 src/VBox/Debugger/DBGPlugInWinNt.cpp (limited to 'src/VBox/Debugger/DBGPlugInWinNt.cpp') diff --git a/src/VBox/Debugger/DBGPlugInWinNt.cpp b/src/VBox/Debugger/DBGPlugInWinNt.cpp new file mode 100644 index 00000000..2a9f595c --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInWinNt.cpp @@ -0,0 +1,1093 @@ +/* $Id: DBGPlugInWinNt.cpp $ */ +/** @file + * DBGPlugInWindows - Debugger and Guest OS Digger Plugin For Windows NT. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** @name Internal WinNT structures + * @{ */ +/** + * PsLoadedModuleList entry for 32-bit NT aka LDR_DATA_TABLE_ENTRY. + * Tested with XP. + */ +typedef struct NTMTE32 +{ + struct + { + uint32_t Flink; + uint32_t Blink; + } InLoadOrderLinks, + InMemoryOrderModuleList, + InInitializationOrderModuleList; + uint32_t DllBase; + uint32_t EntryPoint; + /** @note This field is not a size in NT 3.1. It's NULL for images loaded by the + * boot loader, for other images it looks like some kind of pointer. */ + uint32_t SizeOfImage; + struct + { + uint16_t Length; + uint16_t MaximumLength; + uint32_t Buffer; + } FullDllName, + BaseDllName; + uint32_t Flags; + uint16_t LoadCount; + uint16_t TlsIndex; + /* ... there is more ... */ +} NTMTE32; +typedef NTMTE32 *PNTMTE32; + +/** + * PsLoadedModuleList entry for 64-bit NT aka LDR_DATA_TABLE_ENTRY. + */ +typedef struct NTMTE64 +{ + struct + { + uint64_t Flink; + uint64_t Blink; + } InLoadOrderLinks, /**< 0x00 */ + InMemoryOrderModuleList, /**< 0x10 */ + InInitializationOrderModuleList; /**< 0x20 */ + uint64_t DllBase; /**< 0x30 */ + uint64_t EntryPoint; /**< 0x38 */ + uint32_t SizeOfImage; /**< 0x40 */ + uint32_t Alignment; /**< 0x44 */ + struct + { + uint16_t Length; /**< 0x48,0x58 */ + uint16_t MaximumLength; /**< 0x4a,0x5a */ + uint32_t Alignment; /**< 0x4c,0x5c */ + uint64_t Buffer; /**< 0x50,0x60 */ + } FullDllName, /**< 0x48 */ + BaseDllName; /**< 0x58 */ + uint32_t Flags; /**< 0x68 */ + uint16_t LoadCount; /**< 0x6c */ + uint16_t TlsIndex; /**< 0x6e */ + /* ... there is more ... */ +} NTMTE64; +typedef NTMTE64 *PNTMTE64; + +/** MTE union. */ +typedef union NTMTE +{ + NTMTE32 vX_32; + NTMTE64 vX_64; +} NTMTE; +typedef NTMTE *PNTMTE; + + +/** + * The essential bits of the KUSER_SHARED_DATA structure. + */ +typedef struct NTKUSERSHAREDDATA +{ + uint32_t TickCountLowDeprecated; + uint32_t TickCountMultiplier; + struct + { + uint32_t LowPart; + int32_t High1Time; + int32_t High2Time; + + } InterruptTime, + SystemTime, + TimeZoneBias; + uint16_t ImageNumberLow; + uint16_t ImageNumberHigh; + RTUTF16 NtSystemRoot[260]; + uint32_t MaxStackTraceDepth; + uint32_t CryptoExponent; + uint32_t TimeZoneId; + uint32_t LargePageMinimum; + uint32_t Reserved2[7]; + uint32_t NtProductType; + uint8_t ProductTypeIsValid; + uint8_t abPadding[3]; + uint32_t NtMajorVersion; + uint32_t NtMinorVersion; + /* uint8_t ProcessorFeatures[64]; + ... + */ +} NTKUSERSHAREDDATA; +typedef NTKUSERSHAREDDATA *PNTKUSERSHAREDDATA; + +/** KI_USER_SHARED_DATA for i386 */ +#define NTKUSERSHAREDDATA_WINNT32 UINT32_C(0xffdf0000) +/** KI_USER_SHARED_DATA for AMD64 */ +#define NTKUSERSHAREDDATA_WINNT64 UINT64_C(0xfffff78000000000) + +/** NTKUSERSHAREDDATA::NtProductType */ +typedef enum NTPRODUCTTYPE +{ + kNtProductType_Invalid = 0, + kNtProductType_WinNt = 1, + kNtProductType_LanManNt, + kNtProductType_Server +} NTPRODUCTTYPE; + + +/** NT image header union. */ +typedef union NTHDRSU +{ + IMAGE_NT_HEADERS32 vX_32; + IMAGE_NT_HEADERS64 vX_64; +} NTHDRS; +/** Pointer to NT image header union. */ +typedef NTHDRS *PNTHDRS; +/** Pointer to const NT image header union. */ +typedef NTHDRS const *PCNTHDRS; + +/** @} */ + + + +typedef enum DBGDIGGERWINNTVER +{ + DBGDIGGERWINNTVER_UNKNOWN, + DBGDIGGERWINNTVER_3_1, + DBGDIGGERWINNTVER_3_5, + DBGDIGGERWINNTVER_4_0, + DBGDIGGERWINNTVER_5_0, + DBGDIGGERWINNTVER_5_1, + DBGDIGGERWINNTVER_6_0 +} DBGDIGGERWINNTVER; + +/** + * WinNT guest OS digger instance data. + */ +typedef struct DBGDIGGERWINNT +{ + /** Whether the information is valid or not. + * (For fending off illegal interface method calls.) */ + bool fValid; + /** 32-bit (true) or 64-bit (false) */ + bool f32Bit; + /** Set if NT 3.1 was detected. + * This implies both Misc.VirtualSize and NTMTE32::SizeOfImage are zero. */ + bool fNt31; + + /** The NT version. */ + DBGDIGGERWINNTVER enmVer; + /** NTKUSERSHAREDDATA::NtProductType */ + NTPRODUCTTYPE NtProductType; + /** NTKUSERSHAREDDATA::NtMajorVersion */ + uint32_t NtMajorVersion; + /** NTKUSERSHAREDDATA::NtMinorVersion */ + uint32_t NtMinorVersion; + + /** The address of the ntoskrnl.exe image. */ + DBGFADDRESS KernelAddr; + /** The address of the ntoskrnl.exe module table entry. */ + DBGFADDRESS KernelMteAddr; + /** The address of PsLoadedModuleList. */ + DBGFADDRESS PsLoadedModuleListAddr; +} DBGDIGGERWINNT; +/** Pointer to the linux guest OS digger instance data. */ +typedef DBGDIGGERWINNT *PDBGDIGGERWINNT; + + +/** + * The WinNT digger's loader reader instance data. + */ +typedef struct DBGDIGGERWINNTRDR +{ + /** The VM handle (referenced). */ + PUVM pUVM; + /** The image base. */ + DBGFADDRESS ImageAddr; + /** The image size. */ + uint32_t cbImage; + /** The file offset of the SizeOfImage field in the optional header if it + * needs patching, otherwise set to UINT32_MAX. */ + uint32_t offSizeOfImage; + /** The correct image size. */ + uint32_t cbCorrectImageSize; + /** Number of entries in the aMappings table. */ + uint32_t cMappings; + /** Mapping hint. */ + uint32_t iHint; + /** Mapping file offset to memory offsets, ordered by file offset. */ + struct + { + /** The file offset. */ + uint32_t offFile; + /** The size of this mapping. */ + uint32_t cbMem; + /** The offset to the memory from the start of the image. */ + uint32_t offMem; + } aMappings[1]; +} DBGDIGGERWINNTRDR; +/** Pointer a WinNT loader reader instance data. */ +typedef DBGDIGGERWINNTRDR *PDBGDIGGERWINNTRDR; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Validates a 32-bit Windows NT kernel address */ +#define WINNT32_VALID_ADDRESS(Addr) ((Addr) > UINT32_C(0x80000000) && (Addr) < UINT32_C(0xfffff000)) +/** Validates a 64-bit Windows NT kernel address */ +#define WINNT64_VALID_ADDRESS(Addr) ((Addr) > UINT64_C(0xffff800000000000) && (Addr) < UINT64_C(0xfffffffffffff000)) +/** Validates a kernel address. */ +#define WINNT_VALID_ADDRESS(pThis, Addr) ((pThis)->f32Bit ? WINNT32_VALID_ADDRESS(Addr) : WINNT64_VALID_ADDRESS(Addr)) +/** Versioned and bitness wrapper. */ +#define WINNT_UNION(pThis, pUnion, Member) ((pThis)->f32Bit ? (pUnion)->vX_32. Member : (pUnion)->vX_64. Member ) + +/** The length (in chars) of the kernel file name (no path). */ +#define WINNT_KERNEL_BASE_NAME_LEN 12 + +/** WindowsNT on little endian ASCII systems. */ +#define DIG_WINNT_MOD_TAG UINT64_C(0x54696e646f774e54) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) dbgDiggerWinNtInit(PUVM pUVM, void *pvData); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Kernel names. */ +static const RTUTF16 g_wszKernelNames[][WINNT_KERNEL_BASE_NAME_LEN + 1] = +{ + { 'n', 't', 'o', 's', 'k', 'r', 'n', 'l', '.', 'e', 'x', 'e' } +}; + + + +/** + * Process a PE image found in guest memory. + * + * @param pThis The instance data. + * @param pUVM The user mode VM handle. + * @param pszName The module name. + * @param pszFilename The image filename. + * @param pImageAddr The image address. + * @param cbImage The size of the image. + */ +static void dbgDiggerWinNtProcessImage(PDBGDIGGERWINNT pThis, PUVM pUVM, const char *pszName, const char *pszFilename, + PCDBGFADDRESS pImageAddr, uint32_t cbImage) +{ + LogFlow(("DigWinNt: %RGp %#x %s\n", pImageAddr->FlatPtr, cbImage, pszName)); + + /* + * Do some basic validation first. + */ + if ( (cbImage < sizeof(IMAGE_NT_HEADERS64) && !pThis->fNt31) + || cbImage >= _1M * 256) + { + Log(("DigWinNt: %s: Bad image size: %#x\n", pszName, cbImage)); + return; + } + + /* + * Use the common in-memory module reader to create a debug module. + */ + RTERRINFOSTATIC ErrInfo; + RTDBGMOD hDbgMod = NIL_RTDBGMOD; + int rc = DBGFR3ModInMem(pUVM, pImageAddr, pThis->fNt31 ? DBGFMODINMEM_F_PE_NT31 : 0, pszName, pszFilename, + pThis->f32Bit ? RTLDRARCH_X86_32 : RTLDRARCH_AMD64, cbImage, + &hDbgMod, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + /* + * Tag the module. + */ + rc = RTDbgModSetTag(hDbgMod, DIG_WINNT_MOD_TAG); + AssertRC(rc); + + /* + * Link the module. + */ + RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL); + if (hAs != NIL_RTDBGAS) + rc = RTDbgAsModuleLink(hAs, hDbgMod, pImageAddr->FlatPtr, RTDBGASLINK_FLAGS_REPLACE /*fFlags*/); + else + rc = VERR_INTERNAL_ERROR; + RTDbgModRelease(hDbgMod); + RTDbgAsRelease(hAs); + } + else if (RTErrInfoIsSet(&ErrInfo.Core)) + Log(("DigWinNt: %s: DBGFR3ModInMem failed: %Rrc - %s\n", pszName, rc, ErrInfo.Core.pszMsg)); + else + Log(("DigWinNt: %s: DBGFR3ModInMem failed: %Rrc\n", pszName, rc)); +} + + +/** + * Generate a debugger compatible module name from a filename. + * + * @returns Pointer to module name (doesn't need to be pszName). + * @param pszFilename The source filename. + * @param pszName Buffer to put the module name in. + * @param cbName Buffer size. + */ +static const char *dbgDiggerWintNtFilenameToModuleName(const char *pszFilename, char *pszName, size_t cbName) +{ + /* Skip to the filename part of the filename. :-) */ + pszFilename = RTPathFilenameEx(pszFilename, RTPATH_STR_F_STYLE_DOS); + + /* We try use 'nt' for the kernel. */ + if ( RTStrICmpAscii(pszFilename, "ntoskrnl.exe") == 0 + || RTStrICmpAscii(pszFilename, "ntkrnlmp.exe") == 0) + return "nt"; + + + /* Drop the extension if .dll or .sys. */ + size_t cchFilename = strlen(pszFilename); + if ( cchFilename > 4 + && pszFilename[cchFilename - 4] == '.') + { + if ( RTStrICmpAscii(&pszFilename[cchFilename - 4], ".sys") == 0 + || RTStrICmpAscii(&pszFilename[cchFilename - 4], ".dll") == 0) + cchFilename -= 4; + } + + /* Copy and do replacements. */ + if (cchFilename >= cbName) + cchFilename = cbName - 1; + size_t off; + for (off = 0; off < cchFilename; off++) + { + char ch = pszFilename[off]; + if (!RT_C_IS_ALNUM(ch)) + ch = '_'; + pszName[off] = ch; + } + pszName[off] = '\0'; + return pszName; +} + + +/** + * @copydoc DBGFOSREG::pfnStackUnwindAssist + */ +static DECLCALLBACK(int) dbgDiggerWinNtStackUnwindAssist(PUVM pUVM, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, + PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, + uint64_t *puScratch) +{ + Assert(pInitialCtx); + + /* + * We want to locate trap frames here. The trap frame structure contains + * the 64-bit IRET frame, so given unwind information it's easy to identify + * using the return type and frame address. + */ + if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_64BIT) + { + /* + * Is this a trap frame? If so, try read the trap frame. + */ + if ( pFrame->enmReturnType == RTDBGRETURNTYPE_IRET64 + && !(pFrame->AddrFrame.FlatPtr & 0x7) + && WINNT64_VALID_ADDRESS(pFrame->AddrFrame.FlatPtr) ) + { + KTRAP_FRAME_AMD64 TrapFrame; + RT_ZERO(TrapFrame); + uint64_t const uTrapFrameAddr = pFrame->AddrFrame.FlatPtr + - RT_UOFFSETOF(KTRAP_FRAME_AMD64, ErrCdOrXcptFrameOrS); + int rc = pState->pfnReadStack(pState, uTrapFrameAddr, sizeof(TrapFrame), &TrapFrame); + if (RT_SUCCESS(rc)) + { + /* Valid? Not too much else we can check here (EFlags isn't + reliable in manually construct frames). */ + if (TrapFrame.ExceptionActive <= 2) + { + pFrame->fFlags |= DBGFSTACKFRAME_FLAGS_TRAP_FRAME; + + /* + * Add sure 'register' information from the frame to the frame. + * + * To avoid code duplication, we do this in two steps in a loop. + * The first iteration only figures out how many registers we're + * going to save and allocates room for them. The second iteration + * does the actual adding. + */ + uint32_t cRegs = pFrame->cSureRegs; + PDBGFREGVALEX paSureRegs = NULL; +#define ADD_REG_NAMED(a_Type, a_ValMemb, a_Value, a_pszName) do { \ + if (paSureRegs) \ + { \ + paSureRegs[iReg].pszName = a_pszName;\ + paSureRegs[iReg].enmReg = DBGFREG_END; \ + paSureRegs[iReg].enmType = a_Type; \ + paSureRegs[iReg].Value.a_ValMemb = (a_Value); \ + } \ + iReg++; \ + } while (0) +#define MAYBE_ADD_GREG(a_Value, a_enmReg, a_idxReg) do { \ + if (!(pState->u.x86.Loaded.s.fRegs & RT_BIT(a_idxReg))) \ + { \ + if (paSureRegs) \ + { \ + pState->u.x86.Loaded.s.fRegs |= RT_BIT(a_idxReg); \ + pState->u.x86.auRegs[a_idxReg] = (a_Value); \ + paSureRegs[iReg].Value.u64 = (a_Value); \ + paSureRegs[iReg].enmReg = a_enmReg; \ + paSureRegs[iReg].enmType = DBGFREGVALTYPE_U64; \ + paSureRegs[iReg].pszName = NULL; \ + } \ + iReg++; \ + } \ + } while (0) + for (unsigned iLoop = 0; iLoop < 2; iLoop++) + { + uint32_t iReg = pFrame->cSureRegs; + ADD_REG_NAMED(DBGFREGVALTYPE_U64, u64, uTrapFrameAddr, "TrapFrame"); + ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, TrapFrame.ExceptionActive, "ExceptionActive"); + if (TrapFrame.ExceptionActive == 0) + { + ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, TrapFrame.PreviousIrql, "PrevIrql"); + ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, (uint8_t)TrapFrame.ErrCdOrXcptFrameOrS, "IntNo"); + } + else if ( TrapFrame.ExceptionActive == 1 + && TrapFrame.FaultIndicator == ((TrapFrame.ErrCdOrXcptFrameOrS >> 1) & 0x9)) + ADD_REG_NAMED(DBGFREGVALTYPE_U64, u64, TrapFrame.FaultAddrOrCtxRecOrTS, "cr2-probably"); + if (TrapFrame.SegCs & X86_SEL_RPL) + ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, 1, "UserMode"); + else + ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, 1, "KernelMode"); + if (TrapFrame.ExceptionActive <= 1) + { + MAYBE_ADD_GREG(TrapFrame.Rax, DBGFREG_RAX, X86_GREG_xAX); + MAYBE_ADD_GREG(TrapFrame.Rcx, DBGFREG_RCX, X86_GREG_xCX); + MAYBE_ADD_GREG(TrapFrame.Rdx, DBGFREG_RDX, X86_GREG_xDX); + MAYBE_ADD_GREG(TrapFrame.R8, DBGFREG_R8, X86_GREG_x8); + MAYBE_ADD_GREG(TrapFrame.R9, DBGFREG_R9, X86_GREG_x9); + MAYBE_ADD_GREG(TrapFrame.R10, DBGFREG_R10, X86_GREG_x10); + MAYBE_ADD_GREG(TrapFrame.R11, DBGFREG_R11, X86_GREG_x11); + } + else if (TrapFrame.ExceptionActive == 2) + { + MAYBE_ADD_GREG(TrapFrame.Rbx, DBGFREG_RBX, X86_GREG_xBX); + MAYBE_ADD_GREG(TrapFrame.Rsi, DBGFREG_RSI, X86_GREG_xSI); + MAYBE_ADD_GREG(TrapFrame.Rdi, DBGFREG_RDI, X86_GREG_xDI); + } + // MAYBE_ADD_GREG(TrapFrame.Rbp, DBGFREG_RBP, X86_GREG_xBP); - KiInterrupt[Sub]Dispatch* may leave this invalid. + + /* Done? */ + if (iLoop > 0) + { + Assert(cRegs == iReg); + break; + } + + /* Resize the array, zeroing the extension. */ + if (pFrame->cSureRegs) + paSureRegs = (PDBGFREGVALEX)MMR3HeapRealloc(pFrame->paSureRegs, iReg * sizeof(paSureRegs[0])); + else + paSureRegs = (PDBGFREGVALEX)MMR3HeapAllocU(pUVM, MM_TAG_DBGF_STACK, iReg * sizeof(paSureRegs[0])); + AssertReturn(paSureRegs, VERR_NO_MEMORY); + + pFrame->paSureRegs = paSureRegs; + RT_BZERO(&paSureRegs[pFrame->cSureRegs], (iReg - pFrame->cSureRegs) * sizeof(paSureRegs[0])); + cRegs = iReg; + } +#undef ADD_REG_NAMED +#undef MAYBE_ADD_GREG + + /* Commit the register update. */ + pFrame->cSureRegs = cRegs; + } + } + } + } + + RT_NOREF(pUVM, pvData, idCpu, hAs, pInitialCtx, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerWinNtQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF3(pUVM, pvData, enmIf); + return NULL; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerWinNtQueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion) +{ + RT_NOREF1(pUVM); + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + Assert(pThis->fValid); + const char *pszNtProductType; + switch (pThis->NtProductType) + { + case kNtProductType_WinNt: pszNtProductType = "-WinNT"; break; + case kNtProductType_LanManNt: pszNtProductType = "-LanManNT"; break; + case kNtProductType_Server: pszNtProductType = "-Server"; break; + default: pszNtProductType = ""; break; + } + RTStrPrintf(pszVersion, cchVersion, "%u.%u-%s%s", pThis->NtMajorVersion, pThis->NtMinorVersion, + pThis->f32Bit ? "x86" : "AMD64", pszNtProductType); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnTerm + */ +static DECLCALLBACK(void) dbgDiggerWinNtTerm(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + Assert(pThis->fValid); + + /* + * As long as we're using our private LDR reader implementation, + * we must unlink and ditch the modules we created. + */ + RTDBGAS hDbgAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL); + if (hDbgAs != NIL_RTDBGAS) + { + uint32_t iMod = RTDbgAsModuleCount(hDbgAs); + while (iMod-- > 0) + { + RTDBGMOD hMod = RTDbgAsModuleByIndex(hDbgAs, iMod); + if (hMod != NIL_RTDBGMOD) + { + if (RTDbgModGetTag(hMod) == DIG_WINNT_MOD_TAG) + { + int rc = RTDbgAsModuleUnlink(hDbgAs, hMod); + AssertRC(rc); + } + RTDbgModRelease(hMod); + } + } + RTDbgAsRelease(hDbgAs); + } + + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerWinNtRefresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + /* + * For now we'll flush and reload everything. + */ + dbgDiggerWinNtTerm(pUVM, pvData); + + return dbgDiggerWinNtInit(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnInit + */ +static DECLCALLBACK(int) dbgDiggerWinNtInit(PUVM pUVM, void *pvData) +{ + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + Assert(!pThis->fValid); + + union + { + uint8_t au8[0x2000]; + RTUTF16 wsz[0x2000/2]; + NTKUSERSHAREDDATA UserSharedData; + } u; + DBGFADDRESS Addr; + int rc; + + /* + * Figure the NT version. + */ + DBGFR3AddrFromFlat(pUVM, &Addr, pThis->f32Bit ? NTKUSERSHAREDDATA_WINNT32 : NTKUSERSHAREDDATA_WINNT64); + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &u, PAGE_SIZE); + if (RT_SUCCESS(rc)) + { + pThis->NtProductType = u.UserSharedData.ProductTypeIsValid && u.UserSharedData.NtProductType <= kNtProductType_Server + ? (NTPRODUCTTYPE)u.UserSharedData.NtProductType + : kNtProductType_Invalid; + pThis->NtMajorVersion = u.UserSharedData.NtMajorVersion; + pThis->NtMinorVersion = u.UserSharedData.NtMinorVersion; + } + else if (pThis->fNt31) + { + pThis->NtProductType = kNtProductType_WinNt; + pThis->NtMajorVersion = 3; + pThis->NtMinorVersion = 1; + } + else + { + Log(("DigWinNt: Error reading KUSER_SHARED_DATA: %Rrc\n", rc)); + return rc; + } + + /* + * Dig out the module chain. + */ + DBGFADDRESS AddrPrev = pThis->PsLoadedModuleListAddr; + Addr = pThis->KernelMteAddr; + do + { + /* Read the validate the MTE. */ + NTMTE Mte; + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &Mte, pThis->f32Bit ? sizeof(Mte.vX_32) : sizeof(Mte.vX_64)); + if (RT_FAILURE(rc)) + break; + if (WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Blink) != AddrPrev.FlatPtr) + { + Log(("DigWinNt: Bad Mte At %RGv - backpointer\n", Addr.FlatPtr)); + break; + } + if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Flink)) ) + { + Log(("DigWinNt: Bad Mte at %RGv - forward pointer\n", Addr.FlatPtr)); + break; + } + if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer))) + { + Log(("DigWinNt: Bad Mte at %RGv - BaseDllName=%llx\n", Addr.FlatPtr, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer))); + break; + } + if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, FullDllName.Buffer))) + { + Log(("DigWinNt: Bad Mte at %RGv - FullDllName=%llx\n", Addr.FlatPtr, WINNT_UNION(pThis, &Mte, FullDllName.Buffer))); + break; + } + if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, DllBase))) + { + Log(("DigWinNt: Bad Mte at %RGv - DllBase=%llx\n", Addr.FlatPtr, WINNT_UNION(pThis, &Mte, DllBase) )); + break; + } + + uint32_t const cbImageMte = !pThis->fNt31 ? WINNT_UNION(pThis, &Mte, SizeOfImage) : 0; + if ( !pThis->fNt31 + && ( cbImageMte > _256M + || WINNT_UNION(pThis, &Mte, EntryPoint) - WINNT_UNION(pThis, &Mte, DllBase) > cbImageMte) ) + { + Log(("DigWinNt: Bad Mte at %RGv - EntryPoint=%llx SizeOfImage=%x DllBase=%llx\n", + Addr.FlatPtr, WINNT_UNION(pThis, &Mte, EntryPoint), cbImageMte, WINNT_UNION(pThis, &Mte, DllBase))); + break; + } + + /* Read the full name. */ + DBGFADDRESS AddrName; + DBGFR3AddrFromFlat(pUVM, &AddrName, WINNT_UNION(pThis, &Mte, FullDllName.Buffer)); + uint16_t cbName = WINNT_UNION(pThis, &Mte, FullDllName.Length); + if (cbName < sizeof(u)) + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrName, &u, cbName); + else + rc = VERR_OUT_OF_RANGE; + if (RT_FAILURE(rc)) + { + DBGFR3AddrFromFlat(pUVM, &AddrName, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer)); + cbName = WINNT_UNION(pThis, &Mte, BaseDllName.Length); + if (cbName < sizeof(u)) + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrName, &u, cbName); + else + rc = VERR_OUT_OF_RANGE; + } + if (RT_SUCCESS(rc)) + { + u.wsz[cbName / 2] = '\0'; + + char *pszFilename; + rc = RTUtf16ToUtf8(u.wsz, &pszFilename); + if (RT_SUCCESS(rc)) + { + char szModName[128]; + const char *pszModName = dbgDiggerWintNtFilenameToModuleName(pszFilename, szModName, sizeof(szModName)); + + /* Read the start of the PE image and pass it along to a worker. */ + DBGFADDRESS ImageAddr; + DBGFR3AddrFromFlat(pUVM, &ImageAddr, WINNT_UNION(pThis, &Mte, DllBase)); + dbgDiggerWinNtProcessImage(pThis, pUVM, pszModName, pszFilename, &ImageAddr, cbImageMte); + RTStrFree(pszFilename); + } + } + + /* next */ + AddrPrev = Addr; + DBGFR3AddrFromFlat(pUVM, &Addr, WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Flink)); + } while ( Addr.FlatPtr != pThis->KernelMteAddr.FlatPtr + && Addr.FlatPtr != pThis->PsLoadedModuleListAddr.FlatPtr); + + pThis->fValid = true; + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnProbe + */ +static DECLCALLBACK(bool) dbgDiggerWinNtProbe(PUVM pUVM, void *pvData) +{ + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + DBGFADDRESS Addr; + union + { + uint8_t au8[8192]; + uint16_t au16[8192/2]; + uint32_t au32[8192/4]; + IMAGE_DOS_HEADER MzHdr; + RTUTF16 wsz[8192/2]; + X86DESC64GATE a32Gates[X86_XCPT_PF + 1]; + X86DESC64GATE a64Gates[X86_XCPT_PF + 1]; + } u; + + union + { + NTMTE32 v32; + NTMTE64 v64; + } uMte, uMte2, uMte3; + + /* + * NT only runs in protected or long mode. + */ + CPUMMODE const enmMode = DBGFR3CpuGetMode(pUVM, 0 /*idCpu*/); + if (enmMode != CPUMMODE_PROTECTED && enmMode != CPUMMODE_LONG) + return false; + bool const f64Bit = enmMode == CPUMMODE_LONG; + uint64_t const uStart = f64Bit ? UINT64_C(0xffff080000000000) : UINT32_C(0x80001000); + uint64_t const uEnd = f64Bit ? UINT64_C(0xffffffffffff0000) : UINT32_C(0xffff0000); + + /* + * To approximately locate the kernel we examine the IDTR handlers. + * + * The exception/trap/fault handlers are all in NT kernel image, we pick + * KiPageFault here. + */ + uint64_t uIdtrBase = 0; + uint16_t uIdtrLimit = 0; + int rc = DBGFR3RegCpuQueryXdtr(pUVM, 0, DBGFREG_IDTR, &uIdtrBase, &uIdtrLimit); + AssertRCReturn(rc, false); + + const uint16_t cbMinIdtr = (X86_XCPT_PF + 1) * (f64Bit ? sizeof(X86DESC64GATE) : sizeof(X86DESCGATE)); + if (uIdtrLimit < cbMinIdtr) + return false; + + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, uIdtrBase), &u, cbMinIdtr); + if (RT_FAILURE(rc)) + return false; + + uint64_t uKrnlStart = uStart; + uint64_t uKrnlEnd = uEnd; + if (f64Bit) + { + uint64_t uHandler = u.a64Gates[X86_XCPT_PF].u16OffsetLow + | ((uint32_t)u.a64Gates[X86_XCPT_PF].u16OffsetHigh << 16) + | ((uint64_t)u.a64Gates[X86_XCPT_PF].u32OffsetTop << 32); + if (uHandler < uStart || uHandler > uEnd) + return false; + uKrnlStart = (uHandler & ~(uint64_t)_4M) - _512M; + uKrnlEnd = (uHandler + (uint64_t)_4M) & ~(uint64_t)_4M; + } + else + { + uint32_t uHandler = u.a32Gates[X86_XCPT_PF].u16OffsetLow + | ((uint32_t)u.a64Gates[X86_XCPT_PF].u16OffsetHigh << 16); + if (uHandler < uStart || uHandler > uEnd) + return false; + uKrnlStart = (uHandler & ~(uint64_t)_4M) - _64M; + uKrnlEnd = (uHandler + (uint64_t)_4M) & ~(uint64_t)_4M; + } + + /* + * Look for the PAGELK section name that seems to be a part of all kernels. + * Then try find the module table entry for it. Since it's the first entry + * in the PsLoadedModuleList we can easily validate the list head and report + * success. + * + * Note! We ASSUME the section name is 8 byte aligned. + */ + DBGFADDRESS KernelAddr; + for (DBGFR3AddrFromFlat(pUVM, &KernelAddr, uKrnlStart); + KernelAddr.FlatPtr < uKrnlEnd; + KernelAddr.FlatPtr += PAGE_SIZE) + { + bool fNt31 = false; + DBGFADDRESS const RetryAddress = KernelAddr; + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, uEnd - KernelAddr.FlatPtr, + 8, "PAGELK\0", sizeof("PAGELK\0"), &KernelAddr); + if ( rc == VERR_DBGF_MEM_NOT_FOUND + && enmMode != CPUMMODE_LONG) + { + /* NT3.1 didn't have a PAGELK section, so look for _TEXT instead. The + following VirtualSize is zero, so check for that too. */ + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &RetryAddress, uEnd - RetryAddress.FlatPtr, + 8, "_TEXT\0\0\0\0\0\0", sizeof("_TEXT\0\0\0\0\0\0"), &KernelAddr); + fNt31 = true; + } + if (RT_FAILURE(rc)) + break; + DBGFR3AddrSub(&KernelAddr, KernelAddr.FlatPtr & PAGE_OFFSET_MASK); + + /* MZ + PE header. */ + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &KernelAddr, &u, sizeof(u)); + if ( RT_SUCCESS(rc) + && u.MzHdr.e_magic == IMAGE_DOS_SIGNATURE + && !(u.MzHdr.e_lfanew & 0x7) + && u.MzHdr.e_lfanew >= 0x080 + && u.MzHdr.e_lfanew <= 0x400) /* W8 is at 0x288*/ + { + if (enmMode != CPUMMODE_LONG) + { + IMAGE_NT_HEADERS32 const *pHdrs = (IMAGE_NT_HEADERS32 const *)&u.au8[u.MzHdr.e_lfanew]; + if ( pHdrs->Signature == IMAGE_NT_SIGNATURE + && pHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 + && pHdrs->FileHeader.SizeOfOptionalHeader == sizeof(pHdrs->OptionalHeader) + && pHdrs->FileHeader.NumberOfSections >= 10 /* the kernel has lots */ + && (pHdrs->FileHeader.Characteristics & (IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL)) == IMAGE_FILE_EXECUTABLE_IMAGE + && pHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC + && pHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES + ) + { + /* Find the MTE. */ + RT_ZERO(uMte); + uMte.v32.DllBase = KernelAddr.FlatPtr; + uMte.v32.EntryPoint = KernelAddr.FlatPtr + pHdrs->OptionalHeader.AddressOfEntryPoint; + uMte.v32.SizeOfImage = !fNt31 ? pHdrs->OptionalHeader.SizeOfImage : 0; /* NT 3.1 didn't set the size. */ + DBGFADDRESS HitAddr; + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, uEnd - KernelAddr.FlatPtr, + 4 /*align*/, &uMte.v32.DllBase, 3 * sizeof(uint32_t), &HitAddr); + while (RT_SUCCESS(rc)) + { + /* check the name. */ + DBGFADDRESS MteAddr = HitAddr; + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrSub(&MteAddr, RT_OFFSETOF(NTMTE32, DllBase)), + &uMte2.v32, sizeof(uMte2.v32)); + if ( RT_SUCCESS(rc) + && uMte2.v32.DllBase == uMte.v32.DllBase + && uMte2.v32.EntryPoint == uMte.v32.EntryPoint + && uMte2.v32.SizeOfImage == uMte.v32.SizeOfImage + && WINNT32_VALID_ADDRESS(uMte2.v32.InLoadOrderLinks.Flink) + && WINNT32_VALID_ADDRESS(uMte2.v32.BaseDllName.Buffer) + && WINNT32_VALID_ADDRESS(uMte2.v32.FullDllName.Buffer) + && uMte2.v32.BaseDllName.Length <= 128 + && uMte2.v32.FullDllName.Length <= 260 + ) + { + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, uMte2.v32.BaseDllName.Buffer), + u.wsz, uMte2.v32.BaseDllName.Length); + u.wsz[uMte2.v32.BaseDllName.Length / 2] = '\0'; + if ( RT_SUCCESS(rc) + && ( !RTUtf16ICmp(u.wsz, g_wszKernelNames[0]) + /* || !RTUtf16ICmp(u.wsz, g_wszKernelNames[1]) */ + ) + ) + { + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(pUVM, &Addr, uMte2.v32.InLoadOrderLinks.Blink), + &uMte3.v32, RT_SIZEOFMEMB(NTMTE32, InLoadOrderLinks)); + if ( RT_SUCCESS(rc) + && uMte3.v32.InLoadOrderLinks.Flink == MteAddr.FlatPtr + && WINNT32_VALID_ADDRESS(uMte3.v32.InLoadOrderLinks.Blink) ) + { + Log(("DigWinNt: MteAddr=%RGv KernelAddr=%RGv SizeOfImage=%x &PsLoadedModuleList=%RGv (32-bit)\n", + MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v32.SizeOfImage, Addr.FlatPtr)); + pThis->KernelAddr = KernelAddr; + pThis->KernelMteAddr = MteAddr; + pThis->PsLoadedModuleListAddr = Addr; + pThis->f32Bit = true; + pThis->fNt31 = fNt31; + return true; + } + } + else if (RT_SUCCESS(rc)) + { + Log2(("DigWinNt: Wrong module: MteAddr=%RGv ImageAddr=%RGv SizeOfImage=%#x '%ls'\n", + MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v32.SizeOfImage, u.wsz)); + break; /* Not NT kernel */ + } + } + + /* next */ + DBGFR3AddrAdd(&HitAddr, 4); + if (HitAddr.FlatPtr < uEnd) + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, uEnd - HitAddr.FlatPtr, + 4 /*align*/, &uMte.v32.DllBase, 3 * sizeof(uint32_t), &HitAddr); + else + rc = VERR_DBGF_MEM_NOT_FOUND; + } + } + } + else + { + IMAGE_NT_HEADERS64 const *pHdrs = (IMAGE_NT_HEADERS64 const *)&u.au8[u.MzHdr.e_lfanew]; + if ( pHdrs->Signature == IMAGE_NT_SIGNATURE + && pHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 + && pHdrs->FileHeader.SizeOfOptionalHeader == sizeof(pHdrs->OptionalHeader) + && pHdrs->FileHeader.NumberOfSections >= 10 /* the kernel has lots */ + && (pHdrs->FileHeader.Characteristics & (IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL)) + == IMAGE_FILE_EXECUTABLE_IMAGE + && pHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC + && pHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES + ) + { + /* Find the MTE. */ + RT_ZERO(uMte.v64); + uMte.v64.DllBase = KernelAddr.FlatPtr; + uMte.v64.EntryPoint = KernelAddr.FlatPtr + pHdrs->OptionalHeader.AddressOfEntryPoint; + uMte.v64.SizeOfImage = pHdrs->OptionalHeader.SizeOfImage; + DBGFADDRESS ScanAddr; + DBGFADDRESS HitAddr; + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &ScanAddr, uStart), + uEnd - uStart, 8 /*align*/, &uMte.v64.DllBase, 5 * sizeof(uint32_t), &HitAddr); + while (RT_SUCCESS(rc)) + { + /* Read the start of the MTE and check some basic members. */ + DBGFADDRESS MteAddr = HitAddr; + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrSub(&MteAddr, RT_OFFSETOF(NTMTE64, DllBase)), + &uMte2.v64, sizeof(uMte2.v64)); + if ( RT_SUCCESS(rc) + && uMte2.v64.DllBase == uMte.v64.DllBase + && uMte2.v64.EntryPoint == uMte.v64.EntryPoint + && uMte2.v64.SizeOfImage == uMte.v64.SizeOfImage + && WINNT64_VALID_ADDRESS(uMte2.v64.InLoadOrderLinks.Flink) + && WINNT64_VALID_ADDRESS(uMte2.v64.BaseDllName.Buffer) + && WINNT64_VALID_ADDRESS(uMte2.v64.FullDllName.Buffer) + && uMte2.v64.BaseDllName.Length <= 128 + && uMte2.v64.FullDllName.Length <= 260 + ) + { + /* Try read the base name and compare with known NT kernel names. */ + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, uMte2.v64.BaseDllName.Buffer), + u.wsz, uMte2.v64.BaseDllName.Length); + u.wsz[uMte2.v64.BaseDllName.Length / 2] = '\0'; + if ( RT_SUCCESS(rc) + && ( !RTUtf16ICmp(u.wsz, g_wszKernelNames[0]) + /* || !RTUtf16ICmp(u.wsz, g_wszKernelNames[1]) */ + ) + ) + { + /* Read the link entry of the previous entry in the list and check that its + forward pointer points at the MTE we've found. */ + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(pUVM, &Addr, uMte2.v64.InLoadOrderLinks.Blink), + &uMte3.v64, RT_SIZEOFMEMB(NTMTE64, InLoadOrderLinks)); + if ( RT_SUCCESS(rc) + && uMte3.v64.InLoadOrderLinks.Flink == MteAddr.FlatPtr + && WINNT64_VALID_ADDRESS(uMte3.v64.InLoadOrderLinks.Blink) ) + { + Log(("DigWinNt: MteAddr=%RGv KernelAddr=%RGv SizeOfImage=%x &PsLoadedModuleList=%RGv (32-bit)\n", + MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v64.SizeOfImage, Addr.FlatPtr)); + pThis->KernelAddr = KernelAddr; + pThis->KernelMteAddr = MteAddr; + pThis->PsLoadedModuleListAddr = Addr; + pThis->f32Bit = false; + pThis->fNt31 = false; + return true; + } + } + else if (RT_SUCCESS(rc)) + { + Log2(("DigWinNt: Wrong module: MteAddr=%RGv ImageAddr=%RGv SizeOfImage=%#x '%ls'\n", + MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v64.SizeOfImage, u.wsz)); + break; /* Not NT kernel */ + } + } + + /* next */ + DBGFR3AddrAdd(&HitAddr, 8); + if (HitAddr.FlatPtr < uEnd) + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, uEnd - HitAddr.FlatPtr, + 8 /*align*/, &uMte.v64.DllBase, 3 * sizeof(uint32_t), &HitAddr); + else + rc = VERR_DBGF_MEM_NOT_FOUND; + } + } + } + } + } + return false; +} + + +/** + * @copydoc DBGFOSREG::pfnDestruct + */ +static DECLCALLBACK(void) dbgDiggerWinNtDestruct(PUVM pUVM, void *pvData) +{ + RT_NOREF2(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerWinNtConstruct(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + pThis->fValid = false; + pThis->f32Bit = false; + pThis->enmVer = DBGDIGGERWINNTVER_UNKNOWN; + return VINF_SUCCESS; +} + + +const DBGFOSREG g_DBGDiggerWinNt = +{ + /* .u32Magic = */ DBGFOSREG_MAGIC, + /* .fFlags = */ 0, + /* .cbData = */ sizeof(DBGDIGGERWINNT), + /* .szName = */ "WinNT", + /* .pfnConstruct = */ dbgDiggerWinNtConstruct, + /* .pfnDestruct = */ dbgDiggerWinNtDestruct, + /* .pfnProbe = */ dbgDiggerWinNtProbe, + /* .pfnInit = */ dbgDiggerWinNtInit, + /* .pfnRefresh = */ dbgDiggerWinNtRefresh, + /* .pfnTerm = */ dbgDiggerWinNtTerm, + /* .pfnQueryVersion = */ dbgDiggerWinNtQueryVersion, + /* .pfnQueryInterface = */ dbgDiggerWinNtQueryInterface, + /* .pfnStackUnwindAssist = */ dbgDiggerWinNtStackUnwindAssist, + /* .u32EndMagic = */ DBGFOSREG_MAGIC +}; + -- cgit v1.2.3