/* $Id: $ */ /** @file * * kProfile Mark 2 - Debug Info Reader, DbgHelp Base Reader. * * Copyright (c) 2006 knut st. osmundsen * * * This file is part of kLIBC. * * kLIBC is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * kLIBC 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with kLIBC; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /******************************************************************************* * Header Files * *******************************************************************************/ #include #define _IMAGEHLP64 #include #include #include #include #include #include #include #include #include #include "kDbgBase.h" #include "DBGInternal.h" //#include "internal/ldrPE.h" /******************************************************************************* * Global Variables * *******************************************************************************/ /** The dbghelp.dll module handle. */ static HMODULE g_hDbgHelp = NULL; /** Pointer to the dbhelp.dll SymInitialize function. */ static BOOL (WINAPI *g_pfnSymInitialize)(IN HANDLE,IN LPSTR,IN BOOL); /** Pointer to the dbhelp.dll SymCleanup function. */ static BOOL (WINAPI *g_pfnSymCleanup)(IN HANDLE); /** Pointer to the dbhelp.dll SymSetOptions function. */ static DWORD (WINAPI *g_pfnSymSetOptions)(IN DWORD); /** Pointer to the dbhelp.dll SymLoadModule64 function. */ static DWORD64 (WINAPI *g_pfnSymLoadModule64)(IN HANDLE, IN HANDLE, IN PCSTR, IN PCSTR ModuleName, IN DWORD64, IN DWORD); /** Pointer to the dbhelp.dll SymFromAddr function. */ static DWORD (WINAPI *g_pfnSymFromAddr)(IN HANDLE, IN DWORD64, OUT PDWORD64, OUT PSYMBOL_INFO); /** Pointer to the dbhelp.dll SymGetLineFromAddr64 function. */ static DWORD (WINAPI *g_pfnSymGetLineFromAddr64)(IN HANDLE, IN DWORD64, OUT PDWORD64, OUT PIMAGEHLP_LINE64); /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** * A dbghelp based PE debug reader. */ typedef struct RTDBGMODPE { /** The common module core. */ RTDBGMOD Core; /** The image base. */ DWORD64 ImageBase; /** The "process" handle we present dbghelp. */ HANDLE hSymInst; /** The image size. */ uint32_t cbImage; /** The number of sections. (We've added the implicit header section.) */ int32_t cSections; /** The section headers (variable size). The first section is the * implicit header section.*/ IMAGE_SECTION_HEADER aSections[1]; } RTDBGMODPE, *PRTDBGMODPE; /** * Calcs the RVA for a segment:offset address. * * @returns IPRT status code. * * @param pModPe The PE debug module instance. * @param iSegment The segment number. Special segments are dealt with as well. * @param off The segment offset. * @param puRVA Where to store the RVA on success. */ static int rtDbgModPeSegOffToRVA(PRTDBGMODPE pModPe, int32_t iSegment, RTUINTPTR off, uint32_t *puRVA) { if (iSegment >= 0) { AssertMsgReturn(iSegment < pModPe->cSections, ("iSegment=%RX32 cSections=%RX32\n", iSegment, pModPe->cSections), VERR_DBGMOD_INVALID_ADDRESS); AssertMsgReturn(off < pModPe->aSections[iSegment].Misc.VirtualSize, ("off=%RTptr VirtualSize=%RX32\n", off, pModPe->aSections[iSegment].Misc.VirtualSize), VERR_DBGMOD_INVALID_ADDRESS); *puRVA = pModPe->aSections[iSegment].VirtualAddress + off; return VINF_SUCCESS; } if (iSegment == RTDBGSEG_RVA) { AssertMsgReturn(off < pModPe->cbImage, ("off=%RTptr, cbImage=%RX32\n", off, pModPe->cbImage), VERR_DBGMOD_INVALID_ADDRESS); *puRVA = off; return VINF_SUCCESS; } AssertMsgFailedReturn(("iSegment=%RI32\n", iSegment), VERR_DBGMOD_INVALID_ADDRESS); } /** * Calcs the segment:offset address for a RVA. * * @returns IPRT status code. * * @param pModPe The PE debug module instance. * @param uRVA The RVA. * @param piSegment Where to store the segment number. * @param poff Where to store the segment offset. */ static int rtDbgModPeRVAToSegOff(PRTDBGMODPE pModPe, uint32_t uRVA, int32_t *piSegment, RTUINTPTR *poff) { AssertMsgReturn(uRVA < pModPe->cbImage, ("uRVA=%RX32, cbImage=%RX32\n", uRVA, pModPe->cbImage), VERR_DBGMOD_INVALID_ADDRESS); for (int32_t iSegment = 0; iSegment < pModPe->cSections; iSegment++) { /** @todo should probably be less strict about address in the alignment gaps. */ uint32_t off = uRVA - pModPe->aSections[iSegment].VirtualAddress; if (off < pModPe->aSections[iSegment].Misc.VirtualSize) { *poff = off; *piSegment = iSegment; return VINF_SUCCESS; } } AssertMsgFailedReturn(("uRVA=%RX32\n", uRVA), VERR_DBGMOD_INVALID_ADDRESS); } /** * @copydoc RTDBGMODOPS::pfnQueryLine */ static DECLCALLBACK(int) rtDbgModPEQueryLine(PRTDBGMOD pMod, int32_t iSegment, RTUINTPTR off, PRTDBGLINE pLine) { PRTDBGMODPE pModPe = (PRTDBGMODPE)pMod; /* * Translate the address to an RVA. */ uint32_t uRVA; int rc = rtDbgModPeSegOffToRVA(pModPe, iSegment, off, &uRVA); if (RT_SUCCESS(rc)) { DWORD64 off; IMAGEHLP_LINE64 Line; Line.SizeOfStruct = sizeof(Line); if (g_pfnSymGetLineFromAddr64(pModPe->hSymInst, pModPe->ImageBase + uRVA, &off, &Line)) { pLine->RVA = (RTUINTPTR)(Line.Address - pModPe->ImageBase); rc = rtDbgModPeRVAToSegOff(pModPe, pLine->RVA, &pLine->iSegment, &pLine->offSegment); pLine->iLine = Line.LineNumber; pLine->cchFile = strlen(Line.FileName); if (pLine->cchFile >= sizeof(pLine->szFile)) pLine->cchFile = sizeof(pLine->szFile) - 1; memcpy(pLine->szFile, Line.FileName, pLine->cchFile + 1); } else { DWORD Err = GetLastError(); rc = RTErrConvertFromWin32(Err); } } return rc; } /** * @copydoc RTDBGMODOPS::pfnQuerySymbol */ static DECLCALLBACK(int) rtDbgModPEQuerySymbol(PRTDBGMOD pMod, int32_t iSegment, RTUINTPTR off, PRTDBGSYMBOL pSym) { PRTDBGMODPE pModPe = (PRTDBGMODPE)pMod; /* * Translate the address to an RVA. */ uint32_t uRVA; int rc = rtDbgModPeSegOffToRVA(pModPe, iSegment, off, &uRVA); if (RT_SUCCESS(rc)) { DWORD64 off; union { SYMBOL_INFO Sym; char achBuffer[sizeof(SYMBOL_INFO) + RTDBG_SYMBOL_MAX]; } Buf; Buf.Sym.SizeOfStruct = sizeof(SYMBOL_INFO); Buf.Sym.MaxNameLen = RTDBG_SYMBOL_MAX; if (g_pfnSymFromAddr(pModPe->hSymInst, pModPe->ImageBase + uRVA, &off, &Buf.Sym)) { pSym->cb = Buf.Sym.Size; pSym->fFlags = 0; if (Buf.Sym.Flags & SYMFLAG_FUNCTION) pSym->fFlags |= RTDBGSYM_FLAGS_CODE; else if (Buf.Sym.Flags & SYMFLAG_CONSTANT) pSym->fFlags |= RTDBGSYM_FLAGS_ABS; /** @todo SYMFLAG_CONSTANT must be tested - documentation is too brief to say what is really meant here.*/ else pSym->fFlags |= RTDBGSYM_FLAGS_DATA; if (Buf.Sym.Flags & SYMFLAG_EXPORT) pSym->fFlags |= RTDBGSYM_FLAGS_EXPORTED; if ((Buf.Sym.Flags & (SYMFLAG_VALUEPRESENT | SYMFLAG_CONSTANT)) == (SYMFLAG_VALUEPRESENT | SYMFLAG_CONSTANT)) { pSym->iSegment = RTDBGSEG_ABS; pSym->offSegment = (RTUINTPTR)Buf.Sym.Value; pSym->RVA = (RTUINTPTR)Buf.Sym.Value; } else { pSym->RVA = (RTUINTPTR)(Buf.Sym.Address - pModPe->ImageBase); rc = rtDbgModPeRVAToSegOff(pModPe, pSym->RVA, &pSym->iSegment, &pSym->offSegment); } pSym->cchName = (uint16_t)Buf.Sym.NameLen; if (pSym->cchName >= sizeof(pSym->szName)) pSym->cchName = sizeof(pSym->szName) - 1; memcpy(pSym->szName, Buf.Sym.Name, Buf.Sym.NameLen); pSym->szName[Buf.Sym.NameLen] = '\0'; } else { DWORD Err = GetLastError(); rc = RTErrConvertFromWin32(Err); } } return rc; } /** * @copydoc RTDBGMODOPS::pfnClose */ static DECLCALLBACK(int) rtDbgModPEClose(PRTDBGMOD pMod) { PRTDBGMODPE pModPe = (PRTDBGMODPE)pMod; if (g_pfnSymCleanup(pModPe->hSymInst)) return VINF_SUCCESS; DWORD Err = GetLastError(); int rc = RTErrConvertFromWin32(Err); AssertMsgFailed(("SymInitialize failed: Err=%d rc=%Rrc\n", Err, rc)); return rc; } /** * Methods for a PE module. */ static const RTDBGMODOPS g_rtDbgModPEOps = { "PE (dbghelp)", rtDbgModPEClose, rtDbgModPEQuerySymbol, rtDbgModPEQueryLine }; /** * Checks if the specified dbghelp.dll is usable. * * @returns IPRT status code. * * @param pszPath the path to the dbghelp.dll. */ static int rtDbgModPETryDbgHelp(const char *pszPath, uint32_t *pu32FileVersionMS, uint32_t *pu32FileVersionLS) { int rc; DWORD dwHandle = 0; DWORD cb = GetFileVersionInfoSize(pszPath, &dwHandle); if (cb > 0) { void *pvBuf = alloca(cb); if (GetFileVersionInfo(pszPath, dwHandle, cb, pvBuf)) { UINT cbValue = 0; VS_FIXEDFILEINFO *pFileInfo; if (VerQueryValue(pvBuf, "\\", (void **)&pFileInfo, &cbValue)) { if ( *pu32FileVersionMS < pFileInfo->dwFileVersionMS || ( *pu32FileVersionMS == pFileInfo->dwFileVersionMS && *pu32FileVersionLS > pFileInfo->dwFileVersionLS)) { *pu32FileVersionMS = pFileInfo->dwFileVersionMS; *pu32FileVersionLS = pFileInfo->dwFileVersionLS; } if (pFileInfo->dwFileVersionMS >= 0x60004) rc = VINF_SUCCESS; else rc = VERR_VERSION_MISMATCH; } else rc = VERR_GENERAL_FAILURE; } else rc = RTErrConvertFromWin32(GetLastError()); } else rc = RTErrConvertFromWin32(GetLastError()); return rc; } /** * Find the dbghelp.dll */ static int rtDbgModPEFindDbgHelp(char *pszPath, size_t cchPath) { /* * Try the current directory. */ uint32_t FileVersionMS = 0; uint32_t FileVersionLS = 0; int rc = VERR_GENERAL_FAILURE; static char s_szDbgHelp[] = "\\dbghelp.dll"; if (GetCurrentDirectory(cchPath - sizeof(s_szDbgHelp) + 1, pszPath)) { strcat(pszPath, s_szDbgHelp); int rc2 = rtDbgModPETryDbgHelp(pszPath, &FileVersionMS, &FileVersionLS); if (RT_SUCCESS(rc2)) return rc2; if (rc != VERR_VERSION_MISMATCH) rc = rc2; } /* * Try the application directory. */ if (GetModuleFileName(NULL, pszPath, cchPath - sizeof(s_szDbgHelp) + 1)) { strcat(strrchr(pszPath, '\\'), s_szDbgHelp); int rc2 = rtDbgModPETryDbgHelp(pszPath, &FileVersionMS, &FileVersionLS); if (RT_SUCCESS(rc)) return rc2; if (rc != VERR_VERSION_MISMATCH) rc = rc2; } /* * Try the windows directory. */ if (GetSystemDirectory(pszPath, cchPath - sizeof(s_szDbgHelp) + 1)) { strcat(pszPath, s_szDbgHelp); int rc2 = rtDbgModPETryDbgHelp(pszPath, &FileVersionMS, &FileVersionLS); if (RT_SUCCESS(rc2)) return rc2; if (rc != VERR_VERSION_MISMATCH) rc = rc2; } /* * Try the windows directory. */ if (GetWindowsDirectory(pszPath, cchPath - sizeof(s_szDbgHelp) + 1)) { strcat(pszPath, s_szDbgHelp); int rc2 = rtDbgModPETryDbgHelp(pszPath, &FileVersionMS, &FileVersionLS); if (RT_SUCCESS(rc2)) return rc2; if (rc != VERR_VERSION_MISMATCH) rc = rc2; } /* * Try the path. */ /** @todo find the actual path specs, I'm probably not doing this 100% correctly here. */ DWORD cb = GetEnvironmentVariable("PATH", NULL, 0) + 64; char *pszSearchPath = (char *) alloca(cb); if (GetEnvironmentVariable("PATH", pszSearchPath, cb) < cb) { char *psz = pszSearchPath; while (*psz) { /* find the end of the path. */ char *pszEnd = strchr(psz, ';'); if (!pszEnd) pszEnd = strchr(psz, '\0'); if (pszEnd != psz) { /* construct filename and try it out */ memcpy(pszPath, psz, pszEnd - psz); memcpy(&pszPath[pszEnd - psz], s_szDbgHelp, sizeof(s_szDbgHelp)); int rc2 = rtDbgModPETryDbgHelp(pszPath, &FileVersionMS, &FileVersionLS); if (RT_SUCCESS(rc2)) return rc2; if (rc != VERR_VERSION_MISMATCH) rc = rc2; } /* next path */ if (!*pszEnd) break; psz = pszEnd + 1; } } if (rc == VERR_VERSION_MISMATCH) AssertMsgFailed(("dbghelp.dll found, but it was ancient! The highest file version found was 0x%08x'%08x.\n" "This program require a file version of at least 0x00060004'00000000. Please download\n" "the latest windbg and use the dbghelp.dll from that package. Just put it somewhere in\n" "the PATH and we'll find it.\n", FileVersionMS, FileVersionLS)); else AssertMsgFailed(("dbghelp.dll was not found! Download the latest windbg and use the dbghelp.dll\n" "from that package - just put it somewhere in the PATH and we'll find it.\n")); return rc; } /** * Loads the dbghelp.dll, check that it's the right version, and * resolves all the symbols we need. * * @returns IPRT status code. */ static int rtDbgModPELoadDbgHelp(void) { if (g_hDbgHelp) return VINF_SUCCESS; /* primitive locking - make some useful API for this kind of spinning! */ static volatile uint32_t s_u32Lock = 0; while (!ASMAtomicCmpXchgU32(&s_u32Lock, 1, 0)) while (s_u32Lock) RTThreadYield(); if (g_hDbgHelp) { ASMAtomicXchgU32(&s_u32Lock, 0); return VINF_SUCCESS; } /* * Load it - try current dir first. */ char szPath[260]; int rc = rtDbgModPEFindDbgHelp(szPath, sizeof(szPath)); if (RT_FAILURE(rc)) { ASMAtomicXchgU32(&s_u32Lock, 0); return rc; } HMODULE hmod = LoadLibraryEx(szPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (!hmod) { DWORD Err = GetLastError(); int rc = RTErrConvertFromWin32(Err); AssertMsgFailedReturn(("Failed to load '%s', Err=%d rc=%Rrc\n", szPath, Err, rc), rc); } /* * Check the API version (too). */ LPAPI_VERSION (WINAPI *pfnImagehlpApiVersion)(VOID); FARPROC *ppfn = (FARPROC *)&pfnImagehlpApiVersion; *ppfn = GetProcAddress(hmod, "ImagehlpApiVersion"); if (*ppfn) { LPAPI_VERSION pVersion = pfnImagehlpApiVersion(); if ( pVersion && ( pVersion->MajorVersion > 4 || (pVersion->MajorVersion == 4 && pVersion->MinorVersion > 0) || (pVersion->MajorVersion == 4 && pVersion->MinorVersion == 0 && pVersion->Revision >= 5) ) ) { /* * Resolve the entrypoints we need. */ static const struct { const char *pszName; FARPROC *ppfn; } s_aFunctions[] = { { "SymInitialize", (FARPROC *)&g_pfnSymInitialize }, { "SymCleanup", (FARPROC *)&g_pfnSymCleanup }, { "SymSetOptions", (FARPROC *)&g_pfnSymSetOptions }, { "SymLoadModule64", (FARPROC *)&g_pfnSymLoadModule64 }, { "SymFromAddr", (FARPROC *)&g_pfnSymFromAddr }, { "SymFromAddr", (FARPROC *)&g_pfnSymFromAddr }, { "SymGetLineFromAddr64", (FARPROC *)&g_pfnSymGetLineFromAddr64 }, }; for (unsigned i = 0; i < ELEMENTS(s_aFunctions); i++) { FARPROC pfn = GetProcAddress(hmod, s_aFunctions[i].pszName); if (!pfn) { DWORD Err = GetLastError(); rc = RTErrConvertFromWin32(Err); AssertMsgFailed(("Failed to resolve %s in dbghelp, Err=%d rc=%Rrc\n", s_aFunctions[i].pszName, Err, rc)); break; } *s_aFunctions[i].ppfn = pfn; } if (RT_SUCCESS(rc)) { ASMAtomicXchgSize(&g_hDbgHelp, hmod); ASMAtomicXchgU32(&s_u32Lock, 0); return VINF_SUCCESS; } } else { rc = VERR_VERSION_MISMATCH; AssertMsgFailed(("ImagehlpApiVersion -> %p and MajorVersion=%d.\n", pVersion, pVersion ? pVersion->MajorVersion : 0)); } } else { DWORD Err = GetLastError(); rc = RTErrConvertFromWin32(Err); AssertMsgFailed(("Failed to resolve ImagehlpApiVersionEx in dbghelp, Err=%d rc=%Rrc\n", Err, rc)); } FreeLibrary(hmod); ASMAtomicXchgU32(&s_u32Lock, 0); return rc; } /** * Opens the debug info for a PE image using the windows dbghelp library. * * @returns IPRT status code. * * @param File The handle to the module. * @param offHdr The offset of the PE header. * @param pszModulePath The path to the module. * @param ppDbgMod Where to store the module handle. * */ int rtDbgModPEOpen(RTFILE File, RTFOFF offHdr, const char *pszModulePath, PRTDBGMOD *ppDbgMod) { /* * We need to read the section headers and get the image size. */ IMAGE_FILE_HEADER FHdr; int rc = RTFileReadAt(File, offHdr + KDBG_OFFSETOF(IMAGE_NT_HEADERS32, FileHeader), &FHdr, sizeof(FHdr), NULL); AssertRCReturn(rc, rc); uint32_t cbImage; if (FHdr.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32)) rc = RTFileReadAt(File, offHdr + KDBG_OFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.SizeOfImage), &cbImage, sizeof(cbImage), NULL); else if (FHdr.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER64)) rc = RTFileReadAt(File, offHdr + KDBG_OFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.SizeOfImage), &cbImage, sizeof(cbImage), NULL); else AssertFailedReturn(VERR_BAD_EXE_FORMAT); AssertRCReturn(rc, rc); /* * Load dbghelp.dll. */ rc = rtDbgModPELoadDbgHelp(); if (RT_FAILURE(rc)) return rc; /* * Allocate the module and read/construct the section headers. */ PRTDBGMODPE pModPe = (PRTDBGMODPE)RTMemAlloc(KDBG_OFFSETOF(RTDBGMODPE, aSections[FHdr.NumberOfSections + 2])); AssertReturn(pModPe, VERR_NO_MEMORY); pModPe->Core.u32Magic = RTDBGMOD_MAGIC; pModPe->Core.pOps = &g_rtDbgModPEOps; pModPe->Core.File = File; pModPe->cbImage = cbImage; pModPe->cSections = 1 + FHdr.NumberOfSections; rc = RTFileReadAt(File, offHdr + KDBG_OFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader) + FHdr.SizeOfOptionalHeader, &pModPe->aSections[1], sizeof(pModPe->aSections[0]) * FHdr.NumberOfSections, NULL); if (RT_SUCCESS(rc)) { PIMAGE_SECTION_HEADER pSH = &pModPe->aSections[0]; memcpy(pSH->Name, "headers", sizeof(pSH->Name)); pSH->Misc.VirtualSize = pModPe->aSections[1].VirtualAddress; pSH->VirtualAddress = 0; pSH->SizeOfRawData = pSH->Misc.VirtualSize; pSH->PointerToRawData = 0; pSH->PointerToRelocations = 0; pSH->PointerToLinenumbers = 0; pSH->NumberOfRelocations = 0; pSH->NumberOfLinenumbers = 0; pSH->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_ALIGN_4BYTES | IMAGE_SCN_MEM_READ; uint32_t uTheEnd = pModPe->aSections[FHdr.NumberOfSections].VirtualAddress + pModPe->aSections[FHdr.NumberOfSections].Misc.VirtualSize; if (uTheEnd < cbImage) { pSH = &pModPe->aSections[pModPe->cSections++]; memcpy(pSH->Name, "tail\0\0\0", sizeof(pSH->Name)); pSH->Misc.VirtualSize = cbImage - uTheEnd; pSH->VirtualAddress = uTheEnd; pSH->SizeOfRawData = pSH->Misc.VirtualSize; pSH->PointerToRawData = 0; pSH->PointerToRelocations = 0; pSH->PointerToLinenumbers = 0; pSH->NumberOfRelocations = 0; pSH->NumberOfLinenumbers = 0; pSH->Characteristics = IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_ALIGN_1BYTES | IMAGE_SCN_MEM_READ; } /* * Find a new dbghelp handle. * * We assume 4GB of handles outlast most debugging sessions, or in anyways that * when we start reusing handles they are no longer in use. :-) */ static volatile uint32_t s_u32LastHandle = 1; HANDLE hSymInst = (HANDLE)ASMAtomicIncU32(&s_u32LastHandle); while ( hSymInst == INVALID_HANDLE_VALUE || hSymInst == (HANDLE)0 || hSymInst == GetCurrentProcess()) hSymInst = (HANDLE)ASMAtomicIncU32(&s_u32LastHandle); /* * Initialize dbghelp and try open the specified module. */ if (g_pfnSymInitialize(hSymInst, NULL, FALSE)) { g_pfnSymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_AUTO_PUBLICS | SYMOPT_ALLOW_ABSOLUTE_SYMBOLS); RTFileSeek(File, 0, RTFILE_SEEK_BEGIN, NULL); /* don't know if this is required or not... */ DWORD64 ImageBase = g_pfnSymLoadModule64(hSymInst, (HANDLE)File, pszModulePath, NULL, 0x00400000, 0); if (ImageBase) { pModPe->hSymInst = hSymInst; pModPe->ImageBase = ImageBase; *ppDbgMod = &pModPe->Core; return rc; } DWORD Err = GetLastError(); rc = RTErrConvertFromWin32(Err); AssertMsgFailed(("SymLoadModule64 failed: Err=%d rc=%Rrc\n", Err, rc)); g_pfnSymCleanup(hSymInst); } else { DWORD Err = GetLastError(); rc = RTErrConvertFromWin32(Err); AssertMsgFailed(("SymInitialize failed: Err=%d rc=%Rrc\n", Err, rc)); } } else AssertRC(rc); RTMemFree(pModPe); return rc; }