diff options
Diffstat (limited to 'src/VBox/Runtime/r0drv/nt/ntBldSymDb.cpp')
-rw-r--r-- | src/VBox/Runtime/r0drv/nt/ntBldSymDb.cpp | 1212 |
1 files changed, 1212 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r0drv/nt/ntBldSymDb.cpp b/src/VBox/Runtime/r0drv/nt/ntBldSymDb.cpp new file mode 100644 index 00000000..fbcb0613 --- /dev/null +++ b/src/VBox/Runtime/r0drv/nt/ntBldSymDb.cpp @@ -0,0 +1,1212 @@ +/* $Id: ntBldSymDb.cpp $ */ +/** @file + * IPRT - RTDirCreateUniqueNumbered, generic implementation. + */ + +/* + * Copyright (C) 2013-2020 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <iprt/win/dbghelp.h> + +#include <iprt/alloca.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "r0drv/nt/symdb.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** A structure member we're interested in. */ +typedef struct MYMEMBER +{ + /** The member name. */ + const char * const pszName; + /** Reserved. */ + uint32_t const fFlags; + /** The offset of the member. UINT32_MAX if not found. */ + uint32_t off; + /** The size of the member. */ + uint32_t cb; + /** Alternative names, optional. + * This is a string of zero terminated strings, ending with an zero length + * string (or double '\\0' if you like). */ + const char * const pszzAltNames; +} MYMEMBER; +/** Pointer to a member we're interested. */ +typedef MYMEMBER *PMYMEMBER; + +/** Members we're interested in. */ +typedef struct MYSTRUCT +{ + /** The structure name. */ + const char * const pszName; + /** Array of members we're interested in. */ + MYMEMBER *paMembers; + /** The number of members we're interested in. */ + uint32_t const cMembers; + /** Reserved. */ + uint32_t const fFlags; +} MYSTRUCT; + +/** Architecture. */ +typedef enum MYARCH +{ + MYARCH_X86, + MYARCH_AMD64, + MYARCH_DETECT +} MYARCH; + +/** Set of structures for one kernel. */ +typedef struct MYSET +{ + /** The list entry. */ + RTLISTNODE ListEntry; + /** The source PDB. */ + char *pszPdb; + /** The OS version we've harvested structs for */ + RTNTSDBOSVER OsVerInfo; + /** The architecture. */ + MYARCH enmArch; + /** The structures and their member. */ + MYSTRUCT aStructs[1]; +} MYSET; +/** Pointer a set of structures for one kernel. */ +typedef MYSET *PMYSET; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Verbosity level (-v, --verbose). */ +static uint32_t g_iOptVerbose = 1; +/** Set if we should force ahead despite errors. */ +static bool g_fOptForce = false; + +/** The members of the KPRCB structure that we're interested in. */ +static MYMEMBER g_aKprcbMembers[] = +{ + { "QuantumEnd", 0, UINT32_MAX, UINT32_MAX, NULL }, + { "DpcQueueDepth", 0, UINT32_MAX, UINT32_MAX, "DpcData[0].DpcQueueDepth\0" }, + { "VendorString", 0, UINT32_MAX, UINT32_MAX, NULL }, +}; + +/** The structures we're interested in. */ +static MYSTRUCT g_aStructs[] = +{ + { "_KPRCB", &g_aKprcbMembers[0], RT_ELEMENTS(g_aKprcbMembers), 0 }, +}; + +/** List of data we've found. This is sorted by version info. */ +static RTLISTANCHOR g_SetList; + + + + + +/** + * For debug/verbose output. + * + * @param pszFormat The format string. + * @param ... The arguments referenced in the format string. + */ +static void MyDbgPrintf(const char *pszFormat, ...) +{ + if (g_iOptVerbose > 1) + { + va_list va; + va_start(va, pszFormat); + RTPrintf("debug: "); + RTPrintfV(pszFormat, va); + va_end(va); + } +} + + +/** + * Returns the name we wish to use in the C code. + * @returns Structure name. + * @param pStruct The structure descriptor. + */ +static const char *figureCStructName(MYSTRUCT const *pStruct) +{ + const char *psz = pStruct->pszName; + while (*psz == '_') + psz++; + return psz; +} + + +/** + * Returns the name we wish to use in the C code. + * @returns Member name. + * @param pMember The member descriptor. + */ +static const char *figureCMemberName(MYMEMBER const *pMember) +{ + return pMember->pszName; +} + + +/** + * Creates a MYSET with copies of all the data and inserts it into the + * g_SetList in a orderly fashion. + * + * @param pOut The output stream. + */ +static void generateHeader(PRTSTREAM pOut) +{ + RTStrmPrintf(pOut, + "/* $" "I" "d" ": $ */\n" /* avoid it being expanded */ + "/** @file\n" + " * IPRT - NT kernel type helpers - Autogenerated, do NOT edit.\n" + " */\n" + "\n" + "/*\n" + " * Copyright (C) 2013-2020 Oracle Corporation \n" + " *\n" + " * This file is part of VirtualBox Open Source Edition (OSE), as\n" + " * available from http://www.virtualbox.org. This file is free software;\n" + " * you can redistribute it and/or modify it under the terms of the GNU\n" + " * General Public License (GPL) as published by the Free Software\n" + " * Foundation, in version 2 as it comes in the \"COPYING\" file of the\n" + " * VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n" + " * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n" + " *\n" + " * The contents of this file may alternatively be used under the terms\n" + " * of the Common Development and Distribution License Version 1.0\n" + " * (CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n" + " * VirtualBox OSE distribution, in which case the provisions of the\n" + " * CDDL are applicable instead of those of the GPL.\n" + " *\n" + " * You may elect to license modified versions of this file under the\n" + " * terms and conditions of either the GPL or the CDDL or both.\n" + " */\n" + "\n" + "\n" + "#ifndef IPRT_INCLUDED_SRC_nt_symdbdata_h\n" + "#define IPRT_INCLUDED_SRC_nt_symdbdata_h\n" + "\n" + "#include \"r0drv/nt/symdb.h\"\n" + "\n" + ); + + /* + * Generate types. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(g_aStructs); i++) + { + const char *pszStructName = figureCStructName(&g_aStructs[i]); + + RTStrmPrintf(pOut, + "typedef struct RTNTSDBTYPE_%s\n" + "{\n", + pszStructName); + PMYMEMBER paMembers = g_aStructs[i].paMembers; + for (uint32_t j = 0; j < g_aStructs->cMembers; j++) + { + const char *pszMemName = figureCMemberName(&paMembers[j]); + RTStrmPrintf(pOut, + " uint32_t off%s;\n" + " uint32_t cb%s;\n", + pszMemName, pszMemName); + } + + RTStrmPrintf(pOut, + "} RTNTSDBTYPE_%s;\n" + "\n", + pszStructName); + } + + RTStrmPrintf(pOut, + "\n" + "typedef struct RTNTSDBSET\n" + "{\n" + " RTNTSDBOSVER%-20s OsVerInfo;\n", ""); + for (uint32_t i = 0; i < RT_ELEMENTS(g_aStructs); i++) + { + const char *pszStructName = figureCStructName(&g_aStructs[i]); + RTStrmPrintf(pOut, " RTNTSDBTYPE_%-20s %s;\n", pszStructName, pszStructName); + } + RTStrmPrintf(pOut, + "} RTNTSDBSET;\n" + "typedef RTNTSDBSET const *PCRTNTSDBSET;\n" + "\n"); + + /* + * Output the data. + */ + RTStrmPrintf(pOut, + "\n" + "#ifndef RTNTSDB_NO_DATA\n" + "const RTNTSDBSET g_artNtSdbSets[] = \n" + "{\n"); + PMYSET pSet; + RTListForEach(&g_SetList, pSet, MYSET, ListEntry) + { + const char *pszArch = pSet->enmArch == MYARCH_AMD64 ? "AMD64" : "X86"; + RTStrmPrintf(pOut, + "# ifdef RT_ARCH_%s\n" + " { /* Source: %s */\n" + " /*.OsVerInfo = */\n" + " {\n" + " /* .uMajorVer = */ %u,\n" + " /* .uMinorVer = */ %u,\n" + " /* .fChecked = */ %s,\n" + " /* .fSmp = */ %s,\n" + " /* .uCsdNo = */ %u,\n" + " /* .uBuildNo = */ %u,\n" + " },\n", + pszArch, + pSet->pszPdb, + pSet->OsVerInfo.uMajorVer, + pSet->OsVerInfo.uMinorVer, + pSet->OsVerInfo.fChecked ? "true" : "false", + pSet->OsVerInfo.fSmp ? "true" : "false", + pSet->OsVerInfo.uCsdNo, + pSet->OsVerInfo.uBuildNo); + for (uint32_t i = 0; i < RT_ELEMENTS(pSet->aStructs); i++) + { + const char *pszStructName = figureCStructName(&pSet->aStructs[i]); + RTStrmPrintf(pOut, + " /* .%s = */\n" + " {\n", pszStructName); + PMYMEMBER paMembers = pSet->aStructs[i].paMembers; + for (uint32_t j = 0; j < pSet->aStructs[i].cMembers; j++) + { + const char *pszMemName = figureCMemberName(&paMembers[j]); + RTStrmPrintf(pOut, + " /* .off%-25s = */ %#06x,\n" + " /* .cb%-26s = */ %#06x,\n", + pszMemName, paMembers[j].off, + pszMemName, paMembers[j].cb); + } + RTStrmPrintf(pOut, + " },\n"); + } + RTStrmPrintf(pOut, + " },\n" + "# endif\n" + ); + } + + RTStrmPrintf(pOut, + "};\n" + "#endif /* !RTNTSDB_NO_DATA */\n" + "\n"); + + RTStrmPrintf(pOut, "\n#endif\n\n"); +} + + +/** + * Creates a MYSET with copies of all the data and inserts it into the + * g_SetList in a orderly fashion. + * + * @returns Fully complained exit code. + * @param pOsVerInfo The OS version info. + * @param enmArch The NT architecture of the incoming PDB. + * @param pszPdb The PDB file name. + */ +static RTEXITCODE saveStructures(PRTNTSDBOSVER pOsVerInfo, MYARCH enmArch, const char *pszPdb) +{ + /* + * Allocate one big chunk, figure it's size once. + */ + static size_t s_cbNeeded = 0; + if (s_cbNeeded == 0) + { + s_cbNeeded = RT_UOFFSETOF(MYSET, aStructs[RT_ELEMENTS(g_aStructs)]); + for (uint32_t i = 0; i < RT_ELEMENTS(g_aStructs); i++) + s_cbNeeded += sizeof(MYMEMBER) * g_aStructs[i].cMembers; + } + + size_t cbPdb = strlen(pszPdb) + 1; + PMYSET pSet = (PMYSET)RTMemAlloc(s_cbNeeded + cbPdb); + if (!pSet) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Out of memory!\n"); + + /* + * Copy over the data. + */ + pSet->enmArch = enmArch; + memcpy(&pSet->OsVerInfo, pOsVerInfo, sizeof(pSet->OsVerInfo)); + memcpy(&pSet->aStructs[0], g_aStructs, sizeof(g_aStructs)); + + PMYMEMBER pDst = (PMYMEMBER)&pSet->aStructs[RT_ELEMENTS(g_aStructs)]; + for (uint32_t i = 0; i < RT_ELEMENTS(g_aStructs); i++) + { + pSet->aStructs[i].paMembers = pDst; + memcpy(pDst, g_aStructs[i].paMembers, g_aStructs[i].cMembers * sizeof(*pDst)); + pDst += g_aStructs[i].cMembers; + } + + pSet->pszPdb = (char *)pDst; + memcpy(pDst, pszPdb, cbPdb); + + /* + * Link it. + */ + PMYSET pInsertBefore; + RTListForEach(&g_SetList, pInsertBefore, MYSET, ListEntry) + { + int iDiff = rtNtOsVerInfoCompare(&pInsertBefore->OsVerInfo, &pSet->OsVerInfo); + if (iDiff >= 0) + { + if (iDiff > 0 || pInsertBefore->enmArch > pSet->enmArch) + { + RTListNodeInsertBefore(&pInsertBefore->ListEntry, &pSet->ListEntry); + return RTEXITCODE_SUCCESS; + } + } + } + + RTListAppend(&g_SetList, &pSet->ListEntry); + return RTEXITCODE_SUCCESS; +} + + +/** + * Checks that we found everything. + * + * @returns Fully complained exit code. + */ +static RTEXITCODE checkThatWeFoundEverything(void) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (uint32_t i = 0; i < RT_ELEMENTS(g_aStructs); i++) + { + PMYMEMBER paMembers = g_aStructs[i].paMembers; + uint32_t j = g_aStructs[i].cMembers; + while (j-- > 0) + { + if (paMembers[j].off == UINT32_MAX) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, " Missing %s::%s\n", g_aStructs[i].pszName, paMembers[j].pszName); + } + } + return rcExit; +} + + +/** + * Matches the member against what we're looking for. + * + * @returns Number of hits. + * @param cWantedMembers The number members in paWantedMembers. + * @param paWantedMembers The members we're looking for. + * @param pszPrefix The member name prefix. + * @param pszMember The member name. + * @param offMember The member offset. + * @param cbMember The member size. + */ +static uint32_t matchUpStructMembers(unsigned cWantedMembers, PMYMEMBER paWantedMembers, + const char *pszPrefix, const char *pszMember, + uint32_t offMember, uint32_t cbMember) +{ + size_t cchPrefix = strlen(pszPrefix); + uint32_t cHits = 0; + uint32_t iMember = cWantedMembers; + while (iMember-- > 0) + { + if ( !strncmp(pszPrefix, paWantedMembers[iMember].pszName, cchPrefix) + && !strcmp(pszMember, paWantedMembers[iMember].pszName + cchPrefix)) + { + paWantedMembers[iMember].off = offMember; + paWantedMembers[iMember].cb = cbMember; + cHits++; + } + else if (paWantedMembers[iMember].pszzAltNames) + { + char const *pszCur = paWantedMembers[iMember].pszzAltNames; + while (*pszCur) + { + size_t cchCur = strlen(pszCur); + if ( !strncmp(pszPrefix, pszCur, cchPrefix) + && !strcmp(pszMember, pszCur + cchPrefix)) + { + paWantedMembers[iMember].off = offMember; + paWantedMembers[iMember].cb = cbMember; + cHits++; + break; + } + pszCur += cchCur + 1; + } + } + } + return cHits; +} + + +#if 0 +/** + * Resets the writable structure members prior to processing a PDB. + * + * While processing the PDB, will fill in the sizes and offsets of what we find. + * Afterwards we'll use look for reset values to see that every structure and + * member was located successfully. + */ +static void resetMyStructs(void) +{ + for (uint32_t i = 0; i < RT_ELEMENTS(g_aStructs); i++) + { + PMYMEMBER paMembers = g_aStructs[i].paMembers; + uint32_t j = g_aStructs[i].cMembers; + while (j-- > 0) + { + paMembers[j].off = UINT32_MAX; + paMembers[j].cb = UINT32_MAX; + } + } +} +#endif + + +/** + * Find members in the specified structure type (@a idxType). + * + * @returns Fully bitched exit code. + * @param hFake Fake process handle. + * @param uModAddr The module address. + * @param idxType The type index of the structure which members we're + * going to process. + * @param cWantedMembers The number of wanted members. + * @param paWantedMembers The wanted members. This will be modified. + * @param offDisp Displacement when calculating member offsets. + * @param pszStructNm The top level structure name. + * @param pszPrefix The member name prefix. + * @param pszLogTag The log tag. + */ +static RTEXITCODE findMembers(HANDLE hFake, uint64_t uModAddr, uint32_t idxType, + uint32_t cWantedMembers, PMYMEMBER paWantedMembers, + uint32_t offDisp, const char *pszStructNm, const char *pszPrefix, const char *pszLogTag) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + DWORD cChildren = 0; + if (!SymGetTypeInfo(hFake, uModAddr, idxType, TI_GET_CHILDRENCOUNT, &cChildren)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: TI_GET_CHILDRENCOUNT failed on _KPRCB: %u\n", pszLogTag, GetLastError()); + + MyDbgPrintf(" %s: cChildren=%u (%#x)\n", pszStructNm, cChildren); + TI_FINDCHILDREN_PARAMS *pChildren; + pChildren = (TI_FINDCHILDREN_PARAMS *)alloca(RT_UOFFSETOF_DYN(TI_FINDCHILDREN_PARAMS, ChildId[cChildren])); + pChildren->Start = 0; + pChildren->Count = cChildren; + if (!SymGetTypeInfo(hFake, uModAddr, idxType, TI_FINDCHILDREN, pChildren)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: TI_FINDCHILDREN failed on _KPRCB: %u\n", pszLogTag, GetLastError()); + + for (uint32_t i = 0; i < cChildren; i++) + { + //MyDbgPrintf(" %s: child#%u: TypeIndex=%u\n", pszStructNm, i, pChildren->ChildId[i]); + IMAGEHLP_SYMBOL_TYPE_INFO enmErr; + PWCHAR pwszMember = NULL; + uint32_t idxRefType = 0; + uint32_t offMember = 0; + uint64_t cbMember = 0; + uint32_t cMemberChildren = 0; + if ( SymGetTypeInfo(hFake, uModAddr, pChildren->ChildId[i], enmErr = TI_GET_SYMNAME, &pwszMember) + && SymGetTypeInfo(hFake, uModAddr, pChildren->ChildId[i], enmErr = TI_GET_OFFSET, &offMember) + && SymGetTypeInfo(hFake, uModAddr, pChildren->ChildId[i], enmErr = TI_GET_TYPE, &idxRefType) + && SymGetTypeInfo(hFake, uModAddr, idxRefType, enmErr = TI_GET_LENGTH, &cbMember) + && SymGetTypeInfo(hFake, uModAddr, idxRefType, enmErr = TI_GET_CHILDRENCOUNT, &cMemberChildren) + ) + { + offMember += offDisp; + + char *pszMember; + int rc = RTUtf16ToUtf8(pwszMember, &pszMember); + if (RT_SUCCESS(rc)) + { + matchUpStructMembers(cWantedMembers, paWantedMembers, pszPrefix, pszMember, offMember, cbMember); + + /* + * Gather more info and do some debug printing. We'll use some + * of this info below when recursing into sub-structures + * and arrays. + */ + uint32_t fNested = 0; SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_NESTED, &fNested); + uint32_t uDataKind = 0; SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_DATAKIND, &uDataKind); + uint32_t uBaseType = 0; SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_BASETYPE, &uBaseType); + uint32_t uMembTag = 0; SymGetTypeInfo(hFake, uModAddr, pChildren->ChildId[i], TI_GET_SYMTAG, &uMembTag); + uint32_t uBaseTag = 0; SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_SYMTAG, &uBaseTag); + uint32_t cElements = 0; SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_COUNT, &cElements); + uint32_t idxArrayType = 0; SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_ARRAYINDEXTYPEID, &idxArrayType); + MyDbgPrintf(" %#06x LB %#06llx %c%c %2d %2d %2d %2d %2d %4d %s::%s%s\n", + offMember, cbMember, + cMemberChildren > 0 ? 'c' : '-', + fNested != 0 ? 'n' : '-', + uDataKind, + uBaseType, + uMembTag, + uBaseTag, + cElements, + idxArrayType, + pszStructNm, + pszPrefix, + pszMember); + + /* + * Recurse into children. + */ + if (cMemberChildren > 0) + { + size_t cbNeeded = strlen(pszMember) + strlen(pszPrefix) + sizeof("."); + char *pszSubPrefix = (char *)RTMemTmpAlloc(cbNeeded); + if (pszSubPrefix) + { + strcat(strcat(strcpy(pszSubPrefix, pszPrefix), pszMember), "."); + RTEXITCODE rcExit2 = findMembers(hFake, uModAddr, idxRefType, cWantedMembers, + paWantedMembers, offMember, + pszStructNm, + pszSubPrefix, + pszLogTag); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + RTMemTmpFree(pszSubPrefix); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory\n"); + } + /* + * Recurse into arrays too. + */ + else if (cElements > 0 && idxArrayType > 0) + { + BOOL fRc; + uint32_t idxElementRefType = 0; + fRc = SymGetTypeInfo(hFake, uModAddr, idxRefType, TI_GET_TYPE, &idxElementRefType); Assert(fRc); + uint64_t cbElement = cbMember / cElements; + fRc = SymGetTypeInfo(hFake, uModAddr, idxElementRefType, TI_GET_LENGTH, &cbElement); Assert(fRc); + MyDbgPrintf("idxArrayType=%u idxElementRefType=%u cbElement=%u\n", idxArrayType, idxElementRefType, cbElement); + + size_t cbNeeded = strlen(pszMember) + strlen(pszPrefix) + sizeof("[xxxxxxxxxxxxxxxx]."); + char *pszSubPrefix = (char *)RTMemTmpAlloc(cbNeeded); + if (pszSubPrefix) + { + for (uint32_t iElement = 0; iElement < cElements; iElement++) + { + RTStrPrintf(pszSubPrefix, cbNeeded, "%s%s[%u].", pszPrefix, pszMember, iElement); + RTEXITCODE rcExit2 = findMembers(hFake, uModAddr, idxElementRefType, cWantedMembers, + paWantedMembers, + offMember + iElement * cbElement, + pszStructNm, + pszSubPrefix, + pszLogTag); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + RTMemTmpFree(pszSubPrefix); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory\n"); + } + + RTStrFree(pszMember); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: RTUtf16ToUtf8 failed on %s child#%u: %Rrc\n", + pszLogTag, pszStructNm, i, rc); + } + /* TI_GET_OFFSET fails on bitfields, so just ignore+skip those. */ + else if (enmErr != TI_GET_OFFSET || GetLastError() != ERROR_INVALID_FUNCTION) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: SymGetTypeInfo(,,,%d,) failed on %s child#%u: %u\n", + pszLogTag, enmErr, pszStructNm, i, GetLastError()); + LocalFree(pwszMember); + } /* For each child. */ + + return rcExit; +} + + +/** + * Lookup up structures and members in the given module. + * + * @returns Fully bitched exit code. + * @param hFake Fake process handle. + * @param uModAddr The module address. + * @param pszLogTag The log tag. + * @param pszPdb The full PDB path. + * @param pOsVerInfo The OS version info for altering the error handling + * for older OSes. + */ +static RTEXITCODE findStructures(HANDLE hFake, uint64_t uModAddr, const char *pszLogTag, const char *pszPdb, + PCRTNTSDBOSVER pOsVerInfo) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + PSYMBOL_INFO pSymInfo = (PSYMBOL_INFO)alloca(sizeof(*pSymInfo)); + for (uint32_t iStruct = 0; iStruct < RT_ELEMENTS(g_aStructs); iStruct++) + { + pSymInfo->SizeOfStruct = sizeof(*pSymInfo); + pSymInfo->MaxNameLen = 0; + if (!SymGetTypeFromName(hFake, uModAddr, g_aStructs[iStruct].pszName, pSymInfo)) + { + if (!(pOsVerInfo->uMajorVer == 5 && pOsVerInfo->uMinorVer == 0) /* w2k */) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to find _KPRCB: %u\n", pszPdb, GetLastError()); + RTMsgInfo("%s: Skipping - failed to find _KPRCB: %u\n", pszPdb, GetLastError()); + return RTEXITCODE_SKIPPED; + } + + MyDbgPrintf(" %s: TypeIndex=%u\n", g_aStructs[iStruct].pszName, pSymInfo->TypeIndex); + MyDbgPrintf(" %s: Size=%u (%#x)\n", g_aStructs[iStruct].pszName, pSymInfo->Size, pSymInfo->Size); + + rcExit = findMembers(hFake, uModAddr, pSymInfo->TypeIndex, + g_aStructs[iStruct].cMembers, g_aStructs[iStruct].paMembers, 0 /* offDisp */, + g_aStructs[iStruct].pszName, "", pszLogTag); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + } /* for each struct we want */ + return rcExit; +} + + +#if 0 /* unused */ +static bool strIEndsWith(const char *pszString, const char *pszSuffix) +{ + size_t cchString = strlen(pszString); + size_t cchSuffix = strlen(pszSuffix); + if (cchString < cchSuffix) + return false; + return RTStrICmp(pszString + cchString - cchSuffix, pszSuffix) == 0; +} +#endif + + +/** + * Use various hysterics to figure out the OS version details from the PDB path. + * + * This ASSUMES quite a bunch of things: + * -# Working on unpacked symbol packages. This does not work for + * windbg symbol stores/caches. + * -# The symbol package has been unpacked into a directory with the same + * name as the symbol package (sans suffixes). + * + * @returns Fully complained exit code. + * @param pszPdb The path to the PDB. + * @param pVerInfo Where to return the version info. + * @param penmArch Where to return the architecture. + */ +static RTEXITCODE FigurePdbVersionInfo(const char *pszPdb, PRTNTSDBOSVER pVerInfo, MYARCH *penmArch) +{ + /* + * Split the path. + */ + union + { + RTPATHSPLIT Split; + uint8_t abPad[RTPATH_MAX + 1024]; + } u; + int rc = RTPathSplit(pszPdb, &u.Split, sizeof(u), 0); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathSplit failed on '%s': %Rrc", pszPdb, rc); + if (!(u.Split.fProps & RTPATH_PROP_FILENAME)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPATH_PROP_FILENAME not set for: '%s'", pszPdb); + const char *pszFilename = u.Split.apszComps[u.Split.cComps - 1]; + + /* + * SMP or UNI kernel? + */ + if ( !RTStrICmp(pszFilename, "ntkrnlmp.pdb") + || !RTStrICmp(pszFilename, "ntkrpamp.pdb") + ) + pVerInfo->fSmp = true; + else if ( !RTStrICmp(pszFilename, "ntoskrnl.pdb") + || !RTStrICmp(pszFilename, "ntkrnlpa.pdb") + ) + pVerInfo->fSmp = false; + else + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Doesn't recognize the filename '%s'...", pszFilename); + + /* + * Look for symbol pack names in the path. This is stuff like: + * - WindowsVista.6002.090410-1830.x86fre + * - WindowsVista.6002.090410-1830.amd64chk + * - Windows_Win7.7600.16385.090713-1255.X64CHK + * - Windows_Win7SP1.7601.17514.101119-1850.AMD64FRE + * - Windows_Win8.9200.16384.120725-1247.X86CHK + * - en_windows_8_1_symbols_debug_checked_x64_2712568 + */ + uint32_t i = u.Split.cComps - 1; + while (i-- > 0) + { + static struct + { + const char *pszPrefix; + size_t cchPrefix; + uint8_t uMajorVer; + uint8_t uMinorVer; + uint8_t uCsdNo; + uint32_t uBuildNo; /**< UINT32_MAX means the number immediately after the prefix. */ + } const s_aSymPacks[] = + { + { RT_STR_TUPLE("w2kSP1SYM"), 5, 0, 1, 2195 }, + { RT_STR_TUPLE("w2ksp2srp1"), 5, 0, 2, 2195 }, + { RT_STR_TUPLE("w2ksp2sym"), 5, 0, 2, 2195 }, + { RT_STR_TUPLE("w2ksp3sym"), 5, 0, 3, 2195 }, + { RT_STR_TUPLE("w2ksp4sym"), 5, 0, 4, 2195 }, + { RT_STR_TUPLE("Windows2000-KB891861"), 5, 0, 4, 2195 }, + { RT_STR_TUPLE("windowsxp"), 5, 1, 0, 2600 }, + { RT_STR_TUPLE("xpsp1sym"), 5, 1, 1, 2600 }, + { RT_STR_TUPLE("WindowsXP-KB835935-SP2-"), 5, 1, 2, 2600 }, + { RT_STR_TUPLE("WindowsXP-KB936929-SP3-"), 5, 1, 3, 2600 }, + { RT_STR_TUPLE("Windows2003."), 5, 2, 0, 3790 }, + { RT_STR_TUPLE("Windows2003_sp1."), 5, 2, 1, 3790 }, + { RT_STR_TUPLE("WindowsServer2003-KB933548-v1"), 5, 2, 1, 3790 }, + { RT_STR_TUPLE("WindowsVista.6000."), 6, 0, 0, 6000 }, + { RT_STR_TUPLE("Windows_Longhorn.6001."), 6, 0, 1, 6001 }, /* incl w2k8 */ + { RT_STR_TUPLE("WindowsVista.6002."), 6, 0, 2, 6002 }, /* incl w2k8 */ + { RT_STR_TUPLE("Windows_Winmain.7000"), 6, 1, 0, 7000 }, /* Beta */ + { RT_STR_TUPLE("Windows_Winmain.7100"), 6, 1, 0, 7100 }, /* RC */ + { RT_STR_TUPLE("Windows_Win7.7600"), 6, 1, 0, 7600 }, /* RC */ + { RT_STR_TUPLE("Windows_Win7SP1.7601"), 6, 1, 1, 7601 }, /* RC */ + { RT_STR_TUPLE("Windows_Winmain.8102"), 6, 2, 0, 8102 }, /* preview */ + { RT_STR_TUPLE("Windows_Winmain.8250"), 6, 2, 0, 8250 }, /* beta */ + { RT_STR_TUPLE("Windows_Winmain.8400"), 6, 2, 0, 8400 }, /* RC */ + { RT_STR_TUPLE("Windows_Win8.9200"), 6, 2, 0, 9200 }, /* RTM */ + { RT_STR_TUPLE("en_windows_8_1"), 6, 3, 0, 9600 }, /* RTM */ + { RT_STR_TUPLE("en_windows_10_symbols_"), 10, 0, 0,10240 }, /* RTM */ + { RT_STR_TUPLE("en_windows_10_symbols_"), 10, 0, 0,10240 }, /* RTM */ + { RT_STR_TUPLE("en_windows_10_17134_"), 10, 0, 0,17134 }, /* 1803 */ + }; + + const char *pszComp = u.Split.apszComps[i]; + uint32_t iSymPack = RT_ELEMENTS(s_aSymPacks); + while (iSymPack-- > 0) + if (!RTStrNICmp(pszComp, s_aSymPacks[iSymPack].pszPrefix, s_aSymPacks[iSymPack].cchPrefix)) + break; + if (iSymPack >= RT_ELEMENTS(s_aSymPacks)) + continue; + + pVerInfo->uMajorVer = s_aSymPacks[iSymPack].uMajorVer; + pVerInfo->uMinorVer = s_aSymPacks[iSymPack].uMinorVer; + pVerInfo->uCsdNo = s_aSymPacks[iSymPack].uCsdNo; + pVerInfo->fChecked = false; + pVerInfo->uBuildNo = s_aSymPacks[iSymPack].uBuildNo; + + /* Parse build number if necessary. */ + if (s_aSymPacks[iSymPack].uBuildNo == UINT32_MAX) + { + char *pszNext; + rc = RTStrToUInt32Ex(pszComp + s_aSymPacks[iSymPack].cchPrefix, &pszNext, 10, &pVerInfo->uBuildNo); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to decode build number in '%s': %Rrc", pszComp, rc); + if (*pszNext != '.' && *pszNext != '_' && *pszNext != '-') + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to decode build number in '%s': '%c'", pszComp, *pszNext); + } + + /* Look for build arch and checked/free. */ + if ( RTStrIStr(pszComp, ".x86.chk.") + || RTStrIStr(pszComp, ".x86chk.") + || RTStrIStr(pszComp, "_x86_chk_") + || RTStrIStr(pszComp, "_x86chk_") + || RTStrIStr(pszComp, "-x86-DEBUG") + || (RTStrIStr(pszComp, "-x86-") && RTStrIStr(pszComp, "-DEBUG")) + || RTStrIStr(pszComp, "_debug_checked_x86") + ) + { + pVerInfo->fChecked = true; + *penmArch = MYARCH_X86; + } + else if ( RTStrIStr(pszComp, ".amd64.chk.") + || RTStrIStr(pszComp, ".amd64chk.") + || RTStrIStr(pszComp, ".x64.chk.") + || RTStrIStr(pszComp, ".x64chk.") + || RTStrIStr(pszComp, "_debug_checked_x64") + ) + { + pVerInfo->fChecked = true; + *penmArch = MYARCH_AMD64; + } + else if ( RTStrIStr(pszComp, ".amd64.fre.") + || RTStrIStr(pszComp, ".amd64fre.") + || RTStrIStr(pszComp, ".x64.fre.") + || RTStrIStr(pszComp, ".x64fre.") + ) + { + pVerInfo->fChecked = false; + *penmArch = MYARCH_AMD64; + } + else if ( RTStrIStr(pszComp, "DEBUG") + || RTStrIStr(pszComp, "_chk") + ) + { + pVerInfo->fChecked = true; + *penmArch = MYARCH_X86; + } + else if (RTStrIStr(pszComp, "_x64")) + { + pVerInfo->fChecked = false; + *penmArch = MYARCH_AMD64; + } + else + { + pVerInfo->fChecked = false; + *penmArch = MYARCH_X86; + } + return RTEXITCODE_SUCCESS; + } + + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Giving up on '%s'...\n", pszPdb); +} + + +/** + * Process one PDB. + * + * @returns Fully bitched exit code. + * @param pszPdb The path to the PDB. + */ +static RTEXITCODE processPdb(const char *pszPdb) +{ + /* + * We need the size later on, so get that now and present proper IPRT error + * info if the file is missing or inaccessible. + */ + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(pszPdb, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfo fail on '%s': %Rrc\n", pszPdb, rc); + + /* + * Figure the windows version details for the given PDB. + */ + MYARCH enmArch; + RTNTSDBOSVER OsVerInfo; + RTEXITCODE rcExit = FigurePdbVersionInfo(pszPdb, &OsVerInfo, &enmArch); + if (rcExit != RTEXITCODE_SUCCESS) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to figure the OS version info for '%s'.\n'", pszPdb); + + /* + * Create a fake handle and open the PDB. + */ + static uintptr_t s_iHandle = 0; + HANDLE hFake = (HANDLE)++s_iHandle; + if (!SymInitialize(hFake, NULL, FALSE)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "SymInitialied failed: %u\n", GetLastError()); + + uint64_t uModAddr = UINT64_C(0x1000000); + uModAddr = SymLoadModuleEx(hFake, NULL /*hFile*/, pszPdb, NULL /*pszModuleName*/, + uModAddr, ObjInfo.cbObject, NULL /*pData*/, 0 /*fFlags*/); + if (uModAddr != 0) + { + MyDbgPrintf("*** uModAddr=%#llx \"%s\" ***\n", uModAddr, pszPdb); + + char szLogTag[32]; + RTStrCopy(szLogTag, sizeof(szLogTag), RTPathFilename(pszPdb)); + + /* + * Find the structures. + */ + rcExit = findStructures(hFake, uModAddr, szLogTag, pszPdb, &OsVerInfo); + if (rcExit == RTEXITCODE_SUCCESS) + rcExit = checkThatWeFoundEverything(); + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Save the details for later when we produce the header. + */ + rcExit = saveStructures(&OsVerInfo, enmArch, pszPdb); + } + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymLoadModuleEx failed: %u\n", GetLastError()); + + if (!SymCleanup(hFake)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "SymCleanup failed: %u\n", GetLastError()); + + if (rcExit == RTEXITCODE_SKIPPED) + rcExit = RTEXITCODE_SUCCESS; + return rcExit; +} + + +/** The size of the directory entry buffer we're using. */ +#define MY_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX) + +/** + * Checks if the name is of interest to us. + * + * @returns true/false. + * @param pszName The name. + * @param cchName The length of the name. + */ +static bool isInterestingName(const char *pszName, size_t cchName) +{ + static struct { const char *psz; size_t cch; } const s_aNames[] = + { + RT_STR_TUPLE("ntoskrnl.pdb"), + RT_STR_TUPLE("ntkrnlmp.pdb"), + RT_STR_TUPLE("ntkrnlpa.pdb"), + RT_STR_TUPLE("ntkrpamp.pdb"), + }; + + if ( cchName == s_aNames[0].cch + && (pszName[0] == 'n' || pszName[0] == 'N') + && (pszName[1] == 't' || pszName[1] == 'T') + ) + { + int i = RT_ELEMENTS(s_aNames); + while (i-- > 0) + if ( s_aNames[i].cch == cchName + && !RTStrICmp(s_aNames[i].psz, pszName)) + return true; + } + return false; +} + + +/** + * Recursively processes relevant files in the specified directory. + * + * @returns Fully complained exit code. + * @param pszDir Pointer to the directory buffer. + * @param cchDir The length of pszDir in pszDir. + * @param pDirEntry Pointer to the directory buffer. + * @param iLogDepth The logging depth. + */ +static RTEXITCODE processDirSub(char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry, int iLogDepth) +{ + Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0'); + + /* Make sure we've got some room in the path, to save us extra work further down. */ + if (cchDir + 3 >= RTPATH_MAX) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s'\n", pszDir); + + /* Open directory. */ + RTDIR hDir; + int rc = RTDirOpen(&hDir, pszDir); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirOpen failed on '%s': %Rrc\n", pszDir, rc); + + /* Ensure we've got a trailing slash (there is space for it see above). */ + if (!RTPATH_IS_SEP(pszDir[cchDir - 1])) + { + pszDir[cchDir++] = RTPATH_SLASH; + pszDir[cchDir] = '\0'; + } + + /* + * Process the files and subdirs. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (;;) + { + /* Get the next directory. */ + size_t cbDirEntry = MY_DIRENTRY_BUF_SIZE; + rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_FAILURE(rc)) + break; + + /* Skip the dot and dot-dot links. */ + if (RTDirEntryExIsStdDotLink(pDirEntry)) + continue; + + /* Check length. */ + if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX) + { + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Path too long: '%s' in '%.*s'\n", pDirEntry->szName, cchDir, pszDir); + break; + } + + if (RTFS_IS_FILE(pDirEntry->Info.Attr.fMode)) + { + /* + * Process debug info files of interest. + */ + if (isInterestingName(pDirEntry->szName, pDirEntry->cbName)) + { + memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + RTEXITCODE rcExit2 = processPdb(pszDir); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + } + else if (RTFS_IS_DIRECTORY(pDirEntry->Info.Attr.fMode)) + { + /* + * Recurse into the subdirectory. In order to speed up Win7+ + * symbol pack traversals, we skip directories with ".pdb" suffixes + * unless they match any of the .pdb files we're looking for. + * + * Note! When we get back pDirEntry will be invalid. + */ + if ( pDirEntry->cbName <= 4 + || RTStrICmp(&pDirEntry->szName[pDirEntry->cbName - 4], ".pdb") + || isInterestingName(pDirEntry->szName, pDirEntry->cbName)) + { + memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1); + if (iLogDepth > 0) + RTMsgInfo("%s%s ...\n", pszDir, RTPATH_SLASH_STR); + RTEXITCODE rcExit2 = processDirSub(pszDir, cchDir + pDirEntry->cbName, pDirEntry, iLogDepth - 1); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + } + } + if (rc != VERR_NO_MORE_FILES) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir); + + rc = RTDirClose(hDir); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirClose failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir); + return rcExit; +} + + +/** + * Recursively processes relevant files in the specified directory. + * + * @returns Fully complained exit code. + * @param pszDir The directory to search. + */ +static RTEXITCODE processDir(const char *pszDir) +{ + char szPath[RTPATH_MAX]; + int rc = RTPathAbs(pszDir, szPath, sizeof(szPath)); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed on '%s': %Rrc\n", pszDir, rc); + + union + { + uint8_t abPadding[MY_DIRENTRY_BUF_SIZE]; + RTDIRENTRYEX DirEntry; + } uBuf; + return processDirSub(szPath, strlen(szPath), &uBuf.DirEntry, g_iOptVerbose); +} + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + RTListInit(&g_SetList); + + /* + * Parse options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--force", 'f', RTGETOPT_REQ_NOTHING }, + { "--output", 'o', RTGETOPT_REQ_STRING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + }; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + const char *pszOutput = "-"; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'f': + g_fOptForce = true; + break; + + case 'v': + g_iOptVerbose++; + break; + + case 'q': + g_iOptVerbose++; + break; + + case 'o': + pszOutput = ValueUnion.psz; + break; + + case 'V': + RTPrintf("$Revision: 143400 $"); + break; + + case 'h': + RTPrintf("usage: %s [-v|--verbose] [-q|--quiet] [-f|--force] [-o|--output <file.h>] <dir1|pdb1> [...]\n" + " or: %s [-V|--version]\n" + " or: %s [-h|--help]\n", + argv[0], argv[0], argv[0]); + return RTEXITCODE_SUCCESS; + + case VINF_GETOPT_NOT_OPTION: + { + RTEXITCODE rcExit2; + if (RTFileExists(ValueUnion.psz)) + rcExit2 = processPdb(ValueUnion.psz); + else + rcExit2 = processDir(ValueUnion.psz); + if (rcExit2 != RTEXITCODE_SUCCESS) + { + if (!g_fOptForce) + return rcExit2; + rcExit = rcExit2; + } + break; + } + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (RTListIsEmpty(&g_SetList)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No usable debug files found.\n"); + + /* + * Generate the output. + */ + PRTSTREAM pOut = g_pStdOut; + if (strcmp(pszOutput, "-")) + { + rc = RTStrmOpen(pszOutput, "w", &pOut); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening '%s' for writing: %Rrc\n", pszOutput, rc); + } + + generateHeader(pOut); + + if (pOut != g_pStdOut) + rc = RTStrmClose(pOut); + else + rc = RTStrmFlush(pOut); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error %s '%s': %Rrc\n", pszOutput, + pOut != g_pStdOut ? "closing" : "flushing", rc); + return rcExit; +} |