diff options
Diffstat (limited to 'src/debug/daccess/dacfn.cpp')
-rw-r--r-- | src/debug/daccess/dacfn.cpp | 1505 |
1 files changed, 1505 insertions, 0 deletions
diff --git a/src/debug/daccess/dacfn.cpp b/src/debug/daccess/dacfn.cpp new file mode 100644 index 0000000000..88d45993b3 --- /dev/null +++ b/src/debug/daccess/dacfn.cpp @@ -0,0 +1,1505 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// File: dacfn.cpp +// + +// +// Dac function implementations. +// +//***************************************************************************** + +#include "stdafx.h" + +#include <encee.h> +#ifdef FEATURE_PREJIT +#include "compile.h" +#endif // FEATURE_PREJIT +#ifdef FEATURE_REMOTING +#include <remoting.h> +#include "objectclone.h" +#endif +#include <virtualcallstub.h> +#include "peimagelayout.inl" + +DacTableInfo g_dacTableInfo; +DacGlobals g_dacGlobals; + +struct DacHostVtPtrs +{ +#define VPTR_CLASS(name) PVOID name; +#define VPTR_MULTI_CLASS(name, keyBase) PVOID name##__##keyBase; +#include <vptr_list.h> +#undef VPTR_CLASS +#undef VPTR_MULTI_CLASS +}; + + +const WCHAR *g_dacVtStrings[] = +{ +#define VPTR_CLASS(name) W(#name), +#define VPTR_MULTI_CLASS(name, keyBase) W(#name), +#include <vptr_list.h> +#undef VPTR_CLASS +#undef VPTR_MULTI_CLASS +}; + +DacHostVtPtrs g_dacHostVtPtrs; + +HRESULT +DacGetHostVtPtrs(void) +{ +#define VPTR_CLASS(name) \ + g_dacHostVtPtrs.name = name::VPtrHostVTable(); +#define VPTR_MULTI_CLASS(name, keyBase) \ + g_dacHostVtPtrs.name##__##keyBase = name::VPtrHostVTable(); +#include <vptr_list.h> +#undef VPTR_CLASS +#undef VPTR_MULTI_CLASS + + return S_OK; +} + +bool +DacExceptionFilter(Exception* ex, ClrDataAccess* access, + HRESULT* status) +{ + SUPPORTS_DAC_HOST_ONLY; + + // The DAC support functions throw HRExceptions and + // the underlying code can throw the normal set of + // CLR exceptions. Handle any exception + // other than an unexpected SEH exception. + // If we're not debugging, handle SEH exceptions also + // so that dac absorbs all exceptions by default. + if ((access && access->m_debugMode) && + ex->IsType(SEHException::GetType())) + { + // Indicate this exception should be rethrown. + return FALSE; + } + + // Indicate this exception is handled. + // XXX Microsoft - The C++-based EH has broken the ability + // to get proper SEH results. Make sure that the + // error returned is actually an error code as + // often it's just zero. + *status = ex->GetHR(); + if (!FAILED(*status)) + { + *status = E_FAIL; + } + return TRUE; +} + +void __cdecl +DacWarning(__in char* format, ...) +{ + char text[256]; + va_list args; + + va_start(args, format); + _vsnprintf_s(text, sizeof(text), _TRUNCATE, format, args); + text[sizeof(text) - 1] = 0; + va_end(args); + OutputDebugStringA(text); +} + +void +DacNotImpl(void) +{ + EX_THROW(HRException, (E_NOTIMPL)); +} + +void +DacError(HRESULT err) +{ + EX_THROW(HRException, (err)); +} + +// Ideally DacNoImpl and DacError would be marked no-return, but that will require changing a bunch of existing +// code to avoid "unreachable code" warnings. +void DECLSPEC_NORETURN +DacError_NoRet(HRESULT err) +{ + EX_THROW(HRException, (err)); +} + +TADDR +DacGlobalBase(void) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + return g_dacImpl->m_globalBase; +} + +HRESULT +DacReadAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + ClrSafeInt<TADDR> end = ClrSafeInt<TADDR>(addr) + ClrSafeInt<TADDR>(size); + if( end.IsOverflow() ) + { + // Overflow - corrupt data + DacError(CORDBG_E_TARGET_INCONSISTENT); + } + + HRESULT status; + ULONG32 returned; + +#if defined(DAC_MEASURE_PERF) + unsigned __int64 nStart, nEnd; + nStart = GetCycleCount(); +#endif // #if defined(DAC_MEASURE_PERF) + + status = g_dacImpl->m_pTarget-> + ReadVirtual(addr, (PBYTE)buffer, size, &returned); + +#if defined(DAC_MEASURE_PERF) + nEnd = GetCycleCount(); + g_nReadVirtualTotalTime += nEnd - nStart; +#endif // #if defined(DAC_MEASURE_PERF) + + if (status != S_OK) + { + // Regardless of what status is, it's very important for dump debugging to + // always return CORDBG_E_READVIRTUAL_FAILURE. + if (throwEx) + { + DacError(CORDBG_E_READVIRTUAL_FAILURE); + } + return CORDBG_E_READVIRTUAL_FAILURE; + } + if (returned != size) + { + if (throwEx) + { + DacError(HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)); + } + return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY); + } + + return S_OK; +} + +HRESULT +DacWriteAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + HRESULT status; + + status = g_dacImpl->m_pMutableTarget-> + WriteVirtual(addr, (PBYTE)buffer, size); + if (status != S_OK) + { + if (throwEx) + { + DacError(status); + } + return status; + } + + return S_OK; +} + +#if defined(WIN64EXCEPTIONS) && defined(FEATURE_PAL) +HRESULT +DacVirtualUnwind(DWORD threadId, PCONTEXT context, PT_KNONVOLATILE_CONTEXT_POINTERS contextPointers) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + // The DAC code doesn't use these context pointers but zero them out to be safe. + if (contextPointers != NULL) + { + memset(contextPointers, 0, sizeof(T_KNONVOLATILE_CONTEXT_POINTERS)); + } + + ReleaseHolder<ICorDebugDataTarget4> dt; + HRESULT hr = g_dacImpl->m_pTarget->QueryInterface(IID_ICorDebugDataTarget4, (void **)&dt); + if (SUCCEEDED(hr)) + { + hr = dt->VirtualUnwind(threadId, sizeof(CONTEXT), (BYTE*)context); + } + + return hr; +} +#endif // defined(WIN64EXCEPTIONS) && defined(FEATURE_PAL) + +// DacAllocVirtual - Allocate memory from the target process +// Note: this is only available to clients supporting the legacy +// ICLRDataTarget2 interface. It's currently used by SOS for notification tables. +HRESULT +DacAllocVirtual(TADDR addr, ULONG32 size, + ULONG32 typeFlags, ULONG32 protectFlags, + bool throwEx, TADDR* mem) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + ICLRDataTarget2 * pTarget2 = g_dacImpl->GetLegacyTarget2(); + if (pTarget2 == NULL) + { + DacError(E_NOTIMPL); + UNREACHABLE(); + } + + CLRDATA_ADDRESS cdaMem; + HRESULT status = pTarget2->AllocVirtual( + TO_CDADDR(addr), size, typeFlags, protectFlags, &cdaMem); + if (status != S_OK) + { + if (throwEx) + { + DacError(status); + UNREACHABLE(); + } + + return status; + } + + *mem = CLRDATA_ADDRESS_TO_TADDR(cdaMem); + return S_OK; +} + +// DacFreeVirtual - Free memory from the target process +// Note: this is only available to clients supporting the legacy +// ICLRDataTarget2 interface. This is not currently used. +HRESULT +DacFreeVirtual(TADDR mem, ULONG32 size, ULONG32 typeFlags, + bool throwEx) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + ICLRDataTarget2 * pTarget2 = g_dacImpl->GetLegacyTarget2(); + if (pTarget2 == NULL) + { + DacError(E_NOTIMPL); + UNREACHABLE(); + } + + HRESULT status = pTarget2->FreeVirtual( + TO_CDADDR(mem), size, typeFlags); + + if (status != S_OK && throwEx) + { + DacError(status); + UNREACHABLE(); + } + + return status; +} + +PVOID +DacInstantiateTypeByAddressHelper(TADDR addr, ULONG32 size, bool throwEx, bool fReport) +{ +#ifdef _PREFIX_ + + // Dac accesses are not interesting for PREfix and cause alot of PREfix noise + // so we just return the unmodified pointer for our PREFIX builds + return (PVOID)addr; + +#else // !_PREFIX_ + + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + // Preserve special pointer values. + if (!addr || addr == (TADDR)-1) + { + return (PVOID)addr; + } + + // DacInstanceManager::Alloc will assert (with a non-obvious message) on 0-size instances. + // Fail sooner and more obviously here. + _ASSERTE_MSG( size > 0, "DAC coding error: instance size cannot be 0" ); + + // Do not attempt to allocate more than 64megs for one object instance. While we should + // never even come close to this size, in cases of heap corruption or bogus data passed + // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy + // checks the size to ensure we don't allocate gigs of data. + if (size > 0x4000000) + { + if (throwEx) + { + DacError(E_OUTOFMEMORY); + } + return NULL; + } + + // + // Check the cache for an existing DPTR instance. + // It's possible that a previous access may have been + // smaller than the current access, so we have to + // allow an existing instance to be superseded. + // + + DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr); + DAC_INSTANCE* oldInst = NULL; + if (inst) + { + // If the existing instance is large enough we + // can reuse it, otherwise we need to promote. + // We cannot promote a VPTR as the VPTR data + // has been updated with a host vtable and we + // don't want to lose that. This shouldn't + // happen anyway. + if (inst->size >= size) + { + return inst + 1; + } + else + { + // Existing instance is too small and must + // be superseded. + if (inst->usage == DAC_VPTR) + { + // The same address has already been marshalled as a VPTR, now we're trying to marshal as a + // DPTR. This is not allowed. + _ASSERTE_MSG(false, "DAC coding error: DPTR/VPTR usage conflict"); + DacError(E_INVALIDARG); + UNREACHABLE(); + } + + // Promote the larger instance into the hash + // in place of the smaller, but keep the + // smaller instance around in case code still + // has a pointer to it. But ensure that we can + // create the larger instance and add it to the + // hash table before removing the old one. + oldInst = inst; + } + } + + inst = g_dacImpl->m_instances.Alloc(addr, size, DAC_DPTR); + if (!inst) + { + DacError(E_OUTOFMEMORY); + UNREACHABLE(); + } + + if (fReport == false) + { + // mark the bit if necessary + inst->noReport = 1; + } + else + { + // clear the bit + inst->noReport = 0; + } + HRESULT status = DacReadAll(addr, inst + 1, size, false); + if (status != S_OK) + { + g_dacImpl->m_instances.ReturnAlloc(inst); + if (throwEx) + { + DacError(status); + } + return NULL; + } + + if (!g_dacImpl->m_instances.Add(inst)) + { + g_dacImpl->m_instances.ReturnAlloc(inst); + DacError(E_OUTOFMEMORY); + UNREACHABLE(); + } + + if (oldInst) + { + g_dacImpl->m_instances.Supersede(oldInst); + } + + return inst + 1; + +#endif // !_PREFIX_ +} + +PVOID DacInstantiateTypeByAddress(TADDR addr, ULONG32 size, bool throwEx) +{ + return DacInstantiateTypeByAddressHelper(addr, size, throwEx, true); +} + +PVOID DacInstantiateTypeByAddressNoReport(TADDR addr, ULONG32 size, bool throwEx) +{ + return DacInstantiateTypeByAddressHelper(addr, size, throwEx, false); +} + + +PVOID +DacInstantiateClassByVTable(TADDR addr, ULONG32 minSize, bool throwEx) +{ +#ifdef _PREFIX_ + + // Dac accesses are not interesting for PREfix and cause alot of PREfix noise + // so we just return the unmodified pointer for our PREFIX builds + return (PVOID)addr; + +#else // !_PREFIX_ + + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + // Preserve special pointer values. + if (!addr || addr == (TADDR)-1) + { + return (PVOID)addr; + } + + // Do not attempt to allocate more than 64megs for one object instance. While we should + // never even come close to this size, in cases of heap corruption or bogus data passed + // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy + // checks the size to ensure we don't allocate gigs of data. + if (minSize > 0x4000000) + { + if (throwEx) + { + DacError(E_OUTOFMEMORY); + } + return NULL; + } + + // + // Check the cache for an existing VPTR instance. + // If there is an instance we assume that it's + // the right object. + // + + DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr); + DAC_INSTANCE* oldInst = NULL; + if (inst) + { + // If the existing instance is a VPTR we can + // reuse it, otherwise we need to promote. + if (inst->usage == DAC_VPTR) + { + // Sanity check that the object we're returning is big enough to fill the PTR type it's being + // accessed with. For more information, see the similar check below for the case when the + // object isn't already cached + _ASSERTE_MSG(inst->size >= minSize, "DAC coding error: Attempt to instantiate a VPTR from an object that is too small"); + + return inst + 1; + } + else + { + // Existing instance is not a match and must + // be superseded. + // Promote the new instance into the hash + // in place of the old, but keep the + // old instance around in case code still + // has a pointer to it. But ensure that we can + // create the larger instance and add it to the + // hash table before removing the old one. + oldInst = inst; + } + } + + HRESULT status; + TADDR vtAddr; + ULONG32 size; + PVOID hostVtPtr; + + // Read the vtable pointer to get the actual + // implementation class identity. + if ((status = DacReadAll(addr, &vtAddr, sizeof(vtAddr), throwEx)) != S_OK) + { + return NULL; + } + + // + // Instantiate the right class, using the vtable as + // class identity. + // + +#define VPTR_CLASS(name) \ + if (vtAddr == g_dacImpl->m_globalBase + \ + g_dacGlobals.name##__vtAddr) \ + { \ + size = sizeof(name); \ + hostVtPtr = g_dacHostVtPtrs.name; \ + } \ + else +#define VPTR_MULTI_CLASS(name, keyBase) \ + if (vtAddr == g_dacImpl->m_globalBase + \ + g_dacGlobals.name##__##keyBase##__mvtAddr) \ + { \ + size = sizeof(name); \ + hostVtPtr = g_dacHostVtPtrs.name##__##keyBase; \ + } \ + else +#include <vptr_list.h> +#undef VPTR_CLASS +#undef VPTR_MULTI_CLASS + + { + // Can't identify the vtable pointer. + if (throwEx) + { + _ASSERTE_MSG(false,"DAC coding error: Unrecognized vtable pointer in VPTR marshalling code"); + DacError(E_INVALIDARG); + } + return NULL; + } + + // Sanity check that the object we're returning is big enough to fill the PTR type it's being + // accessed with. + // If this is not true, it means the type being marshalled isn't a sub-type (or the same type) + // as the PTR type it's being used as. For example, trying to marshal an instance of a SystemDomain + // object into a PTR_AppDomain will cause this ASSERT to fire (because both SystemDomain and AppDomain + // derived from BaseDomain, and SystemDomain is smaller than AppDomain). + _ASSERTE_MSG(size >= minSize, "DAC coding error: Attempt to instantiate a VPTR from an object that is too small"); + + inst = g_dacImpl->m_instances.Alloc(addr, size, DAC_VPTR); + if (!inst) + { + DacError(E_OUTOFMEMORY); + UNREACHABLE(); + } + + // Copy the object contents into the host instance. Note that this assumes the host and target + // have the same exact layout. Specifically, it assumes the host and target vtable pointers are + // the same size. + if ((status = DacReadAll(addr, inst + 1, size, false)) != S_OK) + { + g_dacImpl->m_instances.ReturnAlloc(inst); + if (throwEx) + { + DacError(status); + } + return NULL; + } + + // We now have a proper target object with a target + // vtable. We need to patch the vtable to the appropriate + // host vtable so that the virtual functions can be + // called in the host process. + *(PVOID*)(inst + 1) = hostVtPtr; + + if (!g_dacImpl->m_instances.Add(inst)) + { + g_dacImpl->m_instances.ReturnAlloc(inst); + DacError(E_OUTOFMEMORY); + UNREACHABLE(); + } + + if (oldInst) + { + g_dacImpl->m_instances.Supersede(oldInst); + } + return inst + 1; + +#endif // !_PREFIX_ +} + +#define LOCAL_STR_BUF 256 + +PSTR +DacInstantiateStringA(TADDR addr, ULONG32 maxChars, bool throwEx) +{ +#ifdef _PREFIX_ + + // Dac accesses are not interesting for PREfix and cause alot of PREfix noise + // so we just return the unmodified pointer for our PREFIX builds + return (PSTR)addr; + +#else // !_PREFIX_ + + HRESULT status; + + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + // Preserve special pointer values. + if (!addr || addr == (TADDR)-1) + { + return (PSTR)addr; + } + + + // Do not attempt to allocate more than 64megs for a string. While we should + // never even come close to this size, in cases of heap corruption or bogus data passed + // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy + // checks the size to ensure we don't allocate gigs of data. + if (maxChars > 0x4000000) + { + if (throwEx) + { + DacError(E_OUTOFMEMORY); + } + return NULL; + } + + // + // Look for an existing string instance. + // + + DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr); + if (inst && inst->usage == DAC_STRA) + { + return (PSTR)(inst + 1); + } + + // + // Determine the length of the string + // by iteratively reading blocks and scanning them + // for a terminator. + // + + char buf[LOCAL_STR_BUF]; + TADDR scanAddr = addr; + ULONG32 curBytes = 0; + ULONG32 returned; + + for (;;) + { + status = g_dacImpl->m_pTarget-> + ReadVirtual(scanAddr, (PBYTE)buf, sizeof(buf), + &returned); + if (status != S_OK) + { + // We hit invalid memory before finding a terminator. + if (throwEx) + { + DacError(CORDBG_E_READVIRTUAL_FAILURE); + } + return NULL; + } + + PSTR scan = (PSTR)buf; + PSTR scanEnd = scan + (returned / sizeof(*scan)); + while (scan < scanEnd) + { + if (!*scan) + { + break; + } + + scan++; + } + + if (!*scan) + { + // Found a terminator. + scanAddr += ((scan + 1) - buf) * sizeof(*scan); + break; + } + + // Ignore any partial character reads. The character + // will be reread on the next loop if necessary. + returned &= ~(sizeof(buf[0]) - 1); + + // The assumption is that a memory read cannot wrap + // around the address space, thus if we have read to + // the top of memory scanAddr cannot wrap farther + // than to zero. + curBytes += returned; + scanAddr += returned; + + if (!scanAddr || + (curBytes + sizeof(buf[0]) - 1) / sizeof(buf[0]) >= maxChars) + { + // Wrapped around the top of memory or + // we didn't find a terminator within the given bound. + if (throwEx) + { + DacError(E_INVALIDARG); + } + return NULL; + } + } + + // Now that we know the length we can create a + // host copy of the string. + PSTR retVal = (PSTR) + DacInstantiateTypeByAddress(addr, (ULONG32)(scanAddr - addr), throwEx); + if (retVal && + (inst = g_dacImpl->m_instances.Find(addr))) + { + inst->usage = DAC_STRA; + } + return retVal; + +#endif // !_PREFIX_ +} + +PWSTR +DacInstantiateStringW(TADDR addr, ULONG32 maxChars, bool throwEx) +{ +#ifdef _PREFIX_ + + // Dac accesses are not interesting for PREfix and cause alot of PREfix noise + // so we just return the unmodified pointer for our PREFIX builds + return (PWSTR)addr; + +#else // !_PREFIX_ + + HRESULT status; + + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + // Preserve special pointer values. + if (!addr || addr == (TADDR)-1) + { + return (PWSTR)addr; + } + + // Do not attempt to allocate more than 64megs for a string. While we should + // never even come close to this size, in cases of heap corruption or bogus data passed + // into the dac, we can allocate huge amounts of data if we are unlucky. This santiy + // checks the size to ensure we don't allocate gigs of data. + if (maxChars > 0x4000000) + { + if (throwEx) + { + DacError(E_OUTOFMEMORY); + } + return NULL; + } + + + // + // Look for an existing string instance. + // + + DAC_INSTANCE* inst = g_dacImpl->m_instances.Find(addr); + if (inst && inst->usage == DAC_STRW) + { + return (PWSTR)(inst + 1); + } + + // + // Determine the length of the string + // by iteratively reading blocks and scanning them + // for a terminator. + // + + WCHAR buf[LOCAL_STR_BUF]; + TADDR scanAddr = addr; + ULONG32 curBytes = 0; + ULONG32 returned; + + for (;;) + { + status = g_dacImpl->m_pTarget-> + ReadVirtual(scanAddr, (PBYTE)buf, sizeof(buf), + &returned); + if (status != S_OK) + { + // We hit invalid memory before finding a terminator. + if (throwEx) + { + DacError(CORDBG_E_READVIRTUAL_FAILURE); + } + return NULL; + } + + PWSTR scan = (PWSTR)buf; + PWSTR scanEnd = scan + (returned / sizeof(*scan)); + while (scan < scanEnd) + { + if (!*scan) + { + break; + } + + scan++; + } + + if (!*scan) + { + // Found a terminator. + scanAddr += ((scan + 1) - buf) * sizeof(*scan); + break; + } + + // Ignore any partial character reads. The character + // will be reread on the next loop if necessary. + returned &= ~(sizeof(buf[0]) - 1); + + // The assumption is that a memory read cannot wrap + // around the address space, thus if we have read to + // the top of memory scanAddr cannot wrap farther + // than to zero. + curBytes += returned; + scanAddr += returned; + + if (!scanAddr || + (curBytes + sizeof(buf[0]) - 1) / sizeof(buf[0]) >= maxChars) + { + // Wrapped around the top of memory or + // we didn't find a terminator within the given bound. + if (throwEx) + { + DacError(E_INVALIDARG); + } + return NULL; + } + } + + // Now that we know the length we can create a + // host copy of the string. + PWSTR retVal = (PWSTR) + DacInstantiateTypeByAddress(addr, (ULONG32)(scanAddr - addr), throwEx); + if (retVal && + (inst = g_dacImpl->m_instances.Find(addr))) + { + inst->usage = DAC_STRW; + } + return retVal; + +#endif // !_PREFIX_ +} + +TADDR +DacGetTargetAddrForHostAddr(LPCVOID ptr, bool throwEx) +{ +#ifdef _PREFIX_ + + // Dac accesses are not interesting for PREfix and cause alot of PREfix noise + // so we just return the unmodified pointer for our PREFIX builds + return (TADDR) ptr; + +#else // !_PREFIX_ + + // Preserve special pointer values. + if (ptr == NULL || ((TADDR) ptr == (TADDR)-1)) + { + return 0; + } + else + { + TADDR addr = 0; + HRESULT status = E_FAIL; + + EX_TRY + { + DAC_INSTANCE* inst = (DAC_INSTANCE*)ptr - 1; + if (inst->sig == DAC_INSTANCE_SIG) + { + addr = inst->addr; + status = S_OK; + } + else + { + status = E_INVALIDARG; + } + } + EX_CATCH + { + status = E_INVALIDARG; + } + EX_END_CATCH(SwallowAllExceptions) + + if (status != S_OK) + { + if (g_dacImpl && g_dacImpl->m_debugMode) + { + DebugBreak(); + } + + if (throwEx) + { + // This means a pointer was supplied which doesn't actually point to the beginning of + // a marshalled DAC instance. + _ASSERTE_MSG(false, "DAC coding error: Attempt to get target address from a host pointer " + "which is not an instance marshalled by DAC!"); + DacError(status); + } + } + + return addr; + } + +#endif // !_PREFIX_ +} + +// Similar to DacGetTargetAddrForHostAddr above except that ptr can represent any pointer within a host data +// structure marshalled from the target (rather than just a pointer to the first field). +TADDR +DacGetTargetAddrForHostInteriorAddr(LPCVOID ptr, bool throwEx) +{ + // Our algorithm for locating the containing DAC instance will search backwards through memory in + // DAC_INSTANCE_ALIGN increments looking for a valid header. The following constant determines how many of + // these iterations we'll perform before deciding the caller made a mistake and didn't marshal the + // containing instance from the target to the host properly. Lower values will determine the maximum + // offset from the start of a marshalled structure at which an interior pointer can appear. Higher values + // will bound the amount of time it takes to report an error in the case where code has been incorrectly + // DAC-ized. + const DWORD kMaxSearchIterations = 100; + +#ifdef _PREFIX_ + + // Dac accesses are not interesting for PREfix and cause alot of PREfix noise + // so we just return the unmodified pointer for our PREFIX builds + return (TADDR) ptr; + +#else // !_PREFIX_ + + // Preserve special pointer values. + if (ptr == NULL || ((TADDR) ptr == (TADDR)-1)) + { + return 0; + } + else + { + TADDR addr = 0; + HRESULT status = E_FAIL; + + EX_TRY + { + // We're going to search backwards through memory from the pointer looking for a valid DAC + // instance header. Initialize this search pointer to the first legal value it could hold. + // Intuitively this would be ptr - sizeof(DAC_INSTANCE), but DAC_INSTANCE headers are further + // constrained to lie on DAC_INSTANCE_ALIGN boundaries. DAC_INSTANCE_ALIGN is large (16 bytes) due + // to the need to keep the marshalled structure also aligned for any possible need, so we gain + // considerable performance from only needing to test for DAC_INSTANCE headers at + // DAC_INSTANCE_ALIGN aligned addresses. + DAC_INSTANCE * inst = (DAC_INSTANCE*)(((ULONG_PTR)ptr - sizeof(DAC_INSTANCE)) & ~(DAC_INSTANCE_ALIGN - 1)); + + // When code is DAC'ized correctly then our search algorithm is guaranteed to terminate safely + // before reading memory that doesn't belong to the containing DAC instance. Since people do make + // mistakes we want to limit how long and far we search however. The counter below will let us + // assert if we've likely tried to locate an interior host pointer in a non-marshalled structure. + DWORD cIterations = 0; + + bool tryAgain = false; + + // Scan backwards in memory looking for a DAC_INSTANCE header. + while (true) + { + // Step back DAC_INSTANCE_ALIGN bytes at a time (the initialization of inst above guarantees + // we start with an aligned pointer value. Stop every time our potential DAC_INSTANCE header + // has a correct signature value. + while (tryAgain || inst->sig != DAC_INSTANCE_SIG) + { + tryAgain = false; + inst = (DAC_INSTANCE*)((BYTE*)inst - DAC_INSTANCE_ALIGN); + + // If we've searched a lot of memory (currently 100 * 16 == 1600 bytes) without success, + // then assume this is due to an issue DAC-izing code (if you really do have a field within a + // DAC marshalled structure whose offset is >1600 bytes then feel free to update the + // constant at the start of this method). + if (++cIterations > kMaxSearchIterations) + { + status = E_INVALIDARG; + break; + } + } + + // Fall through to a DAC error if we searched too long without finding a header candidate. + if (status == E_INVALIDARG) + break; + + // Validate our candidate header by looking up the target address it claims to map in the + // instance hash. The entry should both exist and correspond exactly to our candidate instance + // pointer. + // TODO: but what if the same memory was marshalled more than once (eg. once as a DPTR, once as a VPTR)? + if (inst == g_dacImpl->m_instances.Find(inst->addr)) + { + // We've found a valid DAC instance. Now validate that the marshalled structure it + // represents really does enclose the pointer we're asking about. If not, someone hasn't + // marshalled a containing structure before trying to map a pointer within that structure + // (we've just gone and found the previous, unrelated marshalled structure in host memory). + BYTE * parent = (BYTE*)(inst + 1); + if (((BYTE*)ptr + sizeof(LPCVOID)) <= (parent + inst->size)) + { + // Everything checks out: we've found a DAC instance header and its address range + // encompasses the pointer we're interested in. Compute the corresponding target + // address by taking into account the offset of the interior pointer into its + // enclosing structure. + addr = inst->addr + ((BYTE*)ptr - parent); + status = S_OK; + } + else + { + // We found a valid DAC instance but it doesn't cover the address range containing our + // input pointer. Fall though to report an erroring DAC-izing code. + status = E_INVALIDARG; + } + break; + } + else + { + // This must not really be a match, perhaps a coincidence? + // Keep searching + tryAgain = true; + } + } + } + EX_CATCH + { + status = E_INVALIDARG; + } + EX_END_CATCH(SwallowAllExceptions) + + if (status != S_OK) + { + if (g_dacImpl && g_dacImpl->m_debugMode) + { + DebugBreak(); + } + + if (throwEx) + { + // This means a pointer was supplied which doesn't actually point to somewhere in a marshalled + // DAC instance. + _ASSERTE_MSG(false, "DAC coding error: Attempt to get target address from a host interior " + "pointer which is not an instance marshalled by DAC!"); + DacError(status); + } + } + + return addr; + } +#endif // !_PREFIX_ +} + +PWSTR DacGetVtNameW(TADDR targetVtable) +{ + PWSTR pszRet = NULL; + + ULONG *targ = &g_dacGlobals.Thread__vtAddr; + ULONG *targStart = targ; + for (ULONG i = 0; i < sizeof(g_dacHostVtPtrs) / sizeof(PVOID); i++) + { + if (targetVtable == (*targ + DacGlobalBase())) + { + pszRet = (PWSTR) *(g_dacVtStrings + (targ - targStart)); + break; + } + + targ++; + } + return pszRet; +} + +TADDR +DacGetTargetVtForHostVt(LPCVOID vtHost, bool throwEx) +{ + PVOID* host; + ULONG* targ; + ULONG i; + + // The host vtable table exactly parallels the + // target vtable table, so just iterate to a match + // return the matching entry. + host = &g_dacHostVtPtrs.Thread; + targ = &g_dacGlobals.Thread__vtAddr; + for (i = 0; i < sizeof(g_dacHostVtPtrs) / sizeof(PVOID); i++) + { + if (*host == vtHost) + { + return *targ + DacGlobalBase(); + } + + host++; + targ++; + } + + if (throwEx) + { + DacError(E_INVALIDARG); + } + return 0; +} + +// +// DacEnumMemoryRegion - report a region of memory to the dump generation code +// +// Parameters: +// addr - target address of the beginning of the memory region +// size - number of bytes to report +// fExpectSuccess - whether or not ASSERTs should be raised if some memory in this region +// is found to be unreadable. Generally we should only report readable +// memory (unless the target is corrupt, in which case we expect asserts +// if target consistency checking is enabled). Reporting memory that +// isn't fully readable often indicates an issue that could cause much worse +// problems (loss of dump data, long/infinite loops in dump generation), +// so we want to try and catch any such usage. Ocassionally we can't say +// for sure how much of the reported region will be readable (eg. for the +// LoaderHeap, we only know the length of the allocated address space, not +// the size of the commit region for every block). In these special cases, +// we pass false to indicate that we're happy reporting up to the first +// unreadable byte. This should be avoided if at all possible. +// +bool DacEnumMemoryRegion(TADDR addr, TSIZE_T size, bool fExpectSuccess /*=true*/) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + return g_dacImpl->ReportMem(addr, size, fExpectSuccess); +} + +// +// DacUpdateMemoryRegion - updates/poisons a region of memory of generated dump +// +// Parameters: +// addr - target address of the beginning of the memory region +// bufferSize - number of bytes to update/poison +// buffer - data to be written at given target address +// +bool DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* buffer) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + return g_dacImpl->DacUpdateMemoryRegion(addr, bufferSize, buffer); +} + +HRESULT +DacWriteHostInstance(PVOID host, bool throwEx) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + TADDR addr = DacGetTargetAddrForHostAddr(host, throwEx); + if (!addr) + { + return S_OK; + } + + DAC_INSTANCE* inst = (DAC_INSTANCE*)host - 1; + return g_dacImpl->m_instances.Write(inst, throwEx); +} + +bool +DacHostPtrHasEnumMark(LPCVOID host) +{ + if (!DacGetTargetAddrForHostAddr(host, false)) + { + // Make it easy to ignore invalid pointers when enumerating. + return true; + } + + DAC_INSTANCE* inst = ((DAC_INSTANCE*)host) - 1; + bool marked = inst->enumMem ? true : false; + inst->enumMem = true; + return marked; +} + +bool +DacHasMethodDescBeenEnumerated(LPCVOID pMD) +{ + if (!DacGetTargetAddrForHostAddr(pMD, false)) + { + // Make it easy to ignore invalid pointers when enumerating. + return true; + } + + DAC_INSTANCE* inst = ((DAC_INSTANCE*) pMD) - 1; + bool MDEnumed = inst->MDEnumed ? true : false; + return MDEnumed; +} + +bool +DacSetMethodDescEnumerated(LPCVOID pMD) +{ + if (!DacGetTargetAddrForHostAddr(pMD, false)) + { + // Make it easy to ignore invalid pointers when enumerating. + return true; + } + + DAC_INSTANCE* inst = ((DAC_INSTANCE*) pMD) - 1; + bool MDEnumed = inst->MDEnumed ? true : false; + inst->MDEnumed = true; + return MDEnumed; +} + +// This gets called from DAC-ized code in the VM. +IMDInternalImport* +DacGetMDImport(const PEFile* peFile, bool throwEx) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + return g_dacImpl->GetMDImport(peFile, throwEx); +} + +IMDInternalImport* +DacGetMDImport(const ReflectionModule* reflectionModule, bool throwEx) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + return g_dacImpl->GetMDImport(reflectionModule, throwEx); +} + +COR_ILMETHOD* +DacGetIlMethod(TADDR methAddr) +{ + ULONG32 methodSize = static_cast<ULONG32>(PEDecoder::ComputeILMethodSize(methAddr)); + + // Sometimes when reading from dumps and inspect NGEN images, but we end up reading metadata from IL image + // the method RVA could not match and we could read from a random address that will translate in inconsistent + // IL code header. If we see the size of the code bigger than 64 Megs we are probably reading a bad IL code header. + // For details see issue DevDiv 273199. + if (methodSize > 0x4000000) + { + DacError(CORDBG_E_TARGET_INCONSISTENT); + UNREACHABLE(); + } + return (COR_ILMETHOD*) + DacInstantiateTypeByAddress(methAddr, methodSize, + true); +} + +#ifdef FEATURE_MINIMETADATA_IN_TRIAGEDUMPS +void +DacMdCacheAddEEName(TADDR taEE, const SString& ssEEName) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + g_dacImpl->MdCacheAddEEName(taEE, ssEEName); +} +bool +DacMdCacheGetEEName(TADDR taEE, SString & eeName) +{ + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + return g_dacImpl->MdCacheGetEEName(taEE, eeName); +} + +#endif // FEATURE_MINIMETADATA_IN_TRIAGEDUMPS + +PVOID +DacAllocHostOnlyInstance(ULONG32 size, bool throwEx) +{ + SUPPORTS_DAC_HOST_ONLY; + if (!g_dacImpl) + { + DacError(E_UNEXPECTED); + UNREACHABLE(); + } + + DAC_INSTANCE* inst = g_dacImpl->m_instances.Alloc(0, size, DAC_DPTR); + if (!inst) + { + DacError(E_OUTOFMEMORY); + UNREACHABLE(); + } + + g_dacImpl->m_instances.AddSuperseded(inst); + + return inst + 1; +} + +// +// Queries whether ASSERTs should be raised when inconsistencies in the target are detected +// +// Return Value: +// true if ASSERTs should be raised in DACized code. +// false if ASSERTs should be ignored. +// +// Notes: +// See code:ClrDataAccess::TargetConsistencyAssertsEnabled for details. +bool DacTargetConsistencyAssertsEnabled() +{ + if (!g_dacImpl) + { + // No ClrDataAccess instance available (maybe we're still initializing). Any asserts when this is + // the case should only be host-asserts (i.e. always bugs), and so we should just return true. + return true; + } + + return g_dacImpl->TargetConsistencyAssertsEnabled(); +} + +// +// DacEnumCodeForStackwalk +// This is a helper function to enumerate the instructions around a call site to aid heuristics +// used by debugger stack walkers. +// +// Arguments: +// taCallEnd - target address of the instruction just after the call instruction for the stack +// frame we want to examine(i.e. the return address for the next frame). +// +// Note that this is shared by our two stackwalks during minidump generation, +// code:Thread::EnumMemoryRegionsWorker and code:ClrDataAccess::EnumMemWalkStackHelper. Ideally +// we'd only have one stackwalk, but we currently have two different APIs for stackwalking +// (CLR StackFrameIterator and IXCLRDataStackWalk), and we must ensure that the memory needed +// for either is captured in a minidump. Eventually, all clients should get moved over to the +// arrowhead debugging architecture, at which time we can rip out all the IXCLRData APIs, and +// so this logic could just be private to the EnumMem code for Thread. +// +void DacEnumCodeForStackwalk(TADDR taCallEnd) +{ + // + // x86 stack walkers often end up having to guess + // about what's a return address on the stack. + // Doing so involves looking at the code at the + // possible call site and seeing if it could + // reach the callee. Save enough code and around + // the call site to allow this with a dump. + // + // For whatever reason 64-bit platforms require us to save + // the instructions around the call sites on the stack as well. + // Otherwise we cannnot show the stack in a minidump. + // + // Note that everything we do here is a heuristic that won't always work in general. + // Eg., part of the 2xMAX_INSTRUCTION_LENGTH range might not be mapped (we could be + // right on a page boundary). More seriously, X86 is not necessarily parsable in reverse + // (eg. there could be a segment-override prefix in front of the call instruction that + // we miss). So we'll dump what we can and ignore any failures. Ideally we'd better + // quantify exactly what debuggers need and why, and try and avoid these ugly heuristics. + // It seems like these heuristics are too tightly coupled to the implementation details + // of some specific debugger stackwalking algorithm. + // + DacEnumMemoryRegion(taCallEnd - MAX_INSTRUCTION_LENGTH, MAX_INSTRUCTION_LENGTH * 2, false); + +#if defined(_TARGET_X86_) + // If it was an indirect call we also need to save the data indirected through. + // Note that this only handles absolute indirect calls (ModR/M byte of 0x15), all the other forms of + // indirect calls are register-relative, and so we'd have to do a much more complicated decoding based + // on the register context. Regardless, it seems like this is fundamentally error-prone because it's + // aways possible that the call instruction was not 6 bytes long, and we could have some other instructions + // that happen to match the pattern we're looking for. + PTR_BYTE callCode = PTR_BYTE(taCallEnd - 6); + PTR_BYTE callMrm = PTR_BYTE(taCallEnd - 5); + PTR_TADDR callInd = PTR_TADDR(taCallEnd - 4); + if (callCode.IsValid() && + (*callCode == 0xff) && + callMrm.IsValid() && + (*callMrm == 0x15) && + callInd.IsValid()) + { + DacEnumMemoryRegion(*callInd, sizeof(TADDR), false); + } +#endif // #ifdef _TARGET_X86_ +} + +// ---------------------------------------------------------------------------- +// DacReplacePatches +// +// Description: +// Given the address and the size of a memory range which is stored in the buffer, replace all the patches +// in the buffer with the real opcodes. This is especially important on X64 where the unwinder needs to +// disassemble the native instructions. +// +// Arguments: +// * range - the address and the size of the memory range +// * pBuffer - the buffer containting the memory range +// +// Return Value: +// Return S_OK if everything succeeds. +// +// Assumptions: +// * The debuggee has to be stopped. +// +// Notes: +// * @dbgtodo ICDProcess - When we DACize code:CordbProcess::ReadMemory, +// we should change it to use this function. +// + +HRESULT DacReplacePatchesInHostMemory(MemoryRange range, PVOID pBuffer) +{ + SUPPORTS_DAC; + + // If the patch table is invalid, then there is no patch to replace. + if (!DebuggerController::GetPatchTableValid()) + { + return S_OK; + } + + HASHFIND info; + + DebuggerPatchTable * pTable = DebuggerController::GetPatchTable(); + DebuggerControllerPatch * pPatch = pTable->GetFirstPatch(&info); + + // <PERF> + // The unwinder needs to read the stack very often to restore pushed registers, retrieve the + // return addres, etc. However, stack addresses should never be patched. + // One way to optimize this code is to pass the stack base and the stack limit of the thread to this + // function and use those two values to filter out stack addresses. + // + // Another thing we can do is instead of enumerating the patches, we could enumerate the address. + // This is more efficient when we have a large number of patches and a small memory range. Perhaps + // we could do a hybrid approach, i.e. use the size of the range and the number of patches to dynamically + // determine which enumeration is more efficient. + // </PERF> + while (pPatch != NULL) + { + CORDB_ADDRESS patchAddress = (CORDB_ADDRESS)dac_cast<TADDR>(pPatch->address); + + if (patchAddress != NULL) + { + PRD_TYPE opcode = pPatch->opcode; + + CORDB_ADDRESS address = (CORDB_ADDRESS)(dac_cast<TADDR>(range.StartAddress())); + SIZE_T cbSize = range.Size(); + + // Check if the address of the patch is in the specified memory range. + if (IsPatchInRequestedRange(address, cbSize, patchAddress)) + { + // Replace the patch in the buffer with the original opcode. + CORDbgSetInstructionEx(reinterpret_cast<PBYTE>(pBuffer), address, patchAddress, opcode, cbSize); + } + } + + pPatch = pTable->GetNextPatch(&info); + } + + return S_OK; +} |