/* $Id: DBGFAddrSpace.cpp 47825 2013-08-17 23:51:10Z vboxsync $ */ /** @file * DBGF - Debugger Facility, Address Space Management. */ /* * Copyright (C) 2008-2013 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. */ /** @page pg_dbgf_addr_space DBGFAddrSpace - Address Space Management * * What's an address space? It's mainly a convenient way of stuffing * module segments and ad-hoc symbols together. It will also help out * when the debugger gets extended to deal with user processes later. * * There are two standard address spaces that will always be present: * - The physical address space. * - The global virtual address space. * * Additional address spaces will be added and removed at runtime for * guest processes. The global virtual address space will be used to * track the kernel parts of the OS, or at least the bits of the kernel * that is part of all address spaces (mac os x and 4G/4G patched linux). * */ /******************************************************************************* * Header Files * *******************************************************************************/ #define LOG_GROUP LOG_GROUP_DBGF #include #include #include #include #ifdef VBOX_WITH_RAW_MODE # include #endif #include "DBGFInternal.h" #include #include #include #include #include #include #include #include #include #include #include /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** * Address space database node. */ typedef struct DBGFASDBNODE { /** The node core for DBGF::AsHandleTree, the key is the address space handle. */ AVLPVNODECORE HandleCore; /** The node core for DBGF::AsPidTree, the key is the process id. */ AVLU32NODECORE PidCore; /** The node core for DBGF::AsNameSpace, the string is the address space name. */ RTSTRSPACECORE NameCore; } DBGFASDBNODE; /** Pointer to an address space database node. */ typedef DBGFASDBNODE *PDBGFASDBNODE; /** * For dbgfR3AsLoadImageOpenData and dbgfR3AsLoadMapOpenData. */ typedef struct DBGFR3ASLOADOPENDATA { const char *pszModName; RTGCUINTPTR uSubtrahend; uint32_t fFlags; RTDBGMOD hMod; } DBGFR3ASLOADOPENDATA; /** * Callback for dbgfR3AsSearchPath and dbgfR3AsSearchEnvPath. * * @returns VBox status code. If success, then the search is completed. * @param pszFilename The file name under evaluation. * @param pvUser The user argument. */ typedef int FNDBGFR3ASSEARCHOPEN(const char *pszFilename, void *pvUser); /** Pointer to a FNDBGFR3ASSEARCHOPEN. */ typedef FNDBGFR3ASSEARCHOPEN *PFNDBGFR3ASSEARCHOPEN; /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** Locks the address space database for writing. */ #define DBGF_AS_DB_LOCK_WRITE(pUVM) \ do { \ int rcSem = RTSemRWRequestWrite((pUVM)->dbgf.s.hAsDbLock, RT_INDEFINITE_WAIT); \ AssertRC(rcSem); \ } while (0) /** Unlocks the address space database after writing. */ #define DBGF_AS_DB_UNLOCK_WRITE(pUVM) \ do { \ int rcSem = RTSemRWReleaseWrite((pUVM)->dbgf.s.hAsDbLock); \ AssertRC(rcSem); \ } while (0) /** Locks the address space database for reading. */ #define DBGF_AS_DB_LOCK_READ(pUVM) \ do { \ int rcSem = RTSemRWRequestRead((pUVM)->dbgf.s.hAsDbLock, RT_INDEFINITE_WAIT); \ AssertRC(rcSem); \ } while (0) /** Unlocks the address space database after reading. */ #define DBGF_AS_DB_UNLOCK_READ(pUVM) \ do { \ int rcSem = RTSemRWReleaseRead((pUVM)->dbgf.s.hAsDbLock); \ AssertRC(rcSem); \ } while (0) /** * Initializes the address space parts of DBGF. * * @returns VBox status code. * @param pUVM The user mode VM handle. */ int dbgfR3AsInit(PUVM pUVM) { Assert(pUVM->pVM); /* * Create the semaphore. */ int rc = RTSemRWCreate(&pUVM->dbgf.s.hAsDbLock); AssertRCReturn(rc, rc); /* * Create the debugging config instance and set it up, defaulting to * deferred loading in order to keep things fast. */ rc = RTDbgCfgCreate(&pUVM->dbgf.s.hDbgCfg, NULL, true /*fNativePaths*/); AssertRCReturn(rc, rc); rc = RTDbgCfgChangeUInt(pUVM->dbgf.s.hDbgCfg, RTDBGCFGPROP_FLAGS, RTDBGCFGOP_PREPEND, RTDBGCFG_FLAGS_DEFERRED); AssertRCReturn(rc, rc); static struct { RTDBGCFGPROP enmProp; const char *pszEnvName; const char *pszCfgName; } const s_aProps[] = { { RTDBGCFGPROP_FLAGS, "VBOXDBG_FLAGS", "Flags" }, { RTDBGCFGPROP_PATH, "VBOXDBG_PATH", "Path" }, { RTDBGCFGPROP_SUFFIXES, "VBOXDBG_SUFFIXES", "Suffixes" }, { RTDBGCFGPROP_SRC_PATH, "VBOXDBG_SRC_PATH", "SrcPath" }, }; PCFGMNODE pCfgDbgf = CFGMR3GetChild(CFGMR3GetRootU(pUVM), "/DBGF"); for (unsigned i = 0; i < RT_ELEMENTS(s_aProps); i++) { const char *pszEnvValue = RTEnvGet(s_aProps[i].pszEnvName); if (pszEnvValue) { rc = RTDbgCfgChangeString(pUVM->dbgf.s.hDbgCfg, s_aProps[i].enmProp, RTDBGCFGOP_PREPEND, pszEnvValue); if (RT_FAILURE(rc)) return VMR3SetError(pUVM, rc, RT_SRC_POS, "DBGF Config Error: %s=%s -> %Rrc", s_aProps[i].pszEnvName, pszEnvValue, rc); } char *pszCfgValue; rc = CFGMR3QueryStringAllocDef(pCfgDbgf, s_aProps[i].pszCfgName, &pszCfgValue, NULL); if (RT_FAILURE(rc)) return VMR3SetError(pUVM, rc, RT_SRC_POS, "DBGF Config Error: Querying /DBGF/%s -> %Rrc", s_aProps[i].pszCfgName, rc); if (pszCfgValue) { rc = RTDbgCfgChangeString(pUVM->dbgf.s.hDbgCfg, s_aProps[i].enmProp, RTDBGCFGOP_PREPEND, pszCfgValue); if (RT_FAILURE(rc)) return VMR3SetError(pUVM, rc, RT_SRC_POS, "DBGF Config Error: /DBGF/%s=%s -> %Rrc", s_aProps[i].pszCfgName, pszCfgValue, rc); } } /* * Prepend the NoArch and VBoxDbgSyms directories to the path. */ char szPath[RTPATH_MAX]; rc = RTPathAppPrivateNoArch(szPath, sizeof(szPath)); AssertRCReturn(rc, rc); #ifdef RT_OS_DARWIN rc = RTPathAppend(szPath, sizeof(szPath), "../Resources/VBoxDbgSyms/"); #else rc = RTDbgCfgChangeString(pUVM->dbgf.s.hDbgCfg, RTDBGCFGPROP_PATH, RTDBGCFGOP_PREPEND, szPath); AssertRCReturn(rc, rc); rc = RTPathAppend(szPath, sizeof(szPath), "VBoxDbgSyms/"); #endif AssertRCReturn(rc, rc); rc = RTDbgCfgChangeString(pUVM->dbgf.s.hDbgCfg, RTDBGCFGPROP_PATH, RTDBGCFGOP_PREPEND, szPath); AssertRCReturn(rc, rc); /* * Create the standard address spaces. */ RTDBGAS hDbgAs; rc = RTDbgAsCreate(&hDbgAs, 0, RTGCPTR_MAX, "Global"); AssertRCReturn(rc, rc); rc = DBGFR3AsAdd(pUVM, hDbgAs, NIL_RTPROCESS); AssertRCReturn(rc, rc); RTDbgAsRetain(hDbgAs); pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_GLOBAL)] = hDbgAs; RTDbgAsRetain(hDbgAs); pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_KERNEL)] = hDbgAs; rc = RTDbgAsCreate(&hDbgAs, 0, RTGCPHYS_MAX, "Physical"); AssertRCReturn(rc, rc); rc = DBGFR3AsAdd(pUVM, hDbgAs, NIL_RTPROCESS); AssertRCReturn(rc, rc); RTDbgAsRetain(hDbgAs); pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_PHYS)] = hDbgAs; rc = RTDbgAsCreate(&hDbgAs, 0, RTRCPTR_MAX, "HyperRawMode"); AssertRCReturn(rc, rc); rc = DBGFR3AsAdd(pUVM, hDbgAs, NIL_RTPROCESS); AssertRCReturn(rc, rc); RTDbgAsRetain(hDbgAs); pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_RC)] = hDbgAs; RTDbgAsRetain(hDbgAs); pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_RC_AND_GC_GLOBAL)] = hDbgAs; rc = RTDbgAsCreate(&hDbgAs, 0, RTR0PTR_MAX, "HyperRing0"); AssertRCReturn(rc, rc); rc = DBGFR3AsAdd(pUVM, hDbgAs, NIL_RTPROCESS); AssertRCReturn(rc, rc); RTDbgAsRetain(hDbgAs); pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_R0)] = hDbgAs; return VINF_SUCCESS; } /** * Callback used by dbgfR3AsTerm / RTAvlPVDestroy to release an address space. * * @returns 0. * @param pNode The address space database node. * @param pvIgnore NULL. */ static DECLCALLBACK(int) dbgfR3AsTermDestroyNode(PAVLPVNODECORE pNode, void *pvIgnore) { PDBGFASDBNODE pDbNode = (PDBGFASDBNODE)pNode; RTDbgAsRelease((RTDBGAS)pDbNode->HandleCore.Key); pDbNode->HandleCore.Key = NIL_RTDBGAS; /* Don't bother freeing it here as MM will free it soon and MM is much at it when doing it wholesale instead of piecemeal. */ NOREF(pvIgnore); return 0; } /** * Terminates the address space parts of DBGF. * * @param pUVM The user mode VM handle. */ void dbgfR3AsTerm(PUVM pUVM) { /* * Create the semaphore. */ int rc = RTSemRWDestroy(pUVM->dbgf.s.hAsDbLock); AssertRC(rc); pUVM->dbgf.s.hAsDbLock = NIL_RTSEMRW; /* * Release all the address spaces. */ RTAvlPVDestroy(&pUVM->dbgf.s.AsHandleTree, dbgfR3AsTermDestroyNode, NULL); for (size_t i = 0; i < RT_ELEMENTS(pUVM->dbgf.s.ahAsAliases); i++) { RTDbgAsRelease(pUVM->dbgf.s.ahAsAliases[i]); pUVM->dbgf.s.ahAsAliases[i] = NIL_RTDBGAS; } } /** * Relocates the RC address space. * * @param pUVM The user mode VM handle. * @param offDelta The relocation delta. */ void dbgfR3AsRelocate(PUVM pUVM, RTGCUINTPTR offDelta) { /* * We will relocate the raw-mode context modules by offDelta if they have * been injected into the the DBGF_AS_RC map. */ if ( pUVM->dbgf.s.afAsAliasPopuplated[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_RC)] && offDelta != 0) { RTDBGAS hAs = pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(DBGF_AS_RC)]; /* Take a snapshot of the modules as we might have overlapping addresses between the previous and new mapping. */ RTDbgAsLockExcl(hAs); uint32_t cModules = RTDbgAsModuleCount(hAs); if (cModules > 0 && cModules < _4K) { struct DBGFASRELOCENTRY { RTDBGMOD hDbgMod; RTRCPTR uOldAddr; } *paEntries = (struct DBGFASRELOCENTRY *)RTMemTmpAllocZ(sizeof(paEntries[0]) * cModules); if (paEntries) { /* Snapshot. */ for (uint32_t i = 0; i < cModules; i++) { paEntries[i].hDbgMod = RTDbgAsModuleByIndex(hAs, i); AssertLogRelMsg(paEntries[i].hDbgMod != NIL_RTDBGMOD, ("iModule=%#x\n", i)); RTDBGASMAPINFO aMappings[1] = { { 0, 0 } }; uint32_t cMappings = 1; int rc = RTDbgAsModuleQueryMapByIndex(hAs, i, &aMappings[0], &cMappings, 0 /*fFlags*/); if (RT_SUCCESS(rc) && cMappings == 1 && aMappings[0].iSeg == NIL_RTDBGSEGIDX) paEntries[i].uOldAddr = (RTRCPTR)aMappings[0].Address; else AssertLogRelMsgFailed(("iModule=%#x rc=%Rrc cMappings=%#x.\n", i, rc, cMappings)); } /* Unlink them. */ for (uint32_t i = 0; i < cModules; i++) { int rc = RTDbgAsModuleUnlink(hAs, paEntries[i].hDbgMod); AssertLogRelMsg(RT_SUCCESS(rc), ("iModule=%#x rc=%Rrc hDbgMod=%p\n", i, rc, paEntries[i].hDbgMod)); } /* Link them at the new locations. */ for (uint32_t i = 0; i < cModules; i++) { RTRCPTR uNewAddr = paEntries[i].uOldAddr + offDelta; int rc = RTDbgAsModuleLink(hAs, paEntries[i].hDbgMod, uNewAddr, RTDBGASLINK_FLAGS_REPLACE); AssertLogRelMsg(RT_SUCCESS(rc), ("iModule=%#x rc=%Rrc hDbgMod=%p %RRv -> %RRv\n", i, rc, paEntries[i].hDbgMod, paEntries[i].uOldAddr, uNewAddr)); RTDbgModRelease(paEntries[i].hDbgMod); } RTMemTmpFree(paEntries); } else AssertLogRelMsgFailed(("No memory for %#x modules.\n", cModules)); } else AssertLogRelMsgFailed(("cModules=%#x\n", cModules)); RTDbgAsUnlockExcl(hAs); } } /** * Gets the IPRT debugging configuration handle (no refs retained). * * @returns Config handle or NIL_RTDBGCFG. * @param pUVM The user mode VM handle. */ VMMR3DECL(RTDBGCFG) DBGFR3AsGetConfig(PUVM pUVM) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, NIL_RTDBGCFG); return pUVM->dbgf.s.hDbgCfg; } /** * Adds the address space to the database. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param hDbgAs The address space handle. The reference of the caller * will NOT be consumed. * @param ProcId The process id or NIL_RTPROCESS. */ VMMR3DECL(int) DBGFR3AsAdd(PUVM pUVM, RTDBGAS hDbgAs, RTPROCESS ProcId) { /* * Input validation. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); const char *pszName = RTDbgAsName(hDbgAs); if (!pszName) return VERR_INVALID_HANDLE; uint32_t cRefs = RTDbgAsRetain(hDbgAs); if (cRefs == UINT32_MAX) return VERR_INVALID_HANDLE; /* * Allocate a tracking node. */ int rc = VERR_NO_MEMORY; PDBGFASDBNODE pDbNode = (PDBGFASDBNODE)MMR3HeapAllocU(pUVM, MM_TAG_DBGF_AS, sizeof(*pDbNode)); if (pDbNode) { pDbNode->HandleCore.Key = hDbgAs; pDbNode->PidCore.Key = ProcId; pDbNode->NameCore.pszString = pszName; pDbNode->NameCore.cchString = strlen(pszName); DBGF_AS_DB_LOCK_WRITE(pUVM); if (RTStrSpaceInsert(&pUVM->dbgf.s.AsNameSpace, &pDbNode->NameCore)) { if (RTAvlPVInsert(&pUVM->dbgf.s.AsHandleTree, &pDbNode->HandleCore)) { DBGF_AS_DB_UNLOCK_WRITE(pUVM); return VINF_SUCCESS; } /* bail out */ RTStrSpaceRemove(&pUVM->dbgf.s.AsNameSpace, pszName); } DBGF_AS_DB_UNLOCK_WRITE(pUVM); MMR3HeapFree(pDbNode); } RTDbgAsRelease(hDbgAs); return rc; } /** * Delete an address space from the database. * * The address space must not be engaged as any of the standard aliases. * * @returns VBox status code. * @retval VERR_SHARING_VIOLATION if in use as an alias. * @retval VERR_NOT_FOUND if not found in the address space database. * * @param pUVM The user mode VM handle. * @param hDbgAs The address space handle. Aliases are not allowed. */ VMMR3DECL(int) DBGFR3AsDelete(PUVM pUVM, RTDBGAS hDbgAs) { /* * Input validation. Retain the address space so it can be released outside * the lock as well as validated. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); if (hDbgAs == NIL_RTDBGAS) return VINF_SUCCESS; uint32_t cRefs = RTDbgAsRetain(hDbgAs); if (cRefs == UINT32_MAX) return VERR_INVALID_HANDLE; RTDbgAsRelease(hDbgAs); DBGF_AS_DB_LOCK_WRITE(pUVM); /* * You cannot delete any of the aliases. */ for (size_t i = 0; i < RT_ELEMENTS(pUVM->dbgf.s.ahAsAliases); i++) if (pUVM->dbgf.s.ahAsAliases[i] == hDbgAs) { DBGF_AS_DB_UNLOCK_WRITE(pUVM); return VERR_SHARING_VIOLATION; } /* * Ok, try remove it from the database. */ PDBGFASDBNODE pDbNode = (PDBGFASDBNODE)RTAvlPVRemove(&pUVM->dbgf.s.AsHandleTree, hDbgAs); if (!pDbNode) { DBGF_AS_DB_UNLOCK_WRITE(pUVM); return VERR_NOT_FOUND; } RTStrSpaceRemove(&pUVM->dbgf.s.AsNameSpace, pDbNode->NameCore.pszString); if (pDbNode->PidCore.Key != NIL_RTPROCESS) RTAvlU32Remove(&pUVM->dbgf.s.AsPidTree, pDbNode->PidCore.Key); DBGF_AS_DB_UNLOCK_WRITE(pUVM); /* * Free the resources. */ RTDbgAsRelease(hDbgAs); MMR3HeapFree(pDbNode); return VINF_SUCCESS; } /** * Changes an alias to point to a new address space. * * Not all the aliases can be changed, currently it's only DBGF_AS_GLOBAL * and DBGF_AS_KERNEL. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param hAlias The alias to change. * @param hAliasFor The address space hAlias should be an alias for. This * can be an alias. The caller's reference to this address * space will NOT be consumed. */ VMMR3DECL(int) DBGFR3AsSetAlias(PUVM pUVM, RTDBGAS hAlias, RTDBGAS hAliasFor) { /* * Input validation. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertMsgReturn(DBGF_AS_IS_ALIAS(hAlias), ("%p\n", hAlias), VERR_INVALID_PARAMETER); AssertMsgReturn(!DBGF_AS_IS_FIXED_ALIAS(hAlias), ("%p\n", hAlias), VERR_INVALID_PARAMETER); RTDBGAS hRealAliasFor = DBGFR3AsResolveAndRetain(pUVM, hAliasFor); if (hRealAliasFor == NIL_RTDBGAS) return VERR_INVALID_HANDLE; /* * Make sure the handle has is already in the database. */ int rc = VERR_NOT_FOUND; DBGF_AS_DB_LOCK_WRITE(pUVM); if (RTAvlPVGet(&pUVM->dbgf.s.AsHandleTree, hRealAliasFor)) { /* * Update the alias table and release the current address space. */ RTDBGAS hAsOld; ASMAtomicXchgHandle(&pUVM->dbgf.s.ahAsAliases[DBGF_AS_ALIAS_2_INDEX(hAlias)], hRealAliasFor, &hAsOld); uint32_t cRefs = RTDbgAsRelease(hAsOld); Assert(cRefs > 0); Assert(cRefs != UINT32_MAX); NOREF(cRefs); rc = VINF_SUCCESS; } DBGF_AS_DB_UNLOCK_WRITE(pUVM); return rc; } /** * @callback_method_impl{FNPDMR3ENUM} */ static DECLCALLBACK(int) dbgfR3AsLazyPopulateR0Callback(PVM pVM, const char *pszFilename, const char *pszName, RTUINTPTR ImageBase, size_t cbImage, PDMLDRCTX enmCtx, void *pvArg) { NOREF(pVM); NOREF(cbImage); /* Only ring-0 modules. */ if (enmCtx == PDMLDRCTX_RING_0) { RTDBGMOD hDbgMod; int rc = RTDbgModCreateFromImage(&hDbgMod, pszFilename, pszName, RTLDRARCH_HOST, pVM->pUVM->dbgf.s.hDbgCfg); if (RT_SUCCESS(rc)) { rc = RTDbgAsModuleLink((RTDBGAS)pvArg, hDbgMod, ImageBase, 0 /*fFlags*/); if (RT_FAILURE(rc)) LogRel(("DBGF: Failed to link module \"%s\" into DBGF_AS_R0 at %RTptr: %Rrc\n", pszName, ImageBase, rc)); } else LogRel(("DBGF: RTDbgModCreateFromImage failed with rc=%Rrc for module \"%s\" (%s)\n", rc, pszName, pszFilename)); } return VINF_SUCCESS; } /** * @callback_method_impl{FNPDMR3ENUM} */ static DECLCALLBACK(int) dbgfR3AsLazyPopulateRCCallback(PVM pVM, const char *pszFilename, const char *pszName, RTUINTPTR ImageBase, size_t cbImage, PDMLDRCTX enmCtx, void *pvArg) { NOREF(pVM); NOREF(cbImage); /* Only raw-mode modules. */ if (enmCtx == PDMLDRCTX_RAW_MODE) { RTDBGMOD hDbgMod; int rc = RTDbgModCreateFromImage(&hDbgMod, pszFilename, pszName, RTLDRARCH_X86_32, pVM->pUVM->dbgf.s.hDbgCfg); if (RT_SUCCESS(rc)) { rc = RTDbgAsModuleLink((RTDBGAS)pvArg, hDbgMod, ImageBase, 0 /*fFlags*/); if (RT_FAILURE(rc)) LogRel(("DBGF: Failed to link module \"%s\" into DBGF_AS_RC at %RTptr: %Rrc\n", pszName, ImageBase, rc)); } else LogRel(("DBGF: RTDbgModCreateFromImage failed with rc=%Rrc for module \"%s\" (%s)\n", rc, pszName, pszFilename)); } return VINF_SUCCESS; } /** * Lazily populates the specified address space. * * @param pUVM The user mode VM handle. * @param hAlias The alias. */ static void dbgfR3AsLazyPopulate(PUVM pUVM, RTDBGAS hAlias) { DBGF_AS_DB_LOCK_WRITE(pUVM); uintptr_t iAlias = DBGF_AS_ALIAS_2_INDEX(hAlias); if (!pUVM->dbgf.s.afAsAliasPopuplated[iAlias]) { RTDBGAS hDbgAs = pUVM->dbgf.s.ahAsAliases[iAlias]; if (hAlias == DBGF_AS_R0 && pUVM->pVM) PDMR3LdrEnumModules(pUVM->pVM, dbgfR3AsLazyPopulateR0Callback, hDbgAs); else if (hAlias == DBGF_AS_RC && pUVM->pVM && !HMIsEnabled(pUVM->pVM)) { LogRel(("DBGF: Lazy init of RC address space\n")); PDMR3LdrEnumModules(pUVM->pVM, dbgfR3AsLazyPopulateRCCallback, hDbgAs); #ifdef VBOX_WITH_RAW_MODE PATMR3DbgPopulateAddrSpace(pUVM->pVM, hDbgAs); #endif } else if (hAlias == DBGF_AS_PHYS && pUVM->pVM) { /** @todo Lazy load pc and vga bios symbols or the EFI stuff. */ } pUVM->dbgf.s.afAsAliasPopuplated[iAlias] = true; } DBGF_AS_DB_UNLOCK_WRITE(pUVM); } /** * Resolves the address space handle into a real handle if it's an alias. * * @returns Real address space handle. NIL_RTDBGAS if invalid handle. * * @param pUVM The user mode VM handle. * @param hAlias The possibly address space alias. * * @remarks Doesn't take any locks. */ VMMR3DECL(RTDBGAS) DBGFR3AsResolve(PUVM pUVM, RTDBGAS hAlias) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); AssertCompileNS(NIL_RTDBGAS == (RTDBGAS)0); uintptr_t iAlias = DBGF_AS_ALIAS_2_INDEX(hAlias); if (iAlias < DBGF_AS_COUNT) ASMAtomicReadHandle(&pUVM->dbgf.s.ahAsAliases[iAlias], &hAlias); return hAlias; } /** * Resolves the address space handle into a real handle if it's an alias, * and retains whatever it is. * * @returns Real address space handle. NIL_RTDBGAS if invalid handle. * * @param pUVM The user mode VM handle. * @param hAlias The possibly address space alias. */ VMMR3DECL(RTDBGAS) DBGFR3AsResolveAndRetain(PUVM pUVM, RTDBGAS hAlias) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); AssertCompileNS(NIL_RTDBGAS == (RTDBGAS)0); uint32_t cRefs; uintptr_t iAlias = DBGF_AS_ALIAS_2_INDEX(hAlias); if (iAlias < DBGF_AS_COUNT) { if (DBGF_AS_IS_FIXED_ALIAS(hAlias)) { /* Perform lazy address space population. */ if (!pUVM->dbgf.s.afAsAliasPopuplated[iAlias]) dbgfR3AsLazyPopulate(pUVM, hAlias); /* Won't ever change, no need to grab the lock. */ hAlias = pUVM->dbgf.s.ahAsAliases[iAlias]; cRefs = RTDbgAsRetain(hAlias); } else { /* May change, grab the lock so we can read it safely. */ DBGF_AS_DB_LOCK_READ(pUVM); hAlias = pUVM->dbgf.s.ahAsAliases[iAlias]; cRefs = RTDbgAsRetain(hAlias); DBGF_AS_DB_UNLOCK_READ(pUVM); } } else /* Not an alias, just retain it. */ cRefs = RTDbgAsRetain(hAlias); return cRefs != UINT32_MAX ? hAlias : NIL_RTDBGAS; } /** * Query an address space by name. * * @returns Retained address space handle if found, NIL_RTDBGAS if not. * * @param pUVM The user mode VM handle. * @param pszName The name. */ VMMR3DECL(RTDBGAS) DBGFR3AsQueryByName(PUVM pUVM, const char *pszName) { /* * Validate the input. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, NIL_RTDBGAS); AssertPtrReturn(pszName, NIL_RTDBGAS); AssertReturn(*pszName, NIL_RTDBGAS); /* * Look it up in the string space and retain the result. */ RTDBGAS hDbgAs = NIL_RTDBGAS; DBGF_AS_DB_LOCK_READ(pUVM); PRTSTRSPACECORE pNode = RTStrSpaceGet(&pUVM->dbgf.s.AsNameSpace, pszName); if (pNode) { PDBGFASDBNODE pDbNode = RT_FROM_MEMBER(pNode, DBGFASDBNODE, NameCore); hDbgAs = (RTDBGAS)pDbNode->HandleCore.Key; uint32_t cRefs = RTDbgAsRetain(hDbgAs); if (RT_UNLIKELY(cRefs == UINT32_MAX)) hDbgAs = NIL_RTDBGAS; } DBGF_AS_DB_UNLOCK_READ(pUVM); return hDbgAs; } /** * Query an address space by process ID. * * @returns Retained address space handle if found, NIL_RTDBGAS if not. * * @param pUVM The user mode VM handle. * @param ProcId The process ID. */ VMMR3DECL(RTDBGAS) DBGFR3AsQueryByPid(PUVM pUVM, RTPROCESS ProcId) { /* * Validate the input. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, NIL_RTDBGAS); AssertReturn(ProcId != NIL_RTPROCESS, NIL_RTDBGAS); /* * Look it up in the PID tree and retain the result. */ RTDBGAS hDbgAs = NIL_RTDBGAS; DBGF_AS_DB_LOCK_READ(pUVM); PAVLU32NODECORE pNode = RTAvlU32Get(&pUVM->dbgf.s.AsPidTree, ProcId); if (pNode) { PDBGFASDBNODE pDbNode = RT_FROM_MEMBER(pNode, DBGFASDBNODE, PidCore); hDbgAs = (RTDBGAS)pDbNode->HandleCore.Key; uint32_t cRefs = RTDbgAsRetain(hDbgAs); if (RT_UNLIKELY(cRefs == UINT32_MAX)) hDbgAs = NIL_RTDBGAS; } DBGF_AS_DB_UNLOCK_READ(pUVM); return hDbgAs; } /** * Searches for the file in the path. * * The file is first tested without any path modification, then we walk the path * looking in each directory. * * @returns VBox status code. * @param pszFilename The file to search for. * @param pszPath The search path. * @param pfnOpen The open callback function. * @param pvUser User argument for the callback. */ static int dbgfR3AsSearchPath(const char *pszFilename, const char *pszPath, PFNDBGFR3ASSEARCHOPEN pfnOpen, void *pvUser) { char szFound[RTPATH_MAX]; /* Check the filename length. */ size_t const cchFilename = strlen(pszFilename); if (cchFilename >= sizeof(szFound)) return VERR_FILENAME_TOO_LONG; const char *pszName = RTPathFilename(pszFilename); if (!pszName) return VERR_IS_A_DIRECTORY; size_t const cchName = strlen(pszName); /* * Try default location first. */ memcpy(szFound, pszFilename, cchFilename + 1); int rc = pfnOpen(szFound, pvUser); if (RT_SUCCESS(rc)) return rc; /* * Walk the search path. */ const char *psz = pszPath; while (*psz) { /* Skip leading blanks - no directories with leading spaces, thank you. */ while (RT_C_IS_BLANK(*psz)) psz++; /* Find the end of this element. */ const char *pszNext; const char *pszEnd = strchr(psz, ';'); if (!pszEnd) pszEnd = pszNext = strchr(psz, '\0'); else pszNext = pszEnd + 1; if (pszEnd != psz) { size_t const cch = pszEnd - psz; if (cch + 1 + cchName < sizeof(szFound)) { /** @todo RTPathCompose, RTPathComposeN(). This code isn't right * for 'E:' on DOS systems. It may also create unwanted double slashes. */ memcpy(szFound, psz, cch); szFound[cch] = '/'; memcpy(szFound + cch + 1, pszName, cchName + 1); int rc2 = pfnOpen(szFound, pvUser); if (RT_SUCCESS(rc2)) return rc2; if ( rc2 != rc && ( rc == VERR_FILE_NOT_FOUND || rc == VERR_OPEN_FAILED)) rc = rc2; } } /* advance */ psz = pszNext; } /* * Walk the path once again, this time do a depth search. */ /** @todo do a depth search using the specified path. */ /* failed */ return rc; } /** * Same as dbgfR3AsSearchEnv, except that the path is taken from the environment. * * If the environment variable doesn't exist, the current directory is searched * instead. * * @returns VBox status code. * @param pszFilename The filename. * @param pszEnvVar The environment variable name. * @param pfnOpen The open callback function. * @param pvUser User argument for the callback. */ static int dbgfR3AsSearchEnvPath(const char *pszFilename, const char *pszEnvVar, PFNDBGFR3ASSEARCHOPEN pfnOpen, void *pvUser) { int rc; char *pszPath = RTEnvDupEx(RTENV_DEFAULT, pszEnvVar); if (pszPath) { rc = dbgfR3AsSearchPath(pszFilename, pszPath, pfnOpen, pvUser); RTStrFree(pszPath); } else rc = dbgfR3AsSearchPath(pszFilename, ".", pfnOpen, pvUser); return rc; } /** * Same as dbgfR3AsSearchEnv, except that the path is taken from the DBGF config * (CFGM). * * Nothing is done if the CFGM variable isn't set. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param pszFilename The filename. * @param pszCfgValue The name of the config variable (under /DBGF/). * @param pfnOpen The open callback function. * @param pvUser User argument for the callback. */ static int dbgfR3AsSearchCfgPath(PUVM pUVM, const char *pszFilename, const char *pszCfgValue, PFNDBGFR3ASSEARCHOPEN pfnOpen, void *pvUser) { char *pszPath; int rc = CFGMR3QueryStringAllocDef(CFGMR3GetChild(CFGMR3GetRootU(pUVM), "/DBGF"), pszCfgValue, &pszPath, NULL); if (RT_FAILURE(rc)) return rc; if (!pszPath) return VERR_FILE_NOT_FOUND; rc = dbgfR3AsSearchPath(pszFilename, pszPath, pfnOpen, pvUser); MMR3HeapFree(pszPath); return rc; } /** * Load symbols from an executable module into the specified address space. * * If an module exist at the specified address it will be replaced by this * call, otherwise a new module is created. * * @returns VBox status code. * * @param pUVM The user mode VM handle. * @param hDbgAs The address space. * @param pszFilename The filename of the executable module. * @param pszModName The module name. If NULL, then then the file name * base is used (no extension or nothing). * @param pModAddress The load address of the module. * @param iModSeg The segment to load, pass NIL_RTDBGSEGIDX to load * the whole image. * @param fFlags Flags reserved for future extensions, must be 0. */ VMMR3DECL(int) DBGFR3AsLoadImage(PUVM pUVM, RTDBGAS hDbgAs, const char *pszFilename, const char *pszModName, PCDBGFADDRESS pModAddress, RTDBGSEGIDX iModSeg, uint32_t fFlags) { /* * Validate input */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); AssertReturn(*pszFilename, VERR_INVALID_PARAMETER); AssertReturn(DBGFR3AddrIsValid(pUVM, pModAddress), VERR_INVALID_PARAMETER); AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); RTDBGAS hRealAS = DBGFR3AsResolveAndRetain(pUVM, hDbgAs); if (hRealAS == NIL_RTDBGAS) return VERR_INVALID_HANDLE; RTDBGMOD hDbgMod; int rc = RTDbgModCreateFromImage(&hDbgMod, pszFilename, pszModName, RTLDRARCH_WHATEVER, pUVM->dbgf.s.hDbgCfg); if (RT_SUCCESS(rc)) { rc = DBGFR3AsLinkModule(pUVM, hRealAS, hDbgMod, pModAddress, iModSeg, 0); if (RT_FAILURE(rc)) RTDbgModRelease(hDbgMod); } RTDbgAsRelease(hRealAS); return rc; } /** * Load symbols from a map file into a module at the specified address space. * * If an module exist at the specified address it will be replaced by this * call, otherwise a new module is created. * * @returns VBox status code. * * @param pUVM The user mode VM handle. * @param hDbgAs The address space. * @param pszFilename The map file. * @param pszModName The module name. If NULL, then then the file name * base is used (no extension or nothing). * @param pModAddress The load address of the module. * @param iModSeg The segment to load, pass NIL_RTDBGSEGIDX to load * the whole image. * @param uSubtrahend Value to to subtract from the symbols in the map * file. This is useful for the linux System.map and * /proc/kallsyms. * @param fFlags Flags reserved for future extensions, must be 0. */ VMMR3DECL(int) DBGFR3AsLoadMap(PUVM pUVM, RTDBGAS hDbgAs, const char *pszFilename, const char *pszModName, PCDBGFADDRESS pModAddress, RTDBGSEGIDX iModSeg, RTGCUINTPTR uSubtrahend, uint32_t fFlags) { /* * Validate input */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); AssertReturn(*pszFilename, VERR_INVALID_PARAMETER); AssertReturn(DBGFR3AddrIsValid(pUVM, pModAddress), VERR_INVALID_PARAMETER); AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); RTDBGAS hRealAS = DBGFR3AsResolveAndRetain(pUVM, hDbgAs); if (hRealAS == NIL_RTDBGAS) return VERR_INVALID_HANDLE; RTDBGMOD hDbgMod; int rc = RTDbgModCreateFromMap(&hDbgMod, pszFilename, pszModName, uSubtrahend, pUVM->dbgf.s.hDbgCfg); if (RT_SUCCESS(rc)) { rc = DBGFR3AsLinkModule(pUVM, hRealAS, hDbgMod, pModAddress, iModSeg, 0); if (RT_FAILURE(rc)) RTDbgModRelease(hDbgMod); } RTDbgAsRelease(hRealAS); return rc; } /** * Wrapper around RTDbgAsModuleLink, RTDbgAsModuleLinkSeg and DBGFR3AsResolve. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param hDbgAs The address space handle. * @param hMod The module handle. * @param pModAddress The link address. * @param iModSeg The segment to link, NIL_RTDBGSEGIDX for the entire image. * @param fFlags Flags to pass to the link functions, see RTDBGASLINK_FLAGS_*. */ VMMR3DECL(int) DBGFR3AsLinkModule(PUVM pUVM, RTDBGAS hDbgAs, RTDBGMOD hMod, PCDBGFADDRESS pModAddress, RTDBGSEGIDX iModSeg, uint32_t fFlags) { /* * Input validation. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertReturn(DBGFR3AddrIsValid(pUVM, pModAddress), VERR_INVALID_PARAMETER); RTDBGAS hRealAS = DBGFR3AsResolveAndRetain(pUVM, hDbgAs); if (hRealAS == NIL_RTDBGAS) return VERR_INVALID_HANDLE; /* * Do the job. */ int rc; if (iModSeg == NIL_RTDBGSEGIDX) rc = RTDbgAsModuleLink(hRealAS, hMod, pModAddress->FlatPtr, fFlags); else rc = RTDbgAsModuleLinkSeg(hRealAS, hMod, iModSeg, pModAddress->FlatPtr, fFlags); RTDbgAsRelease(hRealAS); return rc; } /** * Adds the module name to the symbol name. * * @param pSymbol The symbol info (in/out). * @param hMod The module handle. */ static void dbgfR3AsSymbolJoinNames(PRTDBGSYMBOL pSymbol, RTDBGMOD hMod) { /* Figure the lengths, adjust them if the result is too long. */ const char *pszModName = RTDbgModName(hMod); size_t cchModName = strlen(pszModName); size_t cchSymbol = strlen(pSymbol->szName); if (cchModName + 1 + cchSymbol >= sizeof(pSymbol->szName)) { if (cchModName >= sizeof(pSymbol->szName) / 4) cchModName = sizeof(pSymbol->szName) / 4; if (cchModName + 1 + cchSymbol >= sizeof(pSymbol->szName)) cchSymbol = sizeof(pSymbol->szName) - cchModName - 2; Assert(cchModName + 1 + cchSymbol < sizeof(pSymbol->szName)); } /* Do the moving and copying. */ memmove(&pSymbol->szName[cchModName + 1], &pSymbol->szName[0], cchSymbol + 1); memcpy(&pSymbol->szName[0], pszModName, cchModName); pSymbol->szName[cchModName] = '!'; } /** Temporary symbol conversion function. */ static void dbgfR3AsSymbolConvert(PRTDBGSYMBOL pSymbol, PCDBGFSYMBOL pDbgfSym) { pSymbol->offSeg = pSymbol->Value = pDbgfSym->Value; pSymbol->cb = pDbgfSym->cb; pSymbol->iSeg = 0; pSymbol->fFlags = 0; pSymbol->iOrdinal = UINT32_MAX; strcpy(pSymbol->szName, pDbgfSym->szName); } /** * Query a symbol by address. * * The returned symbol is the one we consider closes to the specified address. * * @returns VBox status code. See RTDbgAsSymbolByAddr. * * @param pUVM The user mode VM handle. * @param hDbgAs The address space handle. * @param pAddress The address to lookup. * @param fFlags One of the RTDBGSYMADDR_FLAGS_XXX flags. * @param poffDisp Where to return the distance between the returned * symbol and pAddress. Optional. * @param pSymbol Where to return the symbol information. The returned * symbol name will be prefixed by the module name as * far as space allows. * @param phMod Where to return the module handle. Optional. */ VMMR3DECL(int) DBGFR3AsSymbolByAddr(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress, uint32_t fFlags, PRTGCINTPTR poffDisp, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod) { /* * Implement the special address space aliases the lazy way. */ if (hDbgAs == DBGF_AS_RC_AND_GC_GLOBAL) { int rc = DBGFR3AsSymbolByAddr(pUVM, DBGF_AS_RC, pAddress, fFlags, poffDisp, pSymbol, phMod); if (RT_FAILURE(rc)) rc = DBGFR3AsSymbolByAddr(pUVM, DBGF_AS_GLOBAL, pAddress, fFlags, poffDisp, pSymbol, phMod); return rc; } /* * Input validation. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertReturn(DBGFR3AddrIsValid(pUVM, pAddress), VERR_INVALID_PARAMETER); AssertPtrNullReturn(poffDisp, VERR_INVALID_POINTER); AssertPtrReturn(pSymbol, VERR_INVALID_POINTER); AssertPtrNullReturn(phMod, VERR_INVALID_POINTER); if (poffDisp) *poffDisp = 0; if (phMod) *phMod = NIL_RTDBGMOD; RTDBGAS hRealAS = DBGFR3AsResolveAndRetain(pUVM, hDbgAs); if (hRealAS == NIL_RTDBGAS) return VERR_INVALID_HANDLE; /* * Do the lookup. */ RTDBGMOD hMod; int rc = RTDbgAsSymbolByAddr(hRealAS, pAddress->FlatPtr, fFlags, poffDisp, pSymbol, &hMod); if (RT_SUCCESS(rc)) { dbgfR3AsSymbolJoinNames(pSymbol, hMod); if (!phMod) RTDbgModRelease(hMod); } return rc; } /** * Convenience function that combines RTDbgSymbolDup and DBGFR3AsSymbolByAddr. * * @returns Pointer to the symbol on success. This must be free using * RTDbgSymbolFree(). NULL is returned if not found or any error * occurs. * * @param pUVM The user mode VM handle. * @param hDbgAs See DBGFR3AsSymbolByAddr. * @param pAddress See DBGFR3AsSymbolByAddr. * @param fFlags See DBGFR3AsSymbolByAddr. * @param poffDisp See DBGFR3AsSymbolByAddr. * @param phMod See DBGFR3AsSymbolByAddr. */ VMMR3DECL(PRTDBGSYMBOL) DBGFR3AsSymbolByAddrA(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress, uint32_t fFlags, PRTGCINTPTR poffDisp, PRTDBGMOD phMod) { RTDBGSYMBOL SymInfo; int rc = DBGFR3AsSymbolByAddr(pUVM, hDbgAs, pAddress, fFlags, poffDisp, &SymInfo, phMod); if (RT_SUCCESS(rc)) return RTDbgSymbolDup(&SymInfo); return NULL; } /** * Query a symbol by name. * * The symbol can be prefixed by a module name pattern to scope the search. The * pattern is a simple string pattern with '*' and '?' as wild chars. See * RTStrSimplePatternMatch(). * * @returns VBox status code. See RTDbgAsSymbolByAddr. * * @param pUVM The user mode VM handle. * @param hDbgAs The address space handle. * @param pszSymbol The symbol to search for, maybe prefixed by a * module pattern. * @param pSymbol Where to return the symbol information. * The returned symbol name will be prefixed by * the module name as far as space allows. * @param phMod Where to return the module handle. Optional. */ VMMR3DECL(int) DBGFR3AsSymbolByName(PUVM pUVM, RTDBGAS hDbgAs, const char *pszSymbol, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod) { /* * Implement the special address space aliases the lazy way. */ if (hDbgAs == DBGF_AS_RC_AND_GC_GLOBAL) { int rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_RC, pszSymbol, pSymbol, phMod); if (RT_FAILURE(rc)) rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_GLOBAL, pszSymbol, pSymbol, phMod); return rc; } /* * Input validation. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertPtrReturn(pSymbol, VERR_INVALID_POINTER); AssertPtrNullReturn(phMod, VERR_INVALID_POINTER); if (phMod) *phMod = NIL_RTDBGMOD; RTDBGAS hRealAS = DBGFR3AsResolveAndRetain(pUVM, hDbgAs); if (hRealAS == NIL_RTDBGAS) return VERR_INVALID_HANDLE; /* * Do the lookup. */ RTDBGMOD hMod; int rc = RTDbgAsSymbolByName(hRealAS, pszSymbol, pSymbol, &hMod); if (RT_SUCCESS(rc)) { dbgfR3AsSymbolJoinNames(pSymbol, hMod); if (!phMod) RTDbgModRelease(hMod); } return rc; } VMMR3DECL(int) DBGFR3AsLineByAddr(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress, PRTGCINTPTR poffDisp, PRTDBGLINE pLine, PRTDBGMOD phMod) { /* * Implement the special address space aliases the lazy way. */ if (hDbgAs == DBGF_AS_RC_AND_GC_GLOBAL) { int rc = DBGFR3AsLineByAddr(pUVM, DBGF_AS_RC, pAddress, poffDisp, pLine, phMod); if (RT_FAILURE(rc)) rc = DBGFR3AsLineByAddr(pUVM, DBGF_AS_GLOBAL, pAddress, poffDisp, pLine, phMod); return rc; } /* * Input validation. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertReturn(DBGFR3AddrIsValid(pUVM, pAddress), VERR_INVALID_PARAMETER); AssertPtrNullReturn(poffDisp, VERR_INVALID_POINTER); AssertPtrReturn(pLine, VERR_INVALID_POINTER); AssertPtrNullReturn(phMod, VERR_INVALID_POINTER); if (poffDisp) *poffDisp = 0; if (phMod) *phMod = NIL_RTDBGMOD; RTDBGAS hRealAS = DBGFR3AsResolveAndRetain(pUVM, hDbgAs); if (hRealAS == NIL_RTDBGAS) return VERR_INVALID_HANDLE; /* * Do the lookup. */ return RTDbgAsLineByAddr(hRealAS, pAddress->FlatPtr, poffDisp, pLine, phMod); } VMMR3DECL(PRTDBGLINE) DBGFR3AsLineByAddrA(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress, PRTGCINTPTR poffDisp, PRTDBGMOD phMod) { RTDBGLINE Line; int rc = DBGFR3AsLineByAddr(pUVM, hDbgAs, pAddress, poffDisp, &Line, phMod); if (RT_SUCCESS(rc)) return RTDbgLineDup(&Line); return NULL; }