//***************************************************************************** // Called to read the data into allocated memory and release the backing store. // Only available on read-only data. //***************************************************************************** HRESULT StgIO::LoadFileToMemory() { HRESULT hr; void *pData; // Allocated buffer for file. ULONG cbData; // Size of the data. ULONG cbRead = 0; // Data actually read. // Make sure it is a read-only file. if (m_fFlags & DBPROP_TMODEF_WRITE) return E_INVALIDARG; // Try to allocate the buffer. cbData = m_cbData; pData = AllocateMemory(cbData); IfNullGo(pData); // Try to read the file into the buffer. IfFailGo(Read(pData, cbData, &cbRead)); if (cbData != cbRead) { _ASSERTE_MSG(FALSE, "Read didn't succeed."); IfFailGo(CLDB_E_FILE_CORRUPT); } // Done with the old data. Close(); // Open with new data. hr = Open(NULL /* szName */, STGIO_READ, pData, cbData, NULL /* IStream* */, NULL /* lpSecurityAttributes */); _ASSERTE(SUCCEEDED(hr)); // should not be a failure code path with open on buffer. // Mark the new memory so that it will be freed later. m_pBaseData = m_pData; m_bFreeMem = true; ErrExit: if (FAILED(hr) && pData) FreeMemory(pData); return hr; } // StgIO::LoadFileToMemory
// impl of interface method ICorDebugMutableDataTarget::WriteVirtual HRESULT STDMETHODCALLTYPE DataTargetAdapter::WriteVirtual( CORDB_ADDRESS address, const BYTE * pBuffer, ULONG32 cbRequestSize) { SUPPORTS_DAC_HOST_ONLY; CLRDATA_ADDRESS cdAddr = TO_CDADDR(address); ULONG32 cbWritten = 0; HRESULT hr = S_OK; hr = m_pLegacyTarget->WriteVirtual(cdAddr, const_cast<BYTE *>(pBuffer), cbRequestSize, &cbWritten); if (SUCCEEDED(hr) && cbWritten != cbRequestSize) { // This shouldn't happen - existing data target implementations make writes atomic (eg. // WriteProcessMemory), even though that isn't strictly required by the old interface. // If this does happen, we technically leave the process in an inconsistent state, and we make no // attempt to recover from that here. _ASSERTE_MSG(false, "Legacy data target WriteVirtual partial write - target left in inconsistent state"); return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY); } return hr; }
// 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_ }
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_ }
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_ }
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_ }
// impl of interface method ICorDebugDataTarget::GetPlatform HRESULT STDMETHODCALLTYPE DataTargetAdapter::GetPlatform( CorDebugPlatform * pPlatform) { SUPPORTS_DAC_HOST_ONLY; // Get the target machine type, and assume it's Windows HRESULT hr; ULONG32 ulMachineType; IfFailRet(m_pLegacyTarget->GetMachineType(&ulMachineType)); ULONG32 ulExpectedPointerSize; CorDebugPlatform platform; switch(ulMachineType) { #ifdef FEATURE_PAL case IMAGE_FILE_MACHINE_I386: ulExpectedPointerSize = 4; platform = CORDB_PLATFORM_POSIX_X86; break; case IMAGE_FILE_MACHINE_AMD64: ulExpectedPointerSize = 8; platform = CORDB_PLATFORM_POSIX_AMD64; break; case IMAGE_FILE_MACHINE_IA64: case IMAGE_FILE_MACHINE_ARMNT: case IMAGE_FILE_MACHINE_ARM64: _ASSERTE_MSG(false, "Not supported platform."); return E_NOTIMPL; #else // FEATURE_PAL case IMAGE_FILE_MACHINE_I386: ulExpectedPointerSize = 4; platform = CORDB_PLATFORM_WINDOWS_X86; break; case IMAGE_FILE_MACHINE_AMD64: ulExpectedPointerSize = 8; platform = CORDB_PLATFORM_WINDOWS_AMD64; break; case IMAGE_FILE_MACHINE_IA64: ulExpectedPointerSize = 8; platform = CORDB_PLATFORM_WINDOWS_IA64; break; case IMAGE_FILE_MACHINE_ARMNT: ulExpectedPointerSize = 4; platform = CORDB_PLATFORM_WINDOWS_ARM; break; case IMAGE_FILE_MACHINE_ARM64: ulExpectedPointerSize = 8; platform = CORDB_PLATFORM_WINDOWS_ARM64; break; #endif // FEATURE_PAL default: // No other platforms are current supported return E_NOTIMPL; } // Validate that the target pointer size matches ULONG32 ulPointerSize; IfFailRet(m_pLegacyTarget->GetPointerSize(&ulPointerSize)); if (ulPointerSize != ulExpectedPointerSize) { return E_UNEXPECTED; } // Found a match *pPlatform = platform; return S_OK; }
//***************************************************************************** // Map the file contents to a memory mapped file and return a pointer to the // data. For read/write with a backing store, map the file using an internal // paging system. //***************************************************************************** HRESULT StgIO::MapFileToMem( // Return code. void *&ptr, // Return pointer to file data. ULONG *pcbSize, // Return size of data. LPSECURITY_ATTRIBUTES pAttributes) // Security token. { char rcShared[MAXSHMEM]; // ANSI version of shared name. HRESULT hr = S_OK; // Don't penalize for multiple calls. Also, allow calls for mem type so // callers don't need to do so much checking. if (IsBackingStore() || IsMemoryMapped() || (m_iType == STGIO_MEM) || (m_iType == STGIO_SHAREDMEM) || (m_iType == STGIO_HFILEMEM)) { ptr = m_pData; if (pcbSize) *pcbSize = m_cbData; return (S_OK); } //#CopySmallFiles // Check the size of the data we want to map. If it is small enough, then // simply allocate a chunk of memory from a finer grained heap. This saves // virtual memory space, page table entries, and should reduce overall working set. // Also, open for read/write needs a full backing store. if ((m_cbData <= SMALL_ALLOC_MAP_SIZE) && (SMALL_ALLOC_MAP_SIZE > 0)) { DWORD cbRead = m_cbData; _ASSERTE(m_pData == 0); // Just malloc a chunk of data to use. m_pBaseData = m_pData = AllocateMemory(m_cbData); if (!m_pData) { hr = OutOfMemory(); goto ErrExit; } // Read all of the file contents into this piece of memory. IfFailGo( Seek(0, FILE_BEGIN) ); if (FAILED(hr = Read(m_pData, cbRead, &cbRead))) { FreeMemory(m_pData); m_pData = 0; goto ErrExit; } _ASSERTE(cbRead == m_cbData); // If the file isn't being opened for exclusive mode, then free it. // If it is for exclusive, then we need to keep the handle open so the // file is locked, preventing other readers. Also leave it open if // in read/write mode so we can truncate and rewrite. if (m_hFile == INVALID_HANDLE_VALUE || ((m_fFlags & DBPROP_TMODEF_EXCLUSIVE) == 0 && (m_fFlags & DBPROP_TMODEF_WRITE) == 0)) { // If there was a handle open, then free it. if (m_hFile != INVALID_HANDLE_VALUE) { VERIFY(CloseHandle(m_hFile)); m_hFile = INVALID_HANDLE_VALUE; } // Free the stream pointer. else if (m_pIStream != 0) { m_pIStream->Release(); m_pIStream = 0; } // Switch the type to memory only access. m_iType = STGIO_MEM; } else m_iType = STGIO_HFILEMEM; // Free the memory when we shut down. m_bFreeMem = true; } // Finally, a real mapping file must be created. else { // Now we will map, so better have it right. _ASSERTE(m_hFile != INVALID_HANDLE_VALUE || m_iType == STGIO_STREAM); _ASSERTE(m_rgPageMap == 0); // For read mode, use a memory mapped file since the size will never // change for the life of the handle. if ((m_fFlags & DBPROP_TMODEF_WRITE) == 0 && m_iType != STGIO_STREAM) { // Create a mapping object for the file. _ASSERTE(m_hMapping == 0); DWORD dwProtectionFlags = PAGE_READONLY; if ((m_hMapping = WszCreateFileMapping(m_hFile, pAttributes, dwProtectionFlags, 0, 0, nullptr)) == 0) { return (MapFileError(GetLastError())); } m_mtMappedType = MTYPE_FLAT; // Check to see if the memory already exists, in which case we have // no guarantees it is the right piece of data. if (GetLastError() == ERROR_ALREADY_EXISTS) { hr = PostError(CLDB_E_SMDUPLICATE, rcShared); goto ErrExit; } // Now map the file into memory so we can read from pointer access. // <REVISIT_TODO>Note: Added a check for IsBadReadPtr per the Services team which // indicates that under some conditions this API can give you back // a totally bogus pointer.</REVISIT_TODO> if ((m_pBaseData = m_pData = MapViewOfFile(m_hMapping, FILE_MAP_READ, 0, 0, 0)) == 0) { hr = MapFileError(GetLastError()); if (SUCCEEDED(hr)) { _ASSERTE_MSG(FALSE, "Error code doesn't indicate error."); hr = PostError(CLDB_E_FILE_CORRUPT); } // In case we got back a bogus pointer. m_pBaseData = m_pData = NULL; goto ErrExit; } } // In write mode, we need the hybrid combination of being able to back up // the data in memory via cache, but then later rewrite the contents and // throw away our cached copy. Memory mapped files are not good for this // case due to poor write characteristics. else { ULONG iMaxSize; // How much memory required for file. // Figure out how many pages we'll require, round up actual data // size to page size. iMaxSize = (((m_cbData - 1) & ~(m_iPageSize - 1)) + m_iPageSize); // Check integer overflow in previous statement if (iMaxSize < m_cbData) { IfFailGo(PostError(COR_E_OVERFLOW)); } // Allocate a bit vector to track loaded pages. if ((m_rgPageMap = new (nothrow) BYTE[iMaxSize / m_iPageSize]) == 0) return (PostError(OutOfMemory())); memset(m_rgPageMap, 0, sizeof(BYTE) * (iMaxSize / m_iPageSize)); // Allocate space for the file contents. if ((m_pBaseData = m_pData = ::ClrVirtualAlloc(0, iMaxSize, MEM_RESERVE, PAGE_NOACCESS)) == 0) { hr = PostError(OutOfMemory()); goto ErrExit; } } } // Reset any changes made by mapping. IfFailGo( Seek(0, FILE_BEGIN) ); ErrExit: // Check for errors and clean up. if (FAILED(hr)) { if (m_hMapping) CloseHandle(m_hMapping); m_hMapping = 0; m_pBaseData = m_pData = 0; m_cbData = 0; } ptr = m_pData; if (pcbSize) *pcbSize = m_cbData; return (hr); }