diff options
author | Tomáš Rylek <trylek@microsoft.com> | 2020-12-08 05:19:44 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-08 05:19:44 +0300 |
commit | 69e114c1abf91241a0eeecf1ecceab4711b8aa62 (patch) | |
tree | b81a0b35748f5e598412bcc504335cdbd322cd43 /src/coreclr/utilcode | |
parent | 0ec07945a9759a72a689edbb01e69b232e26e05a (diff) |
December infra rollout - remove duplicated 'src' from coreclr subrepo (src/coreclr/src becomes src/coreclr) (#44973)
* Move src/coreclr/src/Directory.Build.targets to src/coreclr
Merge src/coreclr/src/CMakeLists.txt into src/coreclr/CMakeLists.txt
* Mechanical move of src/coreclr/src to src/coreclr
* Scripts adjustments to reflect the changed paths
Diffstat (limited to 'src/coreclr/utilcode')
61 files changed, 35439 insertions, 0 deletions
diff --git a/src/coreclr/utilcode/CMakeLists.txt b/src/coreclr/utilcode/CMakeLists.txt new file mode 100644 index 00000000000..c7c5861f129 --- /dev/null +++ b/src/coreclr/utilcode/CMakeLists.txt @@ -0,0 +1,130 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(UTILCODE_COMMON_SOURCES + clrhost_nodependencies.cpp + ccomprc.cpp + ex.cpp + sbuffer.cpp + sstring_com.cpp + fstring.cpp + namespaceutil.cpp + makepath.cpp + splitpath.cpp + clrconfig.cpp + configuration.cpp + collections.cpp + posterror.cpp + fstream.cpp + clrhelpers.cpp + stgpool.cpp + stgpooli.cpp + stgpoolreadonly.cpp + utsem.cpp + peinformation.cpp + check.cpp + log.cpp + arraylist.cpp + bitvector.cpp + comex.cpp + guidfromname.cpp + memorypool.cpp + iallocator.cpp + loaderheap.cpp + outstring.cpp + ilformatter.cpp + opinfo.cpp + corimage.cpp + format1.cpp + prettyprintsig.cpp + regutil.cpp + sha1.cpp + sigbuilder.cpp + sigparser.cpp + sstring.cpp + util_nodependencies.cpp + utilmessagebox.cpp + safewrap.cpp + clrhost.cpp + cycletimer.cpp + md5.cpp + util.cpp + stresslog.cpp + debug.cpp + pedecoder.cpp + winfix.cpp + longfilepathwrappers.cpp + yieldprocessornormalized.cpp +) + +# These source file do not yet compile on Linux. +# They should be moved out from here into the declaration +# of UTILCODE_SOURCES above after fixing compiler errors. +if(CLR_CMAKE_TARGET_WIN32) + list(APPEND UTILCODE_COMMON_SOURCES + dacutil.cpp + dlwrap.cpp + securitywrapper.cpp + securityutil.cpp + stacktrace.cpp + ) +endif(CLR_CMAKE_TARGET_WIN32) + +set(UTILCODE_SOURCES + ${UTILCODE_COMMON_SOURCES} +) + +set(UTILCODE_DAC_SOURCES + ${UTILCODE_COMMON_SOURCES} + hostimpl.cpp +) + +set(UTILCODE_CROSSGEN_SOURCES + ${UTILCODE_COMMON_SOURCES} + hostimpl.cpp +) + +set(UTILCODE_STATICNOHOST_SOURCES + ${UTILCODE_COMMON_SOURCES} + hostimpl.cpp +) + +set (UTILCODE_DEPENDENCIES eventing_headers) + +convert_to_absolute_path(UTILCODE_SOURCES ${UTILCODE_SOURCES}) +convert_to_absolute_path(UTILCODE_DAC_SOURCES ${UTILCODE_DAC_SOURCES}) +convert_to_absolute_path(UTILCODE_CROSSGEN_SOURCES ${UTILCODE_CROSSGEN_SOURCES}) +convert_to_absolute_path(UTILCODE_STATICNOHOST_SOURCES ${UTILCODE_STATICNOHOST_SOURCES}) + +add_library_clr(utilcode_dac STATIC ${UTILCODE_DAC_SOURCES}) +add_library_clr(utilcode_obj OBJECT ${UTILCODE_SOURCES}) +add_library(utilcode INTERFACE) +target_sources(utilcode INTERFACE $<TARGET_OBJECTS:utilcode_obj>) +add_library_clr(utilcodestaticnohost STATIC ${UTILCODE_STATICNOHOST_SOURCES}) +add_library_clr(utilcode_crossgen STATIC ${UTILCODE_CROSSGEN_SOURCES}) + +if(CLR_CMAKE_HOST_UNIX) + target_link_libraries(utilcodestaticnohost nativeresourcestring) + target_link_libraries(utilcode_crossgen nativeresourcestring) + target_link_libraries(utilcode_dac nativeresourcestring) + target_link_libraries(utilcode INTERFACE nativeresourcestring) + add_dependencies(utilcode_dac coreclrpal) + add_dependencies(utilcode_obj coreclrpal) +endif(CLR_CMAKE_HOST_UNIX) + + +if(CLR_CMAKE_HOST_WIN32) + target_compile_definitions(utilcodestaticnohost PRIVATE _CRTIMP=) # use static version of crt +endif(CLR_CMAKE_HOST_WIN32) + +set_target_properties(utilcode_dac PROPERTIES DAC_COMPONENT TRUE) +set_target_properties(utilcode_crossgen PROPERTIES CROSSGEN_COMPONENT TRUE) +target_compile_definitions(utilcode_dac PRIVATE SELF_NO_HOST) +target_compile_definitions(utilcodestaticnohost PRIVATE SELF_NO_HOST) +add_dependencies(utilcode_dac ${UTILCODE_DEPENDENCIES}) +add_dependencies(utilcode_obj ${UTILCODE_DEPENDENCIES}) +add_dependencies(utilcode_crossgen ${UTILCODE_DEPENDENCIES}) +add_dependencies(utilcodestaticnohost ${UTILCODE_DEPENDENCIES}) +target_precompile_headers(utilcode_dac PRIVATE [["stdafx.h"]]) +target_precompile_headers(utilcode_obj PRIVATE [["stdafx.h"]]) +target_precompile_headers(utilcode_crossgen PRIVATE [["stdafx.h"]]) +target_precompile_headers(utilcodestaticnohost PRIVATE [["stdafx.h"]]) diff --git a/src/coreclr/utilcode/arraylist.cpp b/src/coreclr/utilcode/arraylist.cpp new file mode 100644 index 00000000000..c6e538fd38d --- /dev/null +++ b/src/coreclr/utilcode/arraylist.cpp @@ -0,0 +1,224 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" + +#include "arraylist.h" +#include "utilcode.h" +#include "ex.h" + +// +// ArrayList is a simple class which is used to contain a growable +// list of pointers, stored in chunks. Modification is by appending +// only currently. Access is by index (efficient if the number of +// elements stays small) and iteration (efficient in all cases). +// +// An important property of an ArrayList is that the list remains +// coherent while it is being modified (appended to). This means that readers +// never need to lock when accessing it. (Locking is necessary among multiple +// writers, however.) +// + +void ArrayListBase::Clear() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + ArrayListBlock *block = m_firstBlock.m_next; + while (block != NULL) + { + ArrayListBlock *next = block->m_next; + delete [] block; + block = next; + } + m_firstBlock.m_next = 0; + m_count = 0; +} + +PTR_VOID * ArrayListBase::GetPtr(DWORD index) const +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + SUPPORTS_DAC; + + _ASSERTE(index < m_count); + + ArrayListBlock *b = (ArrayListBlock*)&m_firstBlock; + while (index >= b->m_blockSize) + { + PREFIX_ASSUME(b->m_next != NULL); + index -= b->m_blockSize; + b = b->m_next; + } + + return b->m_array + index; +} + +#ifndef DACCESS_COMPILE + +HRESULT ArrayListBase::Append(void *element) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + ArrayListBlock *b = (ArrayListBlock*)&m_firstBlock; + DWORD count = m_count; + + while (count >= b->m_blockSize) + { + count -= b->m_blockSize; + + if (b->m_next == NULL) + { + _ASSERTE(count == 0); + + DWORD nextSize = b->m_blockSize * 2; + + ArrayListBlock *bNew = (ArrayListBlock *) + new (nothrow) BYTE [sizeof(ArrayListBlock) + nextSize * sizeof(void*)]; + + if (bNew == NULL) + return E_OUTOFMEMORY; + + bNew->m_next = NULL; + bNew->m_blockSize = nextSize; + + b->m_next = bNew; + } + + b = b->m_next; + } + + b->m_array[count] = element; + + m_count++; + + return S_OK; +} + +#endif // #ifndef DACCESS_COMPILE + +DWORD ArrayListBase::FindElement(DWORD start, PTR_VOID element) const +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + DWORD index = start; + + _ASSERTE(index <= m_count); + + ArrayListBlock *b = (ArrayListBlock*)&m_firstBlock; + + // + // Skip to the block containing start. + // index should be the index of start in the block. + // + + while (b != NULL && index >= b->m_blockSize) + { + index -= b->m_blockSize; + b = b->m_next; + } + + // + // Adjust start to be the index of the start of the block + // + + start -= index; + + // + // Compute max number of entries from the start of the block + // + + DWORD max = m_count - start; + + while (b != NULL) + { + // + // Compute end of search in this block - either end of the block + // or end of the array + // + + DWORD blockMax; + if (max < b->m_blockSize) + blockMax = max; + else + blockMax = b->m_blockSize; + + // + // Scan for element, until the end. + // + + while (index < blockMax) + { + if (b->m_array[index] == element) + return start + index; + index++; + } + + // + // Otherwise, increment block start index, decrement max count, + // reset index, and go to the next block (if any) + // + + start += b->m_blockSize; + max -= b->m_blockSize; + index = 0; + b = b->m_next; + } + + return (DWORD) NOT_FOUND; +} + +BOOL ArrayListBase::Iterator::Next() +{ + LIMITED_METHOD_DAC_CONTRACT; + + ++m_index; + + if (m_index >= m_remaining) + return FALSE; + + if (m_index >= m_block->m_blockSize) + { + m_remaining -= m_block->m_blockSize; + m_index -= m_block->m_blockSize; + m_total += m_block->m_blockSize; + m_block = m_block->m_next; + } + + return TRUE; +} + +#ifdef DACCESS_COMPILE + +void +ArrayListBase::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + + // Assume that 'this' is enumerated, either explicitly + // or because this class is embedded in another. + + PTR_ArrayListBlock block = m_firstBlock.m_next; + while (block.IsValid()) + { + block.EnumMem(); + block = block->m_next; + } +} + +#endif // #ifdef DACCESS_COMPILE diff --git a/src/coreclr/utilcode/bitvector.cpp b/src/coreclr/utilcode/bitvector.cpp new file mode 100644 index 00000000000..b4c53aa96ed --- /dev/null +++ b/src/coreclr/utilcode/bitvector.cpp @@ -0,0 +1,405 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/***************************************************************************/ +/* BitVector.cpp */ +/***************************************************************************/ +// Routines to support a growable bitvector +/***************************************************************************/ + +#include "stdafx.h" +#include <memory.h> + +#include "utilcode.h" + +#include "bitvector.h" + +#if USE_BITVECTOR + +int BitVector::NumBits() const +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + int count = 0; + ChunkType hiChunk; + + if (isBig()) + { + unsigned maxNonZero = 0; + for (unsigned i=1; (i < m_vals.GetLength()); i++) + { + if (m_vals.m_chunks[i] != 0) + { + maxNonZero = i; + } + } + count = (maxNonZero * CHUNK_BITS) - 1; + hiChunk = m_vals.m_chunks[maxNonZero]; + } + else + { + hiChunk = m_val; + } + + while (hiChunk > 0) + { + hiChunk <<= 1; + count++; + } + + _ASSERTE(count >= 0); + return count; +} + +void BitVector::doBigInit(ChunkType arg) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + m_vals.m_chunks[0] = arg; + m_vals.SetLength(1); +} + +void BitVector::doBigInit(const BitVector& arg) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + if (arg.isBig()) + { + memcpy(m_vals.m_chunks, arg.m_vals.m_chunks, (sizeof(ChunkType) * arg.m_vals.GetLength())); + m_vals.SetLength(arg.m_vals.GetLength()); + } + else + { + m_val = arg.m_val; + } +} + +void BitVector::doBigLeftShiftAssign(unsigned shift) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + if ((m_val == 0) || (shift == 0)) // Zero is a special case, don't need to do anything + return; + + unsigned numWords = shift / CHUNK_BITS; + unsigned numBits = shift % CHUNK_BITS; + + // + // Change to Big representation + // + toBig(); + + int from = m_vals.GetLength()-1; + int to = from + numWords; + unsigned newlen = to + 1; + + ChunkType topBits = 0; + if (numBits > 0) + { + topBits = m_vals.m_chunks[from] >> (CHUNK_BITS - numBits); + } + + if (topBits != 0 || numWords != 0) + { + if (topBits != 0) + { + m_vals.m_chunks[newlen] = topBits; + newlen++; + } + m_vals.SetLength(newlen); + } + + while (to >= 0) + { + m_vals.m_chunks[to] = (from >= 0) ? (m_vals.m_chunks[from] << numBits) : 0; + from--; + + if ((from >= 0) && (numBits > 0)) + { + m_vals.m_chunks[to] |= m_vals.m_chunks[from] >> (CHUNK_BITS - numBits); + } + to--; + } + + // Convert back to small format if necessary + if ((newlen == 1) && (m_vals.m_chunks[0] <= MaxVal)) + { + m_val = ChunkType(m_vals.m_chunks[0] << 1); + } +} + +void BitVector::doBigRightShiftAssign(unsigned shift) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + if ((m_val == 0) || (shift == 0)) // Zero is a special case, don't need to do anything + return; + + unsigned numWords = shift / CHUNK_BITS; + unsigned numBits = shift % CHUNK_BITS; + + // + // Change to Big representation + // + toBig(); + + unsigned from = numWords; + unsigned to = 0; + unsigned len = m_vals.GetLength(); + unsigned newlen = len - numWords; + + if (from >= len) + { + // we always encode zero in short form + m_val = 0; + } + else + { + m_vals.m_chunks[to] = (m_vals.m_chunks[from] >> numBits); + from++; + + while (from < len) + { + if (numBits > 0) + { + m_vals.m_chunks[to] |= m_vals.m_chunks[from] << (CHUNK_BITS - numBits); + } + to++; + + m_vals.m_chunks[to] = (m_vals.m_chunks[from] >> numBits); + from++; + } + + if ((newlen > 1) && (m_vals.m_chunks[newlen-1] == 0)) + { + newlen--; + } + + m_vals.SetLength(newlen); + + // Convert back to small format if necessary + if ((newlen == 1) && (m_vals.m_chunks[0] <= MaxVal)) + { + m_val = ChunkType(m_vals.m_chunks[0] << 1); + } + } +} + +void BitVector::doBigAndAssign(const BitVector& arg) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + // + // Change to Big representation + // + toBig(); + + if (arg.isBig()) + { + bool isZero = true; // until proven otherwise + unsigned myLen = m_vals.GetLength(); + unsigned argLen = arg.m_vals.GetLength(); + + if (myLen > argLen) + { + // shrink our length to match argLen + m_vals.SetLength(argLen); + myLen = argLen; + } + + for (unsigned i = 0; (i < myLen); i++) + { + ChunkType curChunk = m_vals.m_chunks[i] & arg.m_vals.m_chunks[i]; + m_vals.m_chunks[i] = curChunk; + if (curChunk != 0) + isZero = false; + } + + if (isZero) + { + // we always encode zero in short form + m_val = 0; + } + } + else + { + m_val = (m_vals.m_chunks[0] << 1) & arg.m_val; + } +} + +void BitVector::doBigOrAssign(const BitVector& arg) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + // + // Change to Big representation + // + toBig(); + + if (arg.isBig()) + { + unsigned myLen = m_vals.GetLength(); + unsigned argLen = arg.m_vals.GetLength(); + + if (myLen < argLen) + { + // expand our length to match argLen and zero init + memset(m_vals.m_chunks + myLen, 0, sizeof(ChunkType) * (argLen - myLen)); + m_vals.SetLength(argLen); + myLen = argLen; + } + + for(unsigned i = 0; ((i < myLen) && (i < argLen)); i++) + { + m_vals.m_chunks[i] |= arg.m_vals.m_chunks[i]; + } + } + else + { + m_vals.m_chunks[0] |= arg.smallBits(); + } +} + +void BitVector::doBigDiffAssign(const BitVector& arg) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + // + // Change to Big representation + // + toBig(); + + unsigned myLen = m_vals.GetLength(); + unsigned argLen = arg.m_vals.GetLength(); + bool isZero = true; // until proven otherwise + + for (unsigned i = 0; (i < myLen); i++) + { + ChunkType nextChunk = m_vals.m_chunks[i]; + if (i < argLen) + { + nextChunk &= ~arg.m_vals.m_chunks[i]; + m_vals.m_chunks[i] = nextChunk; + } + else if (i == 0) + { + nextChunk &= ~arg.smallBits(); + m_vals.m_chunks[i] = nextChunk; + } + + if (nextChunk != 0) + isZero = false; + } + + if (isZero) + { + // we always encode zero in short form + m_val = 0; + } +} + +BOOL BitVector::doBigEquals(const BitVector& arg) const +{ + CONTRACT(BOOL) + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + unsigned myLen = m_vals.GetLength(); + unsigned argLen = arg.m_vals.GetLength(); + unsigned maxLen = (myLen >= argLen) ? myLen : argLen; + + for (unsigned i=0; (i < maxLen); i++) + { + ChunkType myVal = 0; + ChunkType argVal = 0; + + if (i < myLen) + myVal = m_vals.m_chunks[i]; + + if (i < argLen) + argVal = arg.m_vals.m_chunks[i]; + + if (i == 0) + { + if (myLen == 0) + myVal = smallBits(); + if (argLen == 0) + argVal = arg.smallBits(); + } + + if (myVal != argVal) + RETURN false; + } + RETURN true; +} + +BOOL BitVector::doBigIntersect(const BitVector& arg) const +{ + CONTRACT(BOOL) + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + unsigned myLen = m_vals.GetLength(); + unsigned argLen = arg.m_vals.GetLength(); + unsigned minLen = (myLen <= argLen) ? myLen : argLen; + + for (unsigned i=0; (i <= minLen); i++) + { + ChunkType myVal = 0; + ChunkType argVal = 0; + + if (i < myLen) + myVal = m_vals.m_chunks[i]; + + if (i < argLen) + argVal = arg.m_vals.m_chunks[i]; + + if (i == 0) + { + if (myLen == 0) + myVal = smallBits(); + if (argLen == 0) + argVal = arg.smallBits(); + } + + if ((myVal & argVal) != 0) + RETURN true; + } + RETURN false; +} + +#endif // USE_BITVECTOR diff --git a/src/coreclr/utilcode/ccomprc.cpp b/src/coreclr/utilcode/ccomprc.cpp new file mode 100644 index 00000000000..1b38edf419e --- /dev/null +++ b/src/coreclr/utilcode/ccomprc.cpp @@ -0,0 +1,725 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" // Standard header. +#include <utilcode.h> // Utility helpers. +#include <corerror.h> + +#include "../dlls/mscorrc/resource.h" +#ifdef HOST_UNIX +#include "resourcestring.h" +#define NATIVE_STRING_RESOURCE_NAME mscorrc +__attribute__((visibility("default"))) DECLARE_NATIVE_STRING_RESOURCE_TABLE(NATIVE_STRING_RESOURCE_NAME); +#endif +#include "sstring.h" +#include "stringarraylist.h" +#include "corpriv.h" + +#include <stdlib.h> + +// External prototypes. +extern void* GetClrModuleBase(); + +//***************************************************************************** +// Do the mapping from an langId to an hinstance node +//***************************************************************************** +HRESOURCEDLL CCompRC::LookupNode(LocaleID langId, BOOL &fMissing) +{ + LIMITED_METHOD_CONTRACT; + + if (m_pHash == NULL) return NULL; + +// Linear search + int i; + for(i = 0; i < m_nHashSize; i ++) { + if (m_pHash[i].IsSet() && m_pHash[i].HasID(langId)) { + return m_pHash[i].GetLibraryHandle(); + } + if (m_pHash[i].IsMissing() && m_pHash[i].HasID(langId)) + { + fMissing = TRUE; + return NULL; + } + } + + return NULL; +} + +//***************************************************************************** +// Add a new node to the map and return it. +//***************************************************************************** +const int MAP_STARTSIZE = 7; +const int MAP_GROWSIZE = 5; + +HRESULT CCompRC::AddMapNode(LocaleID langId, HRESOURCEDLL hInst, BOOL fMissing) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END; + + + if (m_pHash == NULL) { + m_pHash = new (nothrow)CCulturedHInstance[MAP_STARTSIZE]; + if (m_pHash==NULL) + return E_OUTOFMEMORY; + m_nHashSize = MAP_STARTSIZE; + } + +// For now, place in first open slot + int i; + for(i = 0; i < m_nHashSize; i ++) { + if (!m_pHash[i].IsSet() && !m_pHash[i].IsMissing()) { + if (fMissing) + { + m_pHash[i].SetMissing(langId); + } + else + { + m_pHash[i].Set(langId,hInst); + } + + return S_OK; + } + } + +// Out of space, regrow + CCulturedHInstance * pNewHash = new (nothrow)CCulturedHInstance[m_nHashSize + MAP_GROWSIZE]; + if (pNewHash) + { + memcpy(pNewHash, m_pHash, sizeof(CCulturedHInstance) * m_nHashSize); + delete [] m_pHash; + m_pHash = pNewHash; + if (fMissing) + { + m_pHash[m_nHashSize].SetMissing(langId); + } + else + { + m_pHash[m_nHashSize].Set(langId,hInst); + } + m_nHashSize += MAP_GROWSIZE; + } + else + return E_OUTOFMEMORY; + return S_OK; +} + +//***************************************************************************** +// Initialize +//***************************************************************************** +LPCWSTR CCompRC::m_pDefaultResource = W("mscorrc.dll"); + +HRESULT CCompRC::Init(LPCWSTR pResourceFile) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END; + + // This function is called during Watson process. We need to make sure + // that this function is restartable. + // + // Make sure to NEVER null out the function callbacks in the Init + // function. They get set for the "Default CCompRC" during EEStartup + // and we want to make sure we don't wipe them out. + + if (m_pResourceFile == NULL) + { + if(pResourceFile) + { + NewArrayHolder<WCHAR> pwszResourceFile(NULL); + + DWORD lgth = (DWORD) wcslen(pResourceFile) + 1; + pwszResourceFile = new(nothrow) WCHAR[lgth]; + if (pwszResourceFile) + { + wcscpy_s(pwszResourceFile, lgth, pResourceFile); + LPCWSTR pFile = pwszResourceFile.Extract(); + if (InterlockedCompareExchangeT(&m_pResourceFile, pFile, NULL) != NULL) + { + delete [] pFile; + } + } + } + else + InterlockedCompareExchangeT(&m_pResourceFile, m_pDefaultResource, NULL); + } + + if (m_pResourceFile == NULL) + { + return E_OUTOFMEMORY; + } + + if (m_csMap == NULL) + { + // NOTE: there are times when the debugger's helper thread is asked to do a favor for another thread in the + // process. Typically, this favor involves putting up a dialog for the user. Putting up a dialog usually ends + // up involving the CCompRC code since (of course) the strings in the dialog are in a resource file. Thus, the + // debugger's helper thread will attempt to acquire this CRST. This is okay, since the helper thread only does + // these favors for other threads when there is no debugger attached. Thus, there are no deadlock hazards with + // this lock, and its safe for the helper thread to take, so this CRST is marked with CRST_DEBUGGER_THREAD. + CRITSEC_COOKIE csMap = ClrCreateCriticalSection(CrstCCompRC, + (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN)); + + if (csMap) + { + if (InterlockedCompareExchangeT(&m_csMap, csMap, NULL) != NULL) + { + ClrDeleteCriticalSection(csMap); + } + } + } + + if (m_csMap == NULL) + return E_OUTOFMEMORY; + + return S_OK; +} + +void CCompRC::SetResourceCultureCallbacks( + FPGETTHREADUICULTURENAMES fpGetThreadUICultureNames, + FPGETTHREADUICULTUREID fpGetThreadUICultureId) +{ + LIMITED_METHOD_CONTRACT; + + m_fpGetThreadUICultureNames = fpGetThreadUICultureNames; + m_fpGetThreadUICultureId = fpGetThreadUICultureId; +} + +void CCompRC::GetResourceCultureCallbacks( + FPGETTHREADUICULTURENAMES* fpGetThreadUICultureNames, + FPGETTHREADUICULTUREID* fpGetThreadUICultureId) +{ + LIMITED_METHOD_CONTRACT; + + if(fpGetThreadUICultureNames) + *fpGetThreadUICultureNames=m_fpGetThreadUICultureNames; + + if(fpGetThreadUICultureId) + *fpGetThreadUICultureId=m_fpGetThreadUICultureId; +} + +void CCompRC::Destroy() +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + } + CONTRACTL_END; + + // Free all resource libraries + + //***************************************************************************** + // Free the loaded library if we ever loaded it and only if we are not on + // Win 95 which has a known bug with DLL unloading (it randomly unloads a + // dll on shut down, not necessarily the one you asked for). This is done + // only in debug mode to make coverage runs accurate. + //***************************************************************************** + +#if defined(_DEBUG) + if (m_Primary.GetLibraryHandle()) { + ::FreeLibrary(m_Primary.GetLibraryHandle()); + } + + if (m_pHash != NULL) { + int i; + for(i = 0; i < m_nHashSize; i ++) { + if (m_pHash[i].GetLibraryHandle() != NULL) { + ::FreeLibrary(m_pHash[i].GetLibraryHandle()); + break; + } + } + } +#endif + + // destroy map structure + if(m_pResourceFile != m_pDefaultResource) + delete [] m_pResourceFile; + m_pResourceFile = NULL; + + if(m_csMap) { + ClrDeleteCriticalSection(m_csMap); + ZeroMemory(&(m_csMap), sizeof(CRITSEC_COOKIE)); + } + + if(m_pHash != NULL) { + delete [] m_pHash; + m_pHash = NULL; + } +} + + +//***************************************************************************** +// Initialization is done lazily, for backwards compatibility "mscorrc.dll" +// is consider the default location for all strings that use CCompRC. +// An instance value for CCompRC can be created to load resources from a different +// resource dll. +//***************************************************************************** +LONG CCompRC::m_dwDefaultInitialized = 0; +CCompRC CCompRC::m_DefaultResourceDll; + +CCompRC* CCompRC::GetDefaultResourceDll() +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + } + CONTRACTL_END; + + if (m_dwDefaultInitialized) + return &m_DefaultResourceDll; + + if(FAILED(m_DefaultResourceDll.Init(NULL))) + { + return NULL; + } + m_dwDefaultInitialized = 1; + + return &m_DefaultResourceDll; +} + +//***************************************************************************** +//***************************************************************************** + +// String resouces packaged as PE files only exist on Windows +#ifdef HOST_WINDOWS +HRESULT CCompRC::GetLibrary(LocaleID langId, HRESOURCEDLL* phInst) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + PRECONDITION(phInst != NULL); + } + CONTRACTL_END; + + HRESULT hr = E_FAIL; + HRESOURCEDLL hInst = 0; +#ifndef DACCESS_COMPILE + HRESOURCEDLL hLibInst = 0; //Holds early library instance + BOOL fLibAlreadyOpen = FALSE; //Determine if we can close the opened library. +#endif + + // Try to match the primary entry, or else use the primary if we don't care. + if (m_Primary.IsSet()) + { + if (langId == UICULTUREID_DONTCARE || m_Primary.HasID(langId)) + { + hInst = m_Primary.GetLibraryHandle(); + hr = S_OK; + } + } + else if(m_Primary.IsMissing()) + { + // If primary is missing then the hash will not have anything either + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } +#ifndef DACCESS_COMPILE + // If this is the first visit, we must set the primary entry + else + { + // Don't immediately return if LoadLibrary fails so we can indicate the file was missing + hr = LoadLibrary(&hLibInst); + // If it's a transient failure, don't cache the failure + if (FAILED(hr) && Exception::IsTransient(hr)) + { + return hr; + } + + CRITSEC_Holder csh (m_csMap); + // As we expected + if (!m_Primary.IsSet() && !m_Primary.IsMissing()) + { + hInst = hLibInst; + if (SUCCEEDED(hr)) + { + m_Primary.Set(langId,hLibInst); + } + else + { + m_Primary.SetMissing(langId); + } + } + + // Someone got into this critical section before us and set the primary already + else if (m_Primary.HasID(langId)) + { + hInst = m_Primary.GetLibraryHandle(); + fLibAlreadyOpen = TRUE; + } + + // If neither case is true, someone got into this critical section before us and + // set the primary to other than the language we want... + else + { + fLibAlreadyOpen = TRUE; + } + + IfFailRet(hr); + + if (fLibAlreadyOpen) + { + FreeLibrary(hLibInst); + fLibAlreadyOpen = FALSE; + } + } +#endif + + // If we enter here, we know that the primary is set to something other than the + // language we want - multiple languages use the hash table + if (hInst == NULL && !m_Primary.IsMissing()) + { + // See if the resource exists in the hash table + { + CRITSEC_Holder csh(m_csMap); + BOOL fMissing = FALSE; + hInst = LookupNode(langId, fMissing); + if (fMissing == TRUE) + { + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + goto Exit; + } + } + +#ifndef DACCESS_COMPILE + // If we didn't find it, we have to load the library and insert it into the hash + if (hInst == NULL) + { + hr = LoadLibrary(&hLibInst); + // If it's a transient failure, don't cache the failure + if (FAILED(hr) && Exception::IsTransient(hr)) + { + return hr; + } + { + CRITSEC_Holder csh (m_csMap); + + // Double check - someone may have entered this section before us + BOOL fMissing = FALSE; + hInst = LookupNode(langId, fMissing); + if (hInst == NULL && !fMissing) + { + if (SUCCEEDED(hr)) + { + hInst = hLibInst; + hr = AddMapNode(langId, hInst); + } else + { + HRESULT hrLoadLibrary = hr; + hr = AddMapNode(langId, hInst, TRUE /* fMissing */); + if (SUCCEEDED(hr)) + { + hr = hrLoadLibrary; + } + } + } + else + { + fLibAlreadyOpen = TRUE; + } + } + + if (fLibAlreadyOpen || FAILED(hr)) + { + FreeLibrary(hLibInst); + } + } + + // We found the node, so set hr to be a success. + else + { + hr = S_OK; + } +#endif // DACCESS_COMPILE + } +Exit: + *phInst = hInst; + return hr; +} +#endif // HOST_WINDOWS + +//***************************************************************************** +// Load the string +// We load the localized libraries and cache the handle for future use. +// Mutliple threads may call this, so the cache structure is thread safe. +//***************************************************************************** +HRESULT CCompRC::LoadString(ResourceCategory eCategory, UINT iResourceID, __out_ecount(iMax) LPWSTR szBuffer, int iMax, int *pcwchUsed) +{ + WRAPPER_NO_CONTRACT; + LocaleIDValue langIdValue; + LocaleID langId; + // Must resolve current thread's langId to a dll. + if(m_fpGetThreadUICultureId) { + int ret = (*m_fpGetThreadUICultureId)(&langIdValue); + + // Callback can't return 0, since that indicates empty. + // To indicate empty, callback should return UICULTUREID_DONTCARE + _ASSERTE(ret != 0); + + if (ret == 0) + return E_UNEXPECTED; + langId=langIdValue; + + } + else { + langId = UICULTUREID_DONTCARE; + } + + + return LoadString(eCategory, langId, iResourceID, szBuffer, iMax, pcwchUsed); +} + +HRESULT CCompRC::LoadString(ResourceCategory eCategory, LocaleID langId, UINT iResourceID, __out_ecount(iMax) LPWSTR szBuffer, int iMax, int *pcwchUsed) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + } + CONTRACTL_END; + +#ifdef HOST_WINDOWS + HRESULT hr; + HRESOURCEDLL hInst = 0; //instance of cultured resource dll + int length; + + hr = GetLibrary(langId, &hInst); + + if (SUCCEEDED(hr)) + { + // Now that we have the proper dll handle, load the string + _ASSERTE(hInst != NULL); + + length = ::WszLoadString(hInst, iResourceID, szBuffer, iMax); + if(length > 0) + { + if(pcwchUsed) + { + *pcwchUsed = length; + } + return (S_OK); + } + if(GetLastError()==ERROR_SUCCESS) + hr=HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + else + hr=HRESULT_FROM_GetLastError(); + } + + // Return an empty string to save the people with a bad error handling + if (szBuffer && iMax) + *szBuffer = W('\0'); + + return hr; +#else // HOST_WINDOWS + return LoadNativeStringResource(NATIVE_STRING_RESOURCE_TABLE(NATIVE_STRING_RESOURCE_NAME), iResourceID, + szBuffer, iMax, pcwchUsed); +#endif // HOST_WINDOWS +} + +#ifndef DACCESS_COMPILE + +// String resouces packaged as PE files only exist on Windows +#ifdef HOST_WINDOWS +HRESULT CCompRC::LoadResourceFile(HRESOURCEDLL * pHInst, LPCWSTR lpFileName) +{ + DWORD dwLoadLibraryFlags; + if(m_pResourceFile == m_pDefaultResource) + { + dwLoadLibraryFlags = LOAD_LIBRARY_AS_DATAFILE; + } + else + { + dwLoadLibraryFlags = 0; + } + + if((*pHInst = WszLoadLibraryEx(lpFileName, NULL, dwLoadLibraryFlags)) == NULL) + { + return HRESULT_FROM_GetLastError(); + } + return S_OK; +} + +//***************************************************************************** +// Load the library for this thread's current language +// Called once per language. +// Search order is: +// 1. Dll in localized path (<dir passed>\<lang name (en-US format)>\mscorrc.dll) +// 2. Dll in localized (parent) path (<dir passed>\<lang name> (en format)\mscorrc.dll) +// 3. Dll in root path (<dir passed>\mscorrc.dll) +// 4. Dll in current path (<current dir>\mscorrc.dll) +//***************************************************************************** +HRESULT CCompRC::LoadLibraryHelper(HRESOURCEDLL *pHInst, + SString& rcPath) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + } + CONTRACTL_END; + + HRESULT hr = E_FAIL; + + + _ASSERTE(m_pResourceFile != NULL); + + // must initialize before calling SString::Empty() + SString::Startup(); + + // Try and get both the culture fallback sequence + + StringArrayList cultureNames; + + if (m_fpGetThreadUICultureNames) + { + hr = (*m_fpGetThreadUICultureNames)(&cultureNames); + } + else + { + EX_TRY + { + cultureNames.Append(SString::Empty()); + } + EX_CATCH_HRESULT(hr); + } + + if (hr == E_OUTOFMEMORY) + return hr; + EX_TRY + { + for (DWORD i=0; i< cultureNames.GetCount();i++) + { + SString& sLang = cultureNames[i]; + + PathString rcPathName(rcPath); + + if (!rcPathName.EndsWith(W("\\"))) + { + rcPathName.Append(W("\\")); + } + + if (!sLang.IsEmpty()) + { + rcPathName.Append(sLang); + rcPathName.Append(W("\\")); + rcPathName.Append(m_pResourceFile); + } + else + { + rcPathName.Append(m_pResourceFile); + } + + // Feedback for debugging to eliminate unecessary loads. + DEBUG_STMT(DbgWriteEx(W("Loading %s to load strings.\n"), rcPath.GetUnicode())); + + // Load the resource library as a data file, so that the OS doesn't have + // to allocate it as code. This only works so long as the file contains + // only strings. + hr = LoadResourceFile(pHInst, rcPathName); + if (SUCCEEDED(hr)) + { + break; + } + } + } + EX_CATCH_HRESULT(hr); + + // Last ditch search effort in current directory + if (FAILED(hr)) + { + hr = LoadResourceFile(pHInst, m_pResourceFile); + } + + return hr; +} + +// Two-stage approach: +// First try module directory, then try CORSystemDirectory for default resource +HRESULT CCompRC::LoadLibraryThrows(HRESOURCEDLL * pHInst) +{ + CONTRACTL + { + GC_NOTRIGGER; + THROWS; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + _ASSERTE(pHInst != NULL); + +#ifdef CROSSGEN_COMPILE + // The resources are embeded into the .exe itself for crossgen + *pHInst = (HINSTANCE)GetClrModuleBase(); +#else + +#ifdef SELF_NO_HOST + _ASSERTE(!"CCompRC::LoadLibraryThrows not implemented for SELF_NO_HOST"); + hr = E_NOTIMPL; +#else // SELF_NO_HOST + PathString rcPath; // Path to resource DLL. + + // Try first in the same directory as this dll. + + hr = GetClrModuleDirectory(rcPath); + if (FAILED(hr)) + return hr; + + hr = LoadLibraryHelper(pHInst, rcPath); +#endif + +#endif // CROSSGEN_COMPILE + + return hr; +} + +HRESULT CCompRC::LoadLibrary(HRESOURCEDLL * pHInst) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; +#ifdef MODE_PREEMPTIVE + MODE_PREEMPTIVE; +#endif + } + CONTRACTL_END; + + HRESULT hr = S_OK; + EX_TRY + { + hr = LoadLibraryThrows(pHInst); + } + EX_CATCH_HRESULT(hr); + + return hr; +} +#endif // DACCESS_COMPILE +#endif //HOST_WINDOWS diff --git a/src/coreclr/utilcode/check.cpp b/src/coreclr/utilcode/check.cpp new file mode 100644 index 00000000000..4d7a0c74163 --- /dev/null +++ b/src/coreclr/utilcode/check.cpp @@ -0,0 +1,281 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +//================================================================================ +// Assertion checking infrastructure +//================================================================================ + +#include "stdafx.h" +#include <check.h> +#include <sstring.h> +#include <ex.h> +#include <contract.h> + +#ifdef _DEBUG +size_t CHECK::s_cLeakedBytes = 0; +size_t CHECK::s_cNumFailures = 0; + +thread_local LONG CHECK::t_count; +#endif + +BOOL CHECK::s_neverEnforceAsserts = 0; + + +// Currently used for scan SPECIAL_HOLDER_* trickery +DEBUG_NOINLINE BOOL CHECK::EnforceAssert_StaticCheckOnly() +{ + return s_neverEnforceAsserts; +} + +#ifdef ENABLE_CONTRACTS_IMPL +// Need a place to stick this, there is no contract.cpp... +BOOL BaseContract::s_alwaysEnforceContracts = 1; + +#define SPECIALIZE_CONTRACT_VIOLATION_HOLDER(mask) \ +template<> void ContractViolationHolder<mask>::Enter() \ +{ \ + SCAN_SCOPE_BEGIN; \ + ANNOTATION_VIOLATION(mask); \ + EnterInternal(mask); \ +}; + +#define SPECIALIZE_AUTO_CLEANUP_CONTRACT_VIOLATION_HOLDER(mask) \ +template<> AutoCleanupContractViolationHolder<mask>::AutoCleanupContractViolationHolder(BOOL fEnterViolation) \ +{ \ + SCAN_SCOPE_BEGIN; \ + ANNOTATION_VIOLATION(mask); \ + EnterInternal(fEnterViolation ? mask : 0); \ +}; + +#define SPECIALIZED_VIOLATION(mask) \ + SPECIALIZE_CONTRACT_VIOLATION_HOLDER(mask); \ + SPECIALIZE_AUTO_CLEANUP_CONTRACT_VIOLATION_HOLDER(mask) + +// There is a special case that requires 0... Why??? Who knows, let's fix that case. + +SPECIALIZED_VIOLATION(0); + +// Basic Specializations + +SPECIALIZED_VIOLATION(AllViolation); +SPECIALIZED_VIOLATION(ThrowsViolation); +SPECIALIZED_VIOLATION(GCViolation); +SPECIALIZED_VIOLATION(ModeViolation); +SPECIALIZED_VIOLATION(FaultViolation); +SPECIALIZED_VIOLATION(FaultNotFatal); +SPECIALIZED_VIOLATION(HostViolation); +SPECIALIZED_VIOLATION(TakesLockViolation); +SPECIALIZED_VIOLATION(LoadsTypeViolation); + +// Other Specializations used by the RUNTIME, if you get a compile time error you need +// to add the specific specialization that you are using here. + +SPECIALIZED_VIOLATION(ThrowsViolation|GCViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|GCViolation|TakesLockViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|ModeViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultNotFatal); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|TakesLockViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation|TakesLockViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation|GCViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation|GCViolation|TakesLockViolation|LoadsTypeViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation|GCViolation|ModeViolation); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation|GCViolation|ModeViolation|FaultNotFatal); +SPECIALIZED_VIOLATION(ThrowsViolation|FaultViolation|GCViolation|ModeViolation|FaultNotFatal|TakesLockViolation); +SPECIALIZED_VIOLATION(GCViolation|FaultViolation); +SPECIALIZED_VIOLATION(GCViolation|FaultNotFatal|ModeViolation); +SPECIALIZED_VIOLATION(GCViolation|FaultNotFatal|TakesLockViolation); +SPECIALIZED_VIOLATION(GCViolation|FaultNotFatal|TakesLockViolation|ModeViolation); +SPECIALIZED_VIOLATION(GCViolation|ModeViolation); +SPECIALIZED_VIOLATION(FaultViolation|FaultNotFatal); +SPECIALIZED_VIOLATION(FaultNotFatal|TakesLockViolation); + + + +#undef SPECIALIZED_VIOLATION +#undef SPECIALIZE_AUTO_CLEANUP_CONTRACT_VIOLATION_HOLDER +#undef SPECIALIZE_CONTRACT_VIOLATION_HOLDER + +#endif + +// Trigger triggers the actual check failure. The trigger may provide a reason +// to include in the failure message. + +void CHECK::Trigger(LPCSTR reason) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + const char *messageString = NULL; + NewHolder<StackScratchBuffer> pScratch(NULL); + NewHolder<StackSString> pMessage(NULL); + + EX_TRY + { + FAULT_NOT_FATAL(); + + pScratch = new StackScratchBuffer(); + pMessage = new StackSString(); + + pMessage->AppendASCII(reason); + pMessage->AppendASCII(": "); + if (m_message != NULL) + pMessage->AppendASCII((m_message != (LPCSTR)1) ? m_message : "<runtime check failure>"); + +#if _DEBUG + pMessage->AppendASCII("FAILED: "); + pMessage->AppendASCII(m_condition); +#endif + + messageString = pMessage->GetANSI(*pScratch); + } + EX_CATCH + { + messageString = "<exception occurred while building failure description>"; + } + EX_END_CATCH(SwallowAllExceptions); + +#if _DEBUG + DbgAssertDialog((char*)m_file, m_line, (char *)messageString); +#else + OutputDebugStringA(messageString); + DebugBreak(); +#endif + +} + +#ifdef _DEBUG +// Setup records context info after a failure. + +void CHECK::Setup(LPCSTR message, LPCSTR condition, LPCSTR file, INT line) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + // + // It might be nice to collect all of the message here. But for now, we will just + // retain the innermost one. + // + + if (m_message == NULL) + { + m_message = message; + m_condition = condition; + m_file = file; + m_line = line; + } + +#ifdef _DEBUG + else if (IsInAssert()) + { + EX_TRY + { + FAULT_NOT_FATAL(); + // Try to build a stack of condition failures + + StackSString context; + context.Printf("%s\n\t%s%s FAILED: %s\n\t\t%s, line: %d", + m_condition, + message && *message ? message : "", + message && *message ? ": " : "", + condition, + file, line); + + m_condition = AllocateDynamicMessage(context); + } + EX_CATCH + { + // If anything goes wrong, we don't push extra context + } + EX_END_CATCH(SwallowAllExceptions) + } +#endif + +#if defined(_DEBUG_IMPL) + if (IsInAssert() && IsDebuggerPresent()) + { + DebugBreak(); + } +#endif +} + +LPCSTR CHECK::FormatMessage(LPCSTR messageFormat, ...) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + LPCSTR result = NULL; + + // We never delete this allocated string. A dtor would conflict with places + // we use this around SEH stuff. We could have some fancy stack-based allocator, + // but that's too much work for now. In fact we believe that leaking is a reasonable + // policy, since allocations will only happen on a failed assert, and a failed assert + // will generally be fatal to the process. + + // The most common place for format strings will be in an assert; in + // which case we don't care about leaking. + // But to be safe, if we're not-inside an assert, then we'll just use + // the format string literal to avoid allocated (and leaking) any memory. + + CHECK chk; + if (!chk.IsInAssert()) + result = messageFormat; + else + { + // This path is only run in debug. TakesLockViolation suppresses + // problems with SString below. + CONTRACT_VIOLATION(FaultNotFatal|TakesLockViolation); + + EX_TRY + { + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + // Format it. + va_list args; + va_start( args, messageFormat); + + SString s; + s.VPrintf(messageFormat, args); + + va_end(args); + + result = AllocateDynamicMessage(s); + + } + EX_CATCH + { + // If anything goes wrong, just use the format string. + result = messageFormat; + } + EX_END_CATCH(SwallowAllExceptions) + } + + return result; +} + +LPCSTR CHECK::AllocateDynamicMessage(const SString &s) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + // Make a copy of it. + StackScratchBuffer buffer; + const char * pMsg = s.GetANSI(buffer); + + // Must copy that into our own field. + size_t len = strlen(pMsg) + 1; + char * p = new char[len]; + strcpy(p, pMsg); + + // But we'll keep counters of how much we're leaking for diagnostic purposes. + s_cLeakedBytes += len; + s_cNumFailures ++; + + // This should never fire. + // Note use an ASSERTE (not a check) to avoid a recursive deadlock. + _ASSERTE(s_cLeakedBytes < 10000 || !"Warning - check misuse - leaked over 10,000B due to unexpected usage pattern"); + return p; +} + +#endif diff --git a/src/coreclr/utilcode/clrconfig.cpp b/src/coreclr/utilcode/clrconfig.cpp new file mode 100644 index 00000000000..5142e395ac0 --- /dev/null +++ b/src/coreclr/utilcode/clrconfig.cpp @@ -0,0 +1,385 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// CLRConfig.cpp +// + +// +// Unified method of accessing configuration values from environment variables, +// registry and config file. See file:../inc/CLRConfigValues.h for details on how to add config values. +// +//***************************************************************************** + +#include "stdafx.h" +#include "clrconfig.h" + +#ifndef ERANGE +#define ERANGE 34 +#endif + +// +// Creating structs using the macro table in CLRConfigValues.h +// + +// These macros intialize ConfigDWORDInfo structs. +#define RETAIL_CONFIG_DWORD_INFO(symbol, name, defaultValue, description) \ + const CLRConfig::ConfigDWORDInfo CLRConfig::symbol = {name, defaultValue, CLRConfig::EEConfig_default}; +#define RETAIL_CONFIG_DWORD_INFO_EX(symbol, name, defaultValue, description, lookupOptions) \ + const CLRConfig::ConfigDWORDInfo CLRConfig::symbol = {name, defaultValue, lookupOptions}; + +// These macros intialize ConfigStringInfo structs. +#define RETAIL_CONFIG_STRING_INFO(symbol, name, description) \ + const CLRConfig::ConfigStringInfo CLRConfig::symbol = {name, CLRConfig::EEConfig_default}; +#define RETAIL_CONFIG_STRING_INFO_EX(symbol, name, description, lookupOptions) \ + const CLRConfig::ConfigStringInfo CLRConfig::symbol = {name, lookupOptions}; + +// TEMPORARY macros that intialize strings for config value accesses that haven't been moved over to +// CLRConfig yet. Once all accesses have been moved, these macros (and corresponding instantiations in +// file:../utilcode/CLRConfig.h) should be removed. +#define RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(symbol, name, description) \ + const LPCWSTR CLRConfig::symbol = name; +#define RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS(symbol, name, description) \ + const LPCWSTR CLRConfig::symbol = name; +// +// Debug versions of the macros +// +#ifdef _DEBUG + #define CONFIG_DWORD_INFO(symbol, name, defaultValue, description) \ + const CLRConfig::ConfigDWORDInfo CLRConfig::symbol = {name, defaultValue, CLRConfig::EEConfig_default}; + #define CONFIG_DWORD_INFO_EX(symbol, name, defaultValue, description, lookupOptions) \ + const CLRConfig::ConfigDWORDInfo CLRConfig::symbol = {name, defaultValue, lookupOptions}; + #define CONFIG_STRING_INFO(symbol, name, description) \ + const CLRConfig::ConfigStringInfo CLRConfig::symbol = {name, CLRConfig::EEConfig_default}; + #define CONFIG_STRING_INFO_EX(symbol, name, description, lookupOptions) \ + const CLRConfig::ConfigStringInfo CLRConfig::symbol = {name, lookupOptions}; + #define CONFIG_DWORD_INFO_DIRECT_ACCESS(symbol, name, description) \ + const LPCWSTR CLRConfig::symbol = name; + #define CONFIG_STRING_INFO_DIRECT_ACCESS(symbol, name, description) \ + const LPCWSTR CLRConfig::symbol = name; +#else + #define CONFIG_DWORD_INFO(symbol, name, defaultValue, description) + #define CONFIG_DWORD_INFO_EX(symbol, name, defaultValue, description, lookupOptions) + #define CONFIG_STRING_INFO(symbol, name, description) + #define CONFIG_STRING_INFO_EX(symbol, name, description, lookupOptions) + #define CONFIG_DWORD_INFO_DIRECT_ACCESS(symbol, name, description) + #define CONFIG_STRING_INFO_DIRECT_ACCESS(symbol, name, description) +#endif // _DEBUG + + // Now that we have defined what what the macros in file:../inc/CLRConfigValues.h mean, include it to generate the code. + #include "clrconfigvalues.h" + +#undef RETAIL_CONFIG_DWORD_INFO +#undef RETAIL_CONFIG_STRING_INFO +#undef RETAIL_CONFIG_DWORD_INFO_EX +#undef RETAIL_CONFIG_STRING_INFO_EX +#undef RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS +#undef RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS +#undef CONFIG_DWORD_INFO +#undef CONFIG_STRING_INFO +#undef CONFIG_DWORD_INFO_EX +#undef CONFIG_STRING_INFO_EX +#undef CONFIG_DWORD_INFO_DIRECT_ACCESS +#undef CONFIG_STRING_INFO_DIRECT_ACCESS + + +// +// Look up a DWORD config value. +// +// Arguments: +// * info - see file:../inc/CLRConfig.h for details. +// +// * useDefaultIfNotSet - if true, fall back to the default value if the value is not set. +// +// * acceptExplicitDefaultFromRegutil - if false, only accept a value returned by REGUTIL if it is +// different from the default value. This parameter is useful as a way to preserve existing +// behavior. +// +// * result - the result. +// +// Return value: +// * true for success, false otherwise. +// +// static +DWORD CLRConfig::GetConfigValue(const ConfigDWORDInfo & info, bool acceptExplicitDefaultFromRegutil, /* [Out] */ bool *isDefault) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + } + CONTRACTL_END; + + _ASSERTE (isDefault != nullptr); + + + // + // Set up REGUTIL options. + // + REGUTIL::CORConfigLevel level = GetConfigLevel(info.options); + BOOL prependCOMPlus = !CheckLookupOption(info, DontPrependCOMPlus_); + + DWORD resultMaybe; + HRESULT hr = REGUTIL::GetConfigDWORD_DontUse_(info.name, info.defaultValue, &resultMaybe, level, prependCOMPlus); + + if (!acceptExplicitDefaultFromRegutil) + { + // Ignore the default value even if it's set explicitly. + if (resultMaybe != info.defaultValue) + { + *isDefault = false; + return resultMaybe; + } + } + else + { + // If we are willing to accept the default value when it's set explicitly, + // checking the HRESULT here is sufficient. E_FAIL is returned when the + // default is used. + if (SUCCEEDED(hr)) + { + *isDefault = false; + return resultMaybe; + } + } + + *isDefault = true; + return info.defaultValue; +} + +// +// Look up a DWORD config value. +// +// Arguments: +// * info - see file:../inc/CLRConfig.h for details +// +// static +DWORD CLRConfig::GetConfigValue(const ConfigDWORDInfo & info) +{ + // We pass false for 'acceptExplicitDefaultFromRegutil' to maintain the existing behavior of this function. + // Callers who don't need that behavior should switch to the other version of this function and pass true. + bool unused; + return GetConfigValue(info, false /* acceptExplicitDefaultFromRegutil */, &unused); +} + +// +// Look up a String config value. +// +// Arguments: +// * info - see file:../inc/CLRConfig.h for details +// +// Return value: +// * Pointer to the string value, if found. You own the string that's returned. Returns NULL if the value +// is not found. +// +// static +LPWSTR CLRConfig::GetConfigValue(const ConfigStringInfo & info) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + } + CONTRACTL_END; + + LPWSTR result = NULL; + + // TODO: We swallow OOM exception here. Is this OK? + FAULT_NOT_FATAL(); + + // If this fails, result will stay NULL. + GetConfigValue(info, &result); + + return result; +} + +// +// Look up a string config value, passing it out through a pointer reference. +// +// Return value: +// * Reports out of memory errors (HRESULT E_OUTOFMEMORY). +// +// Arguments: +// * info - see file:../inc/CLRConfig.h for details +// * outVal - Set to the result string. You own the string that's returned. Set to NULL if the value is +// not found. +// +// static +HRESULT CLRConfig::GetConfigValue(const ConfigStringInfo & info, __deref_out_z LPWSTR * outVal) +{ + CONTRACT(HRESULT) { + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT (CONTRACT_RETURN E_OUTOFMEMORY); + POSTCONDITION(CheckPointer(outVal, NULL_OK)); // TODO: Should this check be *outVal instead of outVal? + } CONTRACT_END; + + LPWSTR result = NULL; + + + // + // Set up REGUTIL options. + // + REGUTIL::CORConfigLevel level = GetConfigLevel(info.options); + BOOL prependCOMPlus = !CheckLookupOption(info, DontPrependCOMPlus_); + + result = REGUTIL::GetConfigString_DontUse_(info.name, prependCOMPlus, level); + + if ((result != NULL) && CheckLookupOption(info, TrimWhiteSpaceFromStringValue)) + { + // If this fails, result remains untouched, so we'll just return the untrimmed + // value. + LPWSTR wszTrimmedResult = NULL; + if (SUCCEEDED(TrimWhiteSpace(result, &wszTrimmedResult)) && + (wszTrimmedResult != NULL)) + { + // wszTrimmedResult should be the result we return. Delete the untrimmed + // result. + delete [] result; + result = wszTrimmedResult; + } + } + + *outVal = result; + RETURN S_OK; +} + +// +// Check whether an option is specified (e.g. explicitly listed) in any location +// +// Arguments: +// * name - the name field of the desired ConfigDWORDInfo/ConfigStringInfo +// +// static +BOOL CLRConfig::IsConfigOptionSpecified(LPCWSTR name) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Check REGUTIL, both with and without the COMPlus_ prefix + { + LPWSTR result = NULL; + + result = REGUTIL::GetConfigString_DontUse_(name, TRUE); + if (result != NULL) + { + FreeConfigString(result); + return TRUE; + } + + result = REGUTIL::GetConfigString_DontUse_(name, FALSE); + if (result != NULL) + { + FreeConfigString(result); + return TRUE; + } + + } + + return FALSE; +} + +//--------------------------------------------------------------------------------------- +// +// Given an input string, returns a newly-allocated string equal to the input but with +// leading and trailing whitespace trimmed off. If input is already trimmed, or if +// trimming would result in an empty string, this function sets the output string to NULL +// +// Caller must free *pwszTrimmed if non-NULL +// +// Arguments: +// * wszOrig - String to trim +// * pwszTrimmed - [out]: On return, points to newly allocated, trimmed string (or +// NULL) +// +// Return Value: +// HRESULT indicating success or failure. +// +HRESULT CLRConfig::TrimWhiteSpace(LPCWSTR wszOrig, __deref_out_z LPWSTR * pwszTrimmed) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(wszOrig != NULL); + _ASSERTE(pwszTrimmed != NULL); + + // In case we return early, set [out] to NULL by default + *pwszTrimmed = NULL; + + // Get pointers into internal string that show where to do the trimming. + size_t cchOrig = wcslen(wszOrig); + if (!FitsIn<DWORD>(cchOrig)) + return COR_E_OVERFLOW; + DWORD cchAfterTrim = (DWORD) cchOrig; + LPCWSTR wszAfterTrim = wszOrig; + ::TrimWhiteSpace(&wszAfterTrim, &cchAfterTrim); + + // Is input string already trimmed? If so, save an allocation and just return. + if ((wszOrig == wszAfterTrim) && (cchOrig == cchAfterTrim)) + { + // Yup, just return success + return S_OK; + } + + if (cchAfterTrim == 0) + { + // After trimming, there's nothing left, so just return NULL + return S_OK; + } + + // Create a new buffer to hold a copy of the trimmed string. Caller will be + // responsible for this buffer if we return it. + NewArrayHolder<WCHAR> wszTrimmedCopy(new (nothrow) WCHAR[cchAfterTrim + 1]); + if (wszTrimmedCopy == NULL) + { + return E_OUTOFMEMORY; + } + + errno_t err = wcsncpy_s(wszTrimmedCopy, cchAfterTrim + 1, wszAfterTrim, cchAfterTrim); + if (err != 0) + { + return E_FAIL; + } + + // Successfully made a copy of the trimmed string. Return it. Caller will be responsible for + // deleting it. + wszTrimmedCopy.SuppressRelease(); + *pwszTrimmed = wszTrimmedCopy; + return S_OK; +} + + +// +// Deallocation function for code:CLRConfig::FreeConfigString +// +void CLRConfig::FreeConfigString(__in_z LPWSTR str) +{ + LIMITED_METHOD_CONTRACT; + + delete [] str; +} + +// +// Helper method to translate LookupOptions to REGUTIL::CORConfigLevel. +// +//static +REGUTIL::CORConfigLevel CLRConfig::GetConfigLevel(LookupOptions options) +{ + LIMITED_METHOD_CONTRACT; + + REGUTIL::CORConfigLevel level = (REGUTIL::CORConfigLevel) 0; + + if(CheckLookupOption(options, IgnoreEnv) == FALSE) + level = static_cast<REGUTIL::CORConfigLevel>(level | REGUTIL::COR_CONFIG_ENV); + + return static_cast<REGUTIL::CORConfigLevel>(level | REGUTIL::COR_CONFIG_USER | REGUTIL::COR_CONFIG_MACHINE); +} diff --git a/src/coreclr/utilcode/clrhelpers.cpp b/src/coreclr/utilcode/clrhelpers.cpp new file mode 100644 index 00000000000..98055909867 --- /dev/null +++ b/src/coreclr/utilcode/clrhelpers.cpp @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// This is file to compile 2 public "headers" from ..\inc directory (we have to include stdafx.h first). +// +/***************************************************************************/ + +#include "stdafx.h" + +#include "../inc/corhlpr.cpp" +#include "../inc/corhlprpriv.cpp" diff --git a/src/coreclr/utilcode/clrhost.cpp b/src/coreclr/utilcode/clrhost.cpp new file mode 100644 index 00000000000..188418fccdf --- /dev/null +++ b/src/coreclr/utilcode/clrhost.cpp @@ -0,0 +1,249 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +// + +#include "stdafx.h" + +#include "clrhost.h" +#include "utilcode.h" +#include "ex.h" +#include "clrnt.h" +#include "contract.h" + +#if HOST_WINDOWS +extern "C" IMAGE_DOS_HEADER __ImageBase; +#else +static void* pImageBase = NULL; +#endif + +void* GetClrModuleBase() +{ + LIMITED_METHOD_CONTRACT; + +#if HOST_WINDOWS + return (void*)&__ImageBase; +#else // HOST_WINDOWS + // PAL_GetSymbolModuleBase defers to dladdr, which is typically a hash lookup through symbols. + // It should be fairly fast, however it may take a loader lock, so we will cache the result. + void* pRet = VolatileLoadWithoutBarrier(&pImageBase); + if (!pRet) + { + pImageBase = pRet = (void*)PAL_GetSymbolModuleBase((void*)GetClrModuleBase); + } + + return pRet; +#endif // HOST_WINDOWS +} + +thread_local int t_CantAllocCount; + +#ifdef FAILPOINTS_ENABLED +typedef int (*FHashStack) (); + +static FHashStack fHashStack = 0; +static _TEB *HashStackSetupThread = NULL; +static _TEB *RFSCustomDataSetupThread = NULL; + +static void SetupHashStack () +{ + CANNOT_HAVE_CONTRACT; + + FHashStack oldValue = InterlockedCompareExchangeT(&fHashStack, + reinterpret_cast<FHashStack>(1), reinterpret_cast<FHashStack>(0)); + if ((size_t) oldValue >= 2) { + return; + } + else if ((size_t) oldValue == 0) { + // We are the first thread to initialize + HashStackSetupThread = NtCurrentTeb(); + + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_HashStack) == 0) { + fHashStack = (FHashStack) 2; + return; + } + + PAL_TRY(void *, unused, NULL) { + FHashStack func; + HMODULE hmod = LoadLibraryExA ("mscorrfs.dll", NULL, 0); + if (hmod) { + func = (FHashStack)GetProcAddress (hmod, "HashStack"); + if (func == 0) { + func = (FHashStack)2; + } + } + else + func = (FHashStack)2; + fHashStack = func; + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + fHashStack = (FHashStack) 2; + } + PAL_ENDTRY; + } + else if (NtCurrentTeb() == HashStackSetupThread) { + // We get here while initializing + return; + } + else { + // All other threads will wait + while (fHashStack == (FHashStack) 1) { + ClrSleepEx (100, FALSE); + } + } +} + +int RFS_HashStack () +{ + CANNOT_HAVE_CONTRACT; + + if ((size_t)fHashStack < 2) { + SetupHashStack (); + } + + if ((size_t)fHashStack <= 2) { + return 0; + } + else + return fHashStack (); +} + +#endif // FAILPOINTS_ENABLED + +DWORD GetClrModulePathName(SString& buffer) +{ +#ifdef HOST_WINDOWS + return WszGetModuleFileName((HINSTANCE)GetClrModuleBase(), buffer); +#else + return WszGetModuleFileName(PAL_GetPalHostModule(), buffer); +#endif +} + +#if defined(SELF_NO_HOST) + +HMODULE CLRLoadLibrary(LPCWSTR lpLibFileName) +{ + WRAPPER_NO_CONTRACT; + return CLRLoadLibraryEx(lpLibFileName, NULL, 0); +} + +HMODULE CLRLoadLibraryEx(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) +{ + WRAPPER_NO_CONTRACT; + return WszLoadLibraryEx(lpLibFileName, hFile, dwFlags); +} + +BOOL CLRFreeLibrary(HMODULE hModule) +{ + WRAPPER_NO_CONTRACT; + return FreeLibrary(hModule); +} + +#endif // defined(SELF_NO_HOST) + + +#if defined(_DEBUG_IMPL) && defined(ENABLE_CONTRACTS_IMPL) + +//----------------------------------------------------------------------------------------------- +// Imposes a new typeload level limit for the scope of the holder. Any attempt to load a type +// past that limit generates a contract violation assert. +// +// Do not invoke this directly. Invoke it through TRIGGERS_TYPE_LOAD or OVERRIDE_TYPE_LOAD_LEVEL_LIMIT. +// +// Arguments: +// fConditional - if FALSE, this holder is a nop - supports the MAYBE_* macros. +// newLevel - a value from classloadlevel.h - specifies the new max limit. +// fEnforceLevelChangeDirection +// - if true, implements TRIGGERS_TYPE_LOAD (level cap only allowed to decrease.) +// if false, implements OVERRIDE (level allowed to increase - may only be used +// by loader and only when recursion is structurally +// impossible.) +// szFunction, +// szFile, +// lineNum - records location of holder so we can print it in assertion boxes +// +// Assumptions: +// ClrDebugState must have been set up (executing any contract will do this.) +// Thread need *not* have a Thread* structure set up. +// +// Notes: +// The holder withholds the assert if a LoadsTypeViolation suppress is in effect (but +// still sets up the new limit.) +// +// As with other contract annotations, however, the violation suppression is *lifted* +// within the scope guarded by the holder itself. +//----------------------------------------------------------------------------------------------- +LoadsTypeHolder::LoadsTypeHolder(BOOL fConditional, + UINT newLevel, + BOOL fEnforceLevelChangeDirection, + const char *szFunction, + const char *szFile, + int lineNum + ) +{ + // This fcn makes non-scoped changes to ClrDebugState so we cannot use a runtime CONTRACT here. + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + + m_fConditional = fConditional; + if (m_fConditional) + { + m_pClrDebugState = CheckClrDebugState(); + _ASSERTE(m_pClrDebugState); + + m_oldClrDebugState = *m_pClrDebugState; + + if (fEnforceLevelChangeDirection) + { + if (newLevel > m_pClrDebugState->GetMaxLoadTypeLevel()) + { + if (!( (LoadsTypeViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + CONTRACT_ASSERT("Illegal attempt to load a type beyond the current level limit.", + (m_pClrDebugState->GetMaxLoadTypeLevel() + 1) << Contract::LOADS_TYPE_Shift, + Contract::LOADS_TYPE_Mask, + szFunction, + szFile, + lineNum + ); + } + } + } + + m_pClrDebugState->ViolationMaskReset(LoadsTypeViolation); + m_pClrDebugState->SetMaxLoadTypeLevel(newLevel); + + m_contractStackRecord.m_szFunction = szFunction; + m_contractStackRecord.m_szFile = szFile; + m_contractStackRecord.m_lineNum = lineNum; + m_contractStackRecord.m_testmask = (Contract::ALL_Disabled & ~((UINT)(Contract::LOADS_TYPE_Mask))) | (((newLevel) + 1) << Contract::LOADS_TYPE_Shift); + m_contractStackRecord.m_construct = fEnforceLevelChangeDirection ? "TRIGGERS_TYPE_LOAD" : "OVERRIDE_TYPE_LOAD_LEVEL_LIMIT"; + m_contractStackRecord.m_pNext = m_pClrDebugState->GetContractStackTrace(); + m_pClrDebugState->SetContractStackTrace(&m_contractStackRecord); + + + } +} // LoadsTypeHolder::LoadsTypeHolder + +//----------------------------------------------------------------------------------------------- +// Restores prior typeload level limit. +//----------------------------------------------------------------------------------------------- +LoadsTypeHolder::~LoadsTypeHolder() +{ + // This fcn makes non-scoped changes to ClrDebugState so we cannot use a runtime CONTRACT here. + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + + if (m_fConditional) + { + *m_pClrDebugState = m_oldClrDebugState; + } +} + +#endif //defined(_DEBUG_IMPL) && defined(ENABLE_CONTRACTS_IMPL) diff --git a/src/coreclr/utilcode/clrhost_nodependencies.cpp b/src/coreclr/utilcode/clrhost_nodependencies.cpp new file mode 100644 index 00000000000..64185df41cb --- /dev/null +++ b/src/coreclr/utilcode/clrhost_nodependencies.cpp @@ -0,0 +1,572 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +// + +#include "stdafx.h" + +#include "clrhost.h" +#include "utilcode.h" +#include "ex.h" +#include "clrnt.h" +#include "contract.h" + +#if defined __llvm__ +# if defined(__has_feature) && __has_feature(address_sanitizer) +# define HAS_ADDRESS_SANITIZER +# endif +#endif + +#ifdef _DEBUG_IMPL + +// +// I'd very much like for this to go away. Its used to disable all THROWS contracts within whatever DLL this +// function is called from. That's obviously very, very bad, since there's no validation of those macros. But it +// can be difficult to remove this without actually fixing every violation at the same time. +// +// When this flag is finally removed, remove RealCLRThrowsExceptionWorker() too and put CONTRACT_THROWS() in place +// of it. +// +// +static BOOL dbg_fDisableThrowCheck = FALSE; + +void DisableThrowCheck() +{ + LIMITED_METHOD_CONTRACT; + + dbg_fDisableThrowCheck = TRUE; +} + +#ifdef HAS_ADDRESS_SANITIZER +// use the functionality from address santizier (which does not throw exceptions) +#else + +#define CLRThrowsExceptionWorker() RealCLRThrowsExceptionWorker(__FUNCTION__, __FILE__, __LINE__) + +static void RealCLRThrowsExceptionWorker(__in_z const char *szFunction, + __in_z const char *szFile, + int lineNum) +{ + WRAPPER_NO_CONTRACT; + + if (dbg_fDisableThrowCheck) + { + return; + } + + CONTRACT_THROWSEX(szFunction, szFile, lineNum); +} + +#endif // HAS_ADDRESS_SANITIZER +#endif //_DEBUG_IMPL + +#if defined(_DEBUG_IMPL) && defined(ENABLE_CONTRACTS_IMPL) + +thread_local ClrDebugState* t_pClrDebugState; + +// Fls callback to deallocate ClrDebugState when our FLS block goes away. +void FreeClrDebugState(LPVOID pTlsData) +{ + ClrDebugState *pClrDebugState = (ClrDebugState*)pTlsData; + + // Make sure the ClrDebugState was initialized by a compatible version of + // utilcode.lib. If it was initialized by an older version, we just let it leak. + if (pClrDebugState && (pClrDebugState->ViolationMask() & CanFreeMe) && !(pClrDebugState->ViolationMask() & BadDebugState)) + { + // Since "!(pClrDebugState->m_violationmask & BadDebugState)", we know we have + // a valid m_pLockData + _ASSERTE(pClrDebugState->GetDbgStateLockData() != NULL); + HeapFree(GetProcessHeap(), 0, pClrDebugState->GetDbgStateLockData()); + + HeapFree(GetProcessHeap(), 0, pClrDebugState); + } +} + +//============================================================================================= +// Used to initialize the per-thread ClrDebugState. This is called once per thread (with +// possible exceptions for OOM scenarios.) +// +// No matter what, this function will not return NULL. If it can't do its job because of OOM reasons, +// it will return a pointer to &gBadClrDebugState which effectively disables contracts for +// this thread. +//============================================================================================= +ClrDebugState *CLRInitDebugState() +{ + // This is our global "bad" debug state that thread use when they OOM on CLRInitDebugState. + // We really only need to initialize it once but initializing each time is convenient + // and has low perf impact. + static ClrDebugState gBadClrDebugState; + gBadClrDebugState.ViolationMaskSet( AllViolation ); + gBadClrDebugState.SetOkToThrow(); + + ClrDebugState *pNewClrDebugState = NULL; + ClrDebugState *pClrDebugState = NULL; + DbgStateLockData *pNewLockData = NULL; + + // Yuck. We cannot call the hosted allocator for ClrDebugState (it is impossible to maintain a guarantee + // that none of code paths, many of them called conditionally, don't themselves trigger a ClrDebugState creation.) + // We have to call the OS directly for this. + pNewClrDebugState = (ClrDebugState*)HeapAlloc(GetProcessHeap(), 0, sizeof(ClrDebugState)); + if (pNewClrDebugState != NULL) + { + // Only allocate a DbgStateLockData if its owning ClrDebugState was successfully allocated + pNewLockData = (DbgStateLockData *)HeapAlloc(GetProcessHeap(), 0, sizeof(DbgStateLockData)); + } + + if ((pNewClrDebugState != NULL) && (pNewLockData != NULL)) + { + // Both allocations succeeded, so initialize the structures, and have + // pNewClrDebugState point to pNewLockData. If either of the allocations + // failed, we'll use gBadClrDebugState for this thread, and free whichever of + // pNewClrDebugState or pNewLockData actually did get allocated (if either did). + // (See code in this function below, outside this block.) + + pNewClrDebugState->SetStartingValues(); + pNewClrDebugState->ViolationMaskSet( CanFreeMe ); + _ASSERTE(!(pNewClrDebugState->ViolationMask() & BadDebugState)); + + pNewLockData->SetStartingValues(); + pNewClrDebugState->SetDbgStateLockData(pNewLockData); + } + + + // This is getting really diseased. All the one-time host init stuff inside the ClrFlsStuff could actually + // have caused mscorwks contracts to be executed since the last time we actually checked to see if the ClrDebugState + // needed creating. + // + // So we must make one last check to see if the ClrDebugState still needs creating. + // + ClrDebugState *pTmp = t_pClrDebugState; + if (pTmp != NULL) + { + // Recursive call set up ClrDebugState for us + pClrDebugState = pTmp; + } + else if ((pNewClrDebugState != NULL) && (pNewLockData != NULL)) + { + // Normal case: our new ClrDebugState will be the one we just allocated. + // Note that we require BOTH the ClrDebugState and the DbgStateLockData + // structures to have been successfully allocated for contracts to be + // enabled for this thread. + _ASSERTE(!(pNewClrDebugState->ViolationMask() & BadDebugState)); + _ASSERTE(pNewClrDebugState->GetDbgStateLockData() == pNewLockData); + pClrDebugState = pNewClrDebugState; + } + else + { + // OOM case: HeapAlloc of newClrDebugState failed. + pClrDebugState = &gBadClrDebugState; + } + + _ASSERTE(pClrDebugState != NULL); + + t_pClrDebugState = pClrDebugState; + + // The ClrDebugState we allocated above made it into FLS iff + // the DbgStateLockData we allocated above made it into + // the FLS's ClrDebugState::m_pLockData + // These debug-only checks enforce this invariant + + if (pClrDebugState != NULL) + { + // If we're here, then typically pClrDebugState is what's in FLS. However, + // it's possible that pClrDebugState is gBadClrDebugState, and FLS is NULL + // (if the last ClrFlsSetValue() failed). Either way, our checks below + // are valid ones to make. + + if (pClrDebugState == pNewClrDebugState) + { + // ClrDebugState we allocated above made it into FLS, so DbgStateLockData + // must be there, too + _ASSERTE(pNewLockData != NULL); + _ASSERTE(pClrDebugState->GetDbgStateLockData() == pNewLockData); + } + else + { + // ClrDebugState we allocated above did NOT make it into FLS, + // so the DbgStateLockData we allocated must not be there, either + _ASSERTE(pClrDebugState->GetDbgStateLockData() == NULL || pClrDebugState->GetDbgStateLockData() != pNewLockData); + } + } + + // One more invariant: Because of ordering & conditions around the HeapAllocs above, + // we'll never have a DbgStateLockData without a ClrDebugState + _ASSERTE((pNewLockData == NULL) || (pNewClrDebugState != NULL)); + + if (pNewClrDebugState != NULL && pClrDebugState != pNewClrDebugState) + { + // We allocated a ClrDebugState which didn't make it into FLS, so free it. + HeapFree(GetProcessHeap(), 0, pNewClrDebugState); + if (pNewLockData != NULL) + { + // We also allocated a DbgStateLockData that didn't make it into FLS, so + // free it, too. (Remember, we asserted above that we can only have + // this unused DbgStateLockData if we had an unused ClrDebugState + // as well (which we just freed).) + HeapFree(GetProcessHeap(), 0, pNewLockData); + } + } + + return pClrDebugState; +} // CLRInitDebugState + +#endif //defined(_DEBUG_IMPL) && defined(ENABLE_CONTRACTS_IMPL) + +const NoThrow nothrow = { 0 }; + +#ifdef HAS_ADDRESS_SANITIZER +// use standard heap functions for address santizier +#else + +#ifdef _DEBUG +#ifdef TARGET_X86 +#define OS_HEAP_ALIGN 8 +#else +#define OS_HEAP_ALIGN 16 +#endif +#define CLRALLOC_TAG 0x2ce145f1 +#endif + +#ifdef HOST_WINDOWS +static HANDLE g_hProcessHeap; +#endif + +FORCEINLINE void* ClrMalloc(size_t size) +{ + STATIC_CONTRACT_NOTHROW; + +#ifdef FAILPOINTS_ENABLED + if (RFS_HashStack()) + return NULL; +#endif + + void* p; + +#ifdef _DEBUG + size += OS_HEAP_ALIGN; +#endif + +#ifdef HOST_WINDOWS + HANDLE hHeap = g_hProcessHeap; + if (hHeap == NULL) + { + InterlockedCompareExchangeT(&g_hProcessHeap, ::GetProcessHeap(), NULL); + hHeap = g_hProcessHeap; + } + + p = HeapAlloc(hHeap, 0, size); +#else + p = malloc(size); +#endif + +#ifdef _DEBUG + // Store the tag to detect heap contamination + if (p != NULL) + { + *((DWORD*)p) = CLRALLOC_TAG; + p = (BYTE*)p + OS_HEAP_ALIGN; + } +#endif + +#ifndef SELF_NO_HOST + if (p == NULL + // If we have not created StressLog ring buffer, we should not try to use it. + // StressLog is going to do a memory allocation. We may enter an endless loop. + && StressLog::t_pCurrentThreadLog != NULL) + { + STRESS_LOG_OOM_STACK(size); + } +#endif + + return p; +} + +FORCEINLINE void ClrFree(void* p) +{ + STATIC_CONTRACT_NOTHROW; + +#ifdef _DEBUG + if (p != NULL) + { + // Check the heap handle to detect heap contamination + p = (BYTE*)p - OS_HEAP_ALIGN; + if (*((DWORD*)p) != CLRALLOC_TAG) + _ASSERTE(!"Heap contamination detected! HeapFree was called on a heap other than the one that memory was allocated from.\n" + "Possible cause: you used new (executable) to allocate the memory, but didn't use DeleteExecutable() to free it."); + } +#endif + +#ifdef HOST_WINDOWS + if (p != NULL) + HeapFree(g_hProcessHeap, 0, p); +#else + free(p); +#endif +} + +void * __cdecl +operator new(size_t n) +{ +#ifdef _DEBUG_IMPL + CLRThrowsExceptionWorker(); +#endif + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + void* result = ClrMalloc(n); + if (result == NULL) { + ThrowOutOfMemory(); + } + TRASH_LASTERROR; + return result; +} + +void * __cdecl +operator new[](size_t n) +{ +#ifdef _DEBUG_IMPL + CLRThrowsExceptionWorker(); +#endif + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + void* result = ClrMalloc(n); + if (result == NULL) { + ThrowOutOfMemory(); + } + TRASH_LASTERROR; + return result; +}; + +#endif // HAS_ADDRESS_SANITIZER + +void * __cdecl operator new(size_t n, const NoThrow&) NOEXCEPT +{ +#ifdef HAS_ADDRESS_SANITIZER + // use standard heap functions for address santizier (which doesn't provide for NoThrow) + void * result = operator new(n); +#else + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + + void* result = ClrMalloc(n); +#endif // HAS_ADDRESS_SANITIZER + TRASH_LASTERROR; + return result; +} + +void * __cdecl operator new[](size_t n, const NoThrow&) NOEXCEPT +{ +#ifdef HAS_ADDRESS_SANITIZER + // use standard heap functions for address santizier (which doesn't provide for NoThrow) + void * result = operator new[](n); +#else + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + + void* result = ClrMalloc(n); +#endif // HAS_ADDRESS_SANITIZER + TRASH_LASTERROR; + return result; +} + +#ifdef HAS_ADDRESS_SANITIZER +// use standard heap functions for address santizier +#else +void __cdecl +operator delete(void *p) NOEXCEPT +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + ClrFree(p); + + TRASH_LASTERROR; +} + +void __cdecl +operator delete[](void *p) NOEXCEPT +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + ClrFree(p); + TRASH_LASTERROR; +} + +#endif // HAS_ADDRESS_SANITIZER + +/* ------------------------------------------------------------------------ * + * New operator overloading for the executable heap + * ------------------------------------------------------------------------ */ + +#ifdef HOST_WINDOWS + +HANDLE ClrGetProcessExecutableHeap() +{ + // Note: this can be called a little early for real contracts, so we use static contracts instead. + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + static HANDLE g_ExecutableHeapHandle; + + // + // Create the executable heap lazily + // + if (g_ExecutableHeapHandle == NULL) + { + + HANDLE ExecutableHeapHandle = HeapCreate( + HEAP_CREATE_ENABLE_EXECUTE, // heap allocation attributes + 0, // initial heap size + 0 // maximum heap size; 0 == growable + ); + + if (ExecutableHeapHandle == NULL) + return NULL; + + HANDLE ExistingValue = InterlockedCompareExchangeT(&g_ExecutableHeapHandle, ExecutableHeapHandle, NULL); + if (ExistingValue != NULL) + { + HeapDestroy(ExecutableHeapHandle); + } + } + + return g_ExecutableHeapHandle; +} + +const CExecutable executable = { 0 }; + +void * __cdecl operator new(size_t n, const CExecutable&) +{ +#if defined(_DEBUG_IMPL) + CLRThrowsExceptionWorker(); +#endif + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + HANDLE hExecutableHeap = ClrGetProcessExecutableHeap(); + if (hExecutableHeap == NULL) { + ThrowOutOfMemory(); + } + + void * result = HeapAlloc(hExecutableHeap, 0, n); + if (result == NULL) { + ThrowOutOfMemory(); + } + TRASH_LASTERROR; + return result; +} + +void * __cdecl operator new[](size_t n, const CExecutable&) +{ +#if defined(_DEBUG_IMPL) + CLRThrowsExceptionWorker(); +#endif + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + HANDLE hExecutableHeap = ClrGetProcessExecutableHeap(); + if (hExecutableHeap == NULL) { + ThrowOutOfMemory(); + } + + void * result = HeapAlloc(hExecutableHeap, 0, n); + if (result == NULL) { + ThrowOutOfMemory(); + } + TRASH_LASTERROR; + return result; +} + +void * __cdecl operator new(size_t n, const CExecutable&, const NoThrow&) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + + HANDLE hExecutableHeap = ClrGetProcessExecutableHeap(); + if (hExecutableHeap == NULL) + return NULL; + + void * result = HeapAlloc(hExecutableHeap, 0, n); + TRASH_LASTERROR; + return result; +} + +void * __cdecl operator new[](size_t n, const CExecutable&, const NoThrow&) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + + HANDLE hExecutableHeap = ClrGetProcessExecutableHeap(); + if (hExecutableHeap == NULL) + return NULL; + + void * result = HeapAlloc(hExecutableHeap, 0, n); + TRASH_LASTERROR; + return result; +} + +#endif // HOST_WINDOWS + +#ifdef _DEBUG + +// This is a DEBUG routing to verify that a memory region complies with executable requirements +BOOL DbgIsExecutable(LPVOID lpMem, SIZE_T length) +{ +#if defined(CROSSGEN_COMPILE) || defined(TARGET_UNIX) + // No NX support on PAL or for crossgen compilations. + return TRUE; +#else // !(CROSSGEN_COMPILE || TARGET_UNIX) + BYTE *regionStart = (BYTE*) ALIGN_DOWN((BYTE*)lpMem, GetOsPageSize()); + BYTE *regionEnd = (BYTE*) ALIGN_UP((BYTE*)lpMem+length, GetOsPageSize()); + _ASSERTE(length > 0); + _ASSERTE(regionStart < regionEnd); + + while(regionStart < regionEnd) + { + MEMORY_BASIC_INFORMATION mbi; + + SIZE_T cbBytes = ClrVirtualQuery(regionStart, &mbi, sizeof(mbi)); + _ASSERTE(cbBytes); + + // The pages must have EXECUTE set + if(!(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) + return FALSE; + + _ASSERTE((BYTE*)mbi.BaseAddress + mbi.RegionSize > regionStart); + regionStart = (BYTE*)mbi.BaseAddress + mbi.RegionSize; + } + + return TRUE; +#endif // CROSSGEN_COMPILE || TARGET_UNIX +} + +#endif //_DEBUG diff --git a/src/coreclr/utilcode/collections.cpp b/src/coreclr/utilcode/collections.cpp new file mode 100644 index 00000000000..ed5271fde77 --- /dev/null +++ b/src/coreclr/utilcode/collections.cpp @@ -0,0 +1,970 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// Collections.cpp +// + +// +// This contains Collections C++ utility classes. +// +//***************************************************************************** + +#include "stdafx.h" +#include "utilcode.h" +#include "ex.h" + +// +// +// CHashTable +// +// + +#ifndef DACCESS_COMPILE + +//***************************************************************************** +// This is the second part of construction where we do all of the work that +// can fail. We also take the array of structs here because the calling class +// presumably needs to allocate it in its NewInit. +//***************************************************************************** +HRESULT CHashTable::NewInit( // Return status. + BYTE *pcEntries, // Array of structs we are managing. + ULONG iEntrySize) // Size of the entries. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE(iEntrySize >= sizeof(FREEHASHENTRY)); + + // Allocate the bucket chain array and init it. + if ((m_piBuckets = new (nothrow) ULONG [m_iBuckets]) == NULL) + return (OutOfMemory()); + memset(m_piBuckets, 0xff, m_iBuckets * sizeof(ULONG)); + + // Save the array of structs we are managing. + m_pcEntries = (TADDR)pcEntries; + m_iEntrySize = iEntrySize; + return (S_OK); +} + +//***************************************************************************** +// Add the struct at the specified index in m_pcEntries to the hash chains. +//***************************************************************************** +BYTE *CHashTable::Add( // New entry. + ULONG iHash, // Hash value of entry to add. + ULONG iIndex) // Index of struct in m_pcEntries. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HASHENTRY *psEntry; // The struct we are adding. + + // Get a pointer to the entry we are adding. + psEntry = EntryPtr(iIndex); + + // Compute the hash value for the entry. + iHash %= m_iBuckets; + + _ASSERTE(m_piBuckets[iHash] != iIndex && + (m_piBuckets[iHash] == UINT32_MAX || EntryPtr(m_piBuckets[iHash])->iPrev != iIndex)); + + // Setup this entry. + psEntry->iPrev = UINT32_MAX; + psEntry->iNext = m_piBuckets[iHash]; + + // Link it into the hash chain. + if (m_piBuckets[iHash] != UINT32_MAX) + EntryPtr(m_piBuckets[iHash])->iPrev = iIndex; + m_piBuckets[iHash] = iIndex; + return ((BYTE *) psEntry); +} + +//***************************************************************************** +// Delete the struct at the specified index in m_pcEntries from the hash chains. +//***************************************************************************** +void CHashTable::Delete( + ULONG iHash, // Hash value of entry to delete. + ULONG iIndex) // Index of struct in m_pcEntries. +{ + WRAPPER_NO_CONTRACT; + + HASHENTRY *psEntry; // Struct to delete. + + // Get a pointer to the entry we are deleting. + psEntry = EntryPtr(iIndex); + Delete(iHash, psEntry); +} + +//***************************************************************************** +// Delete the struct at the specified index in m_pcEntries from the hash chains. +//***************************************************************************** +void CHashTable::Delete( + ULONG iHash, // Hash value of entry to delete. + HASHENTRY *psEntry) // The struct to delete. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // Compute the hash value for the entry. + iHash %= m_iBuckets; + + _ASSERTE(psEntry->iPrev != psEntry->iNext || psEntry->iPrev == UINT32_MAX); + + // Fix the predecessor. + if (psEntry->iPrev == UINT32_MAX) + m_piBuckets[iHash] = psEntry->iNext; + else + EntryPtr(psEntry->iPrev)->iNext = psEntry->iNext; + + // Fix the successor. + if (psEntry->iNext != UINT32_MAX) + EntryPtr(psEntry->iNext)->iPrev = psEntry->iPrev; +} + +//***************************************************************************** +// The item at the specified index has been moved, update the previous and +// next item. +//***************************************************************************** +void CHashTable::Move( + ULONG iHash, // Hash value for the item. + ULONG iNew) // New location. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HASHENTRY *psEntry; // The struct we are deleting. + + psEntry = EntryPtr(iNew); + _ASSERTE(psEntry->iPrev != iNew && psEntry->iNext != iNew); + + if (psEntry->iPrev != UINT32_MAX) + EntryPtr(psEntry->iPrev)->iNext = iNew; + else + m_piBuckets[iHash % m_iBuckets] = iNew; + if (psEntry->iNext != UINT32_MAX) + EntryPtr(psEntry->iNext)->iPrev = iNew; +} + +#endif // !DACCESS_COMPILE + +//***************************************************************************** +// Search the hash table for an entry with the specified key value. +//***************************************************************************** +BYTE *CHashTable::Find( // Index of struct in m_pcEntries. + ULONG iHash, // Hash value of the item. + SIZE_T key) // The key to match. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + + ULONG iNext; // Used to traverse the chains. + HASHENTRY *psEntry; // Used to traverse the chains. + + // Start at the top of the chain. + iNext = m_piBuckets[iHash % m_iBuckets]; + + // Search until we hit the end. + +#ifdef _DEBUG + unsigned count = 0; +#endif + + while (iNext != UINT32_MAX) + { + // Compare the keys. + psEntry = EntryPtr(iNext); + +#ifdef _DEBUG + count++; +#endif + if (!Cmp(key, psEntry)) + { +#ifdef _DEBUG + if (count > m_maxSearch) + m_maxSearch = count; +#endif + + return ((BYTE *) psEntry); + } + + // Advance to the next item in the chain. + iNext = psEntry->iNext; + } + + // We couldn't find it. + return (0); +} + +//***************************************************************************** +// Search the hash table for the next entry with the specified key value. +//***************************************************************************** +ULONG CHashTable::FindNext( // Index of struct in m_pcEntries. + SIZE_T key, // The key to match. + ULONG iIndex) // Index of previous match. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + ULONG iNext; // Used to traverse the chains. + HASHENTRY *psEntry; // Used to traverse the chains. + + // Start at the next entry in the chain. + iNext = EntryPtr(iIndex)->iNext; + + // Search until we hit the end. + while (iNext != UINT32_MAX) + { + // Compare the keys. + psEntry = EntryPtr(iNext); + if (!Cmp(key, psEntry)) + return (iNext); + + // Advance to the next item in the chain. + iNext = psEntry->iNext; + } + + // We couldn't find it. + return (UINT32_MAX); +} + +//***************************************************************************** +// Returns the next entry in the list. +//***************************************************************************** +BYTE *CHashTable::FindNextEntry( // The next entry, or0 for end of list. + HASHFIND *psSrch) // Search object. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + + HASHENTRY *psEntry; // Used to traverse the chains. + + for (;;) + { + // See if we already have one to use and if so, use it. + if (psSrch->iNext != UINT32_MAX) + { + psEntry = EntryPtr(psSrch->iNext); + psSrch->iNext = psEntry->iNext; + return ((BYTE *) psEntry); + } + + // Advance to the next bucket. + if (psSrch->iBucket < m_iBuckets) + psSrch->iNext = m_piBuckets[psSrch->iBucket++]; + else + break; + } + + // There were no more entries to be found. + return (0); +} + +#ifdef DACCESS_COMPILE + +void +CHashTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + ULONG numEntries) +{ + SUPPORTS_DAC; + + // This class may be embedded so do not enum 'this'. + DacEnumMemoryRegion(m_pcEntries, + (ULONG)numEntries * m_iEntrySize); + DacEnumMemoryRegion(dac_cast<TADDR>(m_piBuckets), + (ULONG)m_iBuckets * sizeof(ULONG)); +} + +#endif // #ifdef DACCESS_COMPILE + +// +// +// CClosedHashBase +// +// + +//***************************************************************************** +// Delete the given value. This will simply mark the entry as deleted (in +// order to keep the collision chain intact). There is an optimization that +// consecutive deleted entries leading up to a free entry are themselves freed +// to reduce collisions later on. +//***************************************************************************** +void CClosedHashBase::Delete( + void *pData) // Key value to delete. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + BYTE *ptr; + + // Find the item to delete. + if ((ptr = Find(pData)) == 0) + { + // You deleted something that wasn't there, why? + _ASSERTE(0); + return; + } + + // For a perfect system, there are no collisions so it is free. + if (m_bPerfect) + { + SetStatus(ptr, FREE); + + // One less non free entry. + --m_iCount; + + return; + } + + // Mark this entry deleted. + SetStatus(ptr, DELETED); + + // If the next item is free, then we can go backwards freeing + // deleted entries which are no longer part of a chain. This isn't + // 100% great, but it will reduce collisions. + BYTE *pnext; + if ((pnext = ptr + m_iEntrySize) > EntryPtr(m_iSize - 1)) + pnext = &m_rgData[0]; + if (Status(pnext) != FREE) + return; + + // We can now free consecutive entries starting with the one + // we just deleted, up to the first non-deleted one. + while (Status(ptr) == DELETED) + { + // Free this entry. + SetStatus(ptr, FREE); + + // One less non free entry. + --m_iCount; + + // Check the one before it, handle wrap around. + if ((ptr -= m_iEntrySize) < &m_rgData[0]) + ptr = EntryPtr(m_iSize - 1); + } +} + + +//***************************************************************************** +// Iterates over all active values, passing each one to pDeleteLoopFunc. +// If pDeleteLoopFunc returns TRUE, the entry is deleted. This is safer +// and faster than using FindNext() and Delete(). +//***************************************************************************** +void CClosedHashBase::DeleteLoop( + DELETELOOPFUNC pDeleteLoopFunc, // Decides whether to delete item + void *pCustomizer) // Extra value passed to deletefunc. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int i; + + if (m_rgData == 0) + { + return; + } + + for (i = 0; i < m_iSize; i++) + { + BYTE *pEntry = EntryPtr(i); + if (Status(pEntry) == USED) + { + if (pDeleteLoopFunc(pEntry, pCustomizer)) + { + if (m_bPerfect) + { + SetStatus(pEntry, FREE); + // One less non free entry. + --m_iCount; + } + else + { + SetStatus(pEntry, DELETED); + } + } + } + } + + if (!m_bPerfect) + { + // Now free DELETED entries that are no longer part of a chain. + for (i = 0; i < m_iSize; i++) + { + if (Status(EntryPtr(i)) == FREE) + { + break; + } + } + if (i != m_iSize) + { + int iFirstFree = i; + + do + { + if (i-- == 0) + { + i = m_iSize - 1; + } + while (Status(EntryPtr(i)) == DELETED) + { + SetStatus(EntryPtr(i), FREE); + + + // One less non free entry. + --m_iCount; + + if (i-- == 0) + { + i = m_iSize - 1; + } + } + + while (Status(EntryPtr(i)) != FREE) + { + if (i-- == 0) + { + i = m_iSize - 1; + } + } + + } + while (i != iFirstFree); + } + } + +} + +//***************************************************************************** +// Lookup a key value and return a pointer to the element if found. +//***************************************************************************** +BYTE *CClosedHashBase::Find( // The item if found, 0 if not. + void *pData) // The key to lookup. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + unsigned int iHash; // Hash value for this data. + int iBucket; // Which bucke to start at. + int i; // Loop control. + + // Safety check. + if (!m_rgData || m_iCount == 0) + return (0); + + // Hash to the bucket. + iHash = Hash(pData); + iBucket = iHash % m_iBuckets; + + // For a perfect table, the bucket is the correct one. + if (m_bPerfect) + { + // If the value is there, it is the correct one. + if (Status(EntryPtr(iBucket)) != FREE) + return (EntryPtr(iBucket)); + return (0); + } + + // Walk the bucket list looking for the item. + for (i=iBucket; Status(EntryPtr(i)) != FREE; ) + { + // Don't look at deleted items. + if (Status(EntryPtr(i)) == DELETED) + { + // Handle wrap around. + if (++i >= m_iSize) + i = 0; + continue; + } + + // Check this one. + if (Compare(pData, EntryPtr(i)) == 0) + return (EntryPtr(i)); + + // If we never collided while adding items, then there is + // no point in scanning any further. + if (!m_iCollisions) + return (0); + + // Handle wrap around. + if (++i >= m_iSize) + i = 0; + } + return (0); +} + + + +//***************************************************************************** +// Look for an item in the table. If it isn't found, then create a new one and +// return that. +//***************************************************************************** +BYTE *CClosedHashBase::FindOrAdd( // The item if found, 0 if not. + void *pData, // The key to lookup. + bool &bNew) // true if created. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + unsigned int iHash; // Hash value for this data. + int iBucket; // Which bucke to start at. + int i; // Loop control. + + // If we haven't allocated any memory, or it is too small, fix it. + if (!m_rgData || ((m_iCount + 1) > (m_iSize * 3 / 4) && !m_bPerfect)) + { + if (!ReHash()) + return (0); + } + + // Assume we find it. + bNew = false; + + // Hash to the bucket. + iHash = Hash(pData); + iBucket = iHash % m_iBuckets; + + // For a perfect table, the bucket is the correct one. + if (m_bPerfect) + { + // If the value is there, it is the correct one. + if (Status(EntryPtr(iBucket)) != FREE) + return (EntryPtr(iBucket)); + i = iBucket; + } + else + { + // Walk the bucket list looking for the item. + for (i=iBucket; Status(EntryPtr(i)) != FREE; ) + { + // Don't look at deleted items. + if (Status(EntryPtr(i)) == DELETED) + { + // Handle wrap around. + if (++i >= m_iSize) + i = 0; + continue; + } + + // Check this one. + if (Compare(pData, EntryPtr(i)) == 0) + return (EntryPtr(i)); + + // One more to count. + ++m_iCollisions; + + // Handle wrap around. + if (++i >= m_iSize) + i = 0; + } + } + + // We've found an open slot, use it. + _ASSERTE(Status(EntryPtr(i)) == FREE); + bNew = true; + ++m_iCount; + return (EntryPtr(i)); +} + +//***************************************************************************** +// This helper actually does the add for you. +//***************************************************************************** +BYTE *CClosedHashBase::DoAdd(void *pData, BYTE *rgData, int &iBuckets, int iSize, + int &iCollisions, int &iCount) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + unsigned int iHash; // Hash value for this data. + int iBucket; // Which bucke to start at. + int i; // Loop control. + + // Hash to the bucket. + iHash = Hash(pData); + iBucket = iHash % iBuckets; + + // For a perfect table, the bucket is free. + if (m_bPerfect) + { + i = iBucket; + _ASSERTE(Status(EntryPtr(i, rgData)) == FREE); + } + // Need to scan. + else + { + // Walk the bucket list looking for a slot. + for (i=iBucket; Status(EntryPtr(i, rgData)) != FREE; ) + { + // Handle wrap around. + if (++i >= iSize) + i = 0; + + // If we made it this far, we collided. + ++iCollisions; + } + } + + // One more item in list. + ++iCount; + + // Return the new slot for the caller. + return (EntryPtr(i, rgData)); +} + +//***************************************************************************** +// This function is called either to init the table in the first place, or +// to rehash the table if we ran out of room. +//***************************************************************************** +bool CClosedHashBase::ReHash() // true if successful. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // Allocate memory if we don't have any. + if (!m_rgData) + { + if ((m_rgData = new (nothrow) BYTE [m_iSize * m_iEntrySize]) == 0) + return (false); + InitFree(&m_rgData[0], m_iSize); + return (true); + } + + // We have entries already, allocate a new table. + BYTE *rgTemp, *p; + int iBuckets = m_iBuckets * 2 - 1; + int iSize = iBuckets + 7; + int iCollisions = 0; + int iCount = 0; + + if ((rgTemp = new (nothrow) BYTE [iSize * m_iEntrySize]) == 0) + return (false); + InitFree(&rgTemp[0], iSize); + m_bPerfect = false; + + // Rehash the data. + for (int i=0; i<m_iSize; i++) + { + // Only copy used entries. + if (Status(EntryPtr(i)) != USED) + continue; + + // Add this entry to the list again. + VERIFY((p = DoAdd(GetKey(EntryPtr(i)), rgTemp, iBuckets, + iSize, iCollisions, iCount))); + memmove(p, EntryPtr(i), m_iEntrySize); + } + + // Reset internals. + delete [] m_rgData; + m_rgData = rgTemp; + m_iBuckets = iBuckets; + m_iSize = iSize; + m_iCollisions = iCollisions; + m_iCount = iCount; + return (true); +} + + +// +// +// CStructArray +// +// + + +//***************************************************************************** +// Returns a pointer to the (iIndex)th element of the array, shifts the elements +// in the array if the location is already full. The iIndex cannot exceed the count +// of elements in the array. +//***************************************************************************** +void *CStructArray::InsertThrowing( + int iIndex) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + _ASSERTE(iIndex >= 0); + + // We can not insert an element further than the end of the array. + if (iIndex > m_iCount) + return (NULL); + + // The array should grow, if we can't fit one more element into the array. + Grow(1); + + // The pointer to be returned. + BYTE *pcList = m_pList + iIndex * m_iElemSize; + + // See if we need to slide anything down. + if (iIndex < m_iCount) + memmove(pcList + m_iElemSize, pcList, (m_iCount - iIndex) * m_iElemSize); + ++m_iCount; + return(pcList); +} + +//***************************************************************************** +// Non-throwing variant +//***************************************************************************** +void *CStructArray::Insert(int iIndex) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + void *result = NULL; + EX_TRY + { + result = InsertThrowing(iIndex); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + return result; +} + + +//***************************************************************************** +// Allocate a new element at the end of the dynamic array and return a pointer +// to it. +//***************************************************************************** +void *CStructArray::AppendThrowing() +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + // The array should grow, if we can't fit one more element into the array. + Grow(1); + + return (m_pList + m_iCount++ * m_iElemSize); +} + + +//***************************************************************************** +// Non-throwing variant +//***************************************************************************** +void *CStructArray::Append() +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + void *result = NULL; + EX_TRY + { + result = AppendThrowing(); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + return result; +} + + +//***************************************************************************** +// Allocate enough memory to have at least iCount items. This is a one shot +// check for a block of items, instead of requiring singleton inserts. It also +// avoids realloc headaches caused by growth, since you can do the whole block +// in one piece of code. If the array is already large enough, this is a no-op. +//***************************************************************************** +void CStructArray::AllocateBlockThrowing(int iCount) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + if (m_iSize < m_iCount+iCount) + Grow(iCount); + m_iCount += iCount; +} + +//***************************************************************************** +// Non-throwing variant +//***************************************************************************** +int CStructArray::AllocateBlock(int iCount) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int result = FALSE; + EX_TRY + { + AllocateBlockThrowing(iCount); + result = TRUE; + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + return result; +} + + +//***************************************************************************** +// Deletes the specified element from the array. +//***************************************************************************** +void CStructArray::Delete( + int iIndex) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE(iIndex >= 0); + + // See if we need to slide anything down. + if (iIndex < --m_iCount) + { + BYTE *pcList = m_pList + iIndex * m_iElemSize; + memmove(pcList, pcList + m_iElemSize, (m_iCount - iIndex) * m_iElemSize); + } +} + + +//***************************************************************************** +// Grow the array if it is not possible to fit iCount number of new elements. +//***************************************************************************** +void CStructArray::Grow( + int iCount) +{ + CONTRACTL { + THROWS; + } CONTRACTL_END; + + BYTE *pTemp; // temporary pointer used in realloc. + int iGrow; + + if (m_iSize < m_iCount+iCount) + { + if (m_pList == NULL) + { + iGrow = max(m_iGrowInc, iCount); + + //<TODO>@todo this is a workaround because I don't want to do resize right now.</TODO> + //check added to make PREFAST happy + S_SIZE_T newSize = S_SIZE_T(iGrow) * S_SIZE_T(m_iElemSize); + if(newSize.IsOverflow()) + ThrowOutOfMemory(); + else + { + m_pList = new BYTE[newSize.Value()]; + m_iSize = iGrow; + m_bFree = true; + } + } + else + { + // Adjust grow size as a ratio to avoid too many reallocs. + if (m_iSize / m_iGrowInc >= 3) + { // Don't overflow and go negative. + int newinc = m_iGrowInc * 2; + if (newinc > m_iGrowInc) + m_iGrowInc = newinc; + } + + iGrow = max(m_iGrowInc, iCount); + + // try to allocate memory for reallocation. + S_SIZE_T allocSize = (S_SIZE_T(m_iSize) + S_SIZE_T(iGrow)) * S_SIZE_T(m_iElemSize); + S_SIZE_T copyBytes = S_SIZE_T(m_iSize) * S_SIZE_T(m_iElemSize); + if(allocSize.IsOverflow() || copyBytes.IsOverflow()) + ThrowOutOfMemory(); + if (m_bFree) + { // We already own memory. + pTemp = new BYTE[allocSize.Value()]; + memcpy (pTemp, m_pList, copyBytes.Value()); + delete [] m_pList; + } + else + { // We don't own memory; get our own. + pTemp = new BYTE[allocSize.Value()]; + memcpy(pTemp, m_pList, copyBytes.Value()); + m_bFree = true; + } + m_pList = pTemp; + m_iSize += iGrow; + } + } +} + + +//***************************************************************************** +// Free the memory for this item. +//***************************************************************************** +void CStructArray::Clear() +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // Free the chunk of memory. + if (m_bFree && m_pList != NULL) + delete [] m_pList; + + m_pList = NULL; + m_iSize = 0; + m_iCount = 0; +} diff --git a/src/coreclr/utilcode/comex.cpp b/src/coreclr/utilcode/comex.cpp new file mode 100644 index 00000000000..3a564553d22 --- /dev/null +++ b/src/coreclr/utilcode/comex.cpp @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// --------------------------------------------------------------------------- +// COMEx.cpp +// + +// +// --------------------------------------------------------------------------- + +#include "stdafx.h" +#include "string.h" +#include "ex.h" +#include "holder.h" +#include "corerror.h" + +// --------------------------------------------------------------------------- +// COMException class. Implements exception API for standard COM-based error info +// --------------------------------------------------------------------------- + +COMException::~COMException() +{ + WRAPPER_NO_CONTRACT; + + if (m_pErrorInfo != NULL) + m_pErrorInfo->Release(); +} + +IErrorInfo *COMException::GetErrorInfo() +{ + LIMITED_METHOD_CONTRACT; + + IErrorInfo *pErrorInfo = m_pErrorInfo; + if (pErrorInfo != NULL) + pErrorInfo->AddRef(); + return pErrorInfo; +} + +void COMException::GetMessage(SString &string) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (m_pErrorInfo != NULL) + { + BSTRHolder message(NULL); + if (SUCCEEDED(m_pErrorInfo->GetDescription(&message))) + string.Set(message, SysStringLen(message)); + } +} + diff --git a/src/coreclr/utilcode/configuration.cpp b/src/coreclr/utilcode/configuration.cpp new file mode 100644 index 00000000000..e67507d5bae --- /dev/null +++ b/src/coreclr/utilcode/configuration.cpp @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// -------------------------------------------------------------------------------------------------- +// configuration.cpp +// +// +// Access and update configuration values, falling back on legacy CLRConfig methods where necessary. +// +// -------------------------------------------------------------------------------------------------- + +#include "stdafx.h" + +#include "clrconfig.h" +#include "configuration.h" + +LPCWSTR *knobNames = nullptr; +LPCWSTR *knobValues = nullptr; +int numberOfKnobs = 0; + +void Configuration::InitializeConfigurationKnobs(int numberOfConfigs, LPCWSTR *names, LPCWSTR *values) +{ + numberOfKnobs = numberOfConfigs; + + // Neither should be null, or both should be null + _ASSERT(!((names == nullptr) ^ (values == nullptr))); + + knobNames = names; + knobValues = values; +} + +static LPCWSTR GetConfigurationValue(LPCWSTR name) +{ + _ASSERT(name != nullptr); + if (name == nullptr || knobNames == nullptr || knobValues == nullptr) + { + return nullptr; + } + + for (int i = 0; i < numberOfKnobs; ++i) + { + _ASSERT(knobNames[i] != nullptr); + if (wcscmp(name, knobNames[i]) == 0) + { + return knobValues[i]; + } + } + + return nullptr; +} + +DWORD Configuration::GetKnobDWORDValue(LPCWSTR name, const CLRConfig::ConfigDWORDInfo& dwordInfo) +{ + bool returnedDefaultValue; + DWORD legacyValue = CLRConfig::GetConfigValue(dwordInfo, true /* acceptExplicitDefaultFromRegutil */, &returnedDefaultValue); + if (!returnedDefaultValue) + { + return legacyValue; + } + + LPCWSTR knobValue = GetConfigurationValue(name); + if (knobValue != nullptr) + { + return wcstoul(knobValue, nullptr, 0); + } + + return legacyValue; +} + +DWORD Configuration::GetKnobDWORDValue(LPCWSTR name, DWORD defaultValue) +{ + LPCWSTR knobValue = GetConfigurationValue(name); + if (knobValue != nullptr) + { + return wcstoul(knobValue, nullptr, 0); + } + + return defaultValue; +} + +ULONGLONG Configuration::GetKnobULONGLONGValue(LPCWSTR name, ULONGLONG defaultValue) +{ + LPCWSTR knobValue = GetConfigurationValue(name); + if (knobValue != nullptr) + { + return _wcstoui64(knobValue, nullptr, 0); + } + + return defaultValue; +} + +LPCWSTR Configuration::GetKnobStringValue(LPCWSTR name, const CLRConfig::ConfigStringInfo& stringInfo) +{ + LPCWSTR value = CLRConfig::GetConfigValue(stringInfo); + if (value == nullptr) + { + value = GetConfigurationValue(name); + } + + return value; +} + +LPCWSTR Configuration::GetKnobStringValue(LPCWSTR name) +{ + return GetConfigurationValue(name); +} + +bool Configuration::GetKnobBooleanValue(LPCWSTR name, const CLRConfig::ConfigDWORDInfo& dwordInfo) +{ + bool returnedDefaultValue; + DWORD legacyValue = CLRConfig::GetConfigValue(dwordInfo, true /* acceptExplicitDefaultFromRegutil */, &returnedDefaultValue); + if (!returnedDefaultValue) + { + return (legacyValue != 0); + } + + LPCWSTR knobValue = GetConfigurationValue(name); + if (knobValue != nullptr) + { + return (wcscmp(knobValue, W("true")) == 0); + } + + return (legacyValue != 0); +} + +bool Configuration::GetKnobBooleanValue(LPCWSTR name, bool defaultValue) +{ + LPCWSTR knobValue = GetConfigurationValue(name); + if (knobValue != nullptr) + { + return (wcscmp(knobValue, W("true")) == 0); + } + + return defaultValue; +} diff --git a/src/coreclr/utilcode/corimage.cpp b/src/coreclr/utilcode/corimage.cpp new file mode 100644 index 00000000000..a1aff8fe82d --- /dev/null +++ b/src/coreclr/utilcode/corimage.cpp @@ -0,0 +1,259 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// + +/*============================================================ +** +** CorImage.cpp +** +** IMAGEHLP routines so we can avoid early binding to that DLL. +** +===========================================================*/ + +#include "stdafx.h" +#include "contract.h" +#include <daccess.h> +#include "corimage.h" +#include "safemath.h" + +#define RTL_MEG (1024UL * 1024UL) +#define RTLP_IMAGE_MAX_DOS_HEADER ( 256UL * RTL_MEG) + +// IMAGE_FIRST_SECTION doesn't need 32/64 versions since the file header is +// the same either way. + +#define PTR_IMAGE_FIRST_SECTION( ntheader ) \ + PTR_IMAGE_SECTION_HEADER \ + (dac_cast<TADDR>(ntheader) + \ + FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + \ + VAL16((ntheader)->FileHeader.SizeOfOptionalHeader) \ + ) + +#ifndef DACCESS_COMPILE + +IMAGE_NT_HEADERS *Cor_RtlImageNtHeader(VOID *pvBase, ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + IMAGE_NT_HEADERS *pNtHeaders = NULL; + if (pvBase && (pvBase != (VOID*)-1)) { + struct Param + { + IMAGE_DOS_HEADER *pDos; + ULONG FileLength; + IMAGE_NT_HEADERS *pNtHeaders; + } param; + param.pDos = (IMAGE_DOS_HEADER*)pvBase; + param.FileLength = FileLength; + param.pNtHeaders = pNtHeaders; + + PAL_TRY(Param *, pParam, ¶m) { + if ( (pParam->pDos->e_magic == VAL16(IMAGE_DOS_SIGNATURE)) + && ((DWORD)VAL32(pParam->pDos->e_lfanew) < RTLP_IMAGE_MAX_DOS_HEADER) + && ovadd_lt((DWORD)VAL32(pParam->pDos->e_lfanew), sizeof(IMAGE_FILE_HEADER) + sizeof(DWORD), pParam->FileLength)) { + pParam->pNtHeaders = (IMAGE_NT_HEADERS*)((BYTE*)pParam->pDos + VAL32(pParam->pDos->e_lfanew)); + if (pParam->pNtHeaders->Signature != VAL32(IMAGE_NT_SIGNATURE)) + pParam->pNtHeaders = NULL; + } + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + param.pNtHeaders = NULL; + } + PAL_ENDTRY + + pNtHeaders = param.pNtHeaders; + } + return pNtHeaders; +} + +#endif // #ifndef DACCESS_COMPILE + +EXTERN_C PIMAGE_SECTION_HEADER +Cor_RtlImageRvaToSection32(PTR_IMAGE_NT_HEADERS32 NtHeaders, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + ULONG i; + PTR_IMAGE_SECTION_HEADER NtSection; + + NtSection = PTR_IMAGE_FIRST_SECTION( NtHeaders ); + for (i=0; i<NtHeaders->FileHeader.NumberOfSections; i++) { + if (FileLength && + (((VAL32(NtSection->PointerToRawData) > FileLength)) || + (VAL32(NtSection->SizeOfRawData) > FileLength - VAL32(NtSection->PointerToRawData)))) + return NULL; + if (Rva >= VAL32(NtSection->VirtualAddress) && + Rva < VAL32(NtSection->VirtualAddress) + VAL32(NtSection->SizeOfRawData)) + return NtSection; + + ++NtSection; + } + + return NULL; +} + +EXTERN_C PIMAGE_SECTION_HEADER +Cor_RtlImageRvaToSection64(PTR_IMAGE_NT_HEADERS64 NtHeaders, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + ULONG i; + PTR_IMAGE_SECTION_HEADER NtSection; + + NtSection = PTR_IMAGE_FIRST_SECTION( NtHeaders ); + for (i=0; i<VAL16(NtHeaders->FileHeader.NumberOfSections); i++) { + if (FileLength && + (((VAL32(NtSection->PointerToRawData) > FileLength)) || + (VAL32(NtSection->SizeOfRawData) > FileLength - VAL32(NtSection->PointerToRawData)))) + return NULL; + if (Rva >= VAL32(NtSection->VirtualAddress) && + Rva < VAL32(NtSection->VirtualAddress) + VAL32(NtSection->SizeOfRawData)) + return NtSection; + + ++NtSection; + } + + return NULL; +} + +EXTERN_C PIMAGE_SECTION_HEADER +Cor_RtlImageRvaToSection(PTR_IMAGE_NT_HEADERS NtHeaders, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + if (NtHeaders->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR32_MAGIC)) + return Cor_RtlImageRvaToSection32((PTR_IMAGE_NT_HEADERS32)NtHeaders, + Rva, FileLength); + else if(NtHeaders->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR64_MAGIC)) + return Cor_RtlImageRvaToSection64((PTR_IMAGE_NT_HEADERS64)NtHeaders, + Rva, FileLength); + else { + _ASSERTE(!"Invalid File Type"); + return NULL; + } +} + +EXTERN_C PBYTE Cor_RtlImageRvaToVa32(PTR_IMAGE_NT_HEADERS32 NtHeaders, + PBYTE Base, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + PIMAGE_SECTION_HEADER NtSection = + Cor_RtlImageRvaToSection32(NtHeaders, + Rva, + FileLength); + + if (NtSection != NULL) + return (Base + + (Rva - VAL32(NtSection->VirtualAddress)) + + VAL32(NtSection->PointerToRawData)); + else + return NULL; +} + +EXTERN_C PBYTE Cor_RtlImageRvaToVa64(PTR_IMAGE_NT_HEADERS64 NtHeaders, + PBYTE Base, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + PIMAGE_SECTION_HEADER NtSection = + Cor_RtlImageRvaToSection64(NtHeaders, + Rva, + FileLength); + + if (NtSection != NULL) + return (Base + + (Rva - VAL32(NtSection->VirtualAddress)) + + VAL32(NtSection->PointerToRawData)); + else + return NULL; +} + +EXTERN_C PBYTE Cor_RtlImageRvaToVa(PTR_IMAGE_NT_HEADERS NtHeaders, + PBYTE Base, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + if (NtHeaders->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR32_MAGIC)) + return Cor_RtlImageRvaToVa32((PTR_IMAGE_NT_HEADERS32)NtHeaders, + Base, Rva, FileLength); + else if(NtHeaders->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR64_MAGIC)) + return Cor_RtlImageRvaToVa64((PTR_IMAGE_NT_HEADERS64)NtHeaders, + Base, Rva, FileLength); + else { + _ASSERTE(!"Invalid File Type"); + return NULL; + } +} + +EXTERN_C PBYTE Cor_RtlImageDirToVa(PTR_IMAGE_NT_HEADERS NtHeaders, + PBYTE Base, + UINT DirIndex, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + if (NtHeaders->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR32_MAGIC)) + return Cor_RtlImageRvaToVa32((PTR_IMAGE_NT_HEADERS32)NtHeaders, Base, + VAL32(((PTR_IMAGE_NT_HEADERS32)NtHeaders)->OptionalHeader.DataDirectory[DirIndex].VirtualAddress), + FileLength); + else if(NtHeaders->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR64_MAGIC)) + return Cor_RtlImageRvaToVa64((PTR_IMAGE_NT_HEADERS64)NtHeaders, Base, + VAL32(((PTR_IMAGE_NT_HEADERS64)NtHeaders)->OptionalHeader.DataDirectory[DirIndex].VirtualAddress), + FileLength); + else { + _ASSERTE(!"Invalid File Type"); + return NULL; + } +} + +EXTERN_C PIMAGE_SECTION_HEADER +Cor_RtlImageRvaRangeToSection(PTR_IMAGE_NT_HEADERS NtHeaders, + ULONG Rva, + ULONG Range, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + ULONG i; + PTR_IMAGE_SECTION_HEADER NtSection; + + if (!Range) + return Cor_RtlImageRvaToSection(NtHeaders, Rva, FileLength); + + NtSection = PTR_IMAGE_FIRST_SECTION( NtHeaders ); + for (i=0; i<VAL16(NtHeaders->FileHeader.NumberOfSections); i++) { + if (FileLength && + ((VAL32(NtSection->PointerToRawData) > FileLength) || + (VAL32(NtSection->SizeOfRawData) > FileLength - VAL32(NtSection->PointerToRawData)))) + return NULL; + if (Rva >= VAL32(NtSection->VirtualAddress) && + Rva + Range <= VAL32(NtSection->VirtualAddress) + VAL32(NtSection->SizeOfRawData)) + return NtSection; + + ++NtSection; + } + + return NULL; +} + +EXTERN_C DWORD Cor_RtlImageRvaToOffset(PTR_IMAGE_NT_HEADERS NtHeaders, + ULONG Rva, + ULONG FileLength) +{ + LIMITED_METHOD_CONTRACT; + PIMAGE_SECTION_HEADER NtSection = + Cor_RtlImageRvaToSection(NtHeaders, + Rva, + FileLength); + + if (NtSection) + return ((Rva - VAL32(NtSection->VirtualAddress)) + + VAL32(NtSection->PointerToRawData)); + else + return NULL; +} diff --git a/src/coreclr/utilcode/cycletimer.cpp b/src/coreclr/utilcode/cycletimer.cpp new file mode 100644 index 00000000000..642ce22e630 --- /dev/null +++ b/src/coreclr/utilcode/cycletimer.cpp @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" + +#include "cycletimer.h" +#include "winbase.h" +#include "winwrap.h" +#include "assert.h" +#include "utilcode.h" + +bool CycleTimer::GetThreadCyclesS(unsigned __int64* cycles) +{ + BOOL res = FALSE; + res = QueryThreadCycleTime(GetCurrentThread(), cycles); + return res != FALSE; +} + +static const int SampleLoopSize = 1000000; + +// static +double CycleTimer::CyclesPerSecond() +{ + // Windows does not provide a way of converting cycles to time -- reasonably enough, + // since the frequency of a machine may vary, due, e.g., to power management. + // Windows *does* allow you to translate QueryPerformanceCounter counts into time, + // however. So we'll assume that the clock speed stayed constant, and measure both the + // QPC counts and cycles of a short loop, to get a conversion factor. + LARGE_INTEGER lpFrequency; + if (!QueryPerformanceFrequency(&lpFrequency)) return 0.0; + // Otherwise... + LARGE_INTEGER qpcStart; + unsigned __int64 cycleStart; + if (!QueryPerformanceCounter(&qpcStart)) return 0.0; + if (!GetThreadCyclesS(&cycleStart)) return 0.0; + volatile int sum = 0; + for (int k = 0; k < SampleLoopSize; k++) + { + sum += k; + } + LARGE_INTEGER qpcEnd; + if (!QueryPerformanceCounter(&qpcEnd)) return 0.0; + unsigned __int64 cycleEnd; + if (!GetThreadCyclesS(&cycleEnd)) return 0.0; + + double qpcTicks = ((double)qpcEnd.QuadPart) - ((double)qpcStart.QuadPart); + double secs = (qpcTicks / ((double)lpFrequency.QuadPart)); + double cycles = ((double)cycleEnd) - ((double)cycleStart); + return cycles / secs; +} + +// static +unsigned __int64 CycleTimer::QueryOverhead() +{ + unsigned __int64 tot = 0; + unsigned __int64 startCycles; + unsigned __int64 endCycles; + const int N = 1000; + bool b = GetThreadCyclesS(&startCycles); assert(b); + for (int i = 0; i < N; i++) + { + b = GetThreadCyclesS(&endCycles); assert(b); + tot += (endCycles-startCycles); + startCycles = endCycles; + } + return tot/N; +} + +// static +void CycleTimer::InterlockedAddU64(unsigned __int64* loc, unsigned __int64 amount) +{ + volatile __int64* vloc = (volatile __int64*)loc; + unsigned __int64 prev = *vloc; + for (;;) + { + unsigned __int64 next = prev + amount; + __int64 snext = (__int64)next; + __int64 sprev = (__int64)prev; + __int64 res = InterlockedCompareExchange64(vloc, snext, sprev); + if (res == sprev) return; + else prev = (unsigned __int64)res; + } +} + diff --git a/src/coreclr/utilcode/dacutil.cpp b/src/coreclr/utilcode/dacutil.cpp new file mode 100644 index 00000000000..914007e6781 --- /dev/null +++ b/src/coreclr/utilcode/dacutil.cpp @@ -0,0 +1,246 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// +// Internal data access functionality. +// + +//***************************************************************************** + +#include "stdafx.h" + +#include <winwrap.h> +#include <utilcode.h> +#include <dacprivate.h> + +//---------------------------------------------------------------------------- +// +// LiveProcDataTarget. +// +//---------------------------------------------------------------------------- + +LiveProcDataTarget::LiveProcDataTarget(HANDLE process, + DWORD processId, + CLRDATA_ADDRESS baseAddressOfEngine) +{ + m_process = process; + m_processId = processId; + m_baseAddressOfEngine = baseAddressOfEngine; +} + +STDMETHODIMP +LiveProcDataTarget::QueryInterface( + THIS_ + IN REFIID InterfaceId, + OUT PVOID* Interface + ) +{ + if (InterfaceId == IID_IUnknown || + InterfaceId == IID_ICLRDataTarget) + { + *Interface = (ICLRDataTarget*)this; + // No need to refcount as this class is contained. + return S_OK; + } + else + { + *Interface = NULL; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) +LiveProcDataTarget::AddRef( + THIS + ) +{ + // No need to refcount as this class is contained. + return 1; +} + +STDMETHODIMP_(ULONG) +LiveProcDataTarget::Release( + THIS + ) +{ + SUPPORTS_DAC_HOST_ONLY; + // No need to refcount as this class is contained. + return 0; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::GetMachineType( + /* [out] */ ULONG32 *machine) +{ + LIMITED_METHOD_CONTRACT; + +#if defined(TARGET_X86) + *machine = IMAGE_FILE_MACHINE_I386; +#elif defined(TARGET_AMD64) + *machine = IMAGE_FILE_MACHINE_AMD64; +#elif defined(TARGET_ARM) + *machine = IMAGE_FILE_MACHINE_ARMNT; +#else + PORTABILITY_ASSERT("Unknown Processor"); +#endif + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::GetPointerSize( + /* [out] */ ULONG32 *size) +{ + LIMITED_METHOD_CONTRACT; + + *size = sizeof(void*); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::GetImageBase( + /* [string][in] */ LPCWSTR name, + /* [out] */ CLRDATA_ADDRESS *base) +{ + // + // The only image base that the access code cares + // about right now is the base of mscorwks. + // + + if (wcscmp(name, MAIN_CLR_DLL_NAME_W)) + { + return E_NOINTERFACE; + } + + // + // If a base address was specified, use that + // + if (NULL != m_baseAddressOfEngine) + { + *base = m_baseAddressOfEngine; + return S_OK; + } + + // + // Our creator must have told us WHICH clr to work with. + // + return E_FAIL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::ReadVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [length_is][size_is][out] */ PBYTE buffer, + /* [in] */ ULONG32 request, + /* [optional][out] */ ULONG32 *done) +{ + // ReadProcessMemory will fail if any part of the + // region to read does not have read access. This + // routine attempts to read the largest valid prefix + // so it has to break up reads on page boundaries. + + HRESULT status = S_OK; + ULONG32 totalDone = 0; + SIZE_T read; + ULONG32 readSize; + + while (request > 0) + { + // Calculate bytes to read and don't let read cross + // a page boundary. + readSize = GetOsPageSize() - (ULONG32)(address & (GetOsPageSize() - 1)); + readSize = min(request, readSize); + + if (!ReadProcessMemory(m_process, (PVOID)(ULONG_PTR)address, + buffer, readSize, &read)) + { + if (totalDone == 0) + { + // If we haven't read anything indicate failure. + status = E_FAIL; + } + break; + } + + totalDone += (ULONG32)read; + address += read; + buffer += read; + request -= (ULONG32)read; + } + + *done = totalDone; + return status; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::WriteVirtual( + /* [in] */ CLRDATA_ADDRESS address, + /* [size_is][in] */ PBYTE buffer, + /* [in] */ ULONG32 request, + /* [optional][out] */ ULONG32 *done) +{ + // Not necessary yet. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::GetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [out] */ CLRDATA_ADDRESS* value) +{ + SUPPORTS_DAC; + // Not necessary yet. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::SetTLSValue( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 index, + /* [in] */ CLRDATA_ADDRESS value) +{ + // Not necessary yet. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::GetCurrentThreadID( + /* [out] */ ULONG32* threadID) +{ + SUPPORTS_DAC; + // Not necessary yet. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::GetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextFlags, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + // Not necessary yet. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::SetThreadContext( + /* [in] */ ULONG32 threadID, + /* [in] */ ULONG32 contextSize, + /* [out, size_is(contextSize)] */ PBYTE context) +{ + // Not necessary yet. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE +LiveProcDataTarget::Request( + /* [in] */ ULONG32 reqCode, + /* [in] */ ULONG32 inBufferSize, + /* [size_is][in] */ BYTE *inBuffer, + /* [in] */ ULONG32 outBufferSize, + /* [size_is][out] */ BYTE *outBuffer) +{ + // None supported. + return E_INVALIDARG; +} diff --git a/src/coreclr/utilcode/debug.cpp b/src/coreclr/utilcode/debug.cpp new file mode 100644 index 00000000000..b4f44dc989d --- /dev/null +++ b/src/coreclr/utilcode/debug.cpp @@ -0,0 +1,841 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// Debug.cpp +// +// Helper code for debugging. +//***************************************************************************** +// + + +#include "stdafx.h" +#include "utilcode.h" +#include "ex.h" +#include "corexcep.h" + +#ifdef _DEBUG +#define LOGGING +#endif + + +#include "log.h" + +extern "C" _CRTIMP int __cdecl _flushall(void); + +#ifdef HOST_WINDOWS +void CreateCrashDumpIfEnabled(bool stackoverflow = false); +#endif + +// Global state counter to implement SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE. +Volatile<LONG> g_DbgSuppressAllocationAsserts = 0; + + +#ifdef _DEBUG + +int LowResourceMessageBoxHelperAnsi( + LPCSTR szText, // Text message + LPCSTR szTitle, // Title + UINT uType); // Style of MessageBox + +//***************************************************************************** +// This struct tracks the asserts we want to ignore in the rest of this +// run of the application. +//***************************************************************************** +struct _DBGIGNOREDATA +{ + char rcFile[_MAX_PATH]; + int iLine; + bool bIgnore; +}; + +typedef CDynArray<_DBGIGNOREDATA> DBGIGNORE; +static BYTE grIgnoreMemory[sizeof(DBGIGNORE)]; +inline DBGIGNORE* GetDBGIGNORE() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + static bool fInit; // = false; + if (!fInit) + { + SCAN_IGNORE_THROW; // Doesn't really throw here. + new (grIgnoreMemory) CDynArray<_DBGIGNOREDATA>(); + fInit = true; + } + + return (DBGIGNORE*)grIgnoreMemory; +} + +// Continue the app on an assert. Still output the assert, but +// Don't throw up a GUI. This is useful for testing fatal error +// paths (like FEEE) where the runtime asserts. +BOOL ContinueOnAssert() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + static ConfigDWORD fNoGui; + return fNoGui.val(CLRConfig::INTERNAL_ContinueOnAssert); +} + +void DoRaiseExceptionOnAssert(DWORD chance) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC; + +#if !defined(DACCESS_COMPILE) + if (chance) + { +#ifndef TARGET_UNIX + PAL_TRY_NAKED + { + RaiseException(EXCEPTION_INTERNAL_ASSERT, 0, 0, NULL); + } + PAL_EXCEPT_NAKED((chance == 1) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) + { + } + PAL_ENDTRY_NAKED +#else // TARGET_UNIX + // For PAL always raise the exception. + RaiseException(EXCEPTION_INTERNAL_ASSERT, 0, 0, NULL); +#endif // TARGET_UNIX + } +#endif // !DACCESS_COMPILE +} + +enum RaiseOnAssertOptions { rTestAndRaise, rTestOnly }; + +BOOL RaiseExceptionOnAssert(RaiseOnAssertOptions option = rTestAndRaise) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC; + + // ok for debug-only code to take locks + CONTRACT_VIOLATION(TakesLockViolation); + + DWORD fRet = 0; + +#if !defined(DACCESS_COMPILE) + static ConfigDWORD fRaiseExceptionOnAssert; + // + // we don't want this config key to affect mscordacwks as well! + // + EX_TRY + { + fRet = fRaiseExceptionOnAssert.val(CLRConfig::INTERNAL_RaiseExceptionOnAssert); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + if (option == rTestAndRaise && fRet != 0) + { + DoRaiseExceptionOnAssert(fRet); + } +#endif // !DACCESS_COMPILE + + return fRet != 0; +} + +BOOL DebugBreakOnAssert() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC; + + // ok for debug-only code to take locks + CONTRACT_VIOLATION(TakesLockViolation); + + BOOL fRet = FALSE; + +#ifndef DACCESS_COMPILE + static ConfigDWORD fDebugBreak; + // + // we don't want this config key to affect mscordacwks as well! + // + EX_TRY + { + fRet = fDebugBreak.val(CLRConfig::INTERNAL_DebugBreakOnAssert); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +#endif // DACCESS_COMPILE + + return fRet; +} + +VOID TerminateOnAssert() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + ShutdownLogging(); +#ifdef HOST_WINDOWS + CreateCrashDumpIfEnabled(); +#endif + RaiseFailFastException(NULL, NULL, 0); +} + +thread_local bool f_bDisplayingAssertDlg; + +VOID LogAssert( + LPCSTR szFile, + int iLine, + LPCSTR szExpr +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + // Log asserts to the stress log. Note that we can't include the szExpr b/c that + // may not be a string literal (particularly for formatt-able asserts). + STRESS_LOG2(LF_ASSERT, LL_ALWAYS, "ASSERT:%s, line:%d\n", szFile, iLine); + + SYSTEMTIME st; +#ifndef TARGET_UNIX + GetLocalTime(&st); +#else + GetSystemTime(&st); +#endif + + PathString exename; + WszGetModuleFileName(NULL, exename); + + LOG((LF_ASSERT, + LL_FATALERROR, + "FAILED ASSERT(PID %d [0x%08x], Thread: %d [0x%x]) (%lu/%lu/%lu: %02lu:%02lu:%02lu %s): File: %s, Line %d : %s\n", + GetCurrentProcessId(), + GetCurrentProcessId(), + GetCurrentThreadId(), + GetCurrentThreadId(), + (ULONG)st.wMonth, + (ULONG)st.wDay, + (ULONG)st.wYear, + 1 + (( (ULONG)st.wHour + 11 ) % 12), + (ULONG)st.wMinute, + (ULONG)st.wSecond, + (st.wHour < 12) ? "am" : "pm", + szFile, + iLine, + szExpr)); + LOG((LF_ASSERT, LL_FATALERROR, "RUNNING EXE: %ws\n", exename.GetUnicode())); +} + +//***************************************************************************** + +BOOL LaunchJITDebugger() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + BOOL fSuccess = FALSE; +#ifndef TARGET_UNIX + EX_TRY + { + SString debugger; + GetDebuggerSettingInfo(debugger, NULL); + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + // We can leave this event as it is since it is inherited by a child process. + // We will block one scheduler, but the process is asking a user if they want to attach debugger. + HandleHolder eventHandle = WszCreateEvent(&sa, TRUE, FALSE, NULL); + if (eventHandle == NULL) + ThrowOutOfMemory(); + + SString cmdLine; + cmdLine.Printf(debugger, GetCurrentProcessId(), eventHandle.GetValue()); + + STARTUPINFO StartupInfo; + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + StartupInfo.lpDesktop = const_cast<LPWSTR>(W("Winsta0\\Default")); + + PROCESS_INFORMATION ProcessInformation; + if (WszCreateProcess(NULL, cmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInformation)) + { + WaitForSingleObject(eventHandle.GetValue(), INFINITE); + } + + fSuccess = TRUE; + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +#endif // !TARGET_UNIX + return fSuccess; +} + + +//***************************************************************************** +// This function is called in order to ultimately return an out of memory +// failed hresult. But this code will check what environment you are running +// in and give an assert for running in a debug build environment. Usually +// out of memory on a dev machine is a bogus allocation, and this allows you +// to catch such errors. But when run in a stress envrionment where you are +// trying to get out of memory, assert behavior stops the tests. +//***************************************************************************** +HRESULT _OutOfMemory(LPCSTR szFile, int iLine) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + DbgWriteEx(W("WARNING: Out of memory condition being issued from: %hs, line %d\n"), + szFile, iLine); + return (E_OUTOFMEMORY); +} + +int _DbgBreakCount = 0; +static const char * szLowMemoryAssertMessage = "Assert failure (unable to format)"; + +//***************************************************************************** +// This function will handle ignore codes and tell the user what is happening. +//***************************************************************************** +bool _DbgBreakCheck( + LPCSTR szFile, + int iLine, + LPCSTR szExpr, + BOOL fConstrained) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_DEBUG_ONLY; + + DBGIGNORE* pDBGIFNORE = GetDBGIGNORE(); + _DBGIGNOREDATA *psData; + int i; + + // Check for ignore all. + for (i = 0, psData = pDBGIFNORE->Ptr(); i < pDBGIFNORE->Count(); i++, psData++) + { + if (psData->iLine == iLine && SString::_stricmp(psData->rcFile, szFile) == 0 && psData->bIgnore == true) + { + return false; + } + } + + CONTRACT_VIOLATION(FaultNotFatal | GCViolation | TakesLockViolation); + + SString debugOutput; + SString dialogOutput; + SString modulePath; + SString dialogTitle; + SString dialogIgnoreMessage; + BOOL formattedMessages = FALSE; + + // If we are low on memory we cannot even format a message. If this happens we want to + // contain the exception here but display as much information as we can about the exception. + if (!fConstrained) + { + EX_TRY + { + ClrGetModuleFileName(0, modulePath); + debugOutput.Printf( + W("\nAssert failure(PID %d [0x%08x], Thread: %d [0x%04x]): %hs\n") + W(" File: %hs Line: %d\n") + W(" Image: "), + GetCurrentProcessId(), GetCurrentProcessId(), + GetCurrentThreadId(), GetCurrentThreadId(), + szExpr, szFile, iLine); + debugOutput.Append(modulePath); + debugOutput.Append(W("\n\n")); + + // Change format for message box. The extra spaces in the title + // are there to get around format truncation. + dialogOutput.Printf( + W("%hs\n\n%hs, Line: %d\n\nAbort - Kill program\nRetry - Debug\nIgnore - Keep running\n") + W("\n\nImage:\n"), szExpr, szFile, iLine); + dialogOutput.Append(modulePath); + dialogOutput.Append(W("\n")); + dialogTitle.Printf(W("Assert Failure (PID %d, Thread %d/0x%04x)"), + GetCurrentProcessId(), GetCurrentThreadId(), GetCurrentThreadId()); + + dialogIgnoreMessage.Printf(W("Ignore the assert for the rest of this run?\nYes - Assert will never fire again.\nNo - Assert will continue to fire.\n\n%hs\nLine: %d\n"), + szFile, iLine); + + formattedMessages = TRUE; + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + } + + // Emit assert in debug output and console for easy access. + if (formattedMessages) + { + WszOutputDebugString(debugOutput); + fwprintf(stderr, W("%s"), (const WCHAR*)debugOutput); + } + else + { + // Note: we cannot convert to unicode or concatenate in this situation. + OutputDebugStringA(szLowMemoryAssertMessage); + OutputDebugStringA("\n"); + OutputDebugStringA(szFile); + OutputDebugStringA("\n"); + OutputDebugStringA(szExpr); + OutputDebugStringA("\n"); + printf(szLowMemoryAssertMessage); + printf("\n"); + printf(szFile); + printf("\n"); + printf("%s", szExpr); + printf("\n"); + } + + LogAssert(szFile, iLine, szExpr); + FlushLogging(); // make certain we get the last part of the log + _flushall(); + + if (ContinueOnAssert()) + { + return false; // don't stop debugger. No gui. + } + + if (IsDebuggerPresent() || DebugBreakOnAssert()) + { + return true; // like a retry + } + + if (NoGuiOnAssert()) + { + TerminateOnAssert(); + } + + if (f_bDisplayingAssertDlg) + { + // We are already displaying an assert dialog box on this thread. The reason why we came here is + // the message loop run by the API we call to display the UI. A message was dispatched and execution + // ended up in the runtime where it fired another assertion. If this happens before the dialog had + // a chance to fully initialize the original assert may not be visible which is misleading for the + // user. So we just continue. + return false; + } + + f_bDisplayingAssertDlg = true; + + // Tell user there was an error. + _DbgBreakCount++; + int ret; + if (formattedMessages) + { + ret = UtilMessageBoxCatastrophicNonLocalized( + W("%s"), dialogTitle, MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION, TRUE, (const WCHAR*)dialogOutput); + } + else + { + ret = LowResourceMessageBoxHelperAnsi( + szExpr, szLowMemoryAssertMessage, MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION); + } + --_DbgBreakCount; + + f_bDisplayingAssertDlg = false; + + switch(ret) + { + case 0: +#if 0 + // The message box was not displayed. Tell caller to break. + return true; +#endif + // For abort, just quit the app. + case IDABORT: +#ifdef HOST_WINDOWS + CreateCrashDumpIfEnabled(); +#endif + TerminateProcess(GetCurrentProcess(), 1); + break; + + // Tell caller to break at the correct location. + case IDRETRY: + if (IsDebuggerPresent()) + { + SetErrorMode(0); + } + else + { + LaunchJITDebugger(); + } + return true; + + // If we want to ignore the assert, find out if this is forever. + case IDIGNORE: + if (formattedMessages) + { + if (UtilMessageBoxCatastrophicNonLocalized( + dialogIgnoreMessage, + W("Ignore Assert Forever?"), + MB_ICONQUESTION | MB_YESNO, + TRUE) != IDYES) + { + break; + } + } + else + { + if (LowResourceMessageBoxHelperAnsi( + "Ignore the assert for the rest of this run?\nYes - Assert will never fire again.\nNo - Assert will continue to fire.\n", + "Ignore Assert Forever?", + MB_ICONQUESTION | MB_YESNO) != IDYES) + { + break; + } + } + if ((psData = pDBGIFNORE->Append()) == 0) + { + return false; + } + psData->bIgnore = true; + psData->iLine = iLine; + strcpy(psData->rcFile, szFile); + break; + } + + return false; +} + +bool _DbgBreakCheckNoThrow( + LPCSTR szFile, + int iLine, + LPCSTR szExpr, + BOOL fConstrained) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_DEBUG_ONLY; + + bool failed = false; + bool result = false; + EX_TRY + { + result = _DbgBreakCheck(szFile, iLine, szExpr, fConstrained); + } + EX_CATCH + { + failed = true; + } + EX_END_CATCH(SwallowAllExceptions); + + if (failed) + { + return true; + } + return result; +} + +#ifndef TARGET_UNIX +// Get the timestamp from the PE file header. This is useful +unsigned DbgGetEXETimeStamp() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + static ULONG cache = 0; + if (cache == 0) { + // Use GetModuleHandleA to avoid contracts - this results in a recursive loop initializing the + // debug allocator. + BYTE* imageBase = (BYTE*) GetModuleHandleA(NULL); + if (imageBase == 0) + return(0); + IMAGE_DOS_HEADER *pDOS = (IMAGE_DOS_HEADER*) imageBase; + if ((pDOS->e_magic != VAL16(IMAGE_DOS_SIGNATURE)) || (pDOS->e_lfanew == 0)) + return(0); + + IMAGE_NT_HEADERS *pNT = (IMAGE_NT_HEADERS*) (VAL32(pDOS->e_lfanew) + imageBase); + cache = VAL32(pNT->FileHeader.TimeDateStamp); + } + + return cache; +} +#endif // TARGET_UNIX + +// Called from within the IfFail...() macros. Set a breakpoint here to break on +// errors. +VOID DebBreak() +{ + STATIC_CONTRACT_LEAF; + static int i = 0; // add some code here so that we'll be able to set a BP + i++; +} + +VOID DebBreakHr(HRESULT hr) +{ + STATIC_CONTRACT_LEAF; + static int i = 0; // add some code here so that we'll be able to set a BP + _ASSERTE(hr != (HRESULT) 0xcccccccc); + i++; + + // @CONSIDER: We maybe want this on retail builds. + #ifdef _DEBUG + static DWORD dwBreakHR = 99; + + if (dwBreakHR == 99) + dwBreakHR = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnHR); + if (dwBreakHR == (DWORD)hr) + { + _DbgBreak(); + } + #endif +} + +#ifndef TARGET_UNIX +CHAR g_szExprWithStack2[10480]; +#endif +void *dbgForceToMemory; // dummy pointer that pessimises enregistration + +int g_BufferLock = -1; + +VOID DbgAssertDialog(const char *szFile, int iLine, const char *szExpr) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC_HOST_ONLY; + + DEBUG_ONLY_FUNCTION; + +#ifdef DACCESS_COMPILE + // In the DAC case, asserts can mean one of two things. + // Either there is a bug in the DAC infrastructure itself (a real assert), or just + // that the target is corrupt or being accessed at an inconsistent state (a "target + // consistency failure"). For target consistency failures, we need a mechanism to disable them + // (without affecting other asserts) so that we can test corrupt / inconsistent targets. + + // @dbgtodo DAC: For now we're treating all asserts as if they are target consistency checks. + // In the future we should differentiate the two so that real asserts continue to fire, even when + // we expect the target to be inconsistent. See DevDiv Bugs 31674. + if( !DacTargetConsistencyAssertsEnabled() ) + { + return; + } +#endif // #ifndef DACCESS_COMPILE + + // We increment this every time we use the SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE + // macro below. If it is a big number it means either a lot of threads are asserting + // or we have a recursion in the Assert logic (usually the latter). At least with this + // code in place, we don't get stack overflow (and the process torn down). + // the correct fix is to avoid calling asserting when allocating memory with an assert. + if (g_DbgSuppressAllocationAsserts > 16) + DebugBreak(); + + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + // Raising the assert dialog can cause us to re-enter the host when allocating + // memory for the string. Since this is debug-only code, we can safely skip + // violation asserts here, particularly since they can also cause infinite + // recursion. + PERMANENT_CONTRACT_VIOLATION(HostViolation, ReasonDebugOnly); + + dbgForceToMemory = &szFile; //make certain these args are available in the debugger + dbgForceToMemory = &iLine; + dbgForceToMemory = &szExpr; + + RaiseExceptionOnAssert(rTestAndRaise); + + BOOL fConstrained = FALSE; + + DWORD dwAssertStacktrace = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_AssertStacktrace); + + LONG lAlreadyOwned = InterlockedExchange((LPLONG)&g_BufferLock, 1); + if (fConstrained || dwAssertStacktrace == 0 || lAlreadyOwned == 1) + { + if (_DbgBreakCheckNoThrow(szFile, iLine, szExpr, fConstrained)) + { + _DbgBreak(); + } + } + else + { + char *szExprToDisplay = (char*)szExpr; +#ifdef TARGET_UNIX + BOOL fGotStackTrace = TRUE; +#else + BOOL fGotStackTrace = FALSE; +#ifndef DACCESS_COMPILE + EX_TRY + { + FAULT_NOT_FATAL(); + szExprToDisplay = &g_szExprWithStack2[0]; + strcpy(szExprToDisplay, szExpr); + strcat_s(szExprToDisplay, _countof(g_szExprWithStack2), "\n\n"); + GetStringFromStackLevels(1, 10, szExprToDisplay + strlen(szExprToDisplay)); + fGotStackTrace = TRUE; + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); +#endif // DACCESS_COMPILE +#endif // TARGET_UNIX + + if (_DbgBreakCheckNoThrow(szFile, iLine, szExprToDisplay, !fGotStackTrace)) + { + _DbgBreak(); + } + + g_BufferLock = 0; + } +} // DbgAssertDialog + +#if !defined(DACCESS_COMPILE) +//----------------------------------------------------------------------------- +// Returns an a stacktrace for a given context. +// Very useful inside exception filters. +// Returns true if successful, false on failure (such as OOM). +// This never throws. +//----------------------------------------------------------------------------- +bool GetStackTraceAtContext(SString & s, CONTEXT * pContext) +{ + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + STATIC_CONTRACT_DEBUG_ONLY; + + // NULL means use the current context. + bool fSuccess = false; + + FAULT_NOT_FATAL(); + +#ifndef TARGET_UNIX + EX_TRY + { + const int cTotal = cfrMaxAssertStackLevels - 1; + // If we have a supplied context, then don't skip any frames. Else we'll + // be using the current context, so skip this frame. + const int cSkip = (pContext == NULL) ? 1 : 0; + char * szString = s.OpenANSIBuffer(cchMaxAssertStackLevelStringLen * cTotal); + GetStringFromStackLevels(cSkip, cTotal, szString, pContext); + s.CloseBuffer((COUNT_T) strlen(szString)); + + // If we made it this far w/o throwing, we succeeded. + fSuccess = true; + } + EX_CATCH + { + // Nothing to do here. + } + EX_END_CATCH(SwallowAllExceptions); +#endif // TARGET_UNIX + + return fSuccess; +} // GetStackTraceAtContext +#endif // !defined(DACCESS_COMPILE) +#endif // _DEBUG + +BOOL NoGuiOnAssert() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_DEBUG_ONLY; + + static ConfigDWORD fNoGui; + return fNoGui.val(CLRConfig::INTERNAL_NoGuiOnAssert); +} + +// This helper will throw up a message box without allocating or using stack if possible, and is +// appropriate for either low memory or low stack situations. +int LowResourceMessageBoxHelperAnsi( + LPCSTR szText, // Text message + LPCSTR szTitle, // Title + UINT uType) // Style of MessageBox +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return IDCANCEL;); + } + CONTRACTL_END; + + // In low memory or stack constrained code we cannot format or convert strings, so use the + // ANSI version. + int result = MessageBoxA(NULL, szText, szTitle, uType); + return result; +} + + +/**************************************************************************** + The following two functions are defined to allow Free builds to call + DebugBreak or to Assert with a stack trace for unexpected fatal errors. + Typically these paths are enabled via a registry key in a Free Build +*****************************************************************************/ + +VOID __FreeBuildDebugBreak() +{ + WRAPPER_NO_CONTRACT; // If we're calling this, we're well past caring about contract consistency! + + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnRetailAssert)) + { + DebugBreak(); + } +} + +void *freForceToMemory; // dummy pointer that pessimises enregistration + +void DECLSPEC_NORETURN __FreeBuildAssertFail(const char *szFile, int iLine, const char *szExpr) +{ + WRAPPER_NO_CONTRACT; // If we're calling this, we're well past caring about contract consistency! + + freForceToMemory = &szFile; //make certain these args are available in the debugger + freForceToMemory = &iLine; + freForceToMemory = &szExpr; + + __FreeBuildDebugBreak(); + + SString buffer; + SString modulePath; + + // Give assert in output for easy access. + ClrGetModuleFileName(0, modulePath); +#ifndef TARGET_UNIX + buffer.Printf(W("CLR: Assert failure(PID %d [0x%08x], Thread: %d [0x%x]): %hs\n") + W(" File: %hs, Line: %d Image:\n"), + GetCurrentProcessId(), GetCurrentProcessId(), + GetCurrentThreadId(), GetCurrentThreadId(), + szExpr, szFile, iLine); + buffer.Append(modulePath); + buffer.Append(W("\n")); + WszOutputDebugString(buffer); + // Write out the error to the console + _putws(buffer); +#else // TARGET_UNIX + // UNIXTODO: Do this for Unix. +#endif // TARGET_UNIX + // Log to the stress log. Note that we can't include the szExpr b/c that + // may not be a string literal (particularly for formatt-able asserts). + STRESS_LOG2(LF_ASSERT, LL_ALWAYS, "ASSERT:%s, line:%d\n", szFile, iLine); + + FlushLogging(); // make certain we get the last part of the log + + _flushall(); + + ShutdownLogging(); + +#ifdef HOST_WINDOWS + CreateCrashDumpIfEnabled(); +#endif + RaiseFailFastException(NULL, NULL, 0); + + UNREACHABLE(); +} diff --git a/src/coreclr/utilcode/dlwrap.cpp b/src/coreclr/utilcode/dlwrap.cpp new file mode 100644 index 00000000000..8cb312f8f81 --- /dev/null +++ b/src/coreclr/utilcode/dlwrap.cpp @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +#include "stdafx.h" // Precompiled header key. +#include "utilcode.h" +#include "metadata.h" +#include "ex.h" +#include "pedecoder.h" + +#include <wininet.h> +#include <urlmon.h> + +DWORD +GetFileVersionInfoSizeW_NoThrow( + LPCWSTR lptstrFilename, /* Filename of version stamped file */ + LPDWORD lpdwHandle + ) +{ + WRAPPER_NO_CONTRACT; + HRESULT hr=S_OK; + DWORD dwRet=0; + EX_TRY + { + dwRet=GetFileVersionInfoSize( (LPWSTR)lptstrFilename, lpdwHandle ); + } + EX_CATCH_HRESULT(hr); + if (hr!=S_OK) + SetLastError(hr); + return dwRet; + +} + +BOOL +GetFileVersionInfoW_NoThrow( + LPCWSTR lptstrFilename, /* Filename of version stamped file */ + DWORD dwHandle, /* Information from GetFileVersionSize */ + DWORD dwLen, /* Length of buffer for info */ + LPVOID lpData + ) +{ + WRAPPER_NO_CONTRACT; + HRESULT hr=S_OK; + BOOL bRet=FALSE; + EX_TRY + { + bRet=GetFileVersionInfo( (LPWSTR)lptstrFilename, dwHandle,dwLen,lpData ); + } + EX_CATCH_HRESULT(hr); + if (hr!=S_OK) + SetLastError(hr); + return bRet; + +} + +BOOL +VerQueryValueW_NoThrow( + const LPVOID pBlock, + LPCWSTR lpSubBlock, + LPVOID * lplpBuffer, + PUINT puLen + ) +{ + WRAPPER_NO_CONTRACT; + HRESULT hr=S_OK; + BOOL bRet=FALSE; + EX_TRY + { + bRet=VerQueryValueW( pBlock, (LPWSTR)lpSubBlock,lplpBuffer,puLen ); + } + EX_CATCH_HRESULT(hr); + if (hr!=S_OK) + SetLastError(hr); + return bRet; + +} + diff --git a/src/coreclr/utilcode/ex.cpp b/src/coreclr/utilcode/ex.cpp new file mode 100644 index 00000000000..7b15e4a98a2 --- /dev/null +++ b/src/coreclr/utilcode/ex.cpp @@ -0,0 +1,1358 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +// +// --------------------------------------------------------------------------- +// Ex.cpp +// --------------------------------------------------------------------------- + +#include "stdafx.h" +#include "string.h" +#include "ex.h" +#include "holder.h" + +// error codes +#include "corerror.h" + +#include "../dlls/mscorrc/resource.h" + +#include "olectl.h" + +#include "corexcep.h" + +#define MAX_EXCEPTION_MSG 200 + +// Set if fatal error (like stack overflow or out of memory) occurred in this process. +GVAL_IMPL_INIT(HRESULT, g_hrFatalError, S_OK); + +// Helper function to get an exception object from outside the exception. In +// the CLR, it may be from the Thread object. Non-CLR users have no thread object, +// and it will do nothing. +void GetLastThrownObjectExceptionFromThread(Exception **ppException); + +// Helper function to get pointer to clr module base +void* GetClrModuleBase(); + +Exception *Exception::g_OOMException = NULL; + +// avoid global constructors +static BYTE g_OOMExceptionInstance[sizeof(OutOfMemoryException)]; + +Exception * Exception::GetOOMException() +{ + LIMITED_METHOD_CONTRACT; + + if (!g_OOMException) { + // Create a local copy on the stack and then copy it over to the static instance. + // This avoids race conditions caused by multiple initializations of vtable in the constructor + + OutOfMemoryException local(TRUE); // Construct a "preallocated" instance. + memcpy((void*)&g_OOMExceptionInstance, (void*)&local, sizeof(OutOfMemoryException)); + + g_OOMException = (OutOfMemoryException*)&g_OOMExceptionInstance; + } + + return g_OOMException; +} + +/*virtual*/ Exception *OutOfMemoryException::Clone() +{ + LIMITED_METHOD_CONTRACT; + + return GetOOMException(); +} + +//------------------------------------------------------------------------------ +void Exception::Delete(Exception* pvMemory) +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + SUPPORTS_DAC_HOST_ONLY; // Exceptions aren't currently marshalled by DAC - just used in the host + } + CONTRACTL_END; + + if ((pvMemory == 0) || pvMemory->IsPreallocatedException()) + { + return; + } + + ::delete((Exception *) pvMemory); +} + +void Exception::GetMessage(SString &result) +{ + WRAPPER_NO_CONTRACT; + + return GenerateTopLevelHRExceptionMessage(GetHR(), result); +} + +void HRMsgException::GetMessage(SString &result) +{ + WRAPPER_NO_CONTRACT; + + if (m_msg.IsEmpty()) + HRException::GetMessage(result); + else + result = m_msg; +} + +Exception *Exception::Clone() +{ + CONTRACTL + { + GC_NOTRIGGER; + THROWS; + } + CONTRACTL_END; + + NewHolder<Exception> retExcep(CloneHelper()); + if (m_innerException) + { + retExcep->m_innerException = m_innerException->Clone(); + } + + retExcep.SuppressRelease(); + return retExcep; +} + +Exception *Exception::CloneHelper() +{ + StackSString s; + GetMessage(s); + return new HRMsgException(GetHR(), s); +} + +Exception *Exception::DomainBoundClone() +{ + CONTRACTL + { + // Because we may call DomainBoundCloneHelper() of ObjrefException or CLRLastThrownObjectException + // this should be GC_TRIGGERS, but we can not include EE contracts in Utilcode. + THROWS; + } + CONTRACTL_END; + + NewHolder<Exception> retExcep(DomainBoundCloneHelper()); + if (m_innerException) + { + retExcep->m_innerException = m_innerException->DomainBoundClone(); + } + + retExcep.SuppressRelease(); + return retExcep; +} + +BOOL Exception::IsTerminal() +{ + CONTRACTL + { + GC_NOTRIGGER; + NOTHROW; + + // CLRException::GetHR() can eventually call BaseDomain::CreateHandle(), + // which can indirectly cause a lock if we get a miss in the handle table + // cache (TableCacheMissOnAlloc). Since CLRException::GetHR() is virtual, + // SCAN won't find this for you (though 40 minutes of one of the sql stress + // tests will :-)) + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + HRESULT hr = GetHR(); + return (COR_E_THREADABORTED == hr); +} + +BOOL Exception::IsTransient() +{ + WRAPPER_NO_CONTRACT; + + return IsTransient(GetHR()); +} + +/* static */ +BOOL Exception::IsTransient(HRESULT hr) +{ + LIMITED_METHOD_CONTRACT; + + return (hr == COR_E_THREADABORTED + || hr == COR_E_THREADINTERRUPTED + || hr == COR_E_THREADSTOP + || hr == COR_E_APPDOMAINUNLOADED + || hr == E_OUTOFMEMORY + || hr == HRESULT_FROM_WIN32(ERROR_COMMITMENT_LIMIT) // ran out of room in pagefile + || hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY) + || hr == (HRESULT)STATUS_NO_MEMORY + || hr == COR_E_STACKOVERFLOW + || hr == MSEE_E_ASSEMBLYLOADINPROGRESS); +} + +//------------------------------------------------------------------------------ +// Functions to manage the preallocated exceptions. +// Virtual +BOOL Exception::IsPreallocatedException() +{ // Most exceptions can't be preallocated. If they can be, their class + // should provide a virtual override of this function. + return FALSE; +} + +BOOL Exception::IsPreallocatedOOMException() +{ // This is the preallocated OOM if it is preallocated and is OOM. + return IsPreallocatedException() && (GetInstanceType() == OutOfMemoryException::GetType()); +} + +//------------------------------------------------------------------------------ +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +LPCSTR Exception::GetHRSymbolicName(HRESULT hr) +{ + LIMITED_METHOD_CONTRACT; + +#define CASE_HRESULT(hrname) case hrname: return #hrname; + + switch (hr) + { + CASE_HRESULT(S_OK)// 0x00000000L + CASE_HRESULT(S_FALSE)// 0x00000001L + + CASE_HRESULT(E_UNEXPECTED)// 0x8000FFFFL + CASE_HRESULT(E_NOTIMPL)// 0x80004001L + CASE_HRESULT(E_OUTOFMEMORY)// 0x8007000EL + CASE_HRESULT(E_INVALIDARG)// 0x80070057L + CASE_HRESULT(E_NOINTERFACE)// 0x80004002L + CASE_HRESULT(E_POINTER)// 0x80004003L + CASE_HRESULT(E_HANDLE)// 0x80070006L + CASE_HRESULT(E_ABORT)// 0x80004004L + CASE_HRESULT(E_FAIL)// 0x80004005L + CASE_HRESULT(E_ACCESSDENIED)// 0x80070005L + +#ifdef FEATURE_COMINTEROP + CASE_HRESULT(CO_E_INIT_TLS)// 0x80004006L + CASE_HRESULT(CO_E_INIT_SHARED_ALLOCATOR)// 0x80004007L + CASE_HRESULT(CO_E_INIT_MEMORY_ALLOCATOR)// 0x80004008L + CASE_HRESULT(CO_E_INIT_CLASS_CACHE)// 0x80004009L + CASE_HRESULT(CO_E_INIT_RPC_CHANNEL)// 0x8000400AL + CASE_HRESULT(CO_E_INIT_TLS_SET_CHANNEL_CONTROL)// 0x8000400BL + CASE_HRESULT(CO_E_INIT_TLS_CHANNEL_CONTROL)// 0x8000400CL + CASE_HRESULT(CO_E_INIT_UNACCEPTED_USER_ALLOCATOR)// 0x8000400DL + CASE_HRESULT(CO_E_INIT_SCM_MUTEX_EXISTS)// 0x8000400EL + CASE_HRESULT(CO_E_INIT_SCM_FILE_MAPPING_EXISTS)// 0x8000400FL + CASE_HRESULT(CO_E_INIT_SCM_MAP_VIEW_OF_FILE)// 0x80004010L + CASE_HRESULT(CO_E_INIT_SCM_EXEC_FAILURE)// 0x80004011L + CASE_HRESULT(CO_E_INIT_ONLY_SINGLE_THREADED)// 0x80004012L + +// ****************** +// FACILITY_ITF +// ****************** + + CASE_HRESULT(OLE_E_OLEVERB)// 0x80040000L + CASE_HRESULT(OLE_E_ADVF)// 0x80040001L + CASE_HRESULT(OLE_E_ENUM_NOMORE)// 0x80040002L + CASE_HRESULT(OLE_E_ADVISENOTSUPPORTED)// 0x80040003L + CASE_HRESULT(OLE_E_NOCONNECTION)// 0x80040004L + CASE_HRESULT(OLE_E_NOTRUNNING)// 0x80040005L + CASE_HRESULT(OLE_E_NOCACHE)// 0x80040006L + CASE_HRESULT(OLE_E_BLANK)// 0x80040007L + CASE_HRESULT(OLE_E_CLASSDIFF)// 0x80040008L + CASE_HRESULT(OLE_E_CANT_GETMONIKER)// 0x80040009L + CASE_HRESULT(OLE_E_CANT_BINDTOSOURCE)// 0x8004000AL + CASE_HRESULT(OLE_E_STATIC)// 0x8004000BL + CASE_HRESULT(OLE_E_PROMPTSAVECANCELLED)// 0x8004000CL + CASE_HRESULT(OLE_E_INVALIDRECT)// 0x8004000DL + CASE_HRESULT(OLE_E_WRONGCOMPOBJ)// 0x8004000EL + CASE_HRESULT(OLE_E_INVALIDHWND)// 0x8004000FL + CASE_HRESULT(OLE_E_NOT_INPLACEACTIVE)// 0x80040010L + CASE_HRESULT(OLE_E_CANTCONVERT)// 0x80040011L + CASE_HRESULT(OLE_E_NOSTORAGE)// 0x80040012L + CASE_HRESULT(DV_E_FORMATETC)// 0x80040064L + CASE_HRESULT(DV_E_DVTARGETDEVICE)// 0x80040065L + CASE_HRESULT(DV_E_STGMEDIUM)// 0x80040066L + CASE_HRESULT(DV_E_STATDATA)// 0x80040067L + CASE_HRESULT(DV_E_LINDEX)// 0x80040068L + CASE_HRESULT(DV_E_TYMED)// 0x80040069L + CASE_HRESULT(DV_E_CLIPFORMAT)// 0x8004006AL + CASE_HRESULT(DV_E_DVASPECT)// 0x8004006BL + CASE_HRESULT(DV_E_DVTARGETDEVICE_SIZE)// 0x8004006CL + CASE_HRESULT(DV_E_NOIVIEWOBJECT)// 0x8004006DL + CASE_HRESULT(DRAGDROP_E_NOTREGISTERED)// 0x80040100L + CASE_HRESULT(DRAGDROP_E_ALREADYREGISTERED)// 0x80040101L + CASE_HRESULT(DRAGDROP_E_INVALIDHWND)// 0x80040102L + CASE_HRESULT(CLASS_E_NOAGGREGATION)// 0x80040110L + CASE_HRESULT(CLASS_E_CLASSNOTAVAILABLE)// 0x80040111L + CASE_HRESULT(VIEW_E_DRAW)// 0x80040140L + CASE_HRESULT(REGDB_E_READREGDB)// 0x80040150L + CASE_HRESULT(REGDB_E_WRITEREGDB)// 0x80040151L + CASE_HRESULT(REGDB_E_KEYMISSING)// 0x80040152L + CASE_HRESULT(REGDB_E_INVALIDVALUE)// 0x80040153L + CASE_HRESULT(REGDB_E_CLASSNOTREG)// 0x80040154L + CASE_HRESULT(CACHE_E_NOCACHE_UPDATED)// 0x80040170L + CASE_HRESULT(OLEOBJ_E_NOVERBS)// 0x80040180L + CASE_HRESULT(INPLACE_E_NOTUNDOABLE)// 0x800401A0L + CASE_HRESULT(INPLACE_E_NOTOOLSPACE)// 0x800401A1L + CASE_HRESULT(CONVERT10_E_OLESTREAM_GET)// 0x800401C0L + CASE_HRESULT(CONVERT10_E_OLESTREAM_PUT)// 0x800401C1L + CASE_HRESULT(CONVERT10_E_OLESTREAM_FMT)// 0x800401C2L + CASE_HRESULT(CONVERT10_E_OLESTREAM_BITMAP_TO_DIB)// 0x800401C3L + CASE_HRESULT(CONVERT10_E_STG_FMT)// 0x800401C4L + CASE_HRESULT(CONVERT10_E_STG_NO_STD_STREAM)// 0x800401C5L + CASE_HRESULT(CONVERT10_E_STG_DIB_TO_BITMAP)// 0x800401C6L + CASE_HRESULT(CLIPBRD_E_CANT_OPEN)// 0x800401D0L + CASE_HRESULT(CLIPBRD_E_CANT_EMPTY)// 0x800401D1L + CASE_HRESULT(CLIPBRD_E_CANT_SET)// 0x800401D2L + CASE_HRESULT(CLIPBRD_E_BAD_DATA)// 0x800401D3L + CASE_HRESULT(CLIPBRD_E_CANT_CLOSE)// 0x800401D4L + CASE_HRESULT(MK_E_CONNECTMANUALLY)// 0x800401E0L + CASE_HRESULT(MK_E_EXCEEDEDDEADLINE)// 0x800401E1L + CASE_HRESULT(MK_E_NEEDGENERIC)// 0x800401E2L + CASE_HRESULT(MK_E_UNAVAILABLE)// 0x800401E3L + CASE_HRESULT(MK_E_SYNTAX)// 0x800401E4L + CASE_HRESULT(MK_E_NOOBJECT)// 0x800401E5L + CASE_HRESULT(MK_E_INVALIDEXTENSION)// 0x800401E6L + CASE_HRESULT(MK_E_INTERMEDIATEINTERFACENOTSUPPORTED)// 0x800401E7L + CASE_HRESULT(MK_E_NOTBINDABLE)// 0x800401E8L + CASE_HRESULT(MK_E_NOTBOUND)// 0x800401E9L + CASE_HRESULT(MK_E_CANTOPENFILE)// 0x800401EAL + CASE_HRESULT(MK_E_MUSTBOTHERUSER)// 0x800401EBL + CASE_HRESULT(MK_E_NOINVERSE)// 0x800401ECL + CASE_HRESULT(MK_E_NOSTORAGE)// 0x800401EDL + CASE_HRESULT(MK_E_NOPREFIX)// 0x800401EEL + CASE_HRESULT(MK_E_ENUMERATION_FAILED)// 0x800401EFL + CASE_HRESULT(CO_E_NOTINITIALIZED)// 0x800401F0L + CASE_HRESULT(CO_E_ALREADYINITIALIZED)// 0x800401F1L + CASE_HRESULT(CO_E_CANTDETERMINECLASS)// 0x800401F2L + CASE_HRESULT(CO_E_CLASSSTRING)// 0x800401F3L + CASE_HRESULT(CO_E_IIDSTRING)// 0x800401F4L + CASE_HRESULT(CO_E_APPNOTFOUND)// 0x800401F5L + CASE_HRESULT(CO_E_APPSINGLEUSE)// 0x800401F6L + CASE_HRESULT(CO_E_ERRORINAPP)// 0x800401F7L + CASE_HRESULT(CO_E_DLLNOTFOUND)// 0x800401F8L + CASE_HRESULT(CO_E_ERRORINDLL)// 0x800401F9L + CASE_HRESULT(CO_E_WRONGOSFORAPP)// 0x800401FAL + CASE_HRESULT(CO_E_OBJNOTREG)// 0x800401FBL + CASE_HRESULT(CO_E_OBJISREG)// 0x800401FCL + CASE_HRESULT(CO_E_OBJNOTCONNECTED)// 0x800401FDL + CASE_HRESULT(CO_E_APPDIDNTREG)// 0x800401FEL + CASE_HRESULT(CO_E_RELEASED)// 0x800401FFL + + CASE_HRESULT(OLE_S_USEREG)// 0x00040000L + CASE_HRESULT(OLE_S_STATIC)// 0x00040001L + CASE_HRESULT(OLE_S_MAC_CLIPFORMAT)// 0x00040002L + CASE_HRESULT(DRAGDROP_S_DROP)// 0x00040100L + CASE_HRESULT(DRAGDROP_S_CANCEL)// 0x00040101L + CASE_HRESULT(DRAGDROP_S_USEDEFAULTCURSORS)// 0x00040102L + CASE_HRESULT(DATA_S_SAMEFORMATETC)// 0x00040130L + CASE_HRESULT(VIEW_S_ALREADY_FROZEN)// 0x00040140L + CASE_HRESULT(CACHE_S_FORMATETC_NOTSUPPORTED)// 0x00040170L + CASE_HRESULT(CACHE_S_SAMECACHE)// 0x00040171L + CASE_HRESULT(CACHE_S_SOMECACHES_NOTUPDATED)// 0x00040172L + CASE_HRESULT(OLEOBJ_S_INVALIDVERB)// 0x00040180L + CASE_HRESULT(OLEOBJ_S_CANNOT_DOVERB_NOW)// 0x00040181L + CASE_HRESULT(OLEOBJ_S_INVALIDHWND)// 0x00040182L + CASE_HRESULT(INPLACE_S_TRUNCATED)// 0x000401A0L + CASE_HRESULT(CONVERT10_S_NO_PRESENTATION)// 0x000401C0L + CASE_HRESULT(MK_S_REDUCED_TO_SELF)// 0x000401E2L + CASE_HRESULT(MK_S_ME)// 0x000401E4L + CASE_HRESULT(MK_S_HIM)// 0x000401E5L + CASE_HRESULT(MK_S_US)// 0x000401E6L + CASE_HRESULT(MK_S_MONIKERALREADYREGISTERED)// 0x000401E7L + +// ****************** +// FACILITY_WINDOWS +// ****************** + + CASE_HRESULT(CO_E_CLASS_CREATE_FAILED)// 0x80080001L + CASE_HRESULT(CO_E_SCM_ERROR)// 0x80080002L + CASE_HRESULT(CO_E_SCM_RPC_FAILURE)// 0x80080003L + CASE_HRESULT(CO_E_BAD_PATH)// 0x80080004L + CASE_HRESULT(CO_E_SERVER_EXEC_FAILURE)// 0x80080005L + CASE_HRESULT(CO_E_OBJSRV_RPC_FAILURE)// 0x80080006L + CASE_HRESULT(MK_E_NO_NORMALIZED)// 0x80080007L + CASE_HRESULT(CO_E_SERVER_STOPPING)// 0x80080008L + CASE_HRESULT(MEM_E_INVALID_ROOT)// 0x80080009L + CASE_HRESULT(MEM_E_INVALID_LINK)// 0x80080010L + CASE_HRESULT(MEM_E_INVALID_SIZE)// 0x80080011L + +// ****************** +// FACILITY_DISPATCH +// ****************** + + CASE_HRESULT(DISP_E_UNKNOWNINTERFACE)// 0x80020001L + CASE_HRESULT(DISP_E_MEMBERNOTFOUND)// 0x80020003L + CASE_HRESULT(DISP_E_PARAMNOTFOUND)// 0x80020004L + CASE_HRESULT(DISP_E_TYPEMISMATCH)// 0x80020005L + CASE_HRESULT(DISP_E_UNKNOWNNAME)// 0x80020006L + CASE_HRESULT(DISP_E_NONAMEDARGS)// 0x80020007L + CASE_HRESULT(DISP_E_BADVARTYPE)// 0x80020008L + CASE_HRESULT(DISP_E_EXCEPTION)// 0x80020009L + CASE_HRESULT(DISP_E_OVERFLOW)// 0x8002000AL + CASE_HRESULT(DISP_E_BADINDEX)// 0x8002000BL + CASE_HRESULT(DISP_E_UNKNOWNLCID)// 0x8002000CL + CASE_HRESULT(DISP_E_ARRAYISLOCKED)// 0x8002000DL + CASE_HRESULT(DISP_E_BADPARAMCOUNT)// 0x8002000EL + CASE_HRESULT(DISP_E_PARAMNOTOPTIONAL)// 0x8002000FL + CASE_HRESULT(DISP_E_BADCALLEE)// 0x80020010L + CASE_HRESULT(DISP_E_NOTACOLLECTION)// 0x80020011L + CASE_HRESULT(TYPE_E_BUFFERTOOSMALL)// 0x80028016L + CASE_HRESULT(TYPE_E_INVDATAREAD)// 0x80028018L + CASE_HRESULT(TYPE_E_UNSUPFORMAT)// 0x80028019L + CASE_HRESULT(TYPE_E_REGISTRYACCESS)// 0x8002801CL + CASE_HRESULT(TYPE_E_LIBNOTREGISTERED)// 0x8002801DL + CASE_HRESULT(TYPE_E_UNDEFINEDTYPE)// 0x80028027L + CASE_HRESULT(TYPE_E_QUALIFIEDNAMEDISALLOWED)// 0x80028028L + CASE_HRESULT(TYPE_E_INVALIDSTATE)// 0x80028029L + CASE_HRESULT(TYPE_E_WRONGTYPEKIND)// 0x8002802AL + CASE_HRESULT(TYPE_E_ELEMENTNOTFOUND)// 0x8002802BL + CASE_HRESULT(TYPE_E_AMBIGUOUSNAME)// 0x8002802CL + CASE_HRESULT(TYPE_E_NAMECONFLICT)// 0x8002802DL + CASE_HRESULT(TYPE_E_UNKNOWNLCID)// 0x8002802EL + CASE_HRESULT(TYPE_E_DLLFUNCTIONNOTFOUND)// 0x8002802FL + CASE_HRESULT(TYPE_E_BADMODULEKIND)// 0x800288BDL + CASE_HRESULT(TYPE_E_SIZETOOBIG)// 0x800288C5L + CASE_HRESULT(TYPE_E_DUPLICATEID)// 0x800288C6L + CASE_HRESULT(TYPE_E_INVALIDID)// 0x800288CFL + CASE_HRESULT(TYPE_E_TYPEMISMATCH)// 0x80028CA0L + CASE_HRESULT(TYPE_E_OUTOFBOUNDS)// 0x80028CA1L + CASE_HRESULT(TYPE_E_IOERROR)// 0x80028CA2L + CASE_HRESULT(TYPE_E_CANTCREATETMPFILE)// 0x80028CA3L + CASE_HRESULT(TYPE_E_CANTLOADLIBRARY)// 0x80029C4AL + CASE_HRESULT(TYPE_E_INCONSISTENTPROPFUNCS)// 0x80029C83L + CASE_HRESULT(TYPE_E_CIRCULARTYPE)// 0x80029C84L + +// ****************** +// FACILITY_STORAGE +// ****************** + + CASE_HRESULT(STG_E_INVALIDFUNCTION)// 0x80030001L + CASE_HRESULT(STG_E_FILENOTFOUND)// 0x80030002L + CASE_HRESULT(STG_E_PATHNOTFOUND)// 0x80030003L + CASE_HRESULT(STG_E_TOOMANYOPENFILES)// 0x80030004L + CASE_HRESULT(STG_E_ACCESSDENIED)// 0x80030005L + CASE_HRESULT(STG_E_INVALIDHANDLE)// 0x80030006L + CASE_HRESULT(STG_E_INSUFFICIENTMEMORY)// 0x80030008L + CASE_HRESULT(STG_E_INVALIDPOINTER)// 0x80030009L + CASE_HRESULT(STG_E_NOMOREFILES)// 0x80030012L + CASE_HRESULT(STG_E_DISKISWRITEPROTECTED)// 0x80030013L + CASE_HRESULT(STG_E_SEEKERROR)// 0x80030019L + CASE_HRESULT(STG_E_WRITEFAULT)// 0x8003001DL + CASE_HRESULT(STG_E_READFAULT)// 0x8003001EL + CASE_HRESULT(STG_E_SHAREVIOLATION)// 0x80030020L + CASE_HRESULT(STG_E_LOCKVIOLATION)// 0x80030021L + CASE_HRESULT(STG_E_FILEALREADYEXISTS)// 0x80030050L + CASE_HRESULT(STG_E_INVALIDPARAMETER)// 0x80030057L + CASE_HRESULT(STG_E_MEDIUMFULL)// 0x80030070L + CASE_HRESULT(STG_E_ABNORMALAPIEXIT)// 0x800300FAL + CASE_HRESULT(STG_E_INVALIDHEADER)// 0x800300FBL + CASE_HRESULT(STG_E_INVALIDNAME)// 0x800300FCL + CASE_HRESULT(STG_E_UNKNOWN)// 0x800300FDL + CASE_HRESULT(STG_E_UNIMPLEMENTEDFUNCTION)// 0x800300FEL + CASE_HRESULT(STG_E_INVALIDFLAG)// 0x800300FFL + CASE_HRESULT(STG_E_INUSE)// 0x80030100L + CASE_HRESULT(STG_E_NOTCURRENT)// 0x80030101L + CASE_HRESULT(STG_E_REVERTED)// 0x80030102L + CASE_HRESULT(STG_E_CANTSAVE)// 0x80030103L + CASE_HRESULT(STG_E_OLDFORMAT)// 0x80030104L + CASE_HRESULT(STG_E_OLDDLL)// 0x80030105L + CASE_HRESULT(STG_E_SHAREREQUIRED)// 0x80030106L + CASE_HRESULT(STG_E_NOTFILEBASEDSTORAGE)// 0x80030107L + CASE_HRESULT(STG_S_CONVERTED)// 0x00030200L + +// ****************** +// FACILITY_RPC +// ****************** + + CASE_HRESULT(RPC_E_CALL_REJECTED)// 0x80010001L + CASE_HRESULT(RPC_E_CALL_CANCELED)// 0x80010002L + CASE_HRESULT(RPC_E_CANTPOST_INSENDCALL)// 0x80010003L + CASE_HRESULT(RPC_E_CANTCALLOUT_INASYNCCALL)// 0x80010004L + CASE_HRESULT(RPC_E_CANTCALLOUT_INEXTERNALCALL)// 0x80010005L + CASE_HRESULT(RPC_E_CONNECTION_TERMINATED)// 0x80010006L + CASE_HRESULT(RPC_E_SERVER_DIED)// 0x80010007L + CASE_HRESULT(RPC_E_CLIENT_DIED)// 0x80010008L + CASE_HRESULT(RPC_E_INVALID_DATAPACKET)// 0x80010009L + CASE_HRESULT(RPC_E_CANTTRANSMIT_CALL)// 0x8001000AL + CASE_HRESULT(RPC_E_CLIENT_CANTMARSHAL_DATA)// 0x8001000BL + CASE_HRESULT(RPC_E_CLIENT_CANTUNMARSHAL_DATA)// 0x8001000CL + CASE_HRESULT(RPC_E_SERVER_CANTMARSHAL_DATA)// 0x8001000DL + CASE_HRESULT(RPC_E_SERVER_CANTUNMARSHAL_DATA)// 0x8001000EL + CASE_HRESULT(RPC_E_INVALID_DATA)// 0x8001000FL + CASE_HRESULT(RPC_E_INVALID_PARAMETER)// 0x80010010L + CASE_HRESULT(RPC_E_CANTCALLOUT_AGAIN)// 0x80010011L + CASE_HRESULT(RPC_E_SERVER_DIED_DNE)// 0x80010012L + CASE_HRESULT(RPC_E_SYS_CALL_FAILED)// 0x80010100L + CASE_HRESULT(RPC_E_OUT_OF_RESOURCES)// 0x80010101L + CASE_HRESULT(RPC_E_ATTEMPTED_MULTITHREAD)// 0x80010102L + CASE_HRESULT(RPC_E_NOT_REGISTERED)// 0x80010103L + CASE_HRESULT(RPC_E_FAULT)// 0x80010104L + CASE_HRESULT(RPC_E_SERVERFAULT)// 0x80010105L + CASE_HRESULT(RPC_E_CHANGED_MODE)// 0x80010106L + CASE_HRESULT(RPC_E_INVALIDMETHOD)// 0x80010107L + CASE_HRESULT(RPC_E_DISCONNECTED)// 0x80010108L + CASE_HRESULT(RPC_E_RETRY)// 0x80010109L + CASE_HRESULT(RPC_E_SERVERCALL_RETRYLATER)// 0x8001010AL + CASE_HRESULT(RPC_E_SERVERCALL_REJECTED)// 0x8001010BL + CASE_HRESULT(RPC_E_INVALID_CALLDATA)// 0x8001010CL + CASE_HRESULT(RPC_E_CANTCALLOUT_ININPUTSYNCCALL)// 0x8001010DL + CASE_HRESULT(RPC_E_WRONG_THREAD)// 0x8001010EL + CASE_HRESULT(RPC_E_THREAD_NOT_INIT)// 0x8001010FL + CASE_HRESULT(RPC_E_UNEXPECTED)// 0x8001FFFFL + +// ****************** +// FACILITY_CTL +// ****************** + + CASE_HRESULT(CTL_E_ILLEGALFUNCTIONCALL) + CASE_HRESULT(CTL_E_OVERFLOW) + CASE_HRESULT(CTL_E_OUTOFMEMORY) + CASE_HRESULT(CTL_E_DIVISIONBYZERO) + CASE_HRESULT(CTL_E_OUTOFSTRINGSPACE) + CASE_HRESULT(CTL_E_OUTOFSTACKSPACE) + CASE_HRESULT(CTL_E_BADFILENAMEORNUMBER) + CASE_HRESULT(CTL_E_FILENOTFOUND) + CASE_HRESULT(CTL_E_BADFILEMODE) + CASE_HRESULT(CTL_E_FILEALREADYOPEN) + CASE_HRESULT(CTL_E_DEVICEIOERROR) + CASE_HRESULT(CTL_E_FILEALREADYEXISTS) + CASE_HRESULT(CTL_E_BADRECORDLENGTH) + CASE_HRESULT(CTL_E_DISKFULL) + CASE_HRESULT(CTL_E_BADRECORDNUMBER) + CASE_HRESULT(CTL_E_BADFILENAME) + CASE_HRESULT(CTL_E_TOOMANYFILES) + CASE_HRESULT(CTL_E_DEVICEUNAVAILABLE) + CASE_HRESULT(CTL_E_PERMISSIONDENIED) + CASE_HRESULT(CTL_E_DISKNOTREADY) + CASE_HRESULT(CTL_E_PATHFILEACCESSERROR) + CASE_HRESULT(CTL_E_PATHNOTFOUND) + CASE_HRESULT(CTL_E_INVALIDPATTERNSTRING) + CASE_HRESULT(CTL_E_INVALIDUSEOFNULL) + CASE_HRESULT(CTL_E_INVALIDFILEFORMAT) + CASE_HRESULT(CTL_E_INVALIDPROPERTYVALUE) + CASE_HRESULT(CTL_E_INVALIDPROPERTYARRAYINDEX) + CASE_HRESULT(CTL_E_SETNOTSUPPORTEDATRUNTIME) + CASE_HRESULT(CTL_E_SETNOTSUPPORTED) + CASE_HRESULT(CTL_E_NEEDPROPERTYARRAYINDEX) + CASE_HRESULT(CTL_E_SETNOTPERMITTED) + CASE_HRESULT(CTL_E_GETNOTSUPPORTEDATRUNTIME) + CASE_HRESULT(CTL_E_GETNOTSUPPORTED) + CASE_HRESULT(CTL_E_PROPERTYNOTFOUND) + CASE_HRESULT(CTL_E_INVALIDCLIPBOARDFORMAT) + CASE_HRESULT(CTL_E_INVALIDPICTURE) + CASE_HRESULT(CTL_E_PRINTERERROR) + CASE_HRESULT(CTL_E_CANTSAVEFILETOTEMP) + CASE_HRESULT(CTL_E_SEARCHTEXTNOTFOUND) + CASE_HRESULT(CTL_E_REPLACEMENTSTOOLONG) +#endif // FEATURE_COMINTEROP + +#ifdef _DEBUG // @todo: do we want to burn strings for this in a free build? + + CASE_HRESULT(CEE_E_CVTRES_NOT_FOUND) + CASE_HRESULT(COR_E_APPDOMAINUNLOADED) + CASE_HRESULT(COR_E_CANNOTUNLOADAPPDOMAIN) + CASE_HRESULT(MSEE_E_ASSEMBLYLOADINPROGRESS) + CASE_HRESULT(COR_E_FIXUPSINEXE) + CASE_HRESULT(COR_E_MODULE_HASH_CHECK_FAILED) + CASE_HRESULT(FUSION_E_LOADFROM_BLOCKED) + CASE_HRESULT(FUSION_E_CACHEFILE_FAILED) + CASE_HRESULT(FUSION_E_REF_DEF_MISMATCH) + CASE_HRESULT(FUSION_E_INVALID_PRIVATE_ASM_LOCATION) + CASE_HRESULT(FUSION_E_ASM_MODULE_MISSING) + CASE_HRESULT(FUSION_E_PRIVATE_ASM_DISALLOWED) + CASE_HRESULT(FUSION_E_SIGNATURE_CHECK_FAILED) + CASE_HRESULT(FUSION_E_INVALID_NAME) + CASE_HRESULT(FUSION_E_CODE_DOWNLOAD_DISABLED) + CASE_HRESULT(CLDB_E_FILE_BADREAD) + CASE_HRESULT(CLDB_E_FILE_BADWRITE) + CASE_HRESULT(CLDB_S_TRUNCATION) + CASE_HRESULT(CLDB_E_FILE_OLDVER) + CASE_HRESULT(CLDB_E_SMDUPLICATE) + CASE_HRESULT(CLDB_E_NO_DATA) + CASE_HRESULT(CLDB_E_INCOMPATIBLE) + CASE_HRESULT(CLDB_E_FILE_CORRUPT) + CASE_HRESULT(CLDB_E_BADUPDATEMODE) + CASE_HRESULT(CLDB_E_INDEX_NOTFOUND) + CASE_HRESULT(CLDB_E_RECORD_NOTFOUND) + CASE_HRESULT(CLDB_E_RECORD_OUTOFORDER) + CASE_HRESULT(CLDB_E_TOO_BIG) + CASE_HRESULT(META_E_BADMETADATA) + CASE_HRESULT(META_E_BAD_SIGNATURE) + CASE_HRESULT(META_E_BAD_INPUT_PARAMETER) + CASE_HRESULT(META_E_CANNOTRESOLVETYPEREF) + CASE_HRESULT(META_S_DUPLICATE) + CASE_HRESULT(META_E_STRINGSPACE_FULL) + CASE_HRESULT(META_E_HAS_UNMARKALL) + CASE_HRESULT(META_E_MUST_CALL_UNMARKALL) + CASE_HRESULT(META_E_CA_INVALID_TARGET) + CASE_HRESULT(META_E_CA_INVALID_VALUE) + CASE_HRESULT(META_E_CA_INVALID_BLOB) + CASE_HRESULT(META_E_CA_REPEATED_ARG) + CASE_HRESULT(META_E_CA_UNKNOWN_ARGUMENT) + CASE_HRESULT(META_E_CA_UNEXPECTED_TYPE) + CASE_HRESULT(META_E_CA_INVALID_ARGTYPE) + CASE_HRESULT(META_E_CA_INVALID_ARG_FOR_TYPE) + CASE_HRESULT(META_E_CA_INVALID_UUID) + CASE_HRESULT(META_E_CA_INVALID_MARSHALAS_FIELDS) + CASE_HRESULT(META_E_CA_NT_FIELDONLY) + CASE_HRESULT(META_E_CA_NEGATIVE_PARAMINDEX) + CASE_HRESULT(META_E_CA_NEGATIVE_CONSTSIZE) + CASE_HRESULT(META_E_CA_FIXEDSTR_SIZE_REQUIRED) + CASE_HRESULT(META_E_CA_CUSTMARSH_TYPE_REQUIRED) + CASE_HRESULT(META_E_CA_BAD_FRIENDS_ARGS) + CASE_HRESULT(VLDTR_E_RID_OUTOFRANGE) + CASE_HRESULT(VLDTR_E_STRING_INVALID) + CASE_HRESULT(VLDTR_E_GUID_INVALID) + CASE_HRESULT(VLDTR_E_BLOB_INVALID) + CASE_HRESULT(VLDTR_E_MR_BADCALLINGCONV) + CASE_HRESULT(VLDTR_E_SIGNULL) + CASE_HRESULT(VLDTR_E_MD_BADCALLINGCONV) + CASE_HRESULT(VLDTR_E_MD_THISSTATIC) + CASE_HRESULT(VLDTR_E_MD_NOTTHISNOTSTATIC) + CASE_HRESULT(VLDTR_E_MD_NOARGCNT) + CASE_HRESULT(VLDTR_E_SIG_MISSELTYPE) + CASE_HRESULT(VLDTR_E_SIG_MISSTKN) + CASE_HRESULT(VLDTR_E_SIG_TKNBAD) + CASE_HRESULT(VLDTR_E_SIG_MISSFPTR) + CASE_HRESULT(VLDTR_E_SIG_MISSFPTRARGCNT) + CASE_HRESULT(VLDTR_E_SIG_MISSRANK) + CASE_HRESULT(VLDTR_E_SIG_MISSNSIZE) + CASE_HRESULT(VLDTR_E_SIG_MISSSIZE) + CASE_HRESULT(VLDTR_E_SIG_MISSNLBND) + CASE_HRESULT(VLDTR_E_SIG_MISSLBND) + CASE_HRESULT(VLDTR_E_SIG_BADELTYPE) + CASE_HRESULT(VLDTR_E_TD_ENCLNOTNESTED) + CASE_HRESULT(VLDTR_E_FMD_PINVOKENOTSTATIC) + CASE_HRESULT(VLDTR_E_SIG_SENTINMETHODDEF) + CASE_HRESULT(VLDTR_E_SIG_SENTMUSTVARARG) + CASE_HRESULT(VLDTR_E_SIG_MULTSENTINELS) + CASE_HRESULT(VLDTR_E_SIG_MISSARG) + CASE_HRESULT(VLDTR_E_SIG_BYREFINFIELD) + CASE_HRESULT(VLDTR_E_SIG_BADVOID) + CASE_HRESULT(CORDBG_E_UNRECOVERABLE_ERROR) + CASE_HRESULT(CORDBG_E_PROCESS_TERMINATED) + CASE_HRESULT(CORDBG_E_PROCESS_NOT_SYNCHRONIZED) + CASE_HRESULT(CORDBG_E_CLASS_NOT_LOADED) + CASE_HRESULT(CORDBG_E_IL_VAR_NOT_AVAILABLE) + CASE_HRESULT(CORDBG_E_BAD_REFERENCE_VALUE) + CASE_HRESULT(CORDBG_E_FIELD_NOT_AVAILABLE) + CASE_HRESULT(CORDBG_E_NON_NATIVE_FRAME) + CASE_HRESULT(CORDBG_E_CODE_NOT_AVAILABLE) + CASE_HRESULT(CORDBG_E_FUNCTION_NOT_IL) + CASE_HRESULT(CORDBG_S_BAD_START_SEQUENCE_POINT) + CASE_HRESULT(CORDBG_S_BAD_END_SEQUENCE_POINT) + CASE_HRESULT(CORDBG_E_CANT_SET_IP_INTO_FINALLY) + CASE_HRESULT(CORDBG_E_CANT_SET_IP_OUT_OF_FINALLY) + CASE_HRESULT(CORDBG_E_CANT_SET_IP_INTO_CATCH) + CASE_HRESULT(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME) + CASE_HRESULT(CORDBG_E_SET_IP_IMPOSSIBLE) + CASE_HRESULT(CORDBG_E_FUNC_EVAL_BAD_START_POINT) + CASE_HRESULT(CORDBG_E_INVALID_OBJECT) + CASE_HRESULT(CORDBG_E_FUNC_EVAL_NOT_COMPLETE) + CASE_HRESULT(CORDBG_S_FUNC_EVAL_HAS_NO_RESULT) + CASE_HRESULT(CORDBG_S_VALUE_POINTS_TO_VOID) + CASE_HRESULT(CORDBG_S_FUNC_EVAL_ABORTED) + CASE_HRESULT(CORDBG_E_STATIC_VAR_NOT_AVAILABLE) + CASE_HRESULT(CORDBG_E_CANT_SETIP_INTO_OR_OUT_OF_FILTER) + CASE_HRESULT(CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE) + CASE_HRESULT(CORDBG_E_CANT_SET_TO_JMC) + CASE_HRESULT(CORDBG_E_BAD_THREAD_STATE) + CASE_HRESULT(CORDBG_E_DEBUGGER_ALREADY_ATTACHED) + CASE_HRESULT(CORDBG_E_SUPERFLOUS_CONTINUE) + CASE_HRESULT(CORDBG_E_SET_VALUE_NOT_ALLOWED_ON_NONLEAF_FRAME) + CASE_HRESULT(CORDBG_E_ENC_MODULE_NOT_ENC_ENABLED) + CASE_HRESULT(CORDBG_E_SET_IP_NOT_ALLOWED_ON_EXCEPTION) + CASE_HRESULT(CORDBG_E_VARIABLE_IS_ACTUALLY_LITERAL) + CASE_HRESULT(CORDBG_E_PROCESS_DETACHED) + CASE_HRESULT(CORDBG_E_ENC_CANT_ADD_FIELD_TO_VALUE_OR_LAYOUT_CLASS) + CASE_HRESULT(CORDBG_E_FIELD_NOT_STATIC) + CASE_HRESULT(CORDBG_E_FIELD_NOT_INSTANCE) + CASE_HRESULT(CORDBG_E_ENC_JIT_CANT_UPDATE) + CASE_HRESULT(CORDBG_E_ENC_INTERNAL_ERROR) + CASE_HRESULT(CORDBG_E_ENC_HANGING_FIELD) + CASE_HRESULT(CORDBG_E_MODULE_NOT_LOADED) + CASE_HRESULT(CORDBG_E_UNABLE_TO_SET_BREAKPOINT) + CASE_HRESULT(CORDBG_E_DEBUGGING_NOT_POSSIBLE) + CASE_HRESULT(CORDBG_E_KERNEL_DEBUGGER_ENABLED) + CASE_HRESULT(CORDBG_E_KERNEL_DEBUGGER_PRESENT) + CASE_HRESULT(CORDBG_E_INCOMPATIBLE_PROTOCOL) + CASE_HRESULT(CORDBG_E_TOO_MANY_PROCESSES) + CASE_HRESULT(CORDBG_E_INTEROP_NOT_SUPPORTED) + CASE_HRESULT(CORDBG_E_NO_REMAP_BREAKPIONT) + CASE_HRESULT(CORDBG_E_OBJECT_NEUTERED) + CASE_HRESULT(CORPROF_E_FUNCTION_NOT_COMPILED) + CASE_HRESULT(CORPROF_E_DATAINCOMPLETE) + CASE_HRESULT(CORPROF_E_FUNCTION_NOT_IL) + CASE_HRESULT(CORPROF_E_NOT_MANAGED_THREAD) + CASE_HRESULT(CORPROF_E_CALL_ONLY_FROM_INIT) + CASE_HRESULT(CORPROF_E_NOT_YET_AVAILABLE) + CASE_HRESULT(SECURITY_E_INCOMPATIBLE_SHARE) + CASE_HRESULT(SECURITY_E_UNVERIFIABLE) + CASE_HRESULT(SECURITY_E_INCOMPATIBLE_EVIDENCE) + CASE_HRESULT(CLDB_E_INTERNALERROR) + CASE_HRESULT(CORSEC_E_POLICY_EXCEPTION) + CASE_HRESULT(CORSEC_E_MIN_GRANT_FAIL) + CASE_HRESULT(CORSEC_E_NO_EXEC_PERM) + CASE_HRESULT(CORSEC_E_XMLSYNTAX) + CASE_HRESULT(CORSEC_E_INVALID_STRONGNAME) + CASE_HRESULT(CORSEC_E_MISSING_STRONGNAME) + CASE_HRESULT(CORSEC_E_INVALID_IMAGE_FORMAT) + CASE_HRESULT(CORSEC_E_CRYPTO) + CASE_HRESULT(CORSEC_E_CRYPTO_UNEX_OPER) + CASE_HRESULT(CORSECATTR_E_BAD_ACTION) + CASE_HRESULT(COR_E_APPLICATION) + CASE_HRESULT(COR_E_ARGUMENTOUTOFRANGE) + CASE_HRESULT(COR_E_ARITHMETIC) + CASE_HRESULT(COR_E_ARRAYTYPEMISMATCH) + CASE_HRESULT(COR_E_CONTEXTMARSHAL) + CASE_HRESULT(COR_E_TIMEOUT) + CASE_HRESULT(COR_E_DIVIDEBYZERO) + CASE_HRESULT(COR_E_EXCEPTION) + CASE_HRESULT(COR_E_EXECUTIONENGINE) + CASE_HRESULT(COR_E_FIELDACCESS) + CASE_HRESULT(COR_E_FORMAT) + CASE_HRESULT(COR_E_BADIMAGEFORMAT) + CASE_HRESULT(COR_E_ASSEMBLYEXPECTED) + CASE_HRESULT(COR_E_TYPEUNLOADED) + CASE_HRESULT(COR_E_INDEXOUTOFRANGE) + CASE_HRESULT(COR_E_INVALIDOPERATION) + CASE_HRESULT(COR_E_INVALIDPROGRAM) + CASE_HRESULT(COR_E_MEMBERACCESS) + CASE_HRESULT(COR_E_METHODACCESS) + CASE_HRESULT(COR_E_MISSINGFIELD) + CASE_HRESULT(COR_E_MISSINGMANIFESTRESOURCE) + CASE_HRESULT(COR_E_MISSINGMEMBER) + CASE_HRESULT(COR_E_MISSINGMETHOD) + CASE_HRESULT(COR_E_MULTICASTNOTSUPPORTED) + CASE_HRESULT(COR_E_NOTFINITENUMBER) + CASE_HRESULT(COR_E_DUPLICATEWAITOBJECT) + CASE_HRESULT(COR_E_PLATFORMNOTSUPPORTED) + CASE_HRESULT(COR_E_NOTSUPPORTED) + CASE_HRESULT(COR_E_OVERFLOW) + CASE_HRESULT(COR_E_RANK) + CASE_HRESULT(COR_E_SECURITY) + CASE_HRESULT(COR_E_SERIALIZATION) + CASE_HRESULT(COR_E_STACKOVERFLOW) + CASE_HRESULT(COR_E_SYNCHRONIZATIONLOCK) + CASE_HRESULT(COR_E_SYSTEM) + CASE_HRESULT(COR_E_THREADABORTED) + CASE_HRESULT(COR_E_THREADINTERRUPTED) + CASE_HRESULT(COR_E_THREADSTATE) + CASE_HRESULT(COR_E_THREADSTOP) + CASE_HRESULT(COR_E_TYPEINITIALIZATION) + CASE_HRESULT(COR_E_TYPELOAD) + CASE_HRESULT(COR_E_ENTRYPOINTNOTFOUND) + CASE_HRESULT(COR_E_DLLNOTFOUND) + CASE_HRESULT(COR_E_VERIFICATION) + CASE_HRESULT(COR_E_INVALIDCOMOBJECT) + CASE_HRESULT(COR_E_MARSHALDIRECTIVE) + CASE_HRESULT(COR_E_INVALIDOLEVARIANTTYPE) + CASE_HRESULT(COR_E_SAFEARRAYTYPEMISMATCH) + CASE_HRESULT(COR_E_SAFEARRAYRANKMISMATCH) + CASE_HRESULT(COR_E_INVALIDFILTERCRITERIA) + CASE_HRESULT(COR_E_REFLECTIONTYPELOAD) + CASE_HRESULT(COR_E_TARGET) + CASE_HRESULT(COR_E_TARGETINVOCATION) + CASE_HRESULT(COR_E_CUSTOMATTRIBUTEFORMAT) + CASE_HRESULT(COR_E_ENDOFSTREAM) + CASE_HRESULT(COR_E_FILELOAD) + CASE_HRESULT(COR_E_FILENOTFOUND) + CASE_HRESULT(COR_E_IO) + CASE_HRESULT(COR_E_DIRECTORYNOTFOUND) + CASE_HRESULT(COR_E_PATHTOOLONG) + CASE_HRESULT(COR_E_OBJECTDISPOSED) + CASE_HRESULT(COR_E_NEWER_RUNTIME) + CASE_HRESULT(CLR_E_SHIM_RUNTIMELOAD) + CASE_HRESULT(VER_E_FIELD_SIG) + CASE_HRESULT(CORDBG_E_THREAD_NOT_SCHEDULED) +#endif + + default: + return NULL; + } +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + + +// --------------------------------------------------------------------------- +// HRException class. Implements exception API for exceptions from HRESULTS +// --------------------------------------------------------------------------- + +HRESULT HRException::GetHR() +{ + LIMITED_METHOD_DAC_CONTRACT; + return m_hr; +} + +// --------------------------------------------------------------------------- +// COMException class. - moved to COMEx.cpp +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// SEHException class. Implements exception API for SEH exception info +// --------------------------------------------------------------------------- + +HRESULT SEHException::GetHR() +{ + LIMITED_METHOD_DAC_CONTRACT; + + if (IsComPlusException(&m_exception)) // EE exception + return (HRESULT) m_exception.ExceptionInformation[0]; + else + return m_exception.ExceptionCode; +} + +IErrorInfo *SEHException::GetErrorInfo() +{ + LIMITED_METHOD_CONTRACT; + return NULL; +} + +void SEHException::GetMessage(SString &string) +{ + WRAPPER_NO_CONTRACT; + + if (IsComPlusException(&m_exception)) // EE exception + { + GenerateTopLevelHRExceptionMessage(GetHR(), string); + } + else + { + if (m_exception.ExceptionCode != 0) + { + string.Printf("Exception code 0x%.8x", m_exception.ExceptionCode); + } + else + { + // If we don't have a valid exception code, then give a generic message that's a little nicer than + // "code 0x00000000". + string.Printf("Unknown exception"); + } + } +} + +//============================================================================== +// DelegatingException class. Implements exception API for "foreign" exceptions. +//============================================================================== + +DelegatingException::DelegatingException() + : m_delegatedException((Exception*)DELEGATE_NOT_YET_SET) +{ + LIMITED_METHOD_DAC_CONTRACT; +} // DelegatingException::DelegatingException() + +//------------------------------------------------------------------------------ +DelegatingException::~DelegatingException() +{ + WRAPPER_NO_CONTRACT; + + // If there is a valid delegate pointer (inited and non-NULL), delete it. + if (IsDelegateValid()) + Delete(m_delegatedException); + + // Avoid confusion. + m_delegatedException = NULL; +} // DelegatingException::~DelegatingException() + +//------------------------------------------------------------------------------ +// Retrieve the delegating exception, or get one from the Thread, or get NULL. +Exception* DelegatingException::GetDelegate() +{ + WRAPPER_NO_CONTRACT; + + // If we haven't gotten the exception pointer before.. + if (!IsDelegateSet()) + { + // .. get it now. NULL in case there isn't one and we take default action. + m_delegatedException = NULL; + GetLastThrownObjectExceptionFromThread(&m_delegatedException); + } + + return m_delegatedException; +} // Exception* DelegatingException::GetDelegate() + +//------------------------------------------------------------------------------ +// Virtual overrides +HRESULT DelegatingException::GetHR() +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC_HOST_ONLY; + + // Retrieve any delegating exception. + Exception *pDelegate = GetDelegate(); + + // If there is a delegate exception, defer to it. Otherwise, + // default to E_FAIL. + return pDelegate ? pDelegate->GetHR() : E_FAIL; + +} // HRESULT DelegatingException::GetHR() + +//------------------------------------------------------------------------------ +IErrorInfo *DelegatingException::GetErrorInfo() +{ + WRAPPER_NO_CONTRACT; + + // Retrieve any delegating exception. + Exception *pDelegate = GetDelegate(); + + // If there is a delegate exception, defer to it. Otherwise, + // default to NULL. + return pDelegate ? pDelegate->GetErrorInfo() : NULL; + +} // IErrorInfo *DelegatingException::GetErrorInfo() + +//------------------------------------------------------------------------------ +void DelegatingException::GetMessage(SString &result) +{ + WRAPPER_NO_CONTRACT; + + // Retrieve any delegating exception. + Exception *pDelegate = GetDelegate(); + + // If there is a delegate exception, defer to it. Otherwise, + // default to a generic message. + if (pDelegate) + { + pDelegate->GetMessage(result); + } + else + { + // If we don't have a valid exception code, then give a generic message + // that's a little nicer than "code 0x00000000". + result.Printf("Unknown exception"); + } +} // void DelegatingException::GetMessage() + +//------------------------------------------------------------------------------ +Exception *DelegatingException::Clone() +{ + WRAPPER_NO_CONTRACT; + + // Clone the base exception, this will also take care of cloning the inner + // exception if there is one. + NewHolder<DelegatingException> retExcep((DelegatingException*)Exception::Clone()); + + // If there is a valid delegating exception... + if (IsDelegateValid()) + { // ... clone it. + retExcep->m_delegatedException = m_delegatedException->Clone(); + } + else + { // ... but if there is not, just copy -- either NULL or DELEGATE_NOT_YET_SET + retExcep->m_delegatedException = m_delegatedException; + } + + retExcep.SuppressRelease(); + return retExcep; +} // virtual Exception *DelegatingException::Clone() + +//============================================================================== +//============================================================================== + +void DECLSPEC_NORETURN ThrowHR(HRESULT hr) +{ + WRAPPER_NO_CONTRACT; + + STRESS_LOG1(LF_EH, LL_INFO100, "ThrowHR: HR = %x\n", hr); + + if (hr == E_OUTOFMEMORY) + ThrowOutOfMemory(); + + // Catchers assume only failing hresults + _ASSERTE(FAILED(hr)); + if (hr == S_OK) + hr = E_FAIL; + + EX_THROW(HRException, (hr)); +} + +void DECLSPEC_NORETURN ThrowHR(HRESULT hr, SString const &msg) +{ + WRAPPER_NO_CONTRACT; + + STRESS_LOG1(LF_EH, LL_INFO100, "ThrowHR: HR = %x\n", hr); + + if (hr == E_OUTOFMEMORY) + ThrowOutOfMemory(); + + // Catchers assume only failing hresults + _ASSERTE(FAILED(hr)); + if (hr == S_OK) + hr = E_FAIL; + + EX_THROW(HRMsgException, (hr, msg)); +} + +void DECLSPEC_NORETURN ThrowHR(HRESULT hr, UINT uText) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC_HOST_ONLY; + + if (hr == E_OUTOFMEMORY) + ThrowOutOfMemory(); + + // Catchers assume only failing hresults + _ASSERTE(FAILED(hr)); + if (hr == S_OK) + hr = E_FAIL; + + SString sExceptionText; + + // We won't check the return value here. If it fails, we'll just + // throw the HR + sExceptionText.LoadResource(CCompRC::Error, uText); + + EX_THROW(HRMsgException, (hr, sExceptionText)); +} + +void DECLSPEC_NORETURN ThrowWin32(DWORD err) +{ + WRAPPER_NO_CONTRACT; + if (err == ERROR_NOT_ENOUGH_MEMORY) + { + ThrowOutOfMemory(); + } + else + { + ThrowHR(HRESULT_FROM_WIN32(err)); + } +} + +void DECLSPEC_NORETURN ThrowLastError() +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + ThrowWin32(GetLastError()); +} + +void DECLSPEC_NORETURN ThrowOutOfMemory() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + +#ifndef DACCESS_COMPILE + + // Use volatile store to prevent compiler from optimizing the static variable away + VolatileStoreWithoutBarrier<HRESULT>(&g_hrFatalError, COR_E_OUTOFMEMORY); + + // Regular CLR builds - throw our pre-created OOM exception object + PAL_CPP_THROW(Exception *, Exception::GetOOMException()); + +#else + + // DAC builds - raise a DacError + DacError(E_OUTOFMEMORY); + + // DacError always throws but isn't marked DECLSPEC_NORETURN so we have to + // tell the compiler that this code is unreachable. We could mark DacError + // (and DacNotImpl) as DECLSPEC_NORETURN, but then we've have to update a + // lot of code where we do something afterwards. Also, due to inlining, + // we'd sometimes have to change functions which call functions that only + // call DacNotImpl. I have these changes in a bbpack and some of them look + // nice, but I'm not sure if it's worth the risk of merge conflicts. + UNREACHABLE(); + +#endif +} + +#include "corexcep.h" + +//-------------------------------------------------------------------------------- +// Helper for EX_THROW_WITH_INNER() +// +// Clones an exception into the current domain. Also handles special cases for +// OOM and other stuff. Making this a function so we don't inline all this logic +// every place we call EX_THROW_WITH_INNER. +// +// If the "inner" is a transient exception such as OOM or ThreadAbort, this function +// will just throw it rather than allow it to be wrapped in another exception. +//-------------------------------------------------------------------------------- +Exception *ExThrowWithInnerHelper(Exception *inner) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END + + // Yes, NULL is a legal case. Makes it easier to author uniform helpers for + // both wrapped and normal exceptions. + if (inner == NULL) + { + return NULL; + } + + if (inner == Exception::GetOOMException()) + { + // We don't want to do allocations if we're already throwing an OOM! + PAL_CPP_THROW(Exception*, inner); + } + + inner = inner->DomainBoundClone(); + + // It isn't useful to wrap OOMs and StackOverflows in other exceptions. Just throw them now. + // + if (inner->IsTransient()) + { + PAL_CPP_THROW(Exception*, inner); + } + return inner; +} + +#ifdef _DEBUG + +#ifdef _MSC_VER +#pragma optimize("", off) +#endif // _MSC_VER + +void ExThrowTrap(const char *fcn, const char *file, int line, const char *szType, HRESULT hr, const char *args) +{ + SUPPORTS_DAC; + return; +} + +#ifdef _MSC_VER +#pragma optimize("", on) +#endif // _MSC_VER + +#endif + + + + +//------------------------------------------------------------------------------------------- +// This routine will generate the most descriptive possible error message for an hresult. +// It will generate at minimum the hex value. It will also try to generate the symbolic name +// (E_POINTER) and the friendly description (from the message tables.) +// +// bNoGeekStuff suppresses hex HR codes. Use this sparingly as most error strings generated by the +// CLR are aimed at developers, not end-users. +//------------------------------------------------------------------------------------------- +void GetHRMsg(HRESULT hr, SString &result, BOOL bNoGeekStuff/* = FALSE*/) +{ + CONTRACTL + { + GC_NOTRIGGER; + THROWS; + } + CONTRACTL_END; + + result = W(""); // Make sure this routine isn't an inadvertent data-leak exploit! + + + + SString strDescr; + BOOL fHaveDescr = FALSE; + + if (FAILED(hr) && HRESULT_FACILITY(hr) == FACILITY_URT && HRESULT_CODE(hr) < MAX_URT_HRESULT_CODE) + { + fHaveDescr = strDescr.LoadResource(CCompRC::Error, MSG_FOR_URT_HR(hr)); + } + else + { + DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM; + dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; + + fHaveDescr = strDescr.FormatMessage(dwFlags, 0, hr, 0); + } + + LPCSTR name = Exception::GetHRSymbolicName(hr); + + // If we can't get a resource string, print the hresult regardless. + if (!fHaveDescr) + { + bNoGeekStuff = FALSE; + } + + if (fHaveDescr) + { + result.Append(strDescr); + } + + if (!bNoGeekStuff) + { + if (fHaveDescr) + { + result.Append(W(" (")); + } + + result.AppendPrintf(W("0x%.8X"), hr); + if (name != NULL) + { + result.AppendPrintf(W(" (%S)"), name); + } + + if (fHaveDescr) + { + result.Append(W(")")); + } + } +} + + +//------------------------------------------------------------------------------------------- +// Similar to GetHRMsg but phrased for top-level exception message. +//------------------------------------------------------------------------------------------- +void GenerateTopLevelHRExceptionMessage(HRESULT hresult, SString &result) +{ + CONTRACTL + { + GC_NOTRIGGER; + THROWS; + } + CONTRACTL_END; + + result = W(""); // Make sure this routine isn't an inadvertent data-leak exploit! + + GetHRMsg(hresult, result); +} + +//=========================================================================================== +// These abstractions hide the difference between legacy desktop CLR's (that don't support +// side-by-side-inproc and rely on a fixed SEH code to identify managed exceptions) and +// new CLR's that support side-by-side inproc. +// +// The new CLR's use a different set of SEH codes to avoid conflicting with the legacy CLR's. +// In addition, to distinguish between EH's raised by different inproc instances of the CLR, +// the module handle of the owning CLR is stored in ExceptionRecord.ExceptionInformation[4]. +// +// (Note: all existing SEH's use either only slot [0] or no slots at all. We are leaving +// slots [1] thru [3] open for future expansion.) +//=========================================================================================== + +// Is this exception code one of the special CLR-specific SEH codes that participate in the +// instance-tagging scheme? +BOOL IsInstanceTaggedSEHCode(DWORD dwExceptionCode) +{ + LIMITED_METHOD_DAC_CONTRACT; + + return dwExceptionCode == EXCEPTION_COMPLUS; +} + +// This set of overloads generates the NumberParameters and ExceptionInformation[] array to +// pass to RaiseException(). +// +// Parameters: +// exceptionArgs: a fixed-size array of size INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE. +// This will get filled in by this function. (The module handle goes +// in the last slot if this is a side-by-side-inproc enabled build.) +// +// exceptionArg1... up to four arguments that go in slots [0]..[3]. These depends +// the specific requirements of your exception code. +// +// Returns: +// The NumberParameters to pass to RaiseException(). +// +// Basically, this is either INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE or the count of your +// fixed arguments depending on whether this tagged-SEH-enabled build. +// +// This function is not permitted to fail. + +// (the existing system can support more overloads up to 4 fixed arguments but we don't need them at this time.) + +static DWORD MarkAsThrownByUsWorker(UINT numArgs, /*out*/ ULONG_PTR exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE], ULONG_PTR arg0 = 0) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + + _ASSERTE(numArgs < INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE); + FillMemory(exceptionArgs, sizeof(ULONG_PTR) * INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE, 0); + + exceptionArgs[0] = arg0; + +#if !defined(FEATURE_UTILCODE_NO_DEPENDENCIES) + exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE - 1] = (ULONG_PTR)GetClrModuleBase(); +#endif // !defined(FEATURE_UTILCODE_NO_DEPENDENCIES) + + return INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE; +} + +DWORD MarkAsThrownByUs(/*out*/ ULONG_PTR exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE]) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + return MarkAsThrownByUsWorker(0, exceptionArgs); +} + +DWORD MarkAsThrownByUs(/*out*/ ULONG_PTR exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE], ULONG_PTR arg0) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + return MarkAsThrownByUsWorker(1, exceptionArgs, arg0); +} + +// Given an exception record, checks if it's exception code matches a specific exception code +// *and* whether it was tagged by the calling instance of the CLR. +// +// If this is a non-tagged-SEH-enabled build, it is blindly assumed to be tagged by the +// calling instance of the CLR. +BOOL WasThrownByUs(const EXCEPTION_RECORD *pcER, DWORD dwExceptionCode) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC; + + _ASSERTE(IsInstanceTaggedSEHCode(dwExceptionCode)); + _ASSERTE(pcER != NULL); + if (dwExceptionCode != pcER->ExceptionCode) + { + return FALSE; + } + + if (pcER->NumberParameters != INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE) + { + return FALSE; + } +#if!defined(FEATURE_UTILCODE_NO_DEPENDENCIES) + if ((ULONG_PTR)GetClrModuleBase() != pcER->ExceptionInformation[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE - 1] ) + { + return FALSE; + } + return TRUE; +#else // !(!defined(FEATURE_UTILCODE_NO_DEPENDENCIES) + return FALSE; +#endif // !defined(FEATURE_UTILCODE_NO_DEPENDENCIES) +} + + + +//----------------------------------------------------------------------------------- +// The following group wraps the basic abstracts specifically for EXCEPTION_COMPLUS. +//----------------------------------------------------------------------------------- +BOOL IsComPlusException(const EXCEPTION_RECORD *pcER) +{ + STATIC_CONTRACT_WRAPPER; + + return WasThrownByUs(pcER, EXCEPTION_COMPLUS); +} + +VOID RaiseComPlusException() +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + + ULONG_PTR exceptionArgs[INSTANCE_TAGGED_SEH_PARAM_ARRAY_SIZE]; + DWORD numParams = MarkAsThrownByUs(exceptionArgs); + RaiseException(EXCEPTION_COMPLUS, 0, numParams, exceptionArgs); +} + +//=========================================================================================== +//=========================================================================================== diff --git a/src/coreclr/utilcode/format1.cpp b/src/coreclr/utilcode/format1.cpp new file mode 100644 index 00000000000..f8ef94406bf --- /dev/null +++ b/src/coreclr/utilcode/format1.cpp @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/***************************************************************************/ +/* routines for parsing file format stuff ... */ +/* this is split off from format.cpp because this uses meta-data APIs that + are not present in many builds. Thus if someone needs things in the format.cpp + file but does not have the meta-data APIs, I want it to link */ + +#include "stdafx.h" +#include "cor.h" +#include "corpriv.h" + +//--------------------------------------------------------------------------------------- +// +static LONG FilterAllExceptions(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam) +{ + if ((pExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) || + (pExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_ARRAY_BOUNDS_EXCEEDED) || + (pExceptionPointers->ExceptionRecord->ExceptionCode == EXCEPTION_IN_PAGE_ERROR)) + return EXCEPTION_EXECUTE_HANDLER; + + return EXCEPTION_CONTINUE_SEARCH; +} + +//--------------------------------------------------------------------------------------- +// +COR_ILMETHOD_DECODER::COR_ILMETHOD_DECODER( + COR_ILMETHOD * header, + void * pInternalImport, + DecoderStatus * wbStatus) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + // Can't put contract because of SEH + // CONTRACTL + // { + // NOTHROW; + // GC_NOTRIGGER; + // FORBID_FAULT; + // } + // CONTRACTL_END + + bool fErrorInInit = false; + struct Param + { + COR_ILMETHOD_DECODER * pThis; + COR_ILMETHOD * header; + } param; + param.pThis = this; + param.header = header; + + PAL_TRY(Param *, pParam, ¶m) + { + // Decode the COR header into a more convenient form + DecoderInit(pParam->pThis, pParam->header); + } + PAL_EXCEPT_FILTER(FilterAllExceptions) + { + fErrorInInit = true; + Code = 0; + SetLocalVarSigTok(0); + if (wbStatus != NULL) + { + *wbStatus = FORMAT_ERROR; + } + } + PAL_ENDTRY + + if (fErrorInInit) + { + return; + } + + // If there is a local variable sig, fetch it into 'LocalVarSig' + if ((GetLocalVarSigTok() != 0) && (pInternalImport != NULL)) + { + IMDInternalImport * pMDI = reinterpret_cast<IMDInternalImport *>(pInternalImport); + + if (wbStatus != NULL) + { + if ((!pMDI->IsValidToken(GetLocalVarSigTok())) || + (TypeFromToken(GetLocalVarSigTok()) != mdtSignature) || + (RidFromToken(GetLocalVarSigTok()) == 0)) + { + *wbStatus = FORMAT_ERROR; // failure bad local variable signature token + return; + } + } + + if (FAILED(pMDI->GetSigFromToken(GetLocalVarSigTok(), &cbLocalVarSig, &LocalVarSig))) + { + // Failure bad local variable signature token + if (wbStatus != NULL) + { + *wbStatus = FORMAT_ERROR; + } + LocalVarSig = NULL; + cbLocalVarSig = 0; + return; + } + + if (wbStatus != NULL) + { + if (FAILED(validateTokenSig(GetLocalVarSigTok(), LocalVarSig, cbLocalVarSig, 0, pMDI)) || + (*LocalVarSig != IMAGE_CEE_CS_CALLCONV_LOCAL_SIG)) + { + *wbStatus = VERIFICATION_ERROR; // failure validating local variable signature + return; + } + } + } + + if (wbStatus != NULL) + { + *wbStatus = SUCCESS; + } +} // COR_ILMETHOD_DECODER::COR_ILMETHOD_DECODER diff --git a/src/coreclr/utilcode/fstream.cpp b/src/coreclr/utilcode/fstream.cpp new file mode 100644 index 00000000000..835e24f6249 --- /dev/null +++ b/src/coreclr/utilcode/fstream.cpp @@ -0,0 +1,294 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +#include "stdafx.h" // Precompiled header key. +#include "fstream.h" + +CFileStream::CFileStream() +: _cRef(1) +, _hFile(INVALID_HANDLE_VALUE) +{ +} + +CFileStream::~CFileStream() +{ + Close(); +} + +HRESULT CFileStream::OpenForRead(LPCWSTR wzFilePath) +{ + HRESULT hr = S_OK; + DWORD dwShareMode = FILE_SHARE_READ; + + dwShareMode |= FILE_SHARE_DELETE; + + _ASSERTE(_hFile == INVALID_HANDLE_VALUE && wzFilePath); + if (_hFile != INVALID_HANDLE_VALUE || !wzFilePath) { + hr = E_INVALIDARG; + goto Exit; + } + + _hFile = WszCreateFile(wzFilePath, GENERIC_READ, + dwShareMode, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (_hFile == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Exit; + } + +Exit: + return hr; +} + +HRESULT CFileStream::OpenForWrite(LPCWSTR wzFilePath) +{ + HRESULT hr = S_OK; + + _ASSERTE(_hFile == INVALID_HANDLE_VALUE && wzFilePath); + if (_hFile != INVALID_HANDLE_VALUE || !wzFilePath) { + hr = E_INVALIDARG; + goto Exit; + } + + _hFile = WszCreateFile(wzFilePath, GENERIC_WRITE, + FILE_SHARE_READ, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (_hFile == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Exit; + } + +Exit: + return hr; +} + +HRESULT CFileStream::QueryInterface(REFIID riid, void **ppv) +{ + HRESULT hr = S_OK; + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IStream)) { + *ppv = static_cast<IStream *>(this); + } + else { + hr = E_NOINTERFACE; + } + + if (*ppv) { + AddRef(); + } + + return hr; +} + +STDMETHODIMP_(ULONG) CFileStream::AddRef() +{ + return InterlockedIncrement(&_cRef); +} + +STDMETHODIMP_(ULONG) CFileStream::Release() +{ + ULONG ulRef = InterlockedDecrement(&_cRef); + + if (!ulRef) { + delete this; + } + + return ulRef; +} + +HRESULT CFileStream::Read(void *pv, ULONG cb, ULONG *pcbRead) +{ + HRESULT hr = S_OK; + ULONG cbRead = 0; + + if (pcbRead != NULL) { + *pcbRead = 0; + } + + _ASSERTE(_hFile != INVALID_HANDLE_VALUE); + if (_hFile == INVALID_HANDLE_VALUE) { + hr = E_UNEXPECTED; + goto Exit; + } + + if (!::ReadFile(_hFile, pv, cb, &cbRead, NULL)) { + hr = HRESULT_FROM_WIN32(::GetLastError()); + goto Exit; + } + + if (cbRead == 0) { + hr = S_FALSE; + } + else { + hr = NOERROR; + } + + if (pcbRead != NULL) { + *pcbRead = cbRead; + } + +Exit: + return hr; +} + +HRESULT CFileStream::Write(void const *pv, ULONG cb, ULONG *pcbWritten) +{ + HRESULT hr = S_OK; + ULONG cbWritten = 0; + + if (pcbWritten != NULL) { + *pcbWritten = 0; + } + + _ASSERTE(_hFile != INVALID_HANDLE_VALUE); + if (_hFile == INVALID_HANDLE_VALUE) { + hr = E_UNEXPECTED; + goto Exit; + } + + if (!::WriteFile(_hFile, pv, cb, &cbWritten, NULL)) { + hr = HRESULT_FROM_WIN32(::GetLastError()); + goto Exit; + } + + if (cbWritten == 0) { + hr = S_FALSE; + } + else { + hr = S_OK; + } + + if (pcbWritten != NULL) { + *pcbWritten = cbWritten; + } + +Exit: + return hr; +} + +HRESULT CFileStream::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) +{ +#if 1 // SetFilePointerEx not supported on Win9x + return E_NOTIMPL; +#else + HRESULT hr = S_OK; + DWORD dwFileOrigin; + BOOL bRet; + + _ASSERTE(_hFile != INVALID_HANDLE_VALUE); + if (_hFile == INVALID_HANDLE_VALUE) { + hr = E_UNEXPECTED; + goto Exit; + } + + switch (dwOrigin) { + case STREAM_SEEK_SET: + dwFileOrigin = FILE_BEGIN; + break; + + case STREAM_SEEK_CUR: + dwFileOrigin = FILE_CURRENT; + break; + + case STREAM_SEEK_END: + dwFileOrigin = FILE_END; + break; + + default: + hr = E_UNEXPECTED; + goto Exit; + } + + bRet = SetFilePointerEx(_hFile, dlibMove, (LARGE_INTEGER *)plibNewPosition, + dwFileOrigin); + if (!bRet) { + hr = HRESULT_FROM_WIN32(::GetLastError()); + goto Exit; + } + + +Exit: + return hr; +#endif +} + +HRESULT CFileStream::SetSize(ULARGE_INTEGER libNewSize) +{ + return E_NOTIMPL; +} + +HRESULT CFileStream::CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) +{ + return E_NOTIMPL; +} + +HRESULT CFileStream::Commit(DWORD grfCommitFlags) +{ + HRESULT hr = S_OK; + + if (grfCommitFlags != 0) { + hr = E_INVALIDARG; + goto Exit; + } + + if (!Close()) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + +Exit: + return hr; +} + +HRESULT CFileStream::Revert() +{ + return E_NOTIMPL; +} + +HRESULT CFileStream::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + return E_NOTIMPL; +} + +HRESULT CFileStream::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) +{ + return E_NOTIMPL; +} + +HRESULT CFileStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) +{ + return E_NOTIMPL; +} + +HRESULT CFileStream::Clone(IStream **ppIStream) +{ + return E_NOTIMPL; +} + + +BOOL CFileStream::Close() +{ + BOOL fSuccess = FALSE; + + if (_hFile != INVALID_HANDLE_VALUE) { + if (!::CloseHandle(_hFile)) { + _hFile = INVALID_HANDLE_VALUE; + goto Exit; + } + + _hFile = INVALID_HANDLE_VALUE; + } + + fSuccess = TRUE; + +Exit: + return fSuccess; +} + diff --git a/src/coreclr/utilcode/fstring.cpp b/src/coreclr/utilcode/fstring.cpp new file mode 100644 index 00000000000..0f4360e9ffe --- /dev/null +++ b/src/coreclr/utilcode/fstring.cpp @@ -0,0 +1,321 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// --------------------------------------------------------------------------- +// FString.cpp +// + +// --------------------------------------------------------------------------- + +#include "stdafx.h" +#include "ex.h" +#include "holder.h" + +#include "fstring.h" + + +namespace FString +{ + +#ifdef _MSC_VER +#pragma optimize("t", on) +#endif // _MSC_VER + +#define MAX_LENGTH 0x1fffff00 + + +HRESULT Unicode_Utf8_Length(__in_z LPCWSTR pString, __out bool * pAllAscii, __out DWORD * pLength) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + * pAllAscii = true; + + LPCWSTR p = pString; + + while (true) + { + WCHAR ch = * p; + + // Single check for termination and non ASCII + if (((unsigned) (ch - 1)) >= 0x7F) + { + if (ch != 0) + { + * pAllAscii = false; + } + + break; + } + + p ++; + } + + if (* pAllAscii) + { + if ((p - pString) > MAX_LENGTH) + { + return COR_E_OVERFLOW; + } + + * pLength = (DWORD) (p - pString); + } + else // use WideCharToMultiByte to calculate result length + { + * pLength = WszWideCharToMultiByte(CP_UTF8, 0, pString, -1, NULL, 0, NULL, NULL); + + if (*pLength == 0) + { + return HRESULT_FROM_GetLastError(); + } + + // Remove the count of null terminator, to be consistent with the all-ASCII case. + --*pLength; + + if (*pLength > MAX_LENGTH) + { + return COR_E_OVERFLOW; + } + } + + return S_OK; +} + + +// UNICODE to UTF8 +HRESULT Unicode_Utf8(__in_z LPCWSTR pString, bool allAscii, __out_z LPSTR pBuffer, DWORD length) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + pBuffer[length] = 0; + + if (allAscii) + { + LPCWSTR p = pString; + + LPSTR q = pBuffer; + + LPCWSTR endP = p + length - 8; + + // Unfold to optimize for long string: 8 chars per iteration + while (p < endP) + { + q[0] = (char) p[0]; + q[1] = (char) p[1]; + q[2] = (char) p[2]; + q[3] = (char) p[3]; + + q[4] = (char) p[4]; + q[5] = (char) p[5]; + q[6] = (char) p[6]; + q[7] = (char) p[7]; + + q += 8; + p += 8; + } + + endP += 8; + + while (p < endP) + { + * q ++ = (char) * p ++; + } + } + else + { + length = WszWideCharToMultiByte(CP_UTF8, 0, pString, -1, pBuffer, (int) length + 1, NULL, NULL); + + if (length == 0) + { + return HRESULT_FROM_GetLastError(); + } + } + + return S_OK; +} + + +HRESULT Utf8_Unicode_Length(__in_z LPCSTR pString, __out bool * pAllAscii, __out DWORD * pLength) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + * pAllAscii = true; + + LPCSTR p = pString; + + while (true) + { + char ch = * p; + + // Single check for termination and non ASCII + if (((unsigned) (ch - 1)) >= 0x7F) + { + if (ch != 0) + { + * pAllAscii = false; + } + + break; + } + + p ++; + } + + if (* pAllAscii) + { + if ((p - pString) > MAX_LENGTH) + { + return COR_E_OVERFLOW; + } + + * pLength = (DWORD)(p - pString); + } + else + { + * pLength = WszMultiByteToWideChar(CP_UTF8, 0, pString, -1, NULL, 0); + + if (* pLength == 0) + { + return HRESULT_FROM_GetLastError(); + } + + // Remove the count of null terminator, to be consistent with the all-ASCII case. + --*pLength; + + if (* pLength > MAX_LENGTH) + { + return COR_E_OVERFLOW; + } + } + + return S_OK; +} + + +// UTF8 to Unicode + +HRESULT Utf8_Unicode(__in_z LPCSTR pString, bool allAscii, __out_z LPWSTR pBuffer, DWORD length) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + pBuffer[length] = 0; + + if (allAscii) + { + LPCSTR p = pString; + + LPWSTR q = pBuffer; + + LPCSTR endP = p + length - 8; + + // Unfold to optimize for long string: 4 chars per iteration + while (p < endP) + { + q[0] = (WCHAR) p[0]; + q[1] = (WCHAR) p[1]; + q[2] = (WCHAR) p[2]; + q[3] = (WCHAR) p[3]; + + q[4] = (WCHAR) p[4]; + q[5] = (WCHAR) p[5]; + q[6] = (WCHAR) p[6]; + q[7] = (WCHAR) p[7]; + + q += 8; + p += 8; + } + + endP += 8; + + while (p < endP) + { + * q ++ = (WCHAR) * p ++; + } + } + else + { + length = WszMultiByteToWideChar(CP_UTF8, 0, pString, -1, pBuffer, (int) length + 1); + + if (length == 0) + { + return HRESULT_FROM_GetLastError(); + } + } + + return S_OK; +} + + +HRESULT ConvertUnicode_Utf8(__in_z LPCWSTR pString, __out_z LPSTR * pBuffer) +{ + bool allAscii; + DWORD length; + + HRESULT hr = Unicode_Utf8_Length(pString, & allAscii, & length); + + if (SUCCEEDED(hr)) + { + * pBuffer = new (nothrow) char[length + 1]; + + if (* pBuffer == NULL) + { + hr = E_OUTOFMEMORY; + } + else + { + hr = Unicode_Utf8(pString, allAscii, * pBuffer, length); + } + } + + return hr; +} + + +HRESULT ConvertUtf8_Unicode(__in_z LPCSTR pString, __out_z LPWSTR * pBuffer) +{ + bool allAscii; + DWORD length; + + HRESULT hr = Utf8_Unicode_Length(pString, & allAscii, & length); + + if (SUCCEEDED(hr)) + { + * pBuffer = new (nothrow) WCHAR[length + 1]; + + if (* pBuffer == NULL) + { + hr = E_OUTOFMEMORY; + } + else + { + hr = Utf8_Unicode(pString, allAscii, * pBuffer, length); + } + } + + return hr; +} + + +#ifdef _MSC_VER +#pragma optimize("", on) +#endif // _MSC_VER + +} // namespace FString diff --git a/src/coreclr/utilcode/guidfromname.cpp b/src/coreclr/utilcode/guidfromname.cpp new file mode 100644 index 00000000000..6c8e9917704 --- /dev/null +++ b/src/coreclr/utilcode/guidfromname.cpp @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// GuidFromName + +/** + +Algorithm from Internet Draft document "UUIDs and GUIDs" +By Paul J. Leach and Rich Sals, February 4, 1998. + +This function has been adapted from the routines in the document + uuid_create_from_name and format_uuid_v3 + +Changes from documented routines: +1. Changed all instances of uuid_t to GUID. + uuid_t field time_low is GUID field Data1. + uuid_t field time_mid is GUID field Data2. + uuid_t field time_hi_and_version is GUID field Data3. + uuid_t field clock_seq_hi_and_reserved is GUID field Data4[0]. + uuid_t field clock_seq_low is GUID field Data4[1]. + uuid_t field node[6] is GUID field Data4[2] through Data4[8]. + +2. Use a c++ implementation of the md5 cryptographic hash function. + +3. Implemented the htonl, htons, ntohl, ntohs socket routines as inlines. + +4. Renamed variables and types to suit my biases. + + +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & +Digital Equipment Corporation, Maynard, Mass. +To anyone who acknowledges that this file is provided "AS IS" +without any express or implied warranty: permission to use, copy, +modify, and distribute this file for any purpose is hereby +granted without fee, provided that the above copyright notices and +this notice appears in all source code copies, and that none of +the names of Open Software Foundation, Inc., Hewlett-Packard +Company, or Digital Equipment Corporation be used in advertising +or publicity pertaining to distribution of the software without +specific, written prior permission. Neither Open Software +Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment +Corporation makes any representations about the suitability of +this software for any purpose. + + +Copyright(C) The Internet Society 1997. All Rights Reserved. + +This document and translations of it may be copied and furnished to others, +and derivative works that comment on or otherwise explain it or assist in +its implementation may be prepared, copied, published and distributed, in +whole or in part, without restriction of any kind, provided that the above +copyright notice and this paragraph are included on all such copies and +derivative works.However, this document itself may not be modified in any +way, such as by removing the copyright notice or references to the Internet +Society or other Internet organizations, except as needed for the purpose of +developing Internet standards in which case the procedures for copyrights +defined in the Internet Standards process must be followed, or as required +to translate it into languages other than English. + +The limited permissions granted above are perpetual and will not be revoked +by the Internet Society or its successors or assigns. + +This document and the information contained herein is provided on an "AS IS" +basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE +DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY +RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A +PARTICULAR PURPOSE. + +*/ + +#include "stdafx.h" + +#include "md5.h" // cryptographic hash function +#include "guidfromname.h" // verify our function signature +#include "contract.h" + +#if BIGENDIAN +#define BigEndian() true +#else +#define BigEndian() false +#endif + +//============================================================================= +// htons, htonl, ntohs, ntohl equivalents copied and adapted from socket library. +//============================================================================= + +// HostToNetworkLong converts a 32-bit long to network byte order + +inline ULONG HostToNetworkLong(ULONG hostlong) +{ + LIMITED_METHOD_CONTRACT; + + if (BigEndian()) + return hostlong; + else + return ( (hostlong >> 24) & 0x000000FFL) | + ( (hostlong >> 8) & 0x0000FF00L) | + ( (hostlong << 8) & 0x00FF0000L) | + ( (hostlong << 24) & 0xFF000000L); +} + +// HostToNetworkLong converts a 16-bit short to network byte order + +inline USHORT HostToNetworkShort(USHORT hostshort) +{ + LIMITED_METHOD_CONTRACT; + + if (BigEndian()) + return hostshort; + else + return ((hostshort >> 8) & 0x00FF) | ((hostshort << 8) & 0xFF00); +} + +// NetworkToHostLong converts a 32-bit long to local host byte order + +inline ULONG NetworkToHostLong(ULONG netlong) +{ + LIMITED_METHOD_CONTRACT; + + if (BigEndian()) + return netlong; + else + return ( (netlong >> 24) & 0x000000FFL) | + ( (netlong >> 8) & 0x0000FF00L) | + ( (netlong << 8) & 0x00FF0000L) | + ( (netlong << 24) & 0xFF000000L); +} + +// NetworkToHostShort converts a 16-bit short to local host byte order + +inline USHORT NetworkToHostShort(USHORT netshort) +{ + LIMITED_METHOD_CONTRACT; + + if (BigEndian()) + return netshort; + else + return ((netshort >> 8) & 0x00FF) | ((netshort << 8) & 0xFF00); +} + +//============================================================================= +// GuidFromName(GUID * pGuidResult, REFGUID refGuidNsid, +// const void * pvName, DWORD dwcbName); +//============================================================================= + +void GuidFromName +( + GUID * pGuidResult, // resulting GUID + REFGUID refGuidNsid, // Name Space GUID, so identical names from + // different name spaces generate different GUIDs + const void * pvName, // the name from which to generate a GUID + DWORD dwcbName // name length in bytes +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + MD5 md5; // Cryptographic hash class instance + MD5HASHDATA md5HashData; // 128-bit hash result + GUID guidNsid; // context NameSpace GUID in network byte order + + GUID guidTemp; + + // put name space ID in network byte order so it hashes the same + // no matter what endian machine we're on + guidNsid = refGuidNsid; + + // The sample code in the IETF draft document discards the result of + // htonl and htons. I've implemented what I think is meant and I've + // sent a note to the author asking for confirmation that this is + // his intent. + if (!BigEndian()) // evaluated at compile time in retail builds + { + guidNsid.Data1 = HostToNetworkLong (guidNsid.Data1); + guidNsid.Data2 = HostToNetworkShort(guidNsid.Data2); + guidNsid.Data3 = HostToNetworkShort(guidNsid.Data3); + } + + md5.Init(); + md5.HashMore(&guidNsid, sizeof(GUID)); + md5.HashMore(pvName, dwcbName); + md5.GetHashValue(&md5HashData); + + // the hash is in network byte order at this point + memcpy(&guidTemp, &md5HashData, sizeof(GUID)); + + // Remainder adapted from function "format_uuid_v3" in IETF draft document + // Construct a version 3 uuid with the pseudo-random number plus a few constants. + // convert GUID from network order to local byte order + if (!BigEndian()) // evaluated at compile time in retail builds + { + guidTemp.Data1 = NetworkToHostLong (guidTemp.Data1); + guidTemp.Data2 = NetworkToHostShort(guidTemp.Data2); + guidTemp.Data3 = NetworkToHostShort(guidTemp.Data3); + } + + // set version number + guidTemp.Data3 &= 0x0FFF; // clear version number nibble + guidTemp.Data3 |= (3 << 12);// set version 3 = name-based + + // set variant field + guidTemp.Data4[0] &= 0x3F; // clear variant bits + guidTemp.Data4[0] |= 0x80; // set variant = 100b + + // If two GuidFromName calls were made from different threads with the same parameters, + // we may get incorrect result even though the expected result is the same, because + // GuidFromName is operating on the same pGuidResult buffer. + // Fix this problem by using a temp GUID buffer and then copy to the pGuidResult buffer. + memcpy(pGuidResult, &guidTemp, sizeof(GUID)); +} + + +// This guid is used for calling GuidFromName function as COM+ runtime uniqualifier +// +// {69F9CBC9-DA05-11d1-9408-0000F8083460} +static const GUID COMPLUS_RUNTIME_GUID = {0x69f9cbc9, 0xda05, 0x11d1, + {0x94, 0x8, 0x0, 0x0, 0xf8, 0x8, 0x34, 0x60}}; + +void CorGuidFromNameW +( + GUID * pGuidResult, // resulting GUID + LPCWSTR wzName, // the unicode name from which to generate a GUID + SIZE_T cchName // name length in count of unicode character +) +{ + WRAPPER_NO_CONTRACT; + + GuidFromName( + pGuidResult, + COMPLUS_RUNTIME_GUID, + wzName, + (DWORD)((cchName == (SIZE_T) -1 ? (wcslen(wzName)+1) : cchName) * sizeof(WCHAR))); +} diff --git a/src/coreclr/utilcode/hostimpl.cpp b/src/coreclr/utilcode/hostimpl.cpp new file mode 100644 index 00000000000..c1e4b53b065 --- /dev/null +++ b/src/coreclr/utilcode/hostimpl.cpp @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" + +#include "mscoree.h" +#include "clrinternal.h" +#include "clrhost.h" +#include "ex.h" + +thread_local size_t t_ThreadType; + +CRITSEC_COOKIE ClrCreateCriticalSection(CrstType crstType, CrstFlags flags) +{ + CRITICAL_SECTION *cs = (CRITICAL_SECTION*)malloc(sizeof(CRITICAL_SECTION)); + InitializeCriticalSection(cs); + return (CRITSEC_COOKIE)cs; +} + +void ClrDeleteCriticalSection(CRITSEC_COOKIE cookie) +{ + _ASSERTE(cookie); + DeleteCriticalSection((CRITICAL_SECTION*)cookie); + free(cookie); +} + +void ClrEnterCriticalSection(CRITSEC_COOKIE cookie) +{ + _ASSERTE(cookie); + EnterCriticalSection((CRITICAL_SECTION*)cookie); +} + +void ClrLeaveCriticalSection(CRITSEC_COOKIE cookie) +{ + _ASSERTE(cookie); + LeaveCriticalSection((CRITICAL_SECTION*)cookie); +} + +DWORD ClrSleepEx(DWORD dwMilliseconds, BOOL bAlertable) +{ + return SleepEx(dwMilliseconds, bAlertable); +} + +LPVOID ClrVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) +{ +#ifdef FAILPOINTS_ENABLED + if (RFS_HashStack ()) + return NULL; +#endif + return VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); +} + +BOOL ClrVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) +{ + return VirtualFree(lpAddress, dwSize, dwFreeType); +} + +SIZE_T ClrVirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) +{ + return VirtualQuery(lpAddress, lpBuffer, dwLength); +} + +BOOL ClrVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) +{ + return VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); +} + +//------------------------------------------------------------------------------ +// Helper function to get an exception from outside the exception. In +// the CLR, it may be from the Thread object. Non-CLR users have no thread object, +// and it will do nothing. + +void GetLastThrownObjectExceptionFromThread(Exception** ppException) +{ + *ppException = NULL; +} + +#ifdef HOST_WINDOWS +void CreateCrashDumpIfEnabled(bool stackoverflow) +{ +} +#endif diff --git a/src/coreclr/utilcode/iallocator.cpp b/src/coreclr/utilcode/iallocator.cpp new file mode 100644 index 00000000000..9e1ea5ed906 --- /dev/null +++ b/src/coreclr/utilcode/iallocator.cpp @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" // Precompiled header key. +#include "iallocator.h" +#include "defaultallocator.h" + +// static +DefaultAllocator DefaultAllocator::s_singleton; diff --git a/src/coreclr/utilcode/ilformatter.cpp b/src/coreclr/utilcode/ilformatter.cpp new file mode 100644 index 00000000000..9c8011dc666 --- /dev/null +++ b/src/coreclr/utilcode/ilformatter.cpp @@ -0,0 +1,818 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/***************************************************************************/ +/* ILFormatter.h */ +/***************************************************************************/ + +#include "stdafx.h" +#include <cor.h> +#include <debugmacros.h> // for ASSERTE +#include "ilformatter.h" +#include "outstring.h" +#include "opinfo.h" + +/***************************************************************************/ +void ILFormatter::init(IMetaDataImport* aMeta, const BYTE* aStart, + const BYTE* aLimit, unsigned maxStack, const COR_ILMETHOD_SECT_EH* eh) { + this->~ILFormatter(); // clean out old stuff + + meta = aMeta; + start = aStart; + limit = aLimit; + if (maxStack == 0) maxStack++; + stackStart = stackCur = new StackEntry[maxStack]; + stackEnd = stackStart + maxStack; + targetStart = targetCur = targetEnd = 0; + if (eh != 0) { + COR_ILMETHOD_SECT_EH_CLAUSE_FAT buff; + const COR_ILMETHOD_SECT_EH_CLAUSE_FAT* clause; + for(unsigned i = 0; i < eh->EHCount(); i++) { + clause = (COR_ILMETHOD_SECT_EH_CLAUSE_FAT*)eh->EHClause(i, &buff); + // is it a regular catch clause ? + if ((clause->GetFlags() & (COR_ILEXCEPTION_CLAUSE_FINALLY | COR_ILEXCEPTION_CLAUSE_FAULT)) == 0) + setTarget(clause->GetHandlerOffset(), 1); + if(clause->GetFlags() & COR_ILEXCEPTION_CLAUSE_FILTER) + setTarget(clause->GetFilterOffset(), 1); + } + } +} + +/***************************************************************************/ +inline size_t ILFormatter::stackDepth() { + return(stackCur - stackStart); +} + +/***************************************************************************/ +inline void ILFormatter::pushAndClear(OutString* val, int prec) { + if (stackCur >= stackEnd) { + _ASSERTE(!"Stack Overflow (can be ignored)"); + return; // Ignore overflow in free build + } + stackCur->val.swap(*val); + val->clear(); + stackCur->prec = prec; + stackCur++; +} + +/***************************************************************************/ +inline OutString* ILFormatter::top() { + if (stackDepth() == 0) { + _ASSERTE(!"Stack underflow (can be ignored)"); + stackStart->val.clear(); + stackStart->val << "<UNDERFLOW ERROR>"; + return (&stackStart->val); + } + return(&stackCur[-1].val); +} + +/***************************************************************************/ +inline OutString* ILFormatter::pop(int prec) { + if (stackDepth() == 0) { + _ASSERTE(!"Stack underflow (can be ignored)"); + stackStart->val.clear(); + stackStart->val << "<UNDERFLOW ERROR>"; + return (&stackStart->val); + } + --stackCur; + if (stackCur->prec < prec) { + stackCur->val.prepend('('); + stackCur->val << ')'; + } + return(&stackCur->val); +} + +/***************************************************************************/ +inline void ILFormatter::popN(size_t num) { + if (stackCur-stackStart < (SSIZE_T)num) { + _ASSERTE(!"Stack underflow (can be ignored)"); + stackCur = stackStart; + return; + } + stackCur -= num; +} + +/***************************************************************************/ +void ILFormatter::setStackAsTarget(size_t ilOffset) { + + Target*ptr = targetStart; + for(;;) { + if (ptr >= targetCur) + return; + if (ptr->ilOffset == ilOffset) + break; + ptr++; + } + + for(size_t i = 0; i < ptr->stackDepth; i++) { + stackStart[i].val.clear(); + stackStart[i].val << "@STK" << (unsigned)i; + } + stackCur = stackStart + ptr->stackDepth; +} + +/***************************************************************************/ +void ILFormatter::setTarget(size_t ilOffset, size_t depth) { + if (depth == 0) + return; + + if (targetCur >= targetEnd) { + Target* targetOld = targetStart; + size_t oldLen = targetCur-targetStart; + targetStart = new Target[oldLen+10]; + targetEnd = &targetStart[oldLen+10]; + targetCur = &targetStart[oldLen]; + memcpy(targetStart, targetOld, sizeof(Target)*oldLen); + delete [] targetOld; + } + targetCur->ilOffset = ilOffset; + targetCur->stackDepth = depth; + targetCur++; +} + +/***************************************************************************/ +void ILFormatter::spillStack(OutString* out) { + + for(unsigned i = 0; i < stackDepth(); i++) { + // don't bother spilling something already spilled. + if (memcmp(stackStart[i].val.val(), "@STK", 4) != 0) + *out << "@STK" << i << " = " << stackStart[i].val.val() << "\n"; + stackStart[i].val.clear(); + stackStart[i].val << "@STK" << i ; + } +} + +/***************************************************************************/ +const BYTE* ILFormatter::formatInstr(const BYTE* instrPtr, OutString* out) { + + _ASSERTE(start < instrPtr && instrPtr < limit); + OpArgsVal arg; + OpInfo op; + instrPtr = op.fetch(instrPtr, &arg); + *out << op.getName(); + if (op.getArgsInfo() != InlineNone) + *out << ' '; + formatInstrArgs(op, arg, out, instrPtr - start); + return(instrPtr); +} + +/***************************************************************************/ +void ILFormatter::formatArgs(unsigned numArgs, OutString* out) { + + *out << '('; + if (numArgs > stackDepth()) { + _ASSERTE(!"Underflow error"); + *out << "<UNDERFLOW ERROR>"; + } + else { + popN(numArgs); + for(unsigned i = 0; i < numArgs; i++) { + if (i != 0) *out << ", "; + *out << stackCur[i].val.val(); + } + } + *out << ')'; +} + +/***************************************************************************/ +void ILFormatter::formatInstrArgs(OpInfo op, OpArgsVal arg, OutString* out, size_t curILOffset) { + + MDUTF8CSTR typeName=0; + HRESULT hr = S_OK; + switch(op.getArgsInfo() & PrimaryMask) { + case InlineNone: + break; + case InlineVar: + *out << arg.i; + break; + case InlineI: + case InlineRVA: + out->hex(arg.i, 0, OutString::put0x); + break; + case InlineR: + *out << arg.r; + break; + case InlineBrTarget: { + _ASSERTE(curILOffset != INVALID_IL_OFFSET); + size_t target = curILOffset + arg.i; + setTarget(target, stackDepth()); + *out << "IL_"; out->hex(static_cast<unsigned __int64>(target), 4, OutString::zeroFill); + } break; + case InlineI8: + out->hex(arg.i, 0, OutString::put0x); + break; + case InlineString: { + ULONG numChars; + WCHAR str[84]; + + hr = meta->GetUserString(arg.i, str, 80, &numChars); + _ASSERTE(SUCCEEDED(hr)); + if (numChars < 80) + str[numChars] = 0; + wcscpy_s(&str[79], 4, W("...")); + *out << '"'; + WCHAR* ptr = str; + while(*ptr != 0) { + if (*ptr == '\n') + *out << "\\n"; + else if (*ptr == '"') + *out << "\\\""; + else if (*ptr < 0x20 || * ptr >= 0x80) { + *out << '\\'; + out->hex(*ptr, 4, OutString::zeroFill); + } + else + *out << char(*ptr); + ptr++; + } + *out << '"'; + } break; + case InlineMethod: + case InlineField: + case InlineTok: { + // Get the typeName if possible + mdToken mdType = mdTypeDefNil; + if (TypeFromToken(arg.i) == mdtMethodDef) + hr = meta->GetMethodProps(mdMethodDef(arg.i), &mdType, 0, 0, 0, 0, 0, 0, 0, 0); + else if (TypeFromToken(arg.i) == mdtMemberRef) + hr = meta->GetMemberRefProps(mdMemberRef(arg.i), &mdType, 0, 0, 0, 0, 0); + else if (TypeFromToken(arg.i) == mdtFieldDef) + hr = meta->GetFieldProps(mdMethodDef(arg.i), &mdType, 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (SUCCEEDED(hr) && mdType != mdTypeDefNil) { + hr = meta->GetNameFromToken(mdType, &typeName); + } + } + FALLTHROUGH; + case InlineType: { + // FIX handle case if (TypeFromToken(arg.i) == mdtTypeSpec) + MDUTF8CSTR name; + hr = meta->GetNameFromToken(arg.i, &name); + if (SUCCEEDED(hr)) { + if (typeName) { + const char* lastDot = strrchr(typeName, '.'); + if (lastDot) typeName = lastDot + 1; + *out << typeName << "::"; + } + *out << name; + } + else { + *out << "TOK<"; + out->hex(arg.i, 0, OutString::put0x); + *out << '>'; + } + } break; + case InlineSig: + *out << "SIG<"; + out->hex(arg.i, 0, OutString::put0x); + *out << '>'; + break; + case InlineSwitch: { + _ASSERTE(curILOffset != INVALID_IL_OFFSET); + unsigned count = arg.switch_.count; + unsigned i; + for (i = 0; i < count; i++) { + size_t target = curILOffset + GET_UNALIGNED_VAL32(&arg.switch_.targets[i]); + setTarget(target, stackDepth()-1); + *out << "IL_"; out->hex(static_cast<unsigned __int64>(target), 4, OutString::zeroFill); + *out << ' '; + } + } break; + case InlinePhi: { + unsigned count = arg.phi.count; + unsigned i; + for (i = 0; i < count; i++) { + *out << GET_UNALIGNED_VAL32(&arg.phi.vars[i]); + *out << ' '; + } + } break; + default: + _ASSERTE(!"BadType"); + } +} + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +/***************************************************************************/ +const BYTE* ILFormatter::formatStatement(const BYTE* instrPtr, OutString* out) { + + OutString result; + OpInfo op; + OutString *lhs, *rhs, *idx; + const char* name; + int prec = 0; + + // set stack as it would be if it was begin jumped to + setStackAsTarget(instrPtr - start); + + while(instrPtr < limit) { + OpArgsVal inlineArg; + instrPtr = op.fetch(instrPtr, &inlineArg); + + switch(op.getOpcode()) { + case CEE_UNALIGNED: + case CEE_TAILCALL: + case CEE_VOLATILE: + // for now just skip these + break; + + case CEE_LDARGA_S: + case CEE_LDARGA: + result << "&"; + goto DO_LDARG; + + case CEE_LDARG_0: + case CEE_LDARG_1: + case CEE_LDARG_2: + case CEE_LDARG_3: + inlineArg.i = op.getOpcode() - CEE_LDARG_0; + goto DO_LDARG; + + case CEE_LDARG: + case CEE_LDARG_S: + DO_LDARG: + name = "arg"; + DO_LDARG_LDLOC: + result << name << inlineArg.i; + prec = 0x1000; + goto DO_PUSH; + DO_PUSH: + pushAndClear(&result, prec); // also clears result! + break; + + case CEE_LDLOCA_S: + case CEE_LDLOCA: + result << "&"; + goto DO_LDLOC; + + case CEE_LDLOC_0: + case CEE_LDLOC_1: + case CEE_LDLOC_2: + case CEE_LDLOC_3: + inlineArg.i = op.getOpcode() - CEE_LDLOC_0; + goto DO_LDLOC; + + case CEE_LDLOC: + case CEE_LDLOC_S: + DO_LDLOC: + name = "loc"; + goto DO_LDARG_LDLOC; + + case CEE_STARG: + case CEE_STARG_S: + name = "arg"; + DO_STARG_STLOC: + lhs = pop(0x10); + result << name << inlineArg.i << " = " << lhs->val(); + DO_STMT: + spillStack(out); + *out << result.val() << '\n'; + // if flow of control does not fall through, + // assume the stack is empty + if (op.getFlow() == FLOW_BRANCH || op.getFlow() == FLOW_RETURN || + op.getFlow() == FLOW_THROW) { + popN(stackDepth()); + } + return(instrPtr); + + case CEE_STLOC_0: + case CEE_STLOC_1: + case CEE_STLOC_2: + case CEE_STLOC_3: + inlineArg.i = op.getOpcode() - CEE_STLOC_0; + goto DO_STLOC; + + case CEE_STLOC: + case CEE_STLOC_S: + DO_STLOC: + name = "loc"; + goto DO_STARG_STLOC; + + case CEE_LDC_I4_M1: + case CEE_LDC_I4_0: + case CEE_LDC_I4_1: + case CEE_LDC_I4_2: + case CEE_LDC_I4_3: + case CEE_LDC_I4_4: + case CEE_LDC_I4_5: + case CEE_LDC_I4_6: + case CEE_LDC_I4_7: + case CEE_LDC_I4_8: + inlineArg.i = op.getOpcode() - CEE_LDC_I4_0; + FALLTHROUGH; + case CEE_LDC_I4: + case CEE_LDC_I4_S: + result << inlineArg.i; + prec = 0x1000; + goto DO_PUSH; + + case CEE_LDC_I8: + result.hex(inlineArg.i8); + prec = 0x1000; + goto DO_PUSH; + + case CEE_LDC_R4: + case CEE_LDC_R8: + result << inlineArg.r; + prec = 0x1000; + goto DO_PUSH; + + case CEE_LDNULL: + result << "null"; + prec = 0x1000; + goto DO_PUSH; + + case CEE_LDSTR: + formatInstrArgs(op, inlineArg, &result); + prec = 0x1000; + goto DO_PUSH; + + case CEE_BEQ: + case CEE_BEQ_S: + name = "=="; prec = 0x40; goto DO_BR_BINOP; + case CEE_BGE: + case CEE_BGE_S: + name = ">="; prec = 0x40; goto DO_BR_BINOP; + case CEE_BGE_UN: + case CEE_BGE_UN_S: + name = ">=un"; prec = 0x40; goto DO_BR_BINOP; + + case CEE_BGT: + case CEE_BGT_S: + name = ">"; prec = 0x40; goto DO_BR_BINOP; + case CEE_BGT_UN: + case CEE_BGT_UN_S: + name = ">un"; prec = 0x40; goto DO_BR_BINOP; + case CEE_BLE: + case CEE_BLE_S: + name = "<="; prec = 0x40; goto DO_BR_BINOP; + case CEE_BLE_UN: + case CEE_BLE_UN_S: + name = "<=un"; prec = 0x40; goto DO_BR_BINOP; + case CEE_BLT: + case CEE_BLT_S: + name = "<"; prec = 0x40; goto DO_BR_BINOP; + case CEE_BLT_UN: + case CEE_BLT_UN_S: + name = "<un"; prec = 0x40; goto DO_BR_BINOP; + case CEE_BNE_UN: + case CEE_BNE_UN_S: + name = "!=un"; prec = 0x40; goto DO_BR_BINOP; + DO_BR_BINOP: + rhs = pop(prec); + lhs = pop(prec-1); + result << "if (" << lhs->val() << ' ' << name << ' ' << rhs->val() << ") "; + goto DO_BR; + + case CEE_LEAVE_S: + case CEE_LEAVE: + while (stackDepth() > 0) { + lhs = pop(); + *lhs << '\n' << result; // put the result in front of anything else + result.swap(*lhs); + } + FALLTHROUGH; + case CEE_BR_S: + case CEE_BR: + DO_BR: { + size_t target = (instrPtr - start) + inlineArg.i; + setTarget(target, stackDepth()); + result << "goto IL_"; result.hex(static_cast<unsigned __int64>(target), 4, OutString::zeroFill); + } goto DO_STMT; + + case CEE_BRFALSE_S: + case CEE_BRFALSE: + name = "!"; + goto DO_BR_UNOP; + case CEE_BRTRUE_S: + case CEE_BRTRUE: + name = ""; + DO_BR_UNOP: + lhs = pop(); + result << "if (" << name << lhs->val() << ") "; + goto DO_BR; + + case CEE_OR: + name = "|"; prec = 0x20; goto DO_BINOP; + case CEE_XOR: + name = "^"; prec = 0x20; goto DO_BINOP; + case CEE_AND: + name = "&"; prec = 0x30; goto DO_BINOP; + case CEE_SHL: + name = "<<"; prec = 0x50; goto DO_BINOP; + case CEE_SHR: + name = ">>"; prec = 0x50; goto DO_BINOP; + case CEE_SHR_UN: + name = ">>un"; prec = 0x50; goto DO_BINOP; + case CEE_CEQ: + name = "=="; prec = 0x40; goto DO_BINOP; + case CEE_CGT: + name = ">"; prec = 0x40; goto DO_BINOP; + case CEE_CGT_UN: + name = ">un"; prec = 0x40; goto DO_BINOP; + case CEE_CLT: + name = "<"; prec = 0x40; goto DO_BINOP; + case CEE_CLT_UN: + name = "<un"; prec = 0x40; goto DO_BINOP; + case CEE_ADD: + name = "+"; prec = 0x60; goto DO_BINOP; + case CEE_ADD_OVF: + name = "+ovf"; prec = 0x60; goto DO_BINOP; + case CEE_ADD_OVF_UN: + name = "+ovf.un";prec = 0x60; goto DO_BINOP; + case CEE_SUB: + name = "-"; prec = 0x60; goto DO_BINOP; + case CEE_SUB_OVF: + name = "-ovf"; prec = 0x60; goto DO_BINOP; + case CEE_SUB_OVF_UN: + name = "-ovf.un";prec = 0x60; goto DO_BINOP; + case CEE_MUL: + name = "*"; prec = 0x70; goto DO_BINOP; + case CEE_MUL_OVF: + name = "*ovf"; prec = 0x70; goto DO_BINOP; + case CEE_MUL_OVF_UN: + name = "*ovf.un";prec = 0x70; goto DO_BINOP; + case CEE_DIV: + name = "/"; prec = 0x70; goto DO_BINOP; + case CEE_DIV_UN: + name = "/un"; prec = 0x70; goto DO_BINOP; + case CEE_REM: + name = "%"; prec = 0x70; goto DO_BINOP; + case CEE_REM_UN: + name = "%un"; prec = 0x70; goto DO_BINOP; + DO_BINOP: + rhs = pop(prec); + lhs = pop(prec-1); + result << lhs->val() << ' ' << name << ' ' << rhs->val(); + goto DO_PUSH; + + case CEE_NOT: + name = "~"; prec = 0x80; goto DO_UNOP; + case CEE_NEG: + name = "-"; prec = 0x80; goto DO_UNOP; + DO_UNOP: + lhs = pop(prec-1); + result << name << lhs->val(); + goto DO_PUSH; + + case CEE_RET: + _ASSERTE(stackDepth() <= 1); + result << "return"; + if (stackDepth() > 0) { + lhs = pop(); + result << ' ' << lhs->val(); + } + goto DO_STMT; + + case CEE_POP: + lhs = pop(); + result.swap(*lhs); + goto DO_STMT; + + case CEE_DUP: + spillStack(out); + lhs = top(); + result << lhs->val(); + prec = 0x1000; // spillstack makes them temps, so they have high prec + goto DO_PUSH; + + case CEE_LDFLDA: + name = "&"; + goto DO_LDFLD_LDFLDA; + case CEE_LDFLD: + name = ""; + DO_LDFLD_LDFLDA: + prec = 0x110; + lhs = pop(prec-1); + result << name << lhs->val() << '.'; + formatInstrArgs(op, inlineArg, &result); + goto DO_PUSH; + + case CEE_LDSFLDA: + name = "&"; + goto DO_LDSFLD_LDSFLDA; + case CEE_LDSFLD: + name = ""; + DO_LDSFLD_LDSFLDA: + prec = 0x1000; + result << name; + formatInstrArgs(op, inlineArg, &result); + goto DO_PUSH; + + case CEE_STFLD: + rhs = pop(0x10); + lhs = pop(0x110-1); + result << lhs->val() << '.'; + formatInstrArgs(op, inlineArg, &result); + result << " = " << rhs->val(); + goto DO_STMT; + + case CEE_STSFLD: + rhs = pop(0x20); + formatInstrArgs(op, inlineArg, &result); + result << " = " << rhs->val(); + goto DO_STMT; + + case CEE_CALLI: + lhs = pop(); + result << "CALLI<" << lhs->val() << '>'; + goto DO_CALL; + + case CEE_NEWOBJ: + result << "new "; + FALLTHROUGH; + case CEE_CALL: + case CEE_CALLVIRT: { + formatInstrArgs(op, inlineArg, &result); + + DO_CALL: + // Get the signature stuff + PCCOR_SIGNATURE sig; + ULONG cSig; + HRESULT hr; + if (TypeFromToken(inlineArg.i) == mdtMethodDef) + hr = meta->GetMethodProps(mdMethodDef(inlineArg.i), 0, 0, 0, 0, 0, &sig, &cSig, 0, 0); + else if (TypeFromToken(inlineArg.i) == mdtMemberRef) + hr = meta->GetMemberRefProps(mdMemberRef(inlineArg.i), 0, 0, 0, 0, &sig, &cSig); + else + hr = meta->GetSigFromToken(mdSignature(inlineArg.i), &sig, &cSig); + _ASSERTE(SUCCEEDED(hr)); + unsigned callConv = CorSigUncompressData(sig); + unsigned hasThis = callConv & IMAGE_CEE_CS_CALLCONV_HASTHIS; + if (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + CorSigUncompressData(sig); + } + unsigned numArgs = CorSigUncompressData(sig); + while(*sig == ELEMENT_TYPE_CMOD_REQD || *sig == ELEMENT_TYPE_CMOD_OPT) { + sig++; + CorSigUncompressToken(sig); + } + + formatArgs(numArgs, &result); + if (hasThis && op.getOpcode() != CEE_NEWOBJ) { + lhs = pop(0x90); + result.swap(*lhs); + result << '.' << lhs->val(); + } + prec = 0x1000; + if (op.getOpcode() == CEE_NEWOBJ || *sig != ELEMENT_TYPE_VOID) + goto DO_PUSH; + } goto DO_STMT; + + case CEE_LDELEM_I1: + case CEE_LDELEM_I2: + case CEE_LDELEM_I4: + case CEE_LDELEM_I8: + case CEE_LDELEM_REF: + case CEE_LDELEM_R4: + case CEE_LDELEM_R8: + case CEE_LDELEM_U1: + case CEE_LDELEM_U2: + case CEE_LDELEM_I: + rhs = pop(0x100); + lhs = pop(); + result << lhs->val() << '[' << rhs->val() << ']'; + prec = 0x100; + goto DO_PUSH; + + case CEE_STELEM_I1: + case CEE_STELEM_I2: + case CEE_STELEM_I4: + case CEE_STELEM_I8: + case CEE_STELEM_REF: + case CEE_STELEM_R4: + case CEE_STELEM_R8: + case CEE_STELEM_I: + rhs = pop(0x100); + idx = pop(); + lhs = pop(0x20); + result << lhs->val() << '[' << idx->val() << "] = " << rhs->val(); + goto DO_STMT; + + case CEE_LDIND_I1: name = "I1"; goto DO_LDIND; + case CEE_LDIND_I2: name = "I2"; goto DO_LDIND; + case CEE_LDIND_I4: name = "I4"; goto DO_LDIND; + case CEE_LDIND_I8: name = "I8"; goto DO_LDIND; + case CEE_LDIND_I: name = "I"; goto DO_LDIND; + case CEE_LDIND_R4: name = "R4"; goto DO_LDIND; + case CEE_LDIND_R8: name = "R8"; goto DO_LDIND; + case CEE_LDIND_U1: name = "U1"; goto DO_LDIND; + case CEE_LDIND_U2: name = "U2"; goto DO_LDIND; + case CEE_LDIND_REF: name = "REF";goto DO_LDIND; + DO_LDIND: + prec = 0x90; + lhs = pop(prec); + result << name << "(*" << lhs->val() << ')'; + goto DO_PUSH; + + case CEE_STIND_I1: name = "I1"; goto DO_STIND; + case CEE_STIND_I2: name = "I2"; goto DO_STIND; + case CEE_STIND_I4: name = "I4"; goto DO_STIND; + case CEE_STIND_I8: name = "I8"; goto DO_STIND; + case CEE_STIND_REF: name = "REF";goto DO_STIND; + case CEE_STIND_R4: name = "R4"; goto DO_STIND; + case CEE_STIND_R8: name = "R8"; goto DO_STIND; + DO_STIND: + rhs = pop(); + lhs = pop(0x90); + result << '*' << lhs->val() << " = " << name << '(' << rhs->val() << ')'; + goto DO_STMT; + + case CEE_LDVIRTFTN: + case CEE_ARGLIST: + case CEE_BREAK: + case CEE_ENDFILTER: + case CEE_CPBLK: + case CEE_INITBLK: + case CEE_LDOBJ: + case CEE_CPOBJ: + case CEE_STOBJ: + case CEE_INITOBJ: + case CEE_LOCALLOC: + case CEE_NOP: + case CEE_SWITCH: + case CEE_CASTCLASS: + case CEE_ISINST: + case CEE_LDLEN: + case CEE_JMP: + case CEE_NEWARR: + case CEE_THROW: + case CEE_RETHROW: + case CEE_LDELEM_U4: + case CEE_LDIND_U4: + case CEE_LDELEMA: + case CEE_ENDFINALLY: + case CEE_STIND_I: + case CEE_CKFINITE: + case CEE_MKREFANY: + case CEE_REFANYTYPE: + case CEE_REFANYVAL: + case CEE_CONV_I1: + case CEE_CONV_I2: + case CEE_CONV_I4: + case CEE_CONV_I8: + case CEE_CONV_R4: + case CEE_CONV_R8: + case CEE_CONV_R_UN: + case CEE_CONV_OVF_I_UN: + case CEE_CONV_OVF_I1_UN: + case CEE_CONV_OVF_I2_UN: + case CEE_CONV_OVF_I4_UN: + case CEE_CONV_OVF_I8_UN: + case CEE_CONV_OVF_U_UN: + case CEE_CONV_OVF_U1_UN: + case CEE_CONV_OVF_U2_UN: + case CEE_CONV_OVF_U4_UN: + case CEE_CONV_OVF_U8_UN: + case CEE_CONV_OVF_I1: + case CEE_CONV_OVF_I2: + case CEE_CONV_OVF_I4: + case CEE_CONV_OVF_I8: + case CEE_CONV_OVF_U1: + case CEE_CONV_OVF_U2: + case CEE_CONV_OVF_U4: + case CEE_CONV_OVF_U8: + case CEE_CONV_U4: + case CEE_CONV_U8: + case CEE_CONV_U2: + case CEE_CONV_U1: + case CEE_CONV_I: + case CEE_CONV_OVF_I: + case CEE_CONV_OVF_U: + case CEE_CONV_U: + case CEE_BOX: + case CEE_LDELEM: + case CEE_STELEM: + case CEE_UNBOX_ANY: + case CEE_UNBOX: + case CEE_LDFTN: + case CEE_LDTOKEN: + case CEE_SIZEOF: + default: + result << op.getName(); + if (op.getArgsInfo() != InlineNone) { + result << '<'; + formatInstrArgs(op, inlineArg, &result, instrPtr-start); + result << '>'; + } + + _ASSERTE(op.getNumPop() >= 0); + if (op.getNumPop() > 0) + formatArgs(op.getNumPop(), &result); + + prec = 0x1000; + _ASSERTE(op.getNumPush() == 0 || op.getNumPush() == 1); + if (op.getNumPush() > 0) + goto DO_PUSH; + goto DO_STMT; + } + } + return(instrPtr); +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + + diff --git a/src/coreclr/utilcode/loaderheap.cpp b/src/coreclr/utilcode/loaderheap.cpp new file mode 100644 index 00000000000..8cfbba75659 --- /dev/null +++ b/src/coreclr/utilcode/loaderheap.cpp @@ -0,0 +1,2254 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" // Precompiled header key. +#include "loaderheap.h" +#include "ex.h" +#include "pedecoder.h" +#define DONOT_DEFINE_ETW_CALLBACK +#include "eventtracebase.h" + +#define LHF_EXECUTABLE 0x1 + +#ifndef DACCESS_COMPILE + +INDEBUG(DWORD UnlockedLoaderHeap::s_dwNumInstancesOfLoaderHeaps = 0;) + +#ifdef RANDOMIZE_ALLOC +#include <time.h> +static class Random +{ +public: + Random() { seed = (unsigned int)time(NULL); } + unsigned int Next() + { + return ((seed = seed * 214013L + 2531011L) >> 16) & 0x7fff; + } +private: + unsigned int seed; +} s_random; +#endif + +namespace +{ +#if !defined(SELF_NO_HOST) // ETW available only in the runtime + inline void EtwAllocRequest(UnlockedLoaderHeap * const pHeap, void* ptr, size_t dwSize) + { + FireEtwAllocRequest(pHeap, ptr, static_cast<unsigned int>(dwSize), 0, 0, GetClrInstanceId()); + } +#else +#define EtwAllocRequest(pHeap, ptr, dwSize) ((void)0) +#endif // SELF_NO_HOST +} + +// +// RangeLists are constructed so they can be searched from multiple +// threads without locking. They do require locking in order to +// be safely modified, though. +// + +RangeList::RangeList() +{ + WRAPPER_NO_CONTRACT; + + InitBlock(&m_starterBlock); + + m_firstEmptyBlock = &m_starterBlock; + m_firstEmptyRange = 0; +} + +RangeList::~RangeList() +{ + LIMITED_METHOD_CONTRACT; + + RangeListBlock *b = m_starterBlock.next; + + while (b != NULL) + { + RangeListBlock *bNext = b->next; + delete b; + b = bNext; + } +} + +void RangeList::InitBlock(RangeListBlock *b) +{ + LIMITED_METHOD_CONTRACT; + + Range *r = b->ranges; + Range *rEnd = r + RANGE_COUNT; + while (r < rEnd) + r++->id = NULL; + + b->next = NULL; +} + +BOOL RangeList::AddRangeWorker(const BYTE *start, const BYTE *end, void *id) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT(return FALSE;); + } + CONTRACTL_END + + _ASSERTE(id != NULL); + + RangeListBlock *b = m_firstEmptyBlock; + Range *r = b->ranges + m_firstEmptyRange; + Range *rEnd = b->ranges + RANGE_COUNT; + + while (TRUE) + { + while (r < rEnd) + { + if (r->id == NULL) + { + r->start = (TADDR)start; + r->end = (TADDR)end; + r->id = (TADDR)id; + + r++; + + m_firstEmptyBlock = b; + m_firstEmptyRange = r - b->ranges; + + return TRUE; + } + r++; + } + + // + // If there are no more blocks, allocate a + // new one. + // + + if (b->next == NULL) + { + RangeListBlock *newBlock = new (nothrow) RangeListBlock; + + if (newBlock == NULL) + { + m_firstEmptyBlock = b; + m_firstEmptyRange = r - b->ranges; + return FALSE; + } + + InitBlock(newBlock); + + newBlock->next = NULL; + b->next = newBlock; + } + + // + // Next block + // + + b = b->next; + r = b->ranges; + rEnd = r + RANGE_COUNT; + } +} + +void RangeList::RemoveRangesWorker(void *id, const BYTE* start, const BYTE* end) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + } + CONTRACTL_END + + RangeListBlock *b = &m_starterBlock; + Range *r = b->ranges; + Range *rEnd = r + RANGE_COUNT; + + // + // Find the first free element, & mark it. + // + + while (TRUE) + { + // + // Clear entries in this block. + // + + while (r < rEnd) + { + if (r->id != NULL) + { + if (start != NULL) + { + _ASSERTE(end != NULL); + + if (r->start >= (TADDR)start && r->start < (TADDR)end) + { + CONSISTENCY_CHECK_MSGF(r->end >= (TADDR)start && + r->end <= (TADDR)end, + ("r: %p start: %p end: %p", r, start, end)); + r->id = NULL; + } + } + else if (r->id == (TADDR)id) + { + r->id = NULL; + } + } + + r++; + } + + // + // If there are no more blocks, we're done. + // + + if (b->next == NULL) + { + m_firstEmptyRange = 0; + m_firstEmptyBlock = &m_starterBlock; + + return; + } + + // + // Next block. + // + + b = b->next; + r = b->ranges; + rEnd = r + RANGE_COUNT; + } +} + +#endif // #ifndef DACCESS_COMPILE + +BOOL RangeList::IsInRangeWorker(TADDR address, TADDR *pID /* = NULL */) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + FORBID_FAULT; + GC_NOTRIGGER; + } + CONTRACTL_END + + SUPPORTS_DAC; + + RangeListBlock* b = &m_starterBlock; + Range* r = b->ranges; + Range* rEnd = r + RANGE_COUNT; + + // + // Look for a matching element + // + + while (TRUE) + { + while (r < rEnd) + { + if (r->id != NULL && + address >= r->start + && address < r->end) + { + if (pID != NULL) + { + *pID = r->id; + } + return TRUE; + } + r++; + } + + // + // If there are no more blocks, we're done. + // + + if (b->next == NULL) + return FALSE; + + // + // Next block. + // + + b = b->next; + r = b->ranges; + rEnd = r + RANGE_COUNT; + } +} + +#ifdef DACCESS_COMPILE + +void +RangeList::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + WRAPPER_NO_CONTRACT; + + // This class is almost always contained in something + // else so there's no enumeration of 'this'. + + RangeListBlock* block = &m_starterBlock; + block->EnumMemoryRegions(flags); + + while (block->next.IsValid()) + { + block->next.EnumMem(); + block = block->next; + + block->EnumMemoryRegions(flags); + } +} + +void +RangeList::RangeListBlock::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + WRAPPER_NO_CONTRACT; + + Range* range; + TADDR BADFOOD; + TSIZE_T size; + int i; + + // The code below iterates each range stored in the RangeListBlock and + // dumps the memory region represented by each range. + // It is too much memory for a mini-dump, so we just bail out for mini-dumps. + if (flags == CLRDATA_ENUM_MEM_MINI || flags == CLRDATA_ENUM_MEM_TRIAGE) + { + return; + } + + BIT64_ONLY( BADFOOD = 0xbaadf00dbaadf00d; ); + NOT_BIT64( BADFOOD = 0xbaadf00d; ); + + for (i=0; i<RANGE_COUNT; i++) + { + range = &(this->ranges[i]); + if (range->id == NULL || range->start == NULL || range->end == NULL || + // just looking at the lower 4bytes is good enough on WIN64 + range->start == BADFOOD || range->end == BADFOOD) + { + break; + } + + size = range->end - range->start; + _ASSERTE( size < UINT32_MAX ); // ranges should be less than 4gig! + + // We can't be sure this entire range is mapped. For example, the code:StubLinkStubManager + // keeps track of all ranges in the code:BaseDomain::m_pStubHeap LoaderHeap, and + // code:LoaderHeap::UnlockedReservePages adds a range for the entire reserved region, instead + // of updating the RangeList when pages are committed. But in that case, the committed region of + // memory will be enumerated by the LoaderHeap anyway, so it's OK if this fails + DacEnumMemoryRegion(range->start, size, false); + } +} + +#endif // #ifdef DACCESS_COMPILE + + +//===================================================================================== +// In DEBUG builds only, we tag live blocks with the requested size and the type of +// allocation (AllocMem, AllocAlignedMem, AllocateOntoReservedMem). This is strictly +// to validate that those who call Backout* are passing in the right values. +// +// For simplicity, we'll use one LoaderHeapValidationTag structure for all types even +// though not all fields are applicable to all types. +//===================================================================================== +#ifdef _DEBUG +enum AllocationType +{ + kAllocMem = 1, + kFreedMem = 4, +}; + +struct LoaderHeapValidationTag +{ + size_t m_dwRequestedSize; // What the caller requested (not what was actually allocated) + AllocationType m_allocationType; // Which api allocated this block. + const char * m_szFile; // Who allocated me + int m_lineNum; // Who allocated me + +}; +#endif //_DEBUG + + + + + +//===================================================================================== +// These classes do detailed loaderheap sniffing to help in debugging heap crashes +//===================================================================================== +#ifdef _DEBUG + +// This structure logs the results of an Alloc or Free call. They are stored in reverse time order +// with UnlockedLoaderHeap::m_pEventList pointing to the most recent event. +struct LoaderHeapEvent +{ + LoaderHeapEvent *m_pNext; + AllocationType m_allocationType; //Which api was called + const char *m_szFile; //Caller Id + int m_lineNum; //Caller Id + const char *m_szAllocFile; //(BackoutEvents): Who allocated the block? + int m_allocLineNum; //(BackoutEvents): Who allocated the block? + void *m_pMem; //Starting address of block + size_t m_dwRequestedSize; //Requested size of block + size_t m_dwSize; //Actual size of block (including validation tags, padding, everything) + + + void Describe(SString *pSString) + { + CONTRACTL + { + INSTANCE_CHECK; + DISABLED(NOTHROW); + GC_NOTRIGGER; + } + CONTRACTL_END + + pSString->AppendASCII("\n"); + + { + StackSString buf; + if (m_allocationType == kFreedMem) + { + buf.Printf(" Freed at: %s (line %d)\n", m_szFile, m_lineNum); + buf.Printf(" (block originally allocated at %s (line %d)\n", m_szAllocFile, m_allocLineNum); + } + else + { + buf.Printf(" Allocated at: %s (line %d)\n", m_szFile, m_lineNum); + } + pSString->Append(buf); + } + + if (!QuietValidate()) + { + pSString->AppendASCII(" *** THIS BLOCK HAS BEEN CORRUPTED ***\n"); + } + + + + { + StackSString buf; + buf.Printf(" Type: "); + switch (m_allocationType) + { + case kAllocMem: + buf.AppendASCII("AllocMem()\n"); + break; + case kFreedMem: + buf.AppendASCII("Free\n"); + break; + default: + break; + } + pSString->Append(buf); + } + + + { + StackSString buf; + buf.Printf(" Start of block: 0x%p\n", m_pMem); + pSString->Append(buf); + } + + { + StackSString buf; + buf.Printf(" End of block: 0x%p\n", ((BYTE*)m_pMem) + m_dwSize - 1); + pSString->Append(buf); + } + + { + StackSString buf; + buf.Printf(" Requested size: %lu (0x%lx)\n", (ULONG)m_dwRequestedSize, (ULONG)m_dwRequestedSize); + pSString->Append(buf); + } + + { + StackSString buf; + buf.Printf(" Actual size: %lu (0x%lx)\n", (ULONG)m_dwSize, (ULONG)m_dwSize); + pSString->Append(buf); + } + + pSString->AppendASCII("\n"); + } + + + + BOOL QuietValidate(); + +}; + + +class LoaderHeapSniffer +{ + public: + static DWORD InitDebugFlags() + { + WRAPPER_NO_CONTRACT; + + DWORD dwDebugFlags = 0; + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_LoaderHeapCallTracing)) + { + dwDebugFlags |= UnlockedLoaderHeap::kCallTracing; + } + return dwDebugFlags; + } + + + static VOID RecordEvent(UnlockedLoaderHeap *pHeap, + AllocationType allocationType, + __in const char *szFile, + int lineNum, + __in const char *szAllocFile, + int allocLineNum, + void *pMem, + size_t dwRequestedSize, + size_t dwSize + ); + + static VOID ClearEvents(UnlockedLoaderHeap *pHeap) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + LoaderHeapEvent *pEvent = pHeap->m_pEventList; + while (pEvent) + { + LoaderHeapEvent *pNext = pEvent->m_pNext; + delete pEvent; + pEvent = pNext; + } + pHeap->m_pEventList = NULL; + } + + + static VOID CompactEvents(UnlockedLoaderHeap *pHeap) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + LoaderHeapEvent **ppEvent = &(pHeap->m_pEventList); + while (*ppEvent) + { + LoaderHeapEvent *pEvent = *ppEvent; + if (pEvent->m_allocationType != kFreedMem) + { + ppEvent = &(pEvent->m_pNext); + } + else + { + LoaderHeapEvent **ppWalk = &(pEvent->m_pNext); + BOOL fMatchFound = FALSE; + while (*ppWalk && !fMatchFound) + { + LoaderHeapEvent *pWalk = *ppWalk; + if (pWalk->m_allocationType != kFreedMem && + pWalk->m_pMem == pEvent->m_pMem && + pWalk->m_dwRequestedSize == pEvent->m_dwRequestedSize) + { + // Delete matched pairs + + // Order is important here - updating *ppWalk may change pEvent->m_pNext, and we want + // to get the updated value when we unlink pEvent. + *ppWalk = pWalk->m_pNext; + *ppEvent = pEvent->m_pNext; + + delete pEvent; + delete pWalk; + fMatchFound = TRUE; + } + else + { + ppWalk = &(pWalk->m_pNext); + } + } + + if (!fMatchFound) + { + ppEvent = &(pEvent->m_pNext); + } + } + } + } + static VOID PrintEvents(UnlockedLoaderHeap *pHeap) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + printf("\n------------- LoaderHeapEvents (in reverse time order!) --------------------"); + + LoaderHeapEvent *pEvent = pHeap->m_pEventList; + while (pEvent) + { + printf("\n"); + switch (pEvent->m_allocationType) + { + case kAllocMem: printf("AllocMem "); break; + case kFreedMem: printf("BackoutMem "); break; + + } + printf(" ptr = 0x%-8p", pEvent->m_pMem); + printf(" rqsize = 0x%-8x", (DWORD)pEvent->m_dwRequestedSize); + printf(" actsize = 0x%-8x", (DWORD)pEvent->m_dwSize); + printf(" (at %s@%d)", pEvent->m_szFile, pEvent->m_lineNum); + if (pEvent->m_allocationType == kFreedMem) + { + printf(" (original allocation at %s@%d)", pEvent->m_szAllocFile, pEvent->m_allocLineNum); + } + + pEvent = pEvent->m_pNext; + + } + printf("\n------------- End of LoaderHeapEvents --------------------------------------"); + printf("\n"); + + } + + + static VOID PitchSniffer(SString *pSString) + { + WRAPPER_NO_CONTRACT; + pSString->AppendASCII("\n" + "\nBecause call-tracing wasn't turned on, we couldn't provide details about who last owned the affected memory block. To get more precise diagnostics," + "\nset the following registry DWORD value:" + "\n" + "\n HKLM\\Software\\Microsoft\\.NETFramework\\LoaderHeapCallTracing = 1" + "\n" + "\nand rerun the scenario that crashed." + "\n" + "\n"); + } + + static LoaderHeapEvent *FindEvent(UnlockedLoaderHeap *pHeap, void *pAddr) + { + LIMITED_METHOD_CONTRACT; + + LoaderHeapEvent *pEvent = pHeap->m_pEventList; + while (pEvent) + { + if (pAddr >= pEvent->m_pMem && pAddr <= ( ((BYTE*)pEvent->m_pMem) + pEvent->m_dwSize - 1)) + { + return pEvent; + } + pEvent = pEvent->m_pNext; + } + return NULL; + + } + + + static void ValidateFreeList(UnlockedLoaderHeap *pHeap); + + static void WeGotAFaultNowWhat(UnlockedLoaderHeap *pHeap) + { + WRAPPER_NO_CONTRACT; + ValidateFreeList(pHeap); + + //If none of the above popped up an assert, pop up a generic one. + _ASSERTE(!("Unexpected AV inside LoaderHeap. The usual reason is that someone overwrote the end of a block or wrote into a freed block.\n")); + + } + +}; + + +#endif + + +#ifdef _DEBUG +#define LOADER_HEAP_BEGIN_TRAP_FAULT BOOL __faulted = FALSE; EX_TRY { +#define LOADER_HEAP_END_TRAP_FAULT } EX_CATCH {__faulted = TRUE; } EX_END_CATCH(SwallowAllExceptions) if (__faulted) LoaderHeapSniffer::WeGotAFaultNowWhat(pHeap); +#else +#define LOADER_HEAP_BEGIN_TRAP_FAULT +#define LOADER_HEAP_END_TRAP_FAULT +#endif + + +size_t AllocMem_TotalSize(size_t dwRequestedSize, UnlockedLoaderHeap *pHeap); + +//===================================================================================== +// This freelist implementation is a first cut and probably needs to be tuned. +// It should be tuned with the following assumptions: +// +// - Freeing LoaderHeap memory is done primarily for OOM backout. LoaderHeaps +// weren't designed to be general purpose heaps and shouldn't be used that way. +// +// - And hence, when memory is freed, expect it to be freed in large clumps and in a +// LIFO order. Since the LoaderHeap normally hands out memory with sequentially +// increasing addresses, blocks will typically be freed with sequentially decreasing +// addresses. +// +// The first cut of the freelist is a single-linked list of free blocks using first-fit. +// Assuming the above alloc-free pattern holds, the list will end up mostly sorted +// in increasing address order. When a block is freed, we'll attempt to coalesce it +// with the first block in the list. We could also choose to be more aggressive about +// sorting and coalescing but this should probably catch most cases in practice. +//===================================================================================== + +// When a block is freed, we place this structure on the first bytes of the freed block (Allocations +// are bumped in size if necessary to make sure there's room.) +struct LoaderHeapFreeBlock +{ + public: + LoaderHeapFreeBlock *m_pNext; // Pointer to next block on free list + size_t m_dwSize; // Total size of this block (including this header) +//! Try not to grow the size of this structure. It places a minimum size on LoaderHeap allocations. + + static void InsertFreeBlock(LoaderHeapFreeBlock **ppHead, void *pMem, size_t dwTotalSize, UnlockedLoaderHeap *pHeap) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + LOADER_HEAP_BEGIN_TRAP_FAULT + + // It's illegal to insert a free block that's smaller than the minimum sized allocation - + // it may stay stranded on the freelist forever. +#ifdef _DEBUG + if (!(dwTotalSize >= AllocMem_TotalSize(1, pHeap))) + { + LoaderHeapSniffer::ValidateFreeList(pHeap); + _ASSERTE(dwTotalSize >= AllocMem_TotalSize(1, pHeap)); + } + + if (!(0 == (dwTotalSize & ALLOC_ALIGN_CONSTANT))) + { + LoaderHeapSniffer::ValidateFreeList(pHeap); + _ASSERTE(0 == (dwTotalSize & ALLOC_ALIGN_CONSTANT)); + } +#endif + + INDEBUG(memset(pMem, 0xcc, dwTotalSize);) + LoaderHeapFreeBlock *pNewBlock = (LoaderHeapFreeBlock*)pMem; + pNewBlock->m_pNext = *ppHead; + pNewBlock->m_dwSize = dwTotalSize; + *ppHead = pNewBlock; + + MergeBlock(pNewBlock, pHeap); + + LOADER_HEAP_END_TRAP_FAULT + } + + + static void *AllocFromFreeList(LoaderHeapFreeBlock **ppHead, size_t dwSize, BOOL fRemoveFromFreeList, UnlockedLoaderHeap *pHeap) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + INCONTRACT(_ASSERTE_IMPL(!ARE_FAULTS_FORBIDDEN())); + + void *pResult = NULL; + LOADER_HEAP_BEGIN_TRAP_FAULT + + LoaderHeapFreeBlock **ppWalk = ppHead; + while (*ppWalk) + { + LoaderHeapFreeBlock *pCur = *ppWalk; + size_t dwCurSize = pCur->m_dwSize; + if (dwCurSize == dwSize) + { + pResult = pCur; + // Exact match. Hooray! + if (fRemoveFromFreeList) + { + *ppWalk = pCur->m_pNext; + } + break; + } + else if (dwCurSize > dwSize && (dwCurSize - dwSize) >= AllocMem_TotalSize(1, pHeap)) + { + // Partial match. Ok... + pResult = pCur; + if (fRemoveFromFreeList) + { + *ppWalk = pCur->m_pNext; + InsertFreeBlock(ppWalk, ((BYTE*)pCur) + dwSize, dwCurSize - dwSize, pHeap ); + } + break; + } + + // Either block is too small or splitting the block would leave a remainder that's smaller than + // the minimum block size. Onto next one. + + ppWalk = &( pCur->m_pNext ); + } + + if (pResult && fRemoveFromFreeList) + { + // Callers of loaderheap assume allocated memory is zero-inited so we must preserve this invariant! + memset(pResult, 0, dwSize); + } + LOADER_HEAP_END_TRAP_FAULT + return pResult; + + + + } + + + private: + // Try to merge pFreeBlock with its immediate successor. Return TRUE if a merge happened. FALSE if no merge happened. + static BOOL MergeBlock(LoaderHeapFreeBlock *pFreeBlock, UnlockedLoaderHeap *pHeap) + { + STATIC_CONTRACT_NOTHROW; + + BOOL result = FALSE; + + LOADER_HEAP_BEGIN_TRAP_FAULT + + LoaderHeapFreeBlock *pNextBlock = pFreeBlock->m_pNext; + size_t dwSize = pFreeBlock->m_dwSize; + + if (pNextBlock == NULL || ((BYTE*)pNextBlock) != (((BYTE*)pFreeBlock) + dwSize)) + { + result = FALSE; + } + else + { + size_t dwCombinedSize = dwSize + pNextBlock->m_dwSize; + LoaderHeapFreeBlock *pNextNextBlock = pNextBlock->m_pNext; + INDEBUG(memset(pFreeBlock, 0xcc, dwCombinedSize);) + pFreeBlock->m_pNext = pNextNextBlock; + pFreeBlock->m_dwSize = dwCombinedSize; + + result = TRUE; + } + + LOADER_HEAP_END_TRAP_FAULT + return result; + + } + +}; + + + + +//===================================================================================== +// These helpers encapsulate the actual layout of a block allocated by AllocMem +// and UnlockedAllocMem(): +// +// ==> Starting address is always pointer-aligned. +// +// - x bytes of user bytes (where "x" is the actual dwSize passed into AllocMem) +// +// - y bytes of "EE" (DEBUG-ONLY) (where "y" == LOADER_HEAP_DEBUG_BOUNDARY (normally 0)) +// - z bytes of pad (DEBUG-ONLY) (where "z" is just enough to pointer-align the following byte) +// - a bytes of tag (DEBUG-ONLY) (where "a" is sizeof(LoaderHeapValidationTag) +// +// - b bytes of pad (if total size after all this < sizeof(LoaderHeapFreeBlock), pad enough to make it the size of LoaderHeapFreeBlock) +// - c bytes of pad (where "c" is just enough to pointer-align the following byte) +// +// ==> Following address is always pointer-aligned +//===================================================================================== + +// Convert the requested size into the total # of bytes we'll actually allocate (including padding) +inline size_t AllocMem_TotalSize(size_t dwRequestedSize, UnlockedLoaderHeap *pHeap) +{ + LIMITED_METHOD_CONTRACT; + + size_t dwSize = dwRequestedSize; +#ifdef _DEBUG + dwSize += LOADER_HEAP_DEBUG_BOUNDARY; + dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT)); +#endif + + if (!pHeap->m_fExplicitControl) + { +#ifdef _DEBUG + dwSize += sizeof(LoaderHeapValidationTag); +#endif + if (dwSize < sizeof(LoaderHeapFreeBlock)) + { + dwSize = sizeof(LoaderHeapFreeBlock); + } + } + dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT)); + + return dwSize; +} + + +#ifdef _DEBUG +LoaderHeapValidationTag *AllocMem_GetTag(LPVOID pBlock, size_t dwRequestedSize) +{ + LIMITED_METHOD_CONTRACT; + + size_t dwSize = dwRequestedSize; + dwSize += LOADER_HEAP_DEBUG_BOUNDARY; + dwSize = ((dwSize + ALLOC_ALIGN_CONSTANT) & (~ALLOC_ALIGN_CONSTANT)); + return (LoaderHeapValidationTag *)( ((BYTE*)pBlock) + dwSize ); +} +#endif + + + + + +//===================================================================================== +// UnlockedLoaderHeap methods +//===================================================================================== + +#ifndef DACCESS_COMPILE + +UnlockedLoaderHeap::UnlockedLoaderHeap(DWORD dwReserveBlockSize, + DWORD dwCommitBlockSize, + const BYTE* dwReservedRegionAddress, + SIZE_T dwReservedRegionSize, + RangeList *pRangeList, + BOOL fMakeExecutable) +{ + CONTRACTL + { + CONSTRUCTOR_CHECK; + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END; + + m_pCurBlock = NULL; + m_pFirstBlock = NULL; + + m_dwReserveBlockSize = dwReserveBlockSize; + m_dwCommitBlockSize = dwCommitBlockSize; + + m_pPtrToEndOfCommittedRegion = NULL; + m_pEndReservedRegion = NULL; + m_pAllocPtr = NULL; + + m_pRangeList = pRangeList; + + // Round to VIRTUAL_ALLOC_RESERVE_GRANULARITY + m_dwTotalAlloc = 0; + +#ifdef _DEBUG + m_dwDebugWastedBytes = 0; + s_dwNumInstancesOfLoaderHeaps++; + m_pEventList = NULL; + m_dwDebugFlags = LoaderHeapSniffer::InitDebugFlags(); + m_fPermitStubsWithUnwindInfo = FALSE; + m_fStubUnwindInfoUnregistered= FALSE; +#endif + + m_Options = 0; + +#ifndef CROSSGEN_COMPILE + if (fMakeExecutable) + m_Options |= LHF_EXECUTABLE; +#endif // CROSSGEN_COMPILE + + m_pFirstFreeBlock = NULL; + + if (dwReservedRegionAddress != NULL && dwReservedRegionSize > 0) + { + m_reservedBlock.Init((void *)dwReservedRegionAddress, dwReservedRegionSize, FALSE); + } +} + +// ~LoaderHeap is not synchronised (obviously) +UnlockedLoaderHeap::~UnlockedLoaderHeap() +{ + CONTRACTL + { + DESTRUCTOR_CHECK; + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + _ASSERTE(!m_fPermitStubsWithUnwindInfo || m_fStubUnwindInfoUnregistered); + + if (m_pRangeList != NULL) + m_pRangeList->RemoveRanges((void *) this); + + LoaderHeapBlock *pSearch, *pNext; + + for (pSearch = m_pFirstBlock; pSearch; pSearch = pNext) + { + void * pVirtualAddress; + BOOL fReleaseMemory; + + pVirtualAddress = pSearch->pVirtualAddress; + fReleaseMemory = pSearch->m_fReleaseMemory; + pNext = pSearch->pNext; + + if (fReleaseMemory) + { + BOOL fSuccess; + fSuccess = ClrVirtualFree(pVirtualAddress, 0, MEM_RELEASE); + _ASSERTE(fSuccess); + } + } + + if (m_reservedBlock.m_fReleaseMemory) + { + BOOL fSuccess; + fSuccess = ClrVirtualFree(m_reservedBlock.pVirtualAddress, 0, MEM_RELEASE); + _ASSERTE(fSuccess); + } + + INDEBUG(s_dwNumInstancesOfLoaderHeaps --;) +} + +void UnlockedLoaderHeap::UnlockedSetReservedRegion(BYTE* dwReservedRegionAddress, SIZE_T dwReservedRegionSize, BOOL fReleaseMemory) +{ + WRAPPER_NO_CONTRACT; + _ASSERTE(m_reservedBlock.pVirtualAddress == NULL); + m_reservedBlock.Init((void *)dwReservedRegionAddress, dwReservedRegionSize, fReleaseMemory); +} + +#endif // #ifndef DACCESS_COMPILE + +#if 0 +// Disables access to all pages in the heap - useful when trying to determine if someone is +// accessing something in the low frequency heap +void UnlockedLoaderHeap::DebugGuardHeap() +{ + WRAPPER_NO_CONTRACT; + LoaderHeapBlock *pSearch, *pNext; + + for (pSearch = m_pFirstBlock; pSearch; pSearch = pNext) + { + void * pResult; + void * pVirtualAddress; + + pVirtualAddress = pSearch->pVirtualAddress; + pNext = pSearch->pNext; + + pResult = ClrVirtualAlloc(pVirtualAddress, pSearch->dwVirtualSize, MEM_COMMIT, PAGE_NOACCESS); + _ASSERTE(pResult != NULL); + } +} +#endif + +size_t UnlockedLoaderHeap::GetBytesAvailCommittedRegion() +{ + LIMITED_METHOD_CONTRACT; + + if (m_pAllocPtr < m_pPtrToEndOfCommittedRegion) + return (size_t)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr); + else + return 0; +} + +size_t UnlockedLoaderHeap::GetBytesAvailReservedRegion() +{ + LIMITED_METHOD_CONTRACT; + + if (m_pAllocPtr < m_pEndReservedRegion) + return (size_t)(m_pEndReservedRegion- m_pAllocPtr); + else + return 0; +} + +#define SETUP_NEW_BLOCK(pData, dwSizeToCommit, dwSizeToReserve) \ + m_pPtrToEndOfCommittedRegion = (BYTE *) (pData) + (dwSizeToCommit); \ + m_pAllocPtr = (BYTE *) (pData) + sizeof(LoaderHeapBlock); \ + m_pEndReservedRegion = (BYTE *) (pData) + (dwSizeToReserve); + + +#ifndef DACCESS_COMPILE + +BOOL UnlockedLoaderHeap::UnlockedReservePages(size_t dwSizeToCommit) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + INJECT_FAULT(return FALSE;); + } + CONTRACTL_END; + + size_t dwSizeToReserve; + + // Add sizeof(LoaderHeapBlock) + dwSizeToCommit += sizeof(LoaderHeapBlock); + + // Round to page size again + dwSizeToCommit = ALIGN_UP(dwSizeToCommit, GetOsPageSize()); + + void *pData = NULL; + BOOL fReleaseMemory = TRUE; + + // We were provided with a reserved memory block at instance creation time, so use it if it's big enough. + if (m_reservedBlock.pVirtualAddress != NULL && + m_reservedBlock.dwVirtualSize >= dwSizeToCommit) + { + // Get the info out of the block. + pData = m_reservedBlock.pVirtualAddress; + dwSizeToReserve = m_reservedBlock.dwVirtualSize; + fReleaseMemory = m_reservedBlock.m_fReleaseMemory; + + // Zero the block so this memory doesn't get used again. + m_reservedBlock.Init(NULL, 0, FALSE); + } + // The caller is asking us to allocate the memory + else + { + if (m_fExplicitControl) + { + return FALSE; + } + + // Figure out how much to reserve + dwSizeToReserve = max(dwSizeToCommit, m_dwReserveBlockSize); + + // Round to VIRTUAL_ALLOC_RESERVE_GRANULARITY + dwSizeToReserve = ALIGN_UP(dwSizeToReserve, VIRTUAL_ALLOC_RESERVE_GRANULARITY); + + _ASSERTE(dwSizeToCommit <= dwSizeToReserve); + + // + // Reserve pages + // + + pData = ClrVirtualAllocExecutable(dwSizeToReserve, MEM_RESERVE, PAGE_NOACCESS); + if (pData == NULL) + { + return FALSE; + } + } + + // When the user passes in the reserved memory, the commit size is 0 and is adjusted to be the sizeof(LoaderHeap). + // If for some reason this is not true then we just catch this via an assertion and the dev who changed code + // would have to add logic here to handle the case when committed mem is more than the reserved mem. One option + // could be to leak the users memory and reserve+commit a new block, Another option would be to fail the alloc mem + // and notify the user to provide more reserved mem. + _ASSERTE((dwSizeToCommit <= dwSizeToReserve) && "Loaderheap tried to commit more memory than reserved by user"); + + if (pData == NULL) + { + //_ASSERTE(!"Unable to ClrVirtualAlloc reserve in a loaderheap"); + return FALSE; + } + + // Commit first set of pages, since it will contain the LoaderHeapBlock + void *pTemp = ClrVirtualAlloc(pData, dwSizeToCommit, MEM_COMMIT, (m_Options & LHF_EXECUTABLE) ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + if (pTemp == NULL) + { + //_ASSERTE(!"Unable to ClrVirtualAlloc commit in a loaderheap"); + + // Unable to commit - release pages + if (fReleaseMemory) + ClrVirtualFree(pData, 0, MEM_RELEASE); + + return FALSE; + } + + // Record reserved range in range list, if one is specified + // Do this AFTER the commit - otherwise we'll have bogus ranges included. + if (m_pRangeList != NULL) + { + if (!m_pRangeList->AddRange((const BYTE *) pData, + ((const BYTE *) pData) + dwSizeToReserve, + (void *) this)) + { + + if (fReleaseMemory) + ClrVirtualFree(pData, 0, MEM_RELEASE); + + return FALSE; + } + } + + m_dwTotalAlloc += dwSizeToCommit; + + LoaderHeapBlock *pNewBlock; + +#if defined(HOST_OSX) && defined(HOST_ARM64) + // Always assume we are touching executable heap + auto jitWriteEnableHolder = PAL_JITWriteEnable(true); +#endif // defined(HOST_OSX) && defined(HOST_ARM64) + + pNewBlock = (LoaderHeapBlock *) pData; + + pNewBlock->dwVirtualSize = dwSizeToReserve; + pNewBlock->pVirtualAddress = pData; + pNewBlock->pNext = NULL; + pNewBlock->m_fReleaseMemory = fReleaseMemory; + + LoaderHeapBlock *pCurBlock = m_pCurBlock; + + // Add to linked list + while (pCurBlock != NULL && + pCurBlock->pNext != NULL) + pCurBlock = pCurBlock->pNext; + + if (pCurBlock != NULL) + m_pCurBlock->pNext = pNewBlock; + else + m_pFirstBlock = pNewBlock; + + // If we want to use the memory immediately... + m_pCurBlock = pNewBlock; + + SETUP_NEW_BLOCK(pData, dwSizeToCommit, dwSizeToReserve); + + return TRUE; +} + +// Get some more committed pages - either commit some more in the current reserved region, or, if it +// has run out, reserve another set of pages. +// Returns: FALSE if we can't get any more memory +// TRUE: We can/did get some more memory - check to see if it's sufficient for +// the caller's needs (see UnlockedAllocMem for example of use) +BOOL UnlockedLoaderHeap::GetMoreCommittedPages(size_t dwMinSize) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + INJECT_FAULT(return FALSE;); + } + CONTRACTL_END; + + // If we have memory we can use, what are you doing here! + _ASSERTE(dwMinSize > (SIZE_T)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr)); + + // Does this fit in the reserved region? + if (dwMinSize <= (size_t)(m_pEndReservedRegion - m_pAllocPtr)) + { + SIZE_T dwSizeToCommit = (m_pAllocPtr + dwMinSize) - m_pPtrToEndOfCommittedRegion; + + if (dwSizeToCommit < m_dwCommitBlockSize) + dwSizeToCommit = min((SIZE_T)(m_pEndReservedRegion - m_pPtrToEndOfCommittedRegion), (SIZE_T)m_dwCommitBlockSize); + + // Round to page size + dwSizeToCommit = ALIGN_UP(dwSizeToCommit, GetOsPageSize()); + + // Yes, so commit the desired number of reserved pages + void *pData = ClrVirtualAlloc(m_pPtrToEndOfCommittedRegion, dwSizeToCommit, MEM_COMMIT, (m_Options & LHF_EXECUTABLE) ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE); + if (pData == NULL) + return FALSE; + + m_dwTotalAlloc += dwSizeToCommit; + + m_pPtrToEndOfCommittedRegion += dwSizeToCommit; + return TRUE; + } + + // Need to allocate a new set of reserved pages + INDEBUG(m_dwDebugWastedBytes += (size_t)(m_pPtrToEndOfCommittedRegion - m_pAllocPtr);) + + // Note, there are unused reserved pages at end of current region -can't do much about that + // Provide dwMinSize here since UnlockedReservePages will round up the commit size again + // after adding in the size of the LoaderHeapBlock header. + return UnlockedReservePages(dwMinSize); +} + +void *UnlockedLoaderHeap::UnlockedAllocMem(size_t dwSize + COMMA_INDEBUG(__in const char *szFile) + COMMA_INDEBUG(int lineNum)) +{ + CONTRACT(void*) + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + INJECT_FAULT(ThrowOutOfMemory();); + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + void *pResult = UnlockedAllocMem_NoThrow( + dwSize COMMA_INDEBUG(szFile) COMMA_INDEBUG(lineNum)); + + if (pResult == NULL) + ThrowOutOfMemory(); + + RETURN pResult; +} + +#ifdef _DEBUG +static DWORD ShouldInjectFault() +{ + static DWORD fInjectFault = 99; + + if (fInjectFault == 99) + fInjectFault = (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_InjectFault) != 0); + return fInjectFault; +} + +#define SHOULD_INJECT_FAULT(return_statement) \ + do { \ + if (ShouldInjectFault() & 0x1) \ + { \ + char *a = new (nothrow) char; \ + if (a == NULL) \ + { \ + return_statement; \ + } \ + delete a; \ + } \ + } while (FALSE) + +#else + +#define SHOULD_INJECT_FAULT(return_statement) do { (void)((void *)0); } while (FALSE) + +#endif + +void *UnlockedLoaderHeap::UnlockedAllocMem_NoThrow(size_t dwSize + COMMA_INDEBUG(__in const char *szFile) + COMMA_INDEBUG(int lineNum)) +{ + CONTRACT(void*) + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT(CONTRACT_RETURN NULL;); + PRECONDITION(dwSize != 0); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + } + CONTRACT_END; + + SHOULD_INJECT_FAULT(RETURN NULL); + + INDEBUG(size_t dwRequestedSize = dwSize;) + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + +#ifdef RANDOMIZE_ALLOC + if (!m_fExplicitControl) + dwSize += s_random.Next() % 256; +#endif + + dwSize = AllocMem_TotalSize(dwSize, this); + +again: + + { + // Any memory available on the free list? + void *pData = LoaderHeapFreeBlock::AllocFromFreeList(&m_pFirstFreeBlock, dwSize, TRUE /*fRemoveFromFreeList*/, this); + if (!pData) + { + // Enough bytes available in committed region? + if (dwSize <= GetBytesAvailCommittedRegion()) + { + pData = m_pAllocPtr; + m_pAllocPtr += dwSize; + } + } + + if (pData) + { +#ifdef _DEBUG + + BYTE *pAllocatedBytes = (BYTE *)pData; +#if LOADER_HEAP_DEBUG_BOUNDARY > 0 + // Don't fill the memory we allocated - it is assumed to be zeroed - fill the memory after it + memset(pAllocatedBytes + dwRequestedSize, 0xEE, LOADER_HEAP_DEBUG_BOUNDARY); +#endif + if (dwRequestedSize > 0) + { + _ASSERTE_MSG(pAllocatedBytes[0] == 0 && memcmp(pAllocatedBytes, pAllocatedBytes + 1, dwRequestedSize - 1) == 0, + "LoaderHeap must return zero-initialized memory"); + } + + if (!m_fExplicitControl) + { + LoaderHeapValidationTag *pTag = AllocMem_GetTag(pData, dwRequestedSize); + pTag->m_allocationType = kAllocMem; + pTag->m_dwRequestedSize = dwRequestedSize; + pTag->m_szFile = szFile; + pTag->m_lineNum = lineNum; + } + + if (m_dwDebugFlags & kCallTracing) + { + LoaderHeapSniffer::RecordEvent(this, + kAllocMem, + szFile, + lineNum, + szFile, + lineNum, + pData, + dwRequestedSize, + dwSize + ); + } + +#endif + + EtwAllocRequest(this, pData, dwSize); + RETURN pData; + } + } + + // Need to commit some more pages in reserved region. + // If we run out of pages in the reserved region, ClrVirtualAlloc some more pages + if (GetMoreCommittedPages(dwSize)) + goto again; + + // We could not satisfy this allocation request + RETURN NULL; +} + +void UnlockedLoaderHeap::UnlockedBackoutMem(void *pMem, + size_t dwRequestedSize + COMMA_INDEBUG(__in const char *szFile) + COMMA_INDEBUG(int lineNum) + COMMA_INDEBUG(__in const char *szAllocFile) + COMMA_INDEBUG(int allocLineNum)) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END; + + // Because the primary use of this function is backout, we'll be nice and + // define Backout(NULL) be a legal NOP. + if (pMem == NULL) + { + return; + } + +#ifdef _DEBUG + { + DEBUG_ONLY_REGION(); + + LoaderHeapValidationTag *pTag = AllocMem_GetTag(pMem, dwRequestedSize); + + if (pTag->m_dwRequestedSize != dwRequestedSize || pTag->m_allocationType != kAllocMem) + { + CONTRACT_VIOLATION(ThrowsViolation|FaultViolation); // We're reporting a heap corruption - who cares about violations + + StackSString message; + message.Printf("HEAP VIOLATION: Invalid BackoutMem() call made at:\n" + "\n" + " File: %s\n" + " Line: %d\n" + "\n" + "Attempting to free block originally allocated at:\n" + "\n" + " File: %s\n" + " Line: %d\n" + "\n" + "The arguments to BackoutMem() were:\n" + "\n" + " Pointer: 0x%p\n" + " Size: %lu (0x%lx)\n" + "\n" + ,szFile + ,lineNum + ,szAllocFile + ,allocLineNum + ,pMem + ,(ULONG)dwRequestedSize + ,(ULONG)dwRequestedSize + ); + + + if (m_dwDebugFlags & kCallTracing) + { + message.AppendASCII("*** CALLTRACING ENABLED ***\n"); + LoaderHeapEvent *pEvent = LoaderHeapSniffer::FindEvent(this, pMem); + if (!pEvent) + { + message.AppendASCII("This pointer doesn't appear to have come from this LoaderHeap.\n"); + } + else + { + message.AppendASCII(pMem == pEvent->m_pMem ? "We have the following data about this pointer:" : "This pointer points to the middle of the following block:"); + pEvent->Describe(&message); + } + } + + if (pTag->m_dwRequestedSize != dwRequestedSize) + { + StackSString buf; + buf.Printf( + "Possible causes:\n" + "\n" + " - This pointer wasn't allocated from this loaderheap.\n" + " - This pointer was allocated by AllocAlignedMem and you didn't adjust for the \"extra.\"\n" + " - This pointer has already been freed.\n" + " - You passed in the wrong size. You must pass the exact same size you passed to AllocMem().\n" + " - Someone wrote past the end of this block making it appear as if one of the above were true.\n" + ); + message.Append(buf); + + } + else + { + message.AppendASCII("This memory block is completely unrecognizable.\n"); + } + + + if (!(m_dwDebugFlags & kCallTracing)) + { + LoaderHeapSniffer::PitchSniffer(&message); + } + + StackScratchBuffer scratch; + DbgAssertDialog(szFile, lineNum, (char*) message.GetANSI(scratch)); + + } + } +#endif + + size_t dwSize = AllocMem_TotalSize(dwRequestedSize, this); + +#ifdef _DEBUG + if (m_dwDebugFlags & kCallTracing) + { + DEBUG_ONLY_REGION(); + + LoaderHeapValidationTag *pTag = m_fExplicitControl ? NULL : AllocMem_GetTag(pMem, dwRequestedSize); + + + LoaderHeapSniffer::RecordEvent(this, + kFreedMem, + szFile, + lineNum, + (pTag && (allocLineNum < 0)) ? pTag->m_szFile : szAllocFile, + (pTag && (allocLineNum < 0)) ? pTag->m_lineNum : allocLineNum, + pMem, + dwRequestedSize, + dwSize + ); + } +#endif + + if (m_pAllocPtr == ( ((BYTE*)pMem) + dwSize )) + { + // Cool. This was the last block allocated. We can just undo the allocation instead + // of going to the freelist. + memset(pMem, 0x00, dwSize); // Fill freed region with 0 + m_pAllocPtr = (BYTE*)pMem; + } + else + { + LoaderHeapFreeBlock::InsertFreeBlock(&m_pFirstFreeBlock, pMem, dwSize, this); + } + +} + + +// Allocates memory aligned on power-of-2 boundary. +// +// The return value is a pointer that's guaranteed to be aligned. +// +// FREEING THIS BLOCK: Underneath, the actual block allocated may +// be larger and start at an address prior to the one you got back. +// It is this adjusted size and pointer that you pass to BackoutMem. +// The required adjustment is passed back thru the pdwExtra pointer. +// +// Here is how to properly backout the memory: +// +// size_t dwExtra; +// void *pMem = UnlockedAllocAlignedMem(dwRequestedSize, alignment, &dwExtra); +// _ASSERTE( 0 == (pMem & (alignment - 1)) ); +// UnlockedBackoutMem( ((BYTE*)pMem) - dExtra, dwRequestedSize + dwExtra ); +// +// If you use the AllocMemHolder or AllocMemTracker, all this is taken care of +// behind the scenes. +// +// +void *UnlockedLoaderHeap::UnlockedAllocAlignedMem_NoThrow(size_t dwRequestedSize, + size_t alignment, + size_t *pdwExtra + COMMA_INDEBUG(__in const char *szFile) + COMMA_INDEBUG(int lineNum)) +{ + CONTRACT(void*) + { + NOTHROW; + + // Macro syntax can't handle this INJECT_FAULT expression - we'll use a precondition instead + //INJECT_FAULT( do{ if (*pdwExtra) {*pdwExtra = 0} RETURN NULL; } while(0) ); + + PRECONDITION( alignment != 0 ); + PRECONDITION(0 == (alignment & (alignment - 1))); // require power of 2 + POSTCONDITION( (RETVAL) ? + (0 == ( ((UINT_PTR)(RETVAL)) & (alignment - 1))) : // If non-null, pointer must be aligned + (pdwExtra == NULL || 0 == *pdwExtra) // or else *pdwExtra must be set to 0 + ); + } + CONTRACT_END + + STATIC_CONTRACT_FAULT; + + // Set default value + if (pdwExtra) + { + *pdwExtra = 0; + } + + SHOULD_INJECT_FAULT(RETURN NULL); + + void *pResult; + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + + // Check for overflow if we align the allocation + if (dwRequestedSize + alignment < dwRequestedSize) + { + RETURN NULL; + } + + // We don't know how much "extra" we need to satisfy the alignment until we know + // which address will be handed out which in turn we don't know because we don't + // know whether the allocation will fit within the current reserved range. + // + // Thus, we'll request as much heap growth as is needed for the worst case (extra == alignment) + size_t dwRoomSize = AllocMem_TotalSize(dwRequestedSize + alignment, this); + if (dwRoomSize > GetBytesAvailCommittedRegion()) + { + if (!GetMoreCommittedPages(dwRoomSize)) + { + RETURN NULL; + } + } + + pResult = m_pAllocPtr; + + size_t extra = alignment - ((size_t)pResult & ((size_t)alignment - 1)); + +// On DEBUG, we force a non-zero extra so people don't forget to adjust for it on backout +#ifndef _DEBUG + if (extra == alignment) + { + extra = 0; + } +#endif + + S_SIZE_T cbAllocSize = S_SIZE_T( dwRequestedSize ) + S_SIZE_T( extra ); + if( cbAllocSize.IsOverflow() ) + { + RETURN NULL; + } + + size_t dwSize = AllocMem_TotalSize( cbAllocSize.Value(), this); + m_pAllocPtr += dwSize; + + + ((BYTE*&)pResult) += extra; + +#ifdef _DEBUG + BYTE *pAllocatedBytes = (BYTE *)pResult; +#if LOADER_HEAP_DEBUG_BOUNDARY > 0 + // Don't fill the entire memory - we assume it is all zeroed -just the memory after our alloc + memset(pAllocatedBytes + dwRequestedSize, 0xee, LOADER_HEAP_DEBUG_BOUNDARY); +#endif + + if (dwRequestedSize != 0) + { + _ASSERTE_MSG(pAllocatedBytes[0] == 0 && memcmp(pAllocatedBytes, pAllocatedBytes + 1, dwRequestedSize - 1) == 0, + "LoaderHeap must return zero-initialized memory"); + } + + if (m_dwDebugFlags & kCallTracing) + { + LoaderHeapSniffer::RecordEvent(this, + kAllocMem, + szFile, + lineNum, + szFile, + lineNum, + ((BYTE*)pResult) - extra, + dwRequestedSize + extra, + dwSize + ); + } + + EtwAllocRequest(this, pResult, dwSize); + + if (!m_fExplicitControl) + { + LoaderHeapValidationTag *pTag = AllocMem_GetTag(((BYTE*)pResult) - extra, dwRequestedSize + extra); + pTag->m_allocationType = kAllocMem; + pTag->m_dwRequestedSize = dwRequestedSize + extra; + pTag->m_szFile = szFile; + pTag->m_lineNum = lineNum; + } +#endif //_DEBUG + + if (pdwExtra) + { + *pdwExtra = extra; + } + + RETURN pResult; + +} + + + +void *UnlockedLoaderHeap::UnlockedAllocAlignedMem(size_t dwRequestedSize, + size_t dwAlignment, + size_t *pdwExtra + COMMA_INDEBUG(__in const char *szFile) + COMMA_INDEBUG(int lineNum)) +{ + CONTRACTL + { + THROWS; + INJECT_FAULT(ThrowOutOfMemory()); + } + CONTRACTL_END + + void *pResult = UnlockedAllocAlignedMem_NoThrow(dwRequestedSize, + dwAlignment, + pdwExtra + COMMA_INDEBUG(szFile) + COMMA_INDEBUG(lineNum)); + + if (!pResult) + { + ThrowOutOfMemory(); + } + + return pResult; + + +} + + + +void *UnlockedLoaderHeap::UnlockedAllocMemForCode_NoThrow(size_t dwHeaderSize, size_t dwCodeSize, DWORD dwCodeAlignment, size_t dwReserveForJumpStubs) +{ + CONTRACT(void*) + { + INSTANCE_CHECK; + NOTHROW; + INJECT_FAULT(CONTRACT_RETURN NULL;); + PRECONDITION(0 == (dwCodeAlignment & (dwCodeAlignment - 1))); // require power of 2 + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + } + CONTRACT_END; + + _ASSERTE(m_fExplicitControl); + + INCONTRACT(_ASSERTE(!ARE_FAULTS_FORBIDDEN())); + + // We don't know how much "extra" we need to satisfy the alignment until we know + // which address will be handed out which in turn we don't know because we don't + // know whether the allocation will fit within the current reserved range. + // + // Thus, we'll request as much heap growth as is needed for the worst case (we request an extra dwCodeAlignment - 1 bytes) + + S_SIZE_T cbAllocSize = S_SIZE_T(dwHeaderSize) + S_SIZE_T(dwCodeSize) + S_SIZE_T(dwCodeAlignment - 1) + S_SIZE_T(dwReserveForJumpStubs); + if( cbAllocSize.IsOverflow() ) + { + RETURN NULL; + } + + if (cbAllocSize.Value() > GetBytesAvailCommittedRegion()) + { + if (GetMoreCommittedPages(cbAllocSize.Value()) == FALSE) + { + RETURN NULL; + } + } + + BYTE *pResult = (BYTE *)ALIGN_UP(m_pAllocPtr + dwHeaderSize, dwCodeAlignment); + EtwAllocRequest(this, pResult, (pResult + dwCodeSize) - m_pAllocPtr); + m_pAllocPtr = pResult + dwCodeSize; + + RETURN pResult; +} + + +#endif // #ifndef DACCESS_COMPILE + +BOOL UnlockedLoaderHeap::IsExecutable() +{ + return (m_Options & LHF_EXECUTABLE); +} + +#ifdef DACCESS_COMPILE + +void UnlockedLoaderHeap::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + WRAPPER_NO_CONTRACT; + + DAC_ENUM_DTHIS(); + + PTR_LoaderHeapBlock block = m_pFirstBlock; + while (block.IsValid()) + { + // All we know is the virtual size of this block. We don't have any way to tell how + // much of this space was actually comitted, so don't expect that this will always + // succeed. + // @dbgtodo : Ideally we'd reduce the risk of corruption causing problems here. + // We could extend LoaderHeapBlock to track a commit size, + // but it seems wasteful (eg. makes each AppDomain objects 32 bytes larger on x64). + TADDR addr = dac_cast<TADDR>(block->pVirtualAddress); + TSIZE_T size = block->dwVirtualSize; + DacEnumMemoryRegion(addr, size, false); + + block = block->pNext; + } +} + +#endif // #ifdef DACCESS_COMPILE + + +void UnlockedLoaderHeap::EnumPageRegions (EnumPageRegionsCallback *pCallback, PTR_VOID pvArgs) +{ + WRAPPER_NO_CONTRACT; + + PTR_LoaderHeapBlock block = m_pFirstBlock; + while (block) + { + if ((*pCallback)(pvArgs, block->pVirtualAddress, block->dwVirtualSize)) + { + break; + } + + block = block->pNext; + } +} + + +#ifdef _DEBUG + +void UnlockedLoaderHeap::DumpFreeList() +{ + LIMITED_METHOD_CONTRACT; + if (m_pFirstFreeBlock == NULL) + { + printf("FREEDUMP: FreeList is empty\n"); + } + else + { + LoaderHeapFreeBlock *pBlock = m_pFirstFreeBlock; + while (pBlock != NULL) + { + size_t dwsize = pBlock->m_dwSize; + BOOL ccbad = FALSE; + BOOL sizeunaligned = FALSE; + BOOL sizesmall = FALSE; + + if ( 0 != (dwsize & ALLOC_ALIGN_CONSTANT) ) + { + sizeunaligned = TRUE; + } + if ( dwsize < sizeof(LoaderHeapBlock)) + { + sizesmall = TRUE; + } + + for (size_t i = sizeof(LoaderHeapFreeBlock); i < dwsize; i++) + { + if ( ((BYTE*)pBlock)[i] != 0xcc ) + { + ccbad = TRUE; + break; + } + } + + printf("Addr = %pxh, Size = %lxh", pBlock, ((ULONG)dwsize)); + if (ccbad) printf(" *** ERROR: NOT CC'd ***"); + if (sizeunaligned) printf(" *** ERROR: size not a multiple of ALLOC_ALIGN_CONSTANT ***"); + if (sizesmall) printf(" *** ERROR: size smaller than sizeof(LoaderHeapFreeBlock) ***"); + printf("\n"); + + pBlock = pBlock->m_pNext; + } + } +} + + +void UnlockedLoaderHeap::UnlockedClearEvents() +{ + WRAPPER_NO_CONTRACT; + LoaderHeapSniffer::ClearEvents(this); +} + +void UnlockedLoaderHeap::UnlockedCompactEvents() +{ + WRAPPER_NO_CONTRACT; + LoaderHeapSniffer::CompactEvents(this); +} + +void UnlockedLoaderHeap::UnlockedPrintEvents() +{ + WRAPPER_NO_CONTRACT; + LoaderHeapSniffer::PrintEvents(this); +} + + +#endif //_DEBUG + +//************************************************************************************ +// LOADERHEAP SNIFFER METHODS +//************************************************************************************ +#ifdef _DEBUG + +/*static*/ VOID LoaderHeapSniffer::RecordEvent(UnlockedLoaderHeap *pHeap, + AllocationType allocationType, + __in const char *szFile, + int lineNum, + __in const char *szAllocFile, + int allocLineNum, + void *pMem, + size_t dwRequestedSize, + size_t dwSize + ) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; //If we OOM in here, we just throw the event away. + } + CONTRACTL_END + + LoaderHeapEvent *pNewEvent; + { + { + FAULT_NOT_FATAL(); + pNewEvent = new (nothrow) LoaderHeapEvent; + } + if (!pNewEvent) + { + if (!(pHeap->m_dwDebugFlags & pHeap->kEncounteredOOM)) + { + pHeap->m_dwDebugFlags |= pHeap->kEncounteredOOM; + _ASSERTE(!"LOADERHEAPSNIFFER: Failed allocation of LoaderHeapEvent. Call tracing information will be incomplete."); + } + } + else + { + pNewEvent->m_allocationType = allocationType; + pNewEvent->m_szFile = szFile; + pNewEvent->m_lineNum = lineNum; + pNewEvent->m_szAllocFile = szAllocFile; + pNewEvent->m_allocLineNum = allocLineNum; + pNewEvent->m_pMem = pMem; + pNewEvent->m_dwRequestedSize = dwRequestedSize; + pNewEvent->m_dwSize = dwSize; + + pNewEvent->m_pNext = pHeap->m_pEventList; + pHeap->m_pEventList = pNewEvent; + } + } +} + + + +/*static*/ +void LoaderHeapSniffer::ValidateFreeList(UnlockedLoaderHeap *pHeap) +{ + CANNOT_HAVE_CONTRACT; + + // No contract. This routine is only called if we've AV'd inside the + // loaderheap. The system is already toast. We're trying to be a hero + // and produce the best diagnostic info we can. Last thing we need here + // is a secondary assert inside the contract stuff. + // + // This contract violation is permanent. + CONTRACT_VIOLATION(ThrowsViolation|FaultViolation|GCViolation|ModeViolation); // This violation won't be removed + + LoaderHeapFreeBlock *pFree = pHeap->m_pFirstFreeBlock; + LoaderHeapFreeBlock *pPrev = NULL; + + + void *pBadAddr = NULL; + LoaderHeapFreeBlock *pProbeThis = NULL; + const char *pExpected = NULL; + + while (pFree != NULL) + { + if ( 0 != ( ((ULONG_PTR)pFree) & ALLOC_ALIGN_CONSTANT )) + { + // Not aligned - can't be a valid freeblock. Most likely we followed a bad pointer from the previous block. + pProbeThis = pPrev; + pBadAddr = pPrev ? &(pPrev->m_pNext) : &(pHeap->m_pFirstFreeBlock); + pExpected = "a pointer to a valid LoaderHeapFreeBlock"; + break; + } + + size_t dwSize = pFree->m_dwSize; + if (dwSize < AllocMem_TotalSize(1, pHeap) || + 0 != (dwSize & ALLOC_ALIGN_CONSTANT)) + { + // Size is not a valid value (out of range or unaligned.) + pProbeThis = pFree; + pBadAddr = &(pFree->m_dwSize); + pExpected = "a valid block size (multiple of pointer size)"; + break; + } + + size_t i; + for (i = sizeof(LoaderHeapFreeBlock); i < dwSize; i++) + { + if ( ((BYTE*)pFree)[i] != 0xcc ) + { + pProbeThis = pFree; + pBadAddr = i + ((BYTE*)pFree); + pExpected = "0xcc (our fill value for free blocks)"; + break; + } + } + if (i != dwSize) + { + break; + } + + + + pPrev = pFree; + pFree = pFree->m_pNext; + } + + if (pFree == NULL) + { + return; // No problems found + } + + { + StackSString message; + + message.Printf("A loaderheap freelist has been corrupted. The bytes at or near address 0x%p appears to have been overwritten. We expected to see %s here.\n" + "\n" + " LoaderHeap: 0x%p\n" + " Suspect address at: 0x%p\n" + " Start of suspect freeblock: 0x%p\n" + "\n" + , pBadAddr + , pExpected + , pHeap + , pBadAddr + , pProbeThis + ); + + if (!(pHeap->m_dwDebugFlags & pHeap->kCallTracing)) + { + message.AppendASCII("\nThe usual reason is that someone wrote past the end of a block or wrote into a block after freeing it." + "\nOf course, the culprit is long gone so it's probably too late to debug this now. Try turning on call-tracing" + "\nand reproing. We can attempt to find out who last owned the surrounding pieces of memory." + "\n" + "\nTo turn on call-tracing, set the following registry DWORD value:" + "\n" + "\n HKLM\\Software\\Microsoft\\.NETFramework\\LoaderHeapCallTracing = 1" + "\n" + ); + + } + else + { + LoaderHeapEvent *pBadAddrEvent = FindEvent(pHeap, pBadAddr); + + message.AppendASCII("*** CALL TRACING ENABLED ***\n\n"); + + if (pBadAddrEvent) + { + message.AppendASCII("\nThe last known owner of the corrupted address was:\n"); + pBadAddrEvent->Describe(&message); + } + else + { + message.AppendASCII("\nNo known owner of last corrupted address.\n"); + } + + LoaderHeapEvent *pPrevEvent = FindEvent(pHeap, ((BYTE*)pProbeThis) - 1); + + int count = 3; + while (count-- && + pPrevEvent != NULL && + ( ((UINT_PTR)pProbeThis) - ((UINT_PTR)(pPrevEvent->m_pMem)) + pPrevEvent->m_dwSize ) < 1024) + { + message.AppendASCII("\nThis block is located close to the corruption point. "); + if (pPrevEvent->QuietValidate()) + { + message.AppendASCII("If it was overrun, it might have caused this."); + } + else + { + message.AppendASCII("*** CORRUPTION DETECTED IN THIS BLOCK ***"); + } + pPrevEvent->Describe(&message); + pPrevEvent = FindEvent(pHeap, ((BYTE*)(pPrevEvent->m_pMem)) - 1); + } + + + } + + StackScratchBuffer scratch; + DbgAssertDialog(__FILE__, __LINE__, (char*) message.GetANSI(scratch)); + + } + + + +} + + +BOOL LoaderHeapEvent::QuietValidate() +{ + WRAPPER_NO_CONTRACT; + + if (m_allocationType == kAllocMem) + { + LoaderHeapValidationTag *pTag = AllocMem_GetTag(m_pMem, m_dwRequestedSize); + return (pTag->m_allocationType == m_allocationType && pTag->m_dwRequestedSize == m_dwRequestedSize); + } + else + { + // We can't easily validate freed blocks. + return TRUE; + } +} + + +#endif //_DEBUG + +#ifndef DACCESS_COMPILE + +AllocMemTracker::AllocMemTracker() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END + + m_FirstBlock.m_pNext = NULL; + m_FirstBlock.m_nextFree = 0; + m_pFirstBlock = &m_FirstBlock; + + m_fReleased = FALSE; +} + +AllocMemTracker::~AllocMemTracker() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + if (!m_fReleased) + { + AllocMemTrackerBlock *pBlock = m_pFirstBlock; + while (pBlock) + { + // Do the loop in reverse - loaderheaps work best if + // we allocate and backout in LIFO order. + for (int i = pBlock->m_nextFree - 1; i >= 0; i--) + { + AllocMemTrackerNode *pNode = &(pBlock->m_Node[i]); + pNode->m_pHeap->RealBackoutMem(pNode->m_pMem + ,pNode->m_dwRequestedSize +#ifdef _DEBUG + ,__FILE__ + ,__LINE__ + ,pNode->m_szAllocFile + ,pNode->m_allocLineNum +#endif + ); + + } + + pBlock = pBlock->m_pNext; + } + } + + + AllocMemTrackerBlock *pBlock = m_pFirstBlock; + while (pBlock != &m_FirstBlock) + { + AllocMemTrackerBlock *pNext = pBlock->m_pNext; + delete pBlock; + pBlock = pNext; + } + + INDEBUG(memset(this, 0xcc, sizeof(*this));) +} + +void *AllocMemTracker::Track(TaggedMemAllocPtr tmap) +{ + CONTRACTL + { + THROWS; + INJECT_FAULT(ThrowOutOfMemory();); + } + CONTRACTL_END + + void *pv = Track_NoThrow(tmap); + if (!pv) + { + ThrowOutOfMemory(); + } + return pv; +} + +void *AllocMemTracker::Track_NoThrow(TaggedMemAllocPtr tmap) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return NULL;); + } + CONTRACTL_END + + // Calling Track() after calling SuppressRelease() is almost certainly a bug. You're supposed to call SuppressRelease() only after you're + // sure no subsequent failure will force you to backout the memory. + _ASSERTE( (!m_fReleased) && "You've already called SuppressRelease on this AllocMemTracker which implies you've passed your point of no failure. Why are you still doing allocations?"); + + + if (tmap.m_pMem != NULL) + { + AllocMemHolder<void*> holder(tmap); // If anything goes wrong in here, this holder will backout the allocation for the caller. + if (m_fReleased) + { + holder.SuppressRelease(); + } + AllocMemTrackerBlock *pBlock = m_pFirstBlock; + if (pBlock->m_nextFree == kAllocMemTrackerBlockSize) + { + AllocMemTrackerBlock *pNewBlock = new (nothrow) AllocMemTrackerBlock; + if (!pNewBlock) + { + return NULL; + } + + pNewBlock->m_pNext = m_pFirstBlock; + pNewBlock->m_nextFree = 0; + + m_pFirstBlock = pNewBlock; + + pBlock = pNewBlock; + } + + // From here on, we can't fail + pBlock->m_Node[pBlock->m_nextFree].m_pHeap = tmap.m_pHeap; + pBlock->m_Node[pBlock->m_nextFree].m_pMem = tmap.m_pMem; + pBlock->m_Node[pBlock->m_nextFree].m_dwRequestedSize = tmap.m_dwRequestedSize; +#ifdef _DEBUG + pBlock->m_Node[pBlock->m_nextFree].m_szAllocFile = tmap.m_szFile; + pBlock->m_Node[pBlock->m_nextFree].m_allocLineNum = tmap.m_lineNum; +#endif + + pBlock->m_nextFree++; + + holder.SuppressRelease(); + + + } + return (void *)tmap; +} + + +void AllocMemTracker::SuppressRelease() +{ + LIMITED_METHOD_CONTRACT; + + m_fReleased = TRUE; +} + +#endif //#ifndef DACCESS_COMPILE diff --git a/src/coreclr/utilcode/log.cpp b/src/coreclr/utilcode/log.cpp new file mode 100644 index 00000000000..37edc05b5cd --- /dev/null +++ b/src/coreclr/utilcode/log.cpp @@ -0,0 +1,420 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// Simple Logging Facility +// +#include "stdafx.h" + +// +// Define LOGGING by default in a checked build. If you want to log in a free +// build, define logging independent of _DEBUG here and each place you want +// to use it. +// +#ifdef _DEBUG +#define LOGGING +#endif + +#include "log.h" +#include "utilcode.h" + +#ifdef LOGGING + +#define DEFAULT_LOGFILE_NAME W("COMPLUS.LOG") + +#define LOG_ENABLE_FILE_LOGGING 0x0001 +#define LOG_ENABLE_FLUSH_FILE 0x0002 +#define LOG_ENABLE_CONSOLE_LOGGING 0x0004 +#define LOG_ENABLE_APPEND_FILE 0x0010 +#define LOG_ENABLE_DEBUGGER_LOGGING 0x0020 +#define LOG_ENABLE 0x0040 + + +static DWORD LogFlags = 0; +static CQuickWSTR szLogFileName; +static HANDLE LogFileHandle = INVALID_HANDLE_VALUE; +static volatile HANDLE LogFileMutex = 0; +static DWORD LogFacilityMask = LF_ALL; +static DWORD LogFacilityMask2 = 0; +static DWORD LogVMLevel = LL_INFO100; + // <TODO>@todo FIX should probably only display warnings and above by default</TODO> + + +VOID InitLogging() +{ + STATIC_CONTRACT_NOTHROW; + + // <TODO>FIX bit of a workaround for now, check for the log file in the + // registry and if there, turn on file logging VPM</TODO> + + LogFlags |= REGUTIL::GetConfigFlag_DontUse_(CLRConfig::INTERNAL_LogEnable, LOG_ENABLE); + LogFacilityMask = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogFacility, LogFacilityMask) | LF_ALWAYS; + LogVMLevel = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::EXTERNAL_LogLevel, LogVMLevel); + LogFlags |= REGUTIL::GetConfigFlag_DontUse_(CLRConfig::INTERNAL_LogFileAppend, LOG_ENABLE_APPEND_FILE); + LogFlags |= REGUTIL::GetConfigFlag_DontUse_(CLRConfig::INTERNAL_LogFlushFile, LOG_ENABLE_FLUSH_FILE); + LogFlags |= REGUTIL::GetConfigFlag_DontUse_(CLRConfig::INTERNAL_LogToDebugger, LOG_ENABLE_DEBUGGER_LOGGING); + LogFlags |= REGUTIL::GetConfigFlag_DontUse_(CLRConfig::INTERNAL_LogToFile, LOG_ENABLE_FILE_LOGGING); + LogFlags |= REGUTIL::GetConfigFlag_DontUse_(CLRConfig::INTERNAL_LogToConsole, LOG_ENABLE_CONSOLE_LOGGING); + + LogFacilityMask2 = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogFacility2, LogFacilityMask2) | LF_ALWAYS; + + if (SUCCEEDED(szLogFileName.ReSizeNoThrow(MAX_LONGPATH))) + { + wcscpy_s(szLogFileName.Ptr(), szLogFileName.Size(), DEFAULT_LOGFILE_NAME); + } + + LPWSTR fileName = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_LogFile); + if (fileName != 0) + { + if (SUCCEEDED(szLogFileName.ReSizeNoThrow(wcslen(fileName) + 32))) + { + wcscpy_s(szLogFileName.Ptr(), szLogFileName.Size(), fileName); + } + delete fileName; + } + + if (REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogWithPid, FALSE)) + { + WCHAR szPid[20]; + swprintf_s(szPid, COUNTOF(szPid), W(".%d"), GetCurrentProcessId()); + wcscat_s(szLogFileName.Ptr(), szLogFileName.Size(), szPid); + } + + if ((LogFlags & LOG_ENABLE) && + (LogFlags & LOG_ENABLE_FILE_LOGGING) && + (szLogFileName.Size() > 0) && + (LogFileHandle == INVALID_HANDLE_VALUE)) + { + DWORD fdwCreate = (LogFlags & LOG_ENABLE_APPEND_FILE) ? OPEN_ALWAYS : CREATE_ALWAYS; + LogFileHandle = WszCreateFile( + szLogFileName.Ptr(), + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + fdwCreate, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | ((LogFlags & LOG_ENABLE_FLUSH_FILE) ? FILE_FLAG_WRITE_THROUGH : 0), + NULL); + + // Some other logging may be going on, try again with another file name + if (LogFileHandle == INVALID_HANDLE_VALUE && wcslen(szLogFileName.Ptr()) + 3 <= szLogFileName.Size()) + { + WCHAR* ptr = szLogFileName.Ptr() + wcslen(szLogFileName.Ptr()) + 1; + ptr[-1] = W('.'); + ptr[0] = W('0'); + ptr[1] = 0; + + for(int i = 0; i < 10; i++) + { + LogFileHandle = WszCreateFile( + szLogFileName.Ptr(), + GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + fdwCreate, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | ((LogFlags & LOG_ENABLE_FLUSH_FILE) ? FILE_FLAG_WRITE_THROUGH : 0), + NULL); + if (LogFileHandle != INVALID_HANDLE_VALUE) + break; + *ptr = *ptr + 1; + } + if (LogFileHandle == INVALID_HANDLE_VALUE) { + int ret = WszWideCharToMultiByte(CP_ACP, 0, szLogFileName.Ptr(), -1, NULL, 0, NULL, NULL); + const char *msg = "Could not open log file, logging to "; + DWORD msgLen = (DWORD)strlen(msg); + CQuickSTR buff; + if (SUCCEEDED(buff.ReSizeNoThrow(ret + msgLen))) + { + strcpy_s(buff.Ptr(), buff.Size(), msg); + WszWideCharToMultiByte(CP_ACP, 0, szLogFileName.Ptr(), -1, buff.Ptr() + msgLen, ret, NULL, NULL); + msg = buff.Ptr(); + } + else + { + msg = "Could not open log file"; + } + DWORD written; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), msg, (DWORD)strlen(msg), &written, 0); + } + } + if (LogFileHandle == INVALID_HANDLE_VALUE) + UtilMessageBoxNonLocalized(NULL, W("Could not open log file"), W("CLR logging"), MB_OK | MB_ICONINFORMATION, FALSE, TRUE); + if (LogFileHandle != INVALID_HANDLE_VALUE) + { + if (LogFlags & LOG_ENABLE_APPEND_FILE) + SetFilePointer(LogFileHandle, 0, NULL, FILE_END); + LogSpew( LF_ALWAYS, FATALERROR, "************************ New Output *****************\n" ); + } + } +} + +VOID EnterLogLock() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + // We don't care about violating CANNOT_TAKE_LOCK in debug-only builds, and it's + // rather hard to care about this, as we LOG all over the place. + CONTRACT_VIOLATION(TakesLockViolation); + + if(LogFileMutex != 0) + { + DWORD status; + status = WaitForSingleObjectEx(LogFileMutex, INFINITE, FALSE); + _ASSERTE(WAIT_OBJECT_0 == status); + } +} + +VOID LeaveLogLock() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if(LogFileMutex != 0) + { + BOOL success; + success = ReleaseMutex(LogFileMutex); + _ASSERTE(success); + } +} + +static volatile bool bLoggingInitialized = false; +VOID InitializeLogging() +{ + STATIC_CONTRACT_NOTHROW; + + if (bLoggingInitialized) + return; + + HANDLE mutex = WszCreateMutex(NULL, FALSE, NULL); + _ASSERTE(mutex != 0); + if (InterlockedCompareExchangeT(&LogFileMutex, mutex, 0) != 0) + { + CloseHandle(mutex); + } + + EnterLogLock(); + if (!bLoggingInitialized) + { + InitLogging(); // You can call this in the debugger to fetch new settings + bLoggingInitialized = true; + } + LeaveLogLock(); +} + +VOID FlushLogging() { + STATIC_CONTRACT_NOTHROW; + + if (LogFileHandle != INVALID_HANDLE_VALUE) + { + // We must take the lock, as an OS deadlock can occur between + // FlushFileBuffers and WriteFile. + EnterLogLock(); + FlushFileBuffers( LogFileHandle ); + LeaveLogLock(); + } +} + +VOID ShutdownLogging() +{ + STATIC_CONTRACT_NOTHROW; + + if (LogFileHandle != INVALID_HANDLE_VALUE) { + LogSpew( LF_ALWAYS, FATALERROR, "Logging shutting down\n"); + CloseHandle( LogFileHandle ); + } + LogFileHandle = INVALID_HANDLE_VALUE; + bLoggingInitialized = false; +} + + +bool LoggingEnabled() +{ + STATIC_CONTRACT_LEAF; + + return ((LogFlags & LOG_ENABLE) != 0); +} + + +bool LoggingOn(DWORD facility, DWORD level) { + STATIC_CONTRACT_LEAF; + + _ASSERTE(LogFacilityMask & LF_ALWAYS); // LF_ALWAYS should always be enabled + + return((LogFlags & LOG_ENABLE) && + level <= LogVMLevel && + (facility & LogFacilityMask)); +} + +bool Logging2On(DWORD facility2, DWORD level) { + STATIC_CONTRACT_LEAF; + + _ASSERTE(LogFacilityMask2 & LF_ALWAYS); // LF_ALWAYS should always be enabled + + return((LogFlags & LOG_ENABLE) && + level <= LogVMLevel && + (facility2 & LogFacilityMask2)); +} + +// +// Don't use me directly, use the macros in log.h +// +VOID LogSpewValist(DWORD facility, DWORD level, const char *fmt, va_list args) +{ + SCAN_IGNORE_FAULT; // calls to new (nothrow) in logging code are OK + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!LoggingOn(facility, level)) + return; + + DEBUG_ONLY_FUNCTION; + + LogSpewAlwaysValist(fmt, args); +} + + +VOID LogSpew2Valist(DWORD facility2, DWORD level, const char *fmt, va_list args) +{ + SCAN_IGNORE_FAULT; // calls to new (nothrow) in logging code are OK + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!Logging2On(facility2, level)) + return; + + DEBUG_ONLY_FUNCTION; + + LogSpewAlwaysValist(fmt, args); +} + + +VOID LogSpewAlwaysValist(const char *fmt, va_list args) +{ + SCAN_IGNORE_FAULT; // calls to new (nothrow) in logging code are OK + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + DEBUG_ONLY_FUNCTION; + + // We can't do heap allocations at all. The current thread may have + // suspended another thread, and the suspended thread may be inside of the + // heap lock. + // + // (Some historical comments:) + // + // We must operate with a very small stack (in case we're logging durring + // a stack overflow) + // + // We're going to bypass our debug memory allocator and just allocate memory from + // the process heap. Why? Because our debug memory allocator will log out of memory + // conditions. If we're low on memory, and we try to log an out of memory condition, and we try + // and allocate memory again using the debug allocator, we could (and probably will) hit + // another low memory condition, try to log it, and we spin indefinately until we hit a stack overflow. + + const int BUFFERSIZE = 1000; + static char rgchBuffer[BUFFERSIZE]; + + EnterLogLock(); + + char * pBuffer = &rgchBuffer[0]; + DWORD buflen = 0; + DWORD written; + + static bool needsPrefix = true; + + if (needsPrefix) + buflen = sprintf_s(pBuffer, COUNTOF(rgchBuffer), "TID %04x: ", GetCurrentThreadId()); + + needsPrefix = (fmt[strlen(fmt)-1] == '\n'); + + int cCountWritten = _vsnprintf_s(&pBuffer[buflen], BUFFERSIZE-buflen, _TRUNCATE, fmt, args ); + pBuffer[BUFFERSIZE-1] = 0; + if (cCountWritten < 0) { + buflen = BUFFERSIZE - 1; + } else { + buflen += cCountWritten; + } + + // Its a little late for this, but at least you wont continue + // trashing your program... + _ASSERTE((buflen < (DWORD) BUFFERSIZE) && "Log text is too long!") ; + +#if !TARGET_UNIX + //convert NL's to CR NL to fixup notepad + const int BUFFERSIZE2 = BUFFERSIZE + 500; + char rgchBuffer2[BUFFERSIZE2]; + char * pBuffer2 = &rgchBuffer2[0]; + + char *d = pBuffer2; + for (char *p = pBuffer; *p != '\0'; p++) + { + if (*p == '\n') { + _ASSERTE(d < pBuffer2 + BUFFERSIZE2); + *(d++) = '\r'; + } + + _ASSERTE(d < pBuffer2 + BUFFERSIZE2); + *(d++) = *p; + } + *d = 0; + + buflen = (DWORD)(d - pBuffer2); + pBuffer = pBuffer2; +#endif // TARGET_UNIX + + if (LogFlags & LOG_ENABLE_FILE_LOGGING && LogFileHandle != INVALID_HANDLE_VALUE) + { + WriteFile(LogFileHandle, pBuffer, buflen, &written, NULL); + if (LogFlags & LOG_ENABLE_FLUSH_FILE) { + FlushFileBuffers( LogFileHandle ); + } + } + + if (LogFlags & LOG_ENABLE_CONSOLE_LOGGING) + { + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), pBuffer, buflen, &written, 0); + //<TODO>@TODO ...Unnecessary to flush console?</TODO> + if (LogFlags & LOG_ENABLE_FLUSH_FILE) + FlushFileBuffers( GetStdHandle(STD_OUTPUT_HANDLE) ); + } + + if (LogFlags & LOG_ENABLE_DEBUGGER_LOGGING) + { + OutputDebugStringA(pBuffer); + } + + LeaveLogLock(); +} + +VOID LogSpew(DWORD facility, DWORD level, const char *fmt, ... ) +{ + STATIC_CONTRACT_WRAPPER; + + va_list args; + va_start(args, fmt); + LogSpewValist (facility, level, fmt, args); + va_end(args); +} + +VOID LogSpew2(DWORD facility2, DWORD level, const char *fmt, ... ) +{ + STATIC_CONTRACT_WRAPPER; + + va_list args; + va_start(args, fmt); + LogSpew2Valist(facility2, level, fmt, args); + va_end(args); +} + +VOID LogSpewAlways (const char *fmt, ... ) +{ + STATIC_CONTRACT_WRAPPER; + + va_list args; + va_start(args, fmt); + LogSpewValist (LF_ALWAYS, LL_ALWAYS, fmt, args); + va_end(args); +} + +#endif // LOGGING + diff --git a/src/coreclr/utilcode/longfilepathwrappers.cpp b/src/coreclr/utilcode/longfilepathwrappers.cpp new file mode 100644 index 00000000000..3afc611c3b2 --- /dev/null +++ b/src/coreclr/utilcode/longfilepathwrappers.cpp @@ -0,0 +1,959 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" +#include "windows.h" +#include "longfilepathwrappers.h" +#include "sstring.h" +#include "ex.h" + +class LongFile +{ +private: +#ifdef HOST_WINDOWS + static const WCHAR* ExtendedPrefix; + static const WCHAR* DevicePathPrefix; + static const WCHAR* UNCPathPrefix; + static const WCHAR* UNCExtendedPathPrefix; + static const WCHAR VolumeSeparatorChar; + #define UNCPATHPREFIX W("\\\\") +#endif //HOST_WINDOWS + static const WCHAR DirectorySeparatorChar; + static const WCHAR AltDirectorySeparatorChar; +public: + static BOOL IsExtended(SString & path); + static BOOL IsUNCExtended(SString & path); + static BOOL ContainsDirectorySeparator(SString & path); + static BOOL IsDirectorySeparator(WCHAR c); + static BOOL IsPathNotFullyQualified(SString & path); + static BOOL IsDevice(SString & path); + + static HRESULT NormalizePath(SString& path); + +#ifdef HOST_WINDOWS + static void NormalizeDirectorySeparators(SString& path); +#endif +}; + +HMODULE +LoadLibraryExWrapper( + LPCWSTR lpLibFileName, + HANDLE hFile, + DWORD dwFlags + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + HMODULE ret = NULL; + DWORD lastError; + + EX_TRY + { + + LongPathString path(LongPathString::Literal, lpLibFileName); + + if (LongFile::IsPathNotFullyQualified(path) || SUCCEEDED(LongFile::NormalizePath(path))) + { +#ifdef HOST_WINDOWS + //Adding the assert to ensure relative paths which are not just filenames are not used for LoadLibrary Calls + _ASSERTE(!LongFile::IsPathNotFullyQualified(path) || !LongFile::ContainsDirectorySeparator(path)); + LongFile::NormalizeDirectorySeparators(path); +#endif //HOST_WINDOWS + + ret = LoadLibraryExW(path.GetUnicode(), hFile, dwFlags); + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if(ret == NULL) + { + SetLastError(lastError); + } + + return ret; +} + +HANDLE +CreateFileWrapper( + _In_ LPCWSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD lastError; + HANDLE ret = INVALID_HANDLE_VALUE; + + EX_TRY + { + LongPathString path(LongPathString::Literal, lpFileName); + + if (SUCCEEDED(LongFile::NormalizePath(path))) + { + ret = CreateFileW(path.GetUnicode(), + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == INVALID_HANDLE_VALUE) + { + SetLastError(lastError); + } + + return ret; +} + +DWORD +GetFileAttributesWrapper( + _In_ LPCWSTR lpFileName + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD ret = INVALID_FILE_ATTRIBUTES; + DWORD lastError; + + EX_TRY + { + LongPathString path(LongPathString::Literal, lpFileName); + + if (SUCCEEDED(LongFile::NormalizePath(path))) + { + ret = GetFileAttributesW( + path.GetUnicode() + ); + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == INVALID_FILE_ATTRIBUTES) + { + SetLastError(lastError); + } + + return ret; +} + +BOOL +GetFileAttributesExWrapper( + _In_ LPCWSTR lpFileName, + _In_ GET_FILEEX_INFO_LEVELS fInfoLevelId, + _Out_writes_bytes_(sizeof(WIN32_FILE_ATTRIBUTE_DATA)) LPVOID lpFileInformation + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + BOOL ret = FALSE; + DWORD lastError; + + EX_TRY + { + LongPathString path(LongPathString::Literal, lpFileName); + + if (SUCCEEDED(LongFile::NormalizePath(path))) + { + ret = GetFileAttributesExW( + path.GetUnicode(), + fInfoLevelId, + lpFileInformation + ); + + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == FALSE) + { + SetLastError(lastError); + } + + return ret; +} + +BOOL +DeleteFileWrapper( + _In_ LPCWSTR lpFileName + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + BOOL ret = FALSE; + DWORD lastError; + + EX_TRY + { + LongPathString path(LongPathString::Literal, lpFileName); + + if (SUCCEEDED(LongFile::NormalizePath(path))) + { + ret = DeleteFileW( + path.GetUnicode() + ); + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == FALSE) + { + SetLastError(lastError); + } + + return ret; +} + +BOOL +MoveFileExWrapper( + _In_ LPCWSTR lpExistingFileName, + _In_opt_ LPCWSTR lpNewFileName, + _In_ DWORD dwFlags + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + BOOL ret = FALSE; + DWORD lastError; + + EX_TRY + { + LongPathString Existingpath(LongPathString::Literal, lpExistingFileName); + LongPathString Newpath(LongPathString::Literal, lpNewFileName); + + if (SUCCEEDED(LongFile::NormalizePath(Existingpath)) && SUCCEEDED(LongFile::NormalizePath(Newpath))) + { + ret = MoveFileExW( + Existingpath.GetUnicode(), + Newpath.GetUnicode(), + dwFlags + ); + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == FALSE) + { + SetLastError(lastError); + } + + return ret; + +} + +DWORD +SearchPathWrapper( + _In_opt_ LPCWSTR lpPath, + _In_ LPCWSTR lpFileName, + _In_opt_ LPCWSTR lpExtension, + _In_ BOOL getPath, + SString& lpBuffer, + _Out_opt_ LPWSTR * lpFilePart + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD ret = 0; + DWORD lastError; + + EX_TRY + { + LongPathString Existingpath(LongPathString::Literal, lpPath); + + if (lpPath != NULL) + { + if (FAILED(LongFile::NormalizePath(Existingpath))) + { + ret = FALSE; + } + else + { + lpPath = Existingpath.GetUnicode(); + } + } + + if (!getPath) + { + ret = SearchPathW( + lpPath, + lpFileName, + lpExtension, + 0, + NULL, + NULL + ); + } + else + { + COUNT_T size = lpBuffer.GetUnicodeAllocation() + 1; + + ret = SearchPathW( + lpPath, + lpFileName, + lpExtension, + size, + lpBuffer.OpenUnicodeBuffer(size - 1), + lpFilePart + ); + + if (ret > size) + { + lpBuffer.CloseBuffer(); + ret = SearchPathW( + lpPath, + lpFileName, + lpExtension, + ret, + lpBuffer.OpenUnicodeBuffer(ret - 1), + lpFilePart + ); + } + + lpBuffer.CloseBuffer(ret); + + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if (ret == 0) + { + SetLastError(lastError); + } + + return ret; + +} + +DWORD +GetModuleFileNameWrapper( + _In_opt_ HMODULE hModule, + SString& buffer + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD ret = 0; + DWORD lastError; + + EX_TRY + { + COUNT_T size = buffer.GetUnicodeAllocation() + 1; + + ret = GetModuleFileNameW( + hModule, + buffer.OpenUnicodeBuffer(size - 1), + (DWORD)size + ); + + + while (ret == size ) + { + buffer.CloseBuffer(); + size = size * 2; + ret = GetModuleFileNameW( + hModule, + buffer.OpenUnicodeBuffer(size - 1), + (DWORD)size + ); + + } + + + lastError = GetLastError(); + buffer.CloseBuffer(ret); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if (ret == 0) + { + SetLastError(lastError); + } + + return ret; +} + +UINT WINAPI GetTempFileNameWrapper( + _In_ LPCTSTR lpPathName, + _In_ LPCTSTR lpPrefixString, + _In_ UINT uUnique, + SString& lpTempFileName + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + UINT ret = 0; + DWORD lastError; + + EX_TRY + { + //Change the behaviour in Redstone to retry + COUNT_T size = MAX_LONGPATH; + WCHAR* buffer = lpTempFileName.OpenUnicodeBuffer(size - 1); + ret = GetTempFileNameW( + lpPathName, + lpPrefixString, + uUnique, + buffer + ); + + lastError = GetLastError(); + size = (COUNT_T)wcslen(buffer); + lpTempFileName.CloseBuffer(size); + + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if (ret == 0) + { + SetLastError(lastError); + } + + return ret; +} +DWORD WINAPI GetTempPathWrapper( + SString& lpBuffer + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD ret = 0; + DWORD lastError; + + EX_TRY + { + //Change the behaviour in Redstone to retry + COUNT_T size = MAX_LONGPATH; + + ret = GetTempPathW( + size, + lpBuffer.OpenUnicodeBuffer(size - 1) + ); + + lastError = GetLastError(); + lpBuffer.CloseBuffer(ret); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if (ret == 0) + { + SetLastError(lastError); + } + + return ret; +} + +DWORD WINAPI GetCurrentDirectoryWrapper( + SString& lpBuffer + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD ret = 0; + DWORD lastError; + + EX_TRY + { + //Change the behaviour in Redstone to retry + COUNT_T size = MAX_LONGPATH; + + ret = GetCurrentDirectoryW( + size, + lpBuffer.OpenUnicodeBuffer(size - 1) + ); + + lastError = GetLastError(); + lpBuffer.CloseBuffer(ret); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if (ret == 0) + { + SetLastError(lastError); + } + + return ret; +} + +DWORD WINAPI GetEnvironmentVariableWrapper( + _In_opt_ LPCTSTR lpName, + _Out_opt_ SString& lpBuffer + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + DWORD ret = 0; + DWORD lastError; + + EX_TRY + { + + COUNT_T size = lpBuffer.GetUnicodeAllocation() + 1; + + ret = GetEnvironmentVariableW( + lpName, + lpBuffer.OpenUnicodeBuffer(size - 1), + size + ); + + // We loop round getting the length of the env var and then trying to copy + // the value into a the allocated buffer. Usually we'll go through this loop + // precisely once, but the caution is ncessary in case the variable mutates + // beneath us, as the environment variable can be modified by another thread + //between two calls to GetEnvironmentVariableW + + while (ret > size) + { + size = ret; + lpBuffer.CloseBuffer(); + ret = GetEnvironmentVariableW( + lpName, + lpBuffer.OpenUnicodeBuffer(size - 1), + size); + } + + lastError = GetLastError(); + lpBuffer.CloseBuffer(ret); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + else if (ret == 0) + { + SetLastError(lastError); + } + + return ret; +} + + +#ifdef HOST_WINDOWS + +BOOL +CopyFileExWrapper( + _In_ LPCWSTR lpExistingFileName, + _In_ LPCWSTR lpNewFileName, + _In_opt_ LPPROGRESS_ROUTINE lpProgressRoutine, + _In_opt_ LPVOID lpData, + _When_(pbCancel != NULL, _Pre_satisfies_(*pbCancel == FALSE)) + _Inout_opt_ LPBOOL pbCancel, + _In_ DWORD dwCopyFlags + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + BOOL ret = FALSE; + DWORD lastError; + + EX_TRY + { + LongPathString Existingpath(LongPathString::Literal, lpExistingFileName); + LongPathString Newpath(LongPathString::Literal, lpNewFileName); + + if (SUCCEEDED(LongFile::NormalizePath(Existingpath)) && SUCCEEDED(LongFile::NormalizePath(Newpath))) + { + ret = CopyFileExW( + Existingpath.GetUnicode(), + Newpath.GetUnicode(), + lpProgressRoutine, + lpData, + pbCancel, + dwCopyFlags + ); + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == FALSE) + { + SetLastError(lastError); + } + + return ret; +} + +HANDLE +FindFirstFileExWrapper( + _In_ LPCWSTR lpFileName, + _In_ FINDEX_INFO_LEVELS fInfoLevelId, + _Out_writes_bytes_(sizeof(WIN32_FIND_DATAW)) LPVOID lpFindFileData, + _In_ FINDEX_SEARCH_OPS fSearchOp, + _Reserved_ LPVOID lpSearchFilter, + _In_ DWORD dwAdditionalFlags + ) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + HANDLE ret = INVALID_HANDLE_VALUE; + DWORD lastError; + + EX_TRY + { + LongPathString path(LongPathString::Literal, lpFileName); + + if (SUCCEEDED(LongFile::NormalizePath(path))) + { + ret = FindFirstFileExW( + path.GetUnicode(), + fInfoLevelId, + lpFindFileData, + fSearchOp, + lpSearchFilter, + dwAdditionalFlags + ); + } + + lastError = GetLastError(); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK ) + { + SetLastError(hr); + } + else if(ret == INVALID_HANDLE_VALUE) + { + SetLastError(lastError); + } + + return ret; +} +#endif // HOST_WINDOWS + +//Implementation of LongFile Helpers +const WCHAR LongFile::DirectorySeparatorChar = W('\\'); +const WCHAR LongFile::AltDirectorySeparatorChar = W('/'); +#ifdef HOST_WINDOWS +const WCHAR LongFile::VolumeSeparatorChar = W(':'); +const WCHAR* LongFile::ExtendedPrefix = W("\\\\?\\"); +const WCHAR* LongFile::DevicePathPrefix = W("\\\\.\\"); +const WCHAR* LongFile::UNCExtendedPathPrefix = W("\\\\?\\UNC\\"); +const WCHAR* LongFile::UNCPathPrefix = UNCPATHPREFIX; + +void LongFile::NormalizeDirectorySeparators(SString& path) +{ + for(SString::Iterator i = path.Begin(); i < path.End(); ++i) + { + if (*i == AltDirectorySeparatorChar) + { + path.Replace(i, DirectorySeparatorChar); + } + } +} + +BOOL LongFile::IsExtended(SString & path) +{ + return path.BeginsWith(ExtendedPrefix); +} + +BOOL LongFile::IsUNCExtended(SString & path) +{ + + return path.BeginsWith(UNCExtendedPathPrefix); +} + +// Relative here means it could be relative to current directory on the relevant drive +// NOTE: Relative segments ( \..\) are not considered relative +// Returns true if the path specified is relative to the current drive or working directory. +// Returns false if the path is fixed to a specific drive or UNC path. This method does no +// validation of the path (URIs will be returned as relative as a result). +// Handles paths that use the alternate directory separator. It is a frequent mistake to +// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + +BOOL LongFile::IsPathNotFullyQualified(SString & path) +{ + if (path.GetCount() < 2) + { + return TRUE; // It isn't fixed, it must be relative. There is no way to specify a fixed path with one character (or less). + } + + if (IsDirectorySeparator(path[0])) + { + return !IsDirectorySeparator(path[1]); // There is no valid way to specify a relative path with two initial slashes + } + + return !((path.GetCount() >= 3) //The only way to specify a fixed path that doesn't begin with two slashes is the drive, colon, slash format- i.e. "C:\" + && (path[1] == VolumeSeparatorChar) + && IsDirectorySeparator(path[2])); +} + +BOOL LongFile::IsDevice(SString & path) +{ + return path.BeginsWith(DevicePathPrefix); +} + +// This function will normalize paths if the path length exceeds MAX_PATH +// The normalization examples are : +// C:\foo\<long>\bar => \\?\C:\foo\<long>\bar +// \\server\<long>\bar => \\?\UNC\server\<long>\bar +HRESULT LongFile::NormalizePath(SString & path) +{ + HRESULT hr = S_OK; + DWORD ret = 0; + COUNT_T prefixLen = 0; + if (path.IsEmpty()|| IsDevice(path) || IsExtended(path) || IsUNCExtended(path)) + return S_OK; + + if (!IsPathNotFullyQualified(path) && path.GetCount() < MAX_LONGPATH) + return S_OK; + + //Now the path will be normalized + + SString originalPath(path); + SString prefix(ExtendedPrefix); + prefixLen = prefix.GetCount(); + + if (path.BeginsWith(UNCPathPrefix)) + { + prefix.Set(UNCExtendedPathPrefix); + //In this case if path is \\server the extended syntax should be like \\?\UNC\server + //The below logic populates the path from prefixLen offset from the start. This ensures that first 2 characters are overwritten + // + prefixLen = prefix.GetCount() - (COUNT_T)wcslen(UNCPATHPREFIX); + _ASSERTE(prefixLen > 0 ); + } + + + COUNT_T size = path.GetUnicodeAllocation() + 1; + WCHAR* buffer = path.OpenUnicodeBuffer(size - 1); + + ret = GetFullPathNameW( + originalPath.GetUnicode(), + size - prefixLen, //memory avilable for path after reserving for prefix + (buffer + prefixLen), //reserve memory for prefix + NULL + ); + + if (ret == 0) + { + return E_FAIL; + } + + if (ret > size - prefixLen) + { + path.CloseBuffer(); + size = ret + prefixLen; + buffer = path.OpenUnicodeBuffer(size -1); + + ret = GetFullPathNameW( + originalPath.GetUnicode(), + ret, // memory required for the path + (buffer + prefixLen), //reserve memory for prefix + NULL + ); + + _ASSERTE(ret < size - prefixLen); + + if (ret == 0) + { + return E_FAIL; + } + } + + SString fullpath(SString::Literal,buffer + prefixLen); + + //Check if the resolved path is a UNC. By default we assume relative path to resolve to disk + if (fullpath.BeginsWith(UNCPathPrefix) && prefixLen != prefix.GetCount() - (COUNT_T)wcslen(UNCPATHPREFIX)) + { + + //Remove the leading '\\' from the UNC path to be replaced with UNCExtendedPathPrefix + fullpath.Replace(fullpath.Begin(), (COUNT_T)wcslen(UNCPATHPREFIX), UNCExtendedPathPrefix); + path.CloseBuffer(); + path.Set(fullpath); + } + else + { + //wcscpy_s always termintes with NULL, so we are saving the character that will be overwriiten + WCHAR temp = buffer[prefix.GetCount()]; + wcscpy_s(buffer, prefix.GetCount() + 1, prefix.GetUnicode()); + buffer[prefix.GetCount()] = temp; + path.CloseBuffer(ret + prefixLen); + } + + return S_OK; +} +#else +BOOL LongFile::IsExtended(SString & path) +{ + return FALSE; +} + +BOOL LongFile::IsUNCExtended(SString & path) +{ + return FALSE; +} + +BOOL LongFile::IsPathNotFullyQualified(SString & path) +{ + return TRUE; +} + +BOOL LongFile::IsDevice(SString & path) +{ + return FALSE; +} + +//Don't need to do anything For XPlat +HRESULT LongFile::NormalizePath(SString & path) +{ + return S_OK; +} +#endif //HOST_WINDOWS + +BOOL LongFile::ContainsDirectorySeparator(SString & path) +{ + return path.Find(path.Begin(), DirectorySeparatorChar) || path.Find(path.Begin(), AltDirectorySeparatorChar); +} + +BOOL LongFile::IsDirectorySeparator(WCHAR c) +{ + return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar; +} + + + diff --git a/src/coreclr/utilcode/makepath.cpp b/src/coreclr/utilcode/makepath.cpp new file mode 100644 index 00000000000..4bf8787242a --- /dev/null +++ b/src/coreclr/utilcode/makepath.cpp @@ -0,0 +1,212 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +/*** +*makepath.c - create path name from components +* + +* +*Purpose: +* To provide support for creation of full path names from components +* +*******************************************************************************/ +#include "stdafx.h" +#include "winwrap.h" +#include "utilcode.h" +#include "ex.h" + + +/*** +*void Makepath() - build path name from components +* +*Purpose: +* create a path name from its individual components +* +*Entry: +* CQuickWSTR &szPath - Buffer for constructed path +* WCHAR *drive - pointer to drive component, may or may not contain +* trailing ':' +* WCHAR *dir - pointer to subdirectory component, may or may not include +* leading and/or trailing '/' or '\' characters +* WCHAR *fname - pointer to file base name component +* WCHAR *ext - pointer to extension component, may or may not contain +* a leading '.'. +* +*Exit: +* path - pointer to constructed path name +* +*Exceptions: +* +*******************************************************************************/ + +void MakePath ( + __out CQuickWSTR &szPath, + __in LPCWSTR drive, + __in LPCWSTR dir, + __in LPCWSTR fname, + __in LPCWSTR ext + ) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END + + SIZE_T maxCount = 4 // Possible separators between components, plus null terminator + + (drive != nullptr ? 2 : 0) + + (dir != nullptr ? wcslen(dir) : 0) + + (fname != nullptr ? wcslen(fname) : 0) + + (ext != nullptr ? wcslen(ext) : 0); + LPWSTR path = szPath.AllocNoThrow(maxCount); + + const WCHAR *p; + DWORD count = 0; + + /* we assume that the arguments are in the following form (although we + * do not diagnose invalid arguments or illegal filenames (such as + * names longer than 8.3 or with illegal characters in them) + * + * drive: + * A ; or + * A: + * dir: + * \top\next\last\ ; or + * /top/next/last/ ; or + * either of the above forms with either/both the leading + * and trailing / or \ removed. Mixed use of '/' and '\' is + * also tolerated + * fname: + * any valid file name + * ext: + * any valid extension (none if empty or null ) + */ + + /* copy drive */ + + if (drive && *drive) { + *path++ = *drive; + *path++ = _T(':'); + count += 2; + } + + /* copy dir */ + + if ((p = dir)) { + while (*p) { + *path++ = *p++; + count++; + + _ASSERTE(count < maxCount); + } + + // suppress warning for the following line; this is safe but would require significant code + // delta for prefast to understand. +#ifdef _PREFAST_ + #pragma warning( suppress: 26001 ) +#endif + if (*(p-1) != _T('/') && *(p-1) != _T('\\')) { + *path++ = _T('\\'); + count++; + + _ASSERTE(count < maxCount); + } + } + + /* copy fname */ + + if ((p = fname)) { + while (*p) { + *path++ = *p++; + count++; + + _ASSERTE(count < maxCount); + } + } + + /* copy ext, including 0-terminator - check to see if a '.' needs + * to be inserted. + */ + + if ((p = ext)) { + if (*p && *p != _T('.')) { + *path++ = _T('.'); + count++; + + _ASSERTE(count < maxCount); + } + + while ((*path++ = *p++)) { + count++; + + _ASSERTE(count < maxCount); + } + } + else { + /* better add the 0-terminator */ + *path = _T('\0'); + } + + szPath.Shrink(count + 1); +} + + +// Returns the directory for clr module. So, if path was for "C:\Dir1\Dir2\Filename.DLL", +// then this would return "C:\Dir1\Dir2\" (note the trailing backslash).HRESULT GetClrModuleDirectory(SString& wszPath) +HRESULT GetClrModuleDirectory(SString& wszPath) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + DWORD dwRet = GetClrModulePathName(wszPath); + + if (dwRet == 0) + { // Some other error. + return HRESULT_FROM_GetLastError(); + } + + CopySystemDirectory(wszPath, wszPath); + return S_OK; +} + +// +// Returns path name from a file name. +// Example: For input "C:\Windows\System.dll" returns "C:\Windows\". +// Warning: The input file name string might be destroyed. +// +// Arguments: +// pPathString - [in] SString with file name +// +// pBuffer - [out] SString . +// +// Return Value: +// S_OK - Output buffer contains path name. +// other errors - If Sstring throws. +// +HRESULT CopySystemDirectory(const SString& pPathString, + SString& pbuffer) +{ + HRESULT hr = S_OK; + EX_TRY + { + pbuffer.Set(pPathString); + SString::Iterator iter = pbuffer.End(); + if (pbuffer.FindBack(iter,DIRECTORY_SEPARATOR_CHAR_W)) + { + iter++; + pbuffer.Truncate(iter); + } + else + { + hr = E_UNEXPECTED; + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} diff --git a/src/coreclr/utilcode/md5.cpp b/src/coreclr/utilcode/md5.cpp new file mode 100644 index 00000000000..7297114f21f --- /dev/null +++ b/src/coreclr/utilcode/md5.cpp @@ -0,0 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// md5.cpp +// + +// + +#include "stdafx.h" + +#include <stdlib.h> +#include "stdmacros.h" +#include "md5.h" +#include "contract.h" + +void MD5::Init(BOOL fConstructed) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + // These two fields are read only, and so initialization thereof can be + // omitted on the second and subsequent hashes using this same instance. + // + if (!fConstructed) + { + memset(m_padding, 0, 64); + m_padding[0]=0x80; + } + + m_cbitHashed = 0; + m_cbData = 0; + u.m_a = 0x67452301; // magic + u.m_b = 0xefcdab89; // ... constants + u.m_c = 0x98badcfe; // ... per + u.m_d = 0x10325476; // .. RFC1321 + } + + +void MD5::HashMore(const void* pvInput, ULONG cbInput) +// Hash the additional data into the state + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + const BYTE* pbInput = (const BYTE*)pvInput; + + m_cbitHashed += (((ULONGLONG)cbInput) <<3); + + ULONG cbRemaining = 64 - m_cbData; + if (cbInput < cbRemaining) + { + // It doesn't fill up the buffer, so just store it + memcpy(&m_data[m_cbData], pbInput, cbInput); + m_cbData += cbInput; + } + else + { + // It does fill up the buffer. Fill up all that it will take + memcpy(&m_data[m_cbData], pbInput, cbRemaining); + + // Hash the now-full buffer + MD5Transform(m_state, (ULONG*)&m_data[0]); +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:22019) // Suppress this OACR warning 22019: + // 'cbInput-=cbRemaining' may be greater than 'cbInput'. This can be caused by integer underflow. + // This could yield an incorrect loop index 'cbInput>=64' + // We only enter the else clause here if cbInput >= cbRemaining +#endif + cbInput -= cbRemaining; +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + pbInput += cbRemaining; + + // Hash the data in 64-byte runs, starting just after what we've copied + while (cbInput >= 64) + { + if (IS_ALIGNED(pbInput, sizeof(ULONG))) + { + MD5Transform(m_state, (ULONG*)pbInput); + } + else + { + ULONG inputCopy[64 / sizeof(ULONG)]; + memcpy(inputCopy, pbInput, sizeof(inputCopy)); + MD5Transform(m_state, inputCopy); + } + pbInput += 64; + cbInput -= 64; + } + + // Store the tail of the input into the buffer + memcpy(&m_data[0], pbInput, cbInput); + m_cbData = cbInput; + } + } + + +void MD5::GetHashValue(MD5HASHDATA* phash) +// Finalize the hash by appending the necessary padding and length count. Then +// return the final hash value. + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + union { + ULONGLONG cbitHashed; + BYTE rgb[8]; + }u; + + // Remember how many bits there were in the input data + u.cbitHashed = m_cbitHashed; + + // Calculate amount of padding needed. Enough so total byte count hashed is 56 mod 64 + ULONG cbPad = (m_cbData < 56 ? 56-m_cbData : 120-m_cbData); + + // Hash the padding + HashMore(&m_padding[0], cbPad); + + // Hash the (before padding) bit length + HashMore(&u.rgb[0], 8); + + // Return the hash value + memcpy(phash, &this->u.m_a, 16); + } + + + + + //////////////////////////////////////////////////////////////// + // + // ROTATE_LEFT should be a macro that updates its first operand + // with its present value rotated left by the amount of its + // second operand, which is always a constant. + // + // One way to portably do it would be + // + // #define ROL(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + // #define ROTATE_LEFT(x,n) (x) = ROL(x,n) + // + // but our compiler has an intrinsic! + + #if (defined(HOST_X86) || defined(HOST_ARM)) && defined(TARGET_UNIX) + #define ROL(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + #define ROTATE_LEFT(x,n) (x) = ROL(x,n) + #else + #define ROTATE_LEFT(x,n) (x) = _lrotl(x,n) + #endif + + //////////////////////////////////////////////////////////////// + // + // Constants used in each of the various rounds + + #define MD5_S11 7 + #define MD5_S12 12 + #define MD5_S13 17 + #define MD5_S14 22 + #define MD5_S21 5 + #define MD5_S22 9 + #define MD5_S23 14 + #define MD5_S24 20 + #define MD5_S31 4 + #define MD5_S32 11 + #define MD5_S33 16 + #define MD5_S34 23 + #define MD5_S41 6 + #define MD5_S42 10 + #define MD5_S43 15 + #define MD5_S44 21 + + //////////////////////////////////////////////////////////////// + // + // The core twiddle functions + +// #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) // the function per the standard + #define F(x, y, z) ((((z) ^ (y)) & (x)) ^ (z)) // an alternate encoding + +// #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) // the function per the standard + #define G(x, y, z) ((((x) ^ (y)) & (z)) ^ (y)) // an alternate encoding + + #define H(x, y, z) ((x) ^ (y) ^ (z)) + + #define I(x, y, z) ((y) ^ ((x) | (~(z)))) + + #define AC(ac) ((ULONG)(ac)) + + //////////////////////////////////////////////////////////////// + + #define FF(a, b, c, d, x, s, ac) { \ + (a) += F (b,c,d) + (x) + (AC(ac)); \ + ROTATE_LEFT (a, s); \ + (a) += (b); \ + } + + //////////////////////////////////////////////////////////////// + + #define GG(a, b, c, d, x, s, ac) { \ + (a) += G (b,c,d) + (x) + (AC(ac)); \ + ROTATE_LEFT (a, s); \ + (a) += (b); \ + } + + //////////////////////////////////////////////////////////////// + + #define HH(a, b, c, d, x, s, ac) { \ + (a) += H (b,c,d) + (x) + (AC(ac)); \ + ROTATE_LEFT (a, s); \ + (a) += (b); \ + } + + //////////////////////////////////////////////////////////////// + + #define II(a, b, c, d, x, s, ac) { \ + (a) += I (b,c,d) + (x) + (AC(ac)); \ + ROTATE_LEFT (a, s); \ + (a) += (b); \ + } + + void __stdcall MD5Transform(ULONG state[4], const ULONG* data) + { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + _ASSERTE(IS_ALIGNED(data, sizeof(ULONG))); + + ULONG a=state[0]; + ULONG b=state[1]; + ULONG c=state[2]; + ULONG d=state[3]; + + // Round 1 + FF (a, b, c, d, data[ 0], MD5_S11, 0xd76aa478); // 1 + FF (d, a, b, c, data[ 1], MD5_S12, 0xe8c7b756); // 2 + FF (c, d, a, b, data[ 2], MD5_S13, 0x242070db); // 3 + FF (b, c, d, a, data[ 3], MD5_S14, 0xc1bdceee); // 4 + FF (a, b, c, d, data[ 4], MD5_S11, 0xf57c0faf); // 5 + FF (d, a, b, c, data[ 5], MD5_S12, 0x4787c62a); // 6 + FF (c, d, a, b, data[ 6], MD5_S13, 0xa8304613); // 7 + FF (b, c, d, a, data[ 7], MD5_S14, 0xfd469501); // 8 + FF (a, b, c, d, data[ 8], MD5_S11, 0x698098d8); // 9 + FF (d, a, b, c, data[ 9], MD5_S12, 0x8b44f7af); // 10 + FF (c, d, a, b, data[10], MD5_S13, 0xffff5bb1); // 11 + FF (b, c, d, a, data[11], MD5_S14, 0x895cd7be); // 12 + FF (a, b, c, d, data[12], MD5_S11, 0x6b901122); // 13 + FF (d, a, b, c, data[13], MD5_S12, 0xfd987193); // 14 + FF (c, d, a, b, data[14], MD5_S13, 0xa679438e); // 15 + FF (b, c, d, a, data[15], MD5_S14, 0x49b40821); // 16 + + // Round 2 + GG (a, b, c, d, data[ 1], MD5_S21, 0xf61e2562); // 17 + GG (d, a, b, c, data[ 6], MD5_S22, 0xc040b340); // 18 + GG (c, d, a, b, data[11], MD5_S23, 0x265e5a51); // 19 + GG (b, c, d, a, data[ 0], MD5_S24, 0xe9b6c7aa); // 20 + GG (a, b, c, d, data[ 5], MD5_S21, 0xd62f105d); // 21 + GG (d, a, b, c, data[10], MD5_S22, 0x2441453); // 22 + GG (c, d, a, b, data[15], MD5_S23, 0xd8a1e681); // 23 + GG (b, c, d, a, data[ 4], MD5_S24, 0xe7d3fbc8); // 24 + GG (a, b, c, d, data[ 9], MD5_S21, 0x21e1cde6); // 25 + GG (d, a, b, c, data[14], MD5_S22, 0xc33707d6); // 26 + GG (c, d, a, b, data[ 3], MD5_S23, 0xf4d50d87); // 27 + GG (b, c, d, a, data[ 8], MD5_S24, 0x455a14ed); // 28 + GG (a, b, c, d, data[13], MD5_S21, 0xa9e3e905); // 29 + GG (d, a, b, c, data[ 2], MD5_S22, 0xfcefa3f8); // 30 + GG (c, d, a, b, data[ 7], MD5_S23, 0x676f02d9); // 31 + GG (b, c, d, a, data[12], MD5_S24, 0x8d2a4c8a); // 32 + + // Round 3 + HH (a, b, c, d, data[ 5], MD5_S31, 0xfffa3942); // 33 + HH (d, a, b, c, data[ 8], MD5_S32, 0x8771f681); // 34 + HH (c, d, a, b, data[11], MD5_S33, 0x6d9d6122); // 35 + HH (b, c, d, a, data[14], MD5_S34, 0xfde5380c); // 36 + HH (a, b, c, d, data[ 1], MD5_S31, 0xa4beea44); // 37 + HH (d, a, b, c, data[ 4], MD5_S32, 0x4bdecfa9); // 38 + HH (c, d, a, b, data[ 7], MD5_S33, 0xf6bb4b60); // 39 + HH (b, c, d, a, data[10], MD5_S34, 0xbebfbc70); // 40 + HH (a, b, c, d, data[13], MD5_S31, 0x289b7ec6); // 41 + HH (d, a, b, c, data[ 0], MD5_S32, 0xeaa127fa); // 42 + HH (c, d, a, b, data[ 3], MD5_S33, 0xd4ef3085); // 43 + HH (b, c, d, a, data[ 6], MD5_S34, 0x4881d05); // 44 + HH (a, b, c, d, data[ 9], MD5_S31, 0xd9d4d039); // 45 + HH (d, a, b, c, data[12], MD5_S32, 0xe6db99e5); // 46 + HH (c, d, a, b, data[15], MD5_S33, 0x1fa27cf8); // 47 + HH (b, c, d, a, data[ 2], MD5_S34, 0xc4ac5665); // 48 + + // Round 4 + II (a, b, c, d, data[ 0], MD5_S41, 0xf4292244); // 49 + II (d, a, b, c, data[ 7], MD5_S42, 0x432aff97); // 50 + II (c, d, a, b, data[14], MD5_S43, 0xab9423a7); // 51 + II (b, c, d, a, data[ 5], MD5_S44, 0xfc93a039); // 52 + II (a, b, c, d, data[12], MD5_S41, 0x655b59c3); // 53 + II (d, a, b, c, data[ 3], MD5_S42, 0x8f0ccc92); // 54 + II (c, d, a, b, data[10], MD5_S43, 0xffeff47d); // 55 + II (b, c, d, a, data[ 1], MD5_S44, 0x85845dd1); // 56 + II (a, b, c, d, data[ 8], MD5_S41, 0x6fa87e4f); // 57 + II (d, a, b, c, data[15], MD5_S42, 0xfe2ce6e0); // 58 + II (c, d, a, b, data[ 6], MD5_S43, 0xa3014314); // 59 + II (b, c, d, a, data[13], MD5_S44, 0x4e0811a1); // 60 + II (a, b, c, d, data[ 4], MD5_S41, 0xf7537e82); // 61 + II (d, a, b, c, data[11], MD5_S42, 0xbd3af235); // 62 + II (c, d, a, b, data[ 2], MD5_S43, 0x2ad7d2bb); // 63 + II (b, c, d, a, data[ 9], MD5_S44, 0xeb86d391); // 64 + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + } diff --git a/src/coreclr/utilcode/memorypool.cpp b/src/coreclr/utilcode/memorypool.cpp new file mode 100644 index 00000000000..ec2dcd24c85 --- /dev/null +++ b/src/coreclr/utilcode/memorypool.cpp @@ -0,0 +1,316 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" + +#include "memorypool.h" +#include "ex.h" + +size_t MemoryPool::GetSize() +{ + LIMITED_METHOD_CONTRACT; + size_t retval=0; + + Block *block = m_blocks; + while (block != NULL) + { + retval+=(BYTE*)block->elementsEnd-(BYTE*)block->elements; + block = block->next; + } + return retval; +} + +#ifndef DACCESS_COMPILE + +BOOL MemoryPool::AddBlock(SIZE_T elementCount) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT(return FALSE;); + } CONTRACTL_END; + + // + // Check for arithmetic overflow + // + S_SIZE_T cbBlockSize = S_SIZE_T(elementCount) * S_SIZE_T(m_elementSize); + S_SIZE_T cbAllocSize = S_SIZE_T(sizeof(Block)) + cbBlockSize; + if (cbBlockSize.IsOverflow() || cbAllocSize.IsOverflow()) + return FALSE; + + // + // Allocate the new block. + // + + Block *block = (Block *) new (nothrow) BYTE [cbAllocSize.Value()]; + + if (block == NULL) + return FALSE; + + // + // Chain all elements together for the free list + // + + _ASSERTE(m_freeList == NULL); + Element **prev = &m_freeList; + + Element *e = block->elements; + Element *eEnd = (Element *) ((BYTE*) block->elements + elementCount*m_elementSize); + while (e < eEnd) + { + *prev = e; + prev = &e->next; +#if _DEBUG + DeadBeef(e); +#endif + e = (Element*) ((BYTE*)e + m_elementSize); + } + + *prev = NULL; + + // + // Initialize the other block fields & link the block into the block list + // + + block->elementsEnd = e; + block->next = m_blocks; + m_blocks = block; + + return TRUE; +} + +void MemoryPool::DeadBeef(Element *element) +{ +#if _DEBUG + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + } CONTRACTL_END; + + int *i = &element->deadBeef; + int *iEnd = (int*) ((BYTE*)element + m_elementSize); + while (i < iEnd) + *i++ = (int) 0xdeadbeef; +#else + LIMITED_METHOD_CONTRACT; +#endif +} + +MemoryPool::MemoryPool(SIZE_T elementSize, SIZE_T initGrowth, SIZE_T initCount) + : m_elementSize(elementSize), + m_growCount(initGrowth), + m_blocks(NULL), + m_freeList(NULL) +{ + CONTRACTL { + if (initCount) THROWS; else NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + _ASSERTE(elementSize >= sizeof(Element)); + _ASSERTE((elementSize & ((sizeof(PVOID)-1))) == 0); + + if (initCount > 0) + AddBlock(initCount); +} + +MemoryPool::~MemoryPool() +{ + LIMITED_METHOD_CONTRACT; + Block *block = m_blocks; + while (block != NULL) + { + Block *next = block->next; + delete [] (BYTE*) block; + block = next; + } +} + +BOOL MemoryPool::IsElement(void *element) +{ + LIMITED_METHOD_CONTRACT; + + Block *block = m_blocks; + while (block != NULL) + { + if (element >= block->elements && + element < block->elementsEnd ) + { + return ((BYTE *)element - (BYTE*)block->elements) % m_elementSize == 0; + } + block = block->next; + } + + return FALSE; +} + +BOOL MemoryPool::IsAllocatedElement(void *element) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + } CONTRACTL_END; + + if (!IsElement(element)) + return FALSE; + + // + // Now, make sure the element isn't + // in the free list. + // + +#if _DEBUG + // + // In a debug build, all objects on the free list + // will be marked with deadbeef. This means that + // if the object is not deadbeef, it's not on the + // free list. + // + // This check will give us decent performance in + // a debug build for FreeElement, since we + // always expect to return TRUE in that case. + // + + if (((Element*)element)->deadBeef != (int) 0xdeadBeef) + return TRUE; +#endif + + Element *f = m_freeList; + while (f != NULL) + { + if (f == element) + return FALSE; + f = f->next; + } + +#if _DEBUG + // + // We should never get here in a debug build, because + // all free elements should be deadbeefed. + // + _ASSERTE(!"Unreachable"); +#endif + + return TRUE; +} + +void *MemoryPool::AllocateElement() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + } CONTRACTL_END; + + void *element = AllocateElementNoThrow(); + if (element == NULL) + ThrowOutOfMemory(); + + return element; +} + +void *MemoryPool::AllocateElementNoThrow() +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + INJECT_FAULT( return FALSE; ); + } CONTRACTL_END; + + void *element = m_freeList; + + if (element == NULL) + { + if (!AddBlock(m_growCount)) + return NULL; + + m_growCount *= 2; + element = m_freeList; + } + + // if we come there means that addblock succeeded and m_freelist isn't null anymore + PREFIX_ASSUME(m_freeList!= NULL); + m_freeList = m_freeList->next; + + return element; +} + +void MemoryPool::FreeElement(void *element) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + } CONTRACTL_END; + +#if _DEBUG // don't want to do this assert in a non-debug build; it is expensive + _ASSERTE(IsAllocatedElement(element)); +#endif + + Element *e = (Element *) element; + +#if _DEBUG + DeadBeef(e); +#endif + + e->next = m_freeList; + m_freeList = e; +} + +void MemoryPool::FreeAllElements() +{ + LIMITED_METHOD_CONTRACT; + + Block *block = m_blocks; + while (block != NULL) + { + Block *next = block->next; + delete [] block; + block = next; + } + + m_freeList = NULL; + m_blocks = NULL; +} + + + +MemoryPool::Iterator::Iterator(MemoryPool *pool) +{ + LIMITED_METHOD_CONTRACT; + + // + // Warning! This only works if you haven't freed + // any elements. + // + + m_next = pool->m_blocks; + m_e = NULL; + m_eEnd = NULL; + m_end = (BYTE*) pool->m_freeList; + m_size = pool->m_elementSize; +} + +BOOL MemoryPool::Iterator::Next() +{ + LIMITED_METHOD_CONTRACT; + + if (m_e == m_eEnd + || (m_e == m_end && m_end != NULL)) + { + if (m_next == NULL) + return FALSE; + m_e = (BYTE*) m_next->elements; + m_eEnd = (BYTE*) m_next->elementsEnd; + m_next = m_next->next; + if (m_e == m_end) + return FALSE; + } + + m_e += m_size; + + return TRUE; +} + +#endif diff --git a/src/coreclr/utilcode/namespaceutil.cpp b/src/coreclr/utilcode/namespaceutil.cpp new file mode 100644 index 00000000000..d930c6f86d2 --- /dev/null +++ b/src/coreclr/utilcode/namespaceutil.cpp @@ -0,0 +1,678 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// NamespaceUtil.cpp +// + +// +// Helpers for converting namespace separators. +// +//***************************************************************************** +#include "stdafx.h" +#include "corhdr.h" +#include "corhlpr.h" +#include "sstring.h" +#include "utilcode.h" + +#ifndef _ASSERTE +#define _ASSERTE(foo) +#endif + +#include "nsutilpriv.h" + + +//***************************************************************************** +// Determine how many chars large a fully qualified name would be given the +// two parts of the name. The return value includes room for every character +// in both names, as well as room for the separator and a final terminator. +//***************************************************************************** +int ns::GetFullLength( // Number of chars in full name. + const WCHAR *szNameSpace, // Namspace for value. + const WCHAR *szName) // Name of value. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + int iLen = 1; // Null terminator. + if (szNameSpace) + iLen += (int)wcslen(szNameSpace); + if (szName) + iLen += (int)wcslen(szName); + if (szNameSpace && *szNameSpace && szName && *szName) + ++iLen; + return iLen; +} //int ns::GetFullLength() + +int ns::GetFullLength( // Number of chars in full name. + LPCUTF8 szNameSpace, // Namspace for value. + LPCUTF8 szName) // Name of value. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + + int iLen = 1; + if (szNameSpace) + iLen += (int)strlen(szNameSpace); + if (szName) + iLen += (int)strlen(szName); + if (szNameSpace && *szNameSpace && szName && *szName) + ++iLen; + return iLen; +} //int ns::GetFullLength() + + +//***************************************************************************** +// Scan the string from the rear looking for the first valid separator. If +// found, return a pointer to it. Else return null. This code is smart enough +// to skip over special sequences, such as: +// a.b..ctor +// ^ +// | +// The ".ctor" is considered one token. +//***************************************************************************** +WCHAR *ns::FindSep( // Pointer to separator or null. + const WCHAR *szPath) // The path to look in. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + _ASSERTE(szPath); + WCHAR *ptr = (WCHAR*)wcsrchr(szPath, NAMESPACE_SEPARATOR_WCHAR); + if((ptr == NULL) || (ptr == szPath)) return NULL; + if(*(ptr - 1) == NAMESPACE_SEPARATOR_WCHAR) // here ptr is at least szPath+1 + --ptr; + return ptr; +} //WCHAR *ns::FindSep() + +//<TODO>@todo: this isn't dbcs safe if this were ansi, but this is utf8. Still an issue?</TODO> +LPUTF8 ns::FindSep( // Pointer to separator or null. + LPCUTF8 szPath) // The path to look in. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC; + + _ASSERTE(szPath); + LPUTF8 ptr = const_cast<LPUTF8>(strrchr(szPath, NAMESPACE_SEPARATOR_CHAR)); + if((ptr == NULL) || (ptr == szPath)) return NULL; + if(*(ptr - 1) == NAMESPACE_SEPARATOR_CHAR) // here ptr is at least szPath+1 + --ptr; + return ptr; +} //LPUTF8 ns::FindSep() + + + +//***************************************************************************** +// Take a path and find the last separator (nsFindSep), and then replace the +// separator with a '\0' and return a pointer to the name. So for example: +// a.b.c +// becomes two strings "a.b" and "c" and the return value points to "c". +//***************************************************************************** +WCHAR *ns::SplitInline( // Pointer to name portion. + __inout __inout_z WCHAR *szPath) // The path to split. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + WCHAR *ptr = ns::FindSep(szPath); + if (ptr) + { + *ptr = 0; + ++ptr; + } + return ptr; +} // WCHAR *ns::SplitInline() + +LPUTF8 ns::SplitInline( // Pointer to name portion. + __inout __inout_z LPUTF8 szPath) // The path to split. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + LPUTF8 ptr = ns::FindSep(szPath); + if (ptr) + { + *ptr = 0; + ++ptr; + } + return ptr; +} // LPUTF8 ns::SplitInline() + +void ns::SplitInline( + __inout __inout_z LPWSTR szPath, // Path to split. + LPCWSTR &szNameSpace, // Return pointer to namespace. + LPCWSTR &szName) // Return pointer to name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + WCHAR *ptr = SplitInline(szPath); + if (ptr) + { + szNameSpace = szPath; + szName = ptr; + } + else + { + szNameSpace = 0; + szName = szPath; + } +} // void ns::SplitInline() + +void ns::SplitInline( + __inout __inout_z LPUTF8 szPath, // Path to split. + LPCUTF8 &szNameSpace, // Return pointer to namespace. + LPCUTF8 &szName) // Return pointer to name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + LPUTF8 ptr = SplitInline(szPath); + if (ptr) + { + szNameSpace = szPath; + szName = ptr; + } + else + { + szNameSpace = 0; + szName = szPath; + } +} // void ns::SplitInline() + + +//***************************************************************************** +// Split the last parsable element from the end of the string as the name, +// the first part as the namespace. +//***************************************************************************** +int ns::SplitPath( // true ok, false trunction. + const WCHAR *szPath, // Path to split. + __out_ecount(cchNameSpace) WCHAR *szNameSpace, // Output for namespace value. + int cchNameSpace, // Max chars for output. + __out_ecount(cchName) WCHAR *szName, // Output for name. + int cchName) // Max chars for output. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + const WCHAR *ptr = ns::FindSep(szPath); + size_t iLen = (ptr) ? ptr - szPath : 0; + size_t iCopyMax; + int brtn = true; + if (szNameSpace && cchNameSpace) + { + _ASSERTE(cchNameSpace > 1); + iCopyMax = cchNameSpace - 1; + iCopyMax = min(iCopyMax, iLen); + wcsncpy_s(szNameSpace, cchNameSpace, szPath, iCopyMax); + szNameSpace[iCopyMax] = 0; + + if (iLen >= (size_t)cchNameSpace) + brtn = false; + } + + if (szName && cchName) + { + _ASSERTE(cchName > 1); + iCopyMax = cchName - 1; + if (ptr) + ++ptr; + else + ptr = szPath; + iLen = (int)wcslen(ptr); + iCopyMax = min(iCopyMax, iLen); + wcsncpy_s(szName, cchName, ptr, iCopyMax); + szName[iCopyMax] = 0; + + if (iLen >= (size_t)cchName) + brtn = false; + } + return brtn; +} // int ns::SplitPath() + + +int ns::SplitPath( // true ok, false trunction. + LPCUTF8 szPath, // Path to split. + __out_ecount_opt (cchNameSpace) LPUTF8 szNameSpace, // Output for namespace value. + int cchNameSpace, // Max chars for output. + __out_ecount_opt (cchName) LPUTF8 szName, // Output for name. + int cchName) // Max chars for output. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + LPCUTF8 ptr = ns::FindSep(szPath); + size_t iLen = (ptr) ? ptr - szPath : 0; + size_t iCopyMax; + int brtn = true; + if (szNameSpace && cchNameSpace) + { + _ASSERTE(cchNameSpace > 1); + iCopyMax = cchNameSpace-1; + iCopyMax = min(iCopyMax, iLen); + strncpy_s(szNameSpace, cchNameSpace, szPath, iCopyMax); + szNameSpace[iCopyMax] = 0; + + if (iLen >= (size_t)cchNameSpace) + brtn = false; + } + + if (szName && cchName) + { + _ASSERTE(cchName > 1); + iCopyMax = cchName-1; + if (ptr) + ++ptr; + else + ptr = szPath; + iLen = (int)strlen(ptr); + iCopyMax = min(iCopyMax, iLen); + strncpy_s(szName, cchName, ptr, iCopyMax); + szName[iCopyMax] = 0; + + if (iLen >= (size_t)cchName) + brtn = false; + } + return brtn; +} // int ns::SplitPath() + + +//***************************************************************************** +// Take two values and put them together in a fully qualified path using the +// correct separator. +//***************************************************************************** +int ns::MakePath( // true ok, false truncation. + __out_ecount(cchChars) WCHAR *szOut, // output path for name. + int cchChars, // max chars for output path. + const WCHAR *szNameSpace, // Namespace. + const WCHAR *szName) // Name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (cchChars < 1) + return false; + + if (szOut) + *szOut = 0; + else + return false; + + if (szNameSpace && *szNameSpace != W('\0')) + { + if (wcsncpy_s(szOut, cchChars, szNameSpace, _TRUNCATE) == STRUNCATE) + return false; + + // Add namespace separator if a non-empty name was supplied + if (szName && *szName != W('\0')) + { + if (wcsncat_s(szOut, cchChars, NAMESPACE_SEPARATOR_WSTR, _TRUNCATE) == STRUNCATE) + { + return false; + } + } + } + + if (szName && *szName) + { + if (wcsncat_s(szOut, cchChars, szName, _TRUNCATE) == STRUNCATE) + return false; + } + + return true; +} // int ns::MakePath() + +int ns::MakePath( // true ok, false truncation. + __out_ecount(cchChars) LPUTF8 szOut, // output path for name. + int cchChars, // max chars for output path. + LPCUTF8 szNameSpace, // Namespace. + LPCUTF8 szName) // Name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (cchChars < 1) + return false; + + if (szOut) + *szOut = 0; + else + return false; + + if (szNameSpace && *szNameSpace != W('\0')) + { + if (strncpy_s(szOut, cchChars, szNameSpace, _TRUNCATE) == STRUNCATE) + return false; + + // Add namespace separator if a non-empty name was supplied + if (szName && *szName != W('\0')) + { + if (strncat_s(szOut, cchChars, NAMESPACE_SEPARATOR_STR, _TRUNCATE) == STRUNCATE) + { + return false; + } + } + } + + if (szName && *szName) + { + if (strncat_s(szOut, cchChars, szName, _TRUNCATE) == STRUNCATE) + return false; + } + + return true; + +} // int ns::MakePath() + +int ns::MakePath( // true ok, false truncation. + __out_ecount(cchChars) WCHAR *szOut, // output path for name. + int cchChars, // max chars for output path. + LPCUTF8 szNamespace, // Namespace. + LPCUTF8 szName) // Name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (cchChars < 1) + return false; + + if (szOut) + *szOut = 0; + else + return false; + + if (szNamespace != NULL && *szNamespace != '\0') + { + if (cchChars < 2) + return false; + + int count; + + // We use cBuffer - 2 to account for the '.' and at least a 1 character name below. + count = WszMultiByteToWideChar(CP_UTF8, 0, szNamespace, -1, szOut, cchChars-2); + if (count == 0) + return false; // Supply a bigger buffer! + + // buffer access is bounded: WszMultiByteToWideChar returns 0 if access doesn't fit in range +#ifdef _PREFAST_ + #pragma warning( suppress: 26015 ) +#endif + szOut[count-1] = NAMESPACE_SEPARATOR_WCHAR; + szOut += count; + cchChars -= count; + } + + if (((cchChars == 0) && (szName != NULL) && (*szName != '\0')) || + (WszMultiByteToWideChar(CP_UTF8, 0, szName, -1, szOut, cchChars) == 0)) + return false; // supply a bigger buffer! + return true; +} // int ns::MakePath() + +int ns::MakePath( // true ok, false out of memory + CQuickBytes &qb, // Where to put results. + LPCUTF8 szNameSpace, // Namespace for name. + LPCUTF8 szName) // Final part of name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + int iLen = 2; + if (szNameSpace) + iLen += (int)strlen(szNameSpace); + if (szName) + iLen += (int)strlen(szName); + LPUTF8 szOut = (LPUTF8) qb.AllocNoThrow(iLen); + if (!szOut) + return false; + return ns::MakePath(szOut, iLen, szNameSpace, szName); +} // int ns::MakePath() + +int ns::MakePath( // true ok, false out of memory + CQuickArray<WCHAR> &qa, // Where to put results. + LPCUTF8 szNameSpace, // Namespace for name. + LPCUTF8 szName) // Final part of name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + int iLen = 2; + if (szNameSpace) + iLen += (int)strlen(szNameSpace); + if (szName) + iLen += (int)strlen(szName); + WCHAR *szOut = (WCHAR *) qa.AllocNoThrow(iLen); + if (!szOut) + return false; + return ns::MakePath(szOut, iLen, szNameSpace, szName); +} // int ns::MakePath() + +int ns::MakePath( // true ok, false out of memory + CQuickBytes &qb, // Where to put results. + const WCHAR *szNameSpace, // Namespace for name. + const WCHAR *szName) // Final part of name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + int iLen = 2; + if (szNameSpace) + iLen += (int)wcslen(szNameSpace); + if (szName) + iLen += (int)wcslen(szName); + WCHAR *szOut = (WCHAR *) qb.AllocNoThrow(iLen * sizeof(WCHAR)); + if (!szOut) + return false; + return ns::MakePath(szOut, iLen, szNameSpace, szName); +} // int ns::MakePath() + +void ns::MakePath( // throws on out of memory + SString &ssBuf, // Where to put results. + const SString &ssNameSpace, // Namespace for name. + const SString &ssName) // Final part of name. +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + ssBuf.Clear(); + + if (!ssNameSpace.IsEmpty()) + { + if (ssName.IsEmpty()) + { + ssBuf.Set(ssNameSpace); + } + else + { + SString s(SString::Literal, NAMESPACE_SEPARATOR_WSTR); + ssBuf.Set(ssNameSpace, s); + } + } + + if (!ssName.IsEmpty()) + { + ssBuf.Append(ssName); + } +} + +bool ns::MakeAssemblyQualifiedName( // true ok, false truncation + __out_ecount(dwBuffer) WCHAR* pBuffer, // Buffer to recieve the results + int dwBuffer, // Number of characters total in buffer + const WCHAR *szTypeName, // Namespace for name. + int dwTypeName, // Number of characters (not including null) + const WCHAR *szAssemblyName, // Final part of name. + int dwAssemblyName) // Number of characters (not including null) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (dwBuffer < 2) + return false; + + int iCopyMax = 0; + _ASSERTE(pBuffer); + *pBuffer = NULL; + + if (szTypeName && *szTypeName != W('\0')) + { + _ASSERTE(dwTypeName > 0); + iCopyMax = min(dwBuffer-1, dwTypeName); + wcsncpy_s(pBuffer, dwBuffer, szTypeName, iCopyMax); + dwBuffer -= iCopyMax; + } + + if (szAssemblyName && *szAssemblyName != W('\0')) + { + + if(dwBuffer < ASSEMBLY_SEPARATOR_LEN) + return false; + + for(DWORD i = 0; i < ASSEMBLY_SEPARATOR_LEN; i++) + pBuffer[iCopyMax+i] = ASSEMBLY_SEPARATOR_WSTR[i]; + + dwBuffer -= ASSEMBLY_SEPARATOR_LEN; + if(dwBuffer == 0) + return false; + + int iCur = iCopyMax + ASSEMBLY_SEPARATOR_LEN; + _ASSERTE(dwAssemblyName > 0); + iCopyMax = min(dwBuffer-1, dwAssemblyName); + wcsncpy_s(pBuffer + iCur, dwBuffer, szAssemblyName, iCopyMax); + pBuffer[iCur + iCopyMax] = W('\0'); + + if (iCopyMax < dwAssemblyName) + return false; + } + else { + if(dwBuffer == 0) { + PREFIX_ASSUME(iCopyMax > 0); + pBuffer[iCopyMax-1] = W('\0'); + return false; + } + else + pBuffer[iCopyMax] = W('\0'); + } + + return true; +} // int ns::MakePath() + +bool ns::MakeAssemblyQualifiedName( // true ok, false out of memory + CQuickBytes &qb, // Where to put results. + const WCHAR *szTypeName, // Namespace for name. + const WCHAR *szAssemblyName) // Final part of name. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + int iTypeName = 0; + int iAssemblyName = 0; + if (szTypeName) + iTypeName = (int)wcslen(szTypeName); + if (szAssemblyName) + iAssemblyName = (int)wcslen(szAssemblyName); + + int iLen = ASSEMBLY_SEPARATOR_LEN + iTypeName + iAssemblyName + 1; // Space for null terminator + WCHAR *szOut = (WCHAR *) qb.AllocNoThrow(iLen * sizeof(WCHAR)); + if (!szOut) + return false; + + bool ret; + ret = ns::MakeAssemblyQualifiedName(szOut, iLen, szTypeName, iTypeName, szAssemblyName, iAssemblyName); + _ASSERTE(ret); + return true; +} + +int ns::MakeNestedTypeName( // true ok, false out of memory + CQuickBytes &qb, // Where to put results. + LPCUTF8 szEnclosingName, // Full name for enclosing type + LPCUTF8 szNestedName) // Full name for nested type +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + _ASSERTE(szEnclosingName && szNestedName); + int iLen = 2; + iLen += (int)strlen(szEnclosingName); + iLen += (int)strlen(szNestedName); + LPUTF8 szOut = (LPUTF8) qb.AllocNoThrow(iLen); + if (!szOut) + return false; + return ns::MakeNestedTypeName(szOut, iLen, szEnclosingName, szNestedName); +} // int ns::MakeNestedTypeName() + +int ns::MakeNestedTypeName( // true ok, false truncation. + __out_ecount (cchChars) LPUTF8 szOut, // output path for name. + int cchChars, // max chars for output path. + LPCUTF8 szEnclosingName, // Full name for enclosing type + LPCUTF8 szNestedName) // Full name for nested type +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + + if (cchChars < 1) + return false; + + int iCopyMax = 0, iLen; + int brtn = true; + *szOut = 0; + + iLen = (int)strlen(szEnclosingName); + iCopyMax = min(cchChars-1, iLen); + strncpy_s(szOut, cchChars, szEnclosingName, iCopyMax); + + if (iLen >= cchChars) + brtn = false; + + szOut[iCopyMax] = NESTED_SEPARATOR_CHAR; + int iCur = iCopyMax+1; // iCopyMax characters + nested_separator_char + cchChars -= iCur; + if(cchChars == 0) + return false; + + iLen = (int)strlen(szNestedName); + iCopyMax = min(cchChars-1, iLen); + strncpy_s(&szOut[iCur], cchChars, szNestedName, iCopyMax); + szOut[iCur + iCopyMax] = 0; + + if (iLen >= cchChars) + brtn = false; + + return brtn; +} // int ns::MakeNestedTypeName() + +void ns::MakeNestedTypeName( // throws on out of memory + SString &ssBuf, // output path for name. + const SString &ssEnclosingName, // Full name for enclosing type + const SString &ssNestedName) // Full name for nested type +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + + ssBuf.Clear(); + + ssBuf.Append(ssEnclosingName); + ssBuf.Append(NESTED_SEPARATOR_WCHAR); + ssBuf.Append(ssNestedName); +} + diff --git a/src/coreclr/utilcode/opinfo.cpp b/src/coreclr/utilcode/opinfo.cpp new file mode 100644 index 00000000000..0a2efdf28f1 --- /dev/null +++ b/src/coreclr/utilcode/opinfo.cpp @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/***************************************************************************/ +/* OpInfo.cpp */ +/***************************************************************************/ + +#include "stdafx.h" +#include <cor.h> // for debugMacros.h +#include "debugmacros.h" // for ASSERTE +#include "opinfo.h" + + +OpInfo::OpInfoData OpInfo::table[] = { + +#define OPDEF(c,s,pop,push,args,type,l,s1,s2,ctrl) \ + { s, (OPCODE_FORMAT) (args + type), FLOW_ ## ctrl, pop, push, c }, + + // Kind of a workaround, get the prefixes (IInternal) to return InlineOpcode instead of InlineNone +#define IInternal (InlineOpcode - InlineNone) +#define IMacro 0 +#define IPrimitive 0 +#define IAnnotation 0 +#define IObjModel 0 +#define IPrefix 0 + +#define Pop0 0 +#define Pop1 1 +#define PopI 1 +#define PopI4 1 +#define PopR4 1 +#define PopI8 1 +#define PopR8 1 +#define PopRef 1 +#define VarPop -1 + +#define Push0 0 +#define Push1 1 +#define PushI 1 +#define PushI4 1 +#define PushR4 1 +#define PushI8 1 +#define PushR8 1 +#define PushRef 1 +#define VarPush -1 + +#include "opcode.def" +#undef OPDEF +}; + + +/***************************************************************************/ +/* parse instruction at 'instrPtr', into its opcode (OpInfo), and its + (inline)args, 'args' 'instrPtr' is updated */ + +/***************************************************************************/ +const BYTE* OpInfo::fetch(const BYTE* instrPtr, OpArgsVal* args) { + + data = &table[*instrPtr++]; +AGAIN: + _ASSERTE(data - table == data->opcode); + switch(data->format) { + case InlineNone: + break; + case InlineOpcode: + _ASSERTE(*instrPtr + 256 < (int) (sizeof(table) / sizeof(OpInfoData))); + data = &table[256 + *instrPtr++]; + goto AGAIN; + + case ShortInlineVar: + args->i = *instrPtr; instrPtr +=1; + break; + case InlineVar: + args->i = GET_UNALIGNED_VAL16(instrPtr); instrPtr +=2; + break; + case ShortInlineI: + case ShortInlineBrTarget: + args->i = *instrPtr; instrPtr +=1; + break; + case ShortInlineR: { + DWORD f = GET_UNALIGNED_VAL32(instrPtr); instrPtr +=4; + args->r = *((float*) (&f)); + } + break; + case InlineRVA: + case InlineI: + case InlineMethod: + case InlineField: + case InlineType: + case InlineString: + case InlineSig: + case InlineTok: + case InlineBrTarget: + args->i = GET_UNALIGNED_VAL32(instrPtr); instrPtr +=4; + break; + case InlineI8: + args->i8 = GET_UNALIGNED_VAL64(instrPtr); instrPtr +=8; + break; + case InlineR: { + __int64 d = GET_UNALIGNED_VAL64(instrPtr); instrPtr +=8; + args->r = *((double*) (&d)); + } break; + case InlineSwitch: + args->switch_.count = GET_UNALIGNED_VAL32(instrPtr); instrPtr +=4; + args->switch_.targets = (int*) instrPtr; instrPtr += (4 * args->switch_.count); + break; + case InlinePhi: + args->phi.count = GET_UNALIGNED_VAL32(instrPtr); instrPtr +=1; + args->phi.vars = (unsigned short*) instrPtr; instrPtr += (2 * args->phi.count); + break; + default: +#ifdef _DEBUG + _ASSERTE(!"BadType"); +#else + __assume(0); // we are really certain the default case does not happen +#endif + break; + } + return(instrPtr); +} + diff --git a/src/coreclr/utilcode/outstring.cpp b/src/coreclr/utilcode/outstring.cpp new file mode 100644 index 00000000000..40b8e004066 --- /dev/null +++ b/src/coreclr/utilcode/outstring.cpp @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/*****************************************************************/ +/* OutString.cpp */ +/*****************************************************************/ +/* A simple, lightweight, character output stream, with very few + external dependancies (like sprintf ... ) */ + +/* + Date : 2/1/99 */ +/*****************************************************************/ + +#include "stdafx.h" +#include "outstring.h" + + +/*****************************************************************/ +// print out 'count' instances of the character 'c' +OutString& OutString::pad(size_t count, char c) { + if (cur+count > end) + Realloc(count); + memset(cur, c, count); + cur = cur + count; + return(*this); +} + +/*****************************************************************/ +// prints out a decimal representation +OutString& OutString::operator<<(double d) { + + if (d == 0.0) { + *this << "0.0"; + return *this; + } + + if (d < 0) { + d = -d; + *this << '-'; + } + + // compute the exponent + int exponent = 0; + while (d > 10.0) { + d /= 10; + exponent++; + if (exponent > 500) { // avoids a possible infinite loop + *this << "INF"; + return *this; + } + } + while (d < 1.0) { + d *= 10; + --exponent; + if (exponent < -500) { // avoids a possible infinite loop + *this << "0.0"; + return *this; + } + } + + // we now have a normalized d (between 1 and 10) + double delta = .5E-10; + d += delta; // round to the precision we are displaying + + unsigned trailingZeros = 0; + for(unsigned i = 0; i < 10; i++) { + int digit = (int) d; + d = (d - digit) * 10; // ISSUE: does roundoff ever bite us here? + + if (digit == 0) // defer printing traiing zeros + trailingZeros++; + else { + if (trailingZeros > 0) { + this->pad(trailingZeros, '0'); + trailingZeros = 0; + } + *this << (char) ('0' + digit); + } + if (i == 0) + *this << '.'; + + } + if (exponent != 0) { + *this << 'E'; + *this << exponent; + } + return(*this); +} + +/*****************************************************************/ +// prints out a decimal representation +OutString& OutString::dec(int i, size_t minWidth) { + char buff[12]; // big enough for any number (10 digits, - sign, null term) + char* ptr = &buff[11]; + *ptr = 0; + + unsigned val = i; + if (i < 0) + val = -i; // note this happens to also work for minint! + + for(;;) { + if (val < 10) { + *--ptr = '0' + val; + break; + } + *--ptr = '0' + (val % 10); + val = val / 10; + } + + if (i < 0) + *--ptr = '-'; + + size_t len = &buff[11] - ptr; // length of string + if (len < minWidth) + pad(minWidth-len, ' '); + + *this << ptr; + return(*this); +} + +/*****************************************************************/ +OutString& OutString::hex(unsigned __int64 i, int minWidth, unsigned flags) { + + unsigned hi = unsigned(i >> 32); + unsigned low = unsigned(i); + + if (hi != 0) { + minWidth -= 8; + hex(hi, minWidth, flags); // print upper bits + flags = zeroFill; + minWidth = 8; + } + return hex(low, minWidth, flags); // print lower bits +} + +/*****************************************************************/ +OutString& OutString::hex(unsigned i, int minWidth, unsigned flags) { + char buff[12]; // big enough for any number + char* ptr = &buff[11]; + *ptr = 0; + + static const char digits[] = "0123456789ABCDEF"; + + for(;;) { + if (i < 16) { + *--ptr = digits[i]; + break; + } + *--ptr = digits[(i % 16)]; + i = i / 16; + } + + size_t len = &buff[11] - ptr; // length of string + if (flags & put0x) { + if (flags & zeroFill) + *this << "0x"; + else + *--ptr = 'x', *--ptr = '0'; + len += 2; + } + + if (len < (size_t)minWidth) + pad(minWidth-len, (flags & zeroFill) ? '0' : ' '); + + *this << ptr; + return(*this); +} + +/*****************************************************************/ +void OutString::Realloc(size_t neededSpace) { + size_t oldSize = cur-start; + size_t newSize = (oldSize + neededSpace) * 3 / 2 + 32; + char* oldBuff = start; + start = new char[newSize+1]; + memcpy(start, oldBuff, oldSize); + cur = &start[oldSize]; + end = &start[newSize]; + delete [] oldBuff; +} + diff --git a/src/coreclr/utilcode/pedecoder.cpp b/src/coreclr/utilcode/pedecoder.cpp new file mode 100644 index 00000000000..91fde64d297 --- /dev/null +++ b/src/coreclr/utilcode/pedecoder.cpp @@ -0,0 +1,3280 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// -------------------------------------------------------------------------------- +// PEDecoder.cpp +// + +// -------------------------------------------------------------------------------- + +#include "stdafx.h" + +#include "ex.h" +#include "pedecoder.h" +#include "mdcommon.h" +#include "nibblemapmacros.h" + +CHECK PEDecoder::CheckFormat() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + CHECK(HasContents()); + + if (HasNTHeaders()) + { + CHECK(CheckNTHeaders()); + + if (HasCorHeader()) + { + CHECK(CheckCorHeader()); + + if (IsILOnly()) + CHECK(CheckILOnly()); + + if (HasNativeHeader()) + CHECK(CheckNativeHeader()); + + CHECK(CheckWillCreateGuardPage()); + } + } + + CHECK_OK; +} + +CHECK PEDecoder::CheckNTFormat() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + + CHECK(CheckFormat()); + CHECK(HasNTHeaders()); + + CHECK_OK; +} + +CHECK PEDecoder::CheckCORFormat() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + + CHECK(CheckFormat()); + CHECK(HasNTHeaders()); + CHECK(HasCorHeader()); + + CHECK_OK; +} + + +CHECK PEDecoder::CheckILFormat() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + + CHECK(CheckFormat()); + CHECK(HasNTHeaders()); + CHECK(HasCorHeader()); + CHECK(!HasNativeHeader()); + + CHECK_OK; +} + + +CHECK PEDecoder::CheckILOnlyFormat() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + + CHECK(CheckFormat()); + CHECK(HasNTHeaders()); + CHECK(HasCorHeader()); + CHECK(IsILOnly()); + CHECK(!HasNativeHeader()); + + CHECK_OK; +} + +CHECK PEDecoder::CheckNativeFormat() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + +#ifdef FEATURE_PREJIT + CHECK(CheckFormat()); + CHECK(HasNTHeaders()); + CHECK(HasCorHeader()); + CHECK(!IsILOnly()); + CHECK(HasNativeHeader()); +#else // FEATURE_PREJIT + CHECK(false); +#endif // FEATURE_PREJIT + + CHECK_OK; +} + +BOOL PEDecoder::HasNTHeaders() const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + PRECONDITION(HasContents()); + } + CONTRACT_END; + + // Check for a valid DOS header + + if (m_size < sizeof(IMAGE_DOS_HEADER)) + RETURN FALSE; + + IMAGE_DOS_HEADER* pDOS = PTR_IMAGE_DOS_HEADER(m_base); + + { + if (pDOS->e_magic != VAL16(IMAGE_DOS_SIGNATURE) + || (DWORD) pDOS->e_lfanew == VAL32(0)) + { + RETURN FALSE; + } + + // Check for integer overflow + S_SIZE_T cbNTHeaderEnd(S_SIZE_T(static_cast<SIZE_T>(VAL32(pDOS->e_lfanew))) + + S_SIZE_T(sizeof(IMAGE_NT_HEADERS))); + if (cbNTHeaderEnd.IsOverflow()) + { + RETURN FALSE; + } + + // Now check for a valid NT header + if (m_size < cbNTHeaderEnd.Value()) + { + RETURN FALSE; + } + } + + IMAGE_NT_HEADERS *pNT = PTR_IMAGE_NT_HEADERS(m_base + VAL32(pDOS->e_lfanew)); + + if (pNT->Signature != VAL32(IMAGE_NT_SIGNATURE)) + RETURN FALSE; + + if (pNT->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR32_MAGIC)) + { + if (pNT->FileHeader.SizeOfOptionalHeader != VAL16(sizeof(IMAGE_OPTIONAL_HEADER32))) + RETURN FALSE; + } + else if (pNT->OptionalHeader.Magic == VAL16(IMAGE_NT_OPTIONAL_HDR64_MAGIC)) + { + // on 64 bit we can promote this + if (pNT->FileHeader.SizeOfOptionalHeader != VAL16(sizeof(IMAGE_OPTIONAL_HEADER64))) + RETURN FALSE; + + // Check for integer overflow + S_SIZE_T cbNTHeaderEnd(S_SIZE_T(static_cast<SIZE_T>(VAL32(pDOS->e_lfanew))) + + S_SIZE_T(sizeof(IMAGE_NT_HEADERS64))); + + if (cbNTHeaderEnd.IsOverflow()) + { + RETURN FALSE; + } + + // Now check for a valid NT header + if (m_size < cbNTHeaderEnd.Value()) + { + RETURN FALSE; + } + + } + else + RETURN FALSE; + + // Go ahead and cache NT header since we already found it. + const_cast<PEDecoder *>(this)->m_pNTHeaders = dac_cast<PTR_IMAGE_NT_HEADERS>(pNT); + + RETURN TRUE; +} + +CHECK PEDecoder::CheckNTHeaders() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + PRECONDITION(HasContents()); + } + CONTRACT_CHECK_END; + + // Only check once per file + if (m_flags & FLAG_NT_CHECKED) + CHECK_OK; + + CHECK(HasNTHeaders()); + + IMAGE_NT_HEADERS *pNT = FindNTHeaders(); + + CHECK((pNT->FileHeader.Characteristics & VAL16(IMAGE_FILE_SYSTEM)) == 0); + + CHECK(CheckAlignment(VAL32(pNT->OptionalHeader.FileAlignment))); + CHECK(CheckAlignment(VAL32(pNT->OptionalHeader.SectionAlignment))); + + CHECK(CheckAligned((UINT)VAL32(pNT->OptionalHeader.FileAlignment), 512)); + CHECK(CheckAligned((UINT)VAL32(pNT->OptionalHeader.SectionAlignment), VAL32(pNT->OptionalHeader.FileAlignment))); + + CHECK(CheckAligned((UINT)VAL32(pNT->OptionalHeader.SizeOfImage), VAL32(pNT->OptionalHeader.SectionAlignment))); + CHECK(CheckAligned((UINT)VAL32(pNT->OptionalHeader.SizeOfHeaders), VAL32(pNT->OptionalHeader.FileAlignment))); + + // Data directories will be validated later on. + PTR_IMAGE_DATA_DIRECTORY pDataDirectories = NULL; + + if (Has32BitNTHeaders()) + { + IMAGE_NT_HEADERS32* pNT32=GetNTHeaders32(); + CHECK(CheckAligned(VAL32(pNT32->OptionalHeader.ImageBase), 0x10000)); + CHECK((VAL32(pNT32->OptionalHeader.SizeOfStackCommit) <= VAL32(pNT32->OptionalHeader.SizeOfStackReserve))); + CHECK((VAL32(pNT32->OptionalHeader.SizeOfHeapCommit) <= VAL32(pNT32->OptionalHeader.SizeOfHeapReserve))); + pDataDirectories = dac_cast<PTR_IMAGE_DATA_DIRECTORY>( + dac_cast<TADDR>(pNT32) + offsetof(IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory)); + } + else + { + IMAGE_NT_HEADERS64* pNT64=GetNTHeaders64(); + CHECK(CheckAligned(VAL64(pNT64->OptionalHeader.ImageBase), 0x10000)); + CHECK((VAL64(pNT64->OptionalHeader.SizeOfStackCommit) <= VAL64(pNT64->OptionalHeader.SizeOfStackReserve))); + CHECK((VAL64(pNT64->OptionalHeader.SizeOfHeapCommit) <= VAL64(pNT64->OptionalHeader.SizeOfHeapReserve))); + pDataDirectories = dac_cast<PTR_IMAGE_DATA_DIRECTORY>( + dac_cast<TADDR>(pNT64) + offsetof(IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory)); + } + + // @todo: this is a bit awkward here, it would be better to make this assertion on + // PEDecoder instantiation. However, we don't necessarily have the NT headers there (in fact + // they might not exist.) + + if (IsMapped()) + { + // Ideally we would require the layout address to honor the section alignment constraints. + // However, we do have 8K aligned IL only images which we load on 32 bit platforms. + // Also in the case of files embedded within a single-file app, the default alignment for assemblies is 16 bytes. + CHECK(CheckAligned(m_base, 16)); + } + + // @todo: check NumberOfSections for overflow of SizeOfHeaders + + UINT32 currentAddress = 0; + UINT32 currentOffset = 0; + + CHECK(CheckSection(currentAddress, 0, VAL32(pNT->OptionalHeader.SizeOfHeaders), + currentOffset, 0, VAL32(pNT->OptionalHeader.SizeOfHeaders))); + + currentAddress=currentOffset=VAL32(pNT->OptionalHeader.SizeOfHeaders); + + PTR_IMAGE_SECTION_HEADER section = FindFirstSection(pNT); + PTR_IMAGE_SECTION_HEADER sectionEnd = section + VAL16(pNT->FileHeader.NumberOfSections); + + CHECK(sectionEnd >= section); + + + while (section < sectionEnd) + { + + // + // NOTE: the if condition is becuase of a design issue in the CLR and OS loader's remapping + // of PE32 headers to PE32+. Because IMAGE_NT_HEADERS64 is bigger than IMAGE_NT_HEADERS32, + // the remapping will expand this part of the header and push out the following + // IMAGE_SECTION_HEADER entries. When IMAGE_DOS_HEADER::e_lfanew is large enough + // (size is proportional to the number of tools used to produce the inputs to the C++ linker) + // this can push the last section header + // beyond the boundary set by IMAGE_NT_HEADERS::OptionalHeader.SizeOfHeaders (e.g., this + // was recently seen where the unaligned size of the headers was 0x1f8 and SizeOfHeaders was + // 0x200, and the header remapping resulted in new headers size of 0x208). To compensate + // for this issue (it would be quite difficult to fix in the remapping code; see Dev11 430008) + // we assume that when the image is mapped that the needed validation has already been done. + // + + if (!IsMapped()) + { + CHECK(CheckBounds(dac_cast<PTR_CVOID>(pNT),VAL32(pNT->OptionalHeader.SizeOfHeaders), + section,sizeof(IMAGE_SECTION_HEADER))); + } + + // Check flags + // Only allow a small list of characteristics + CHECK(!(section->Characteristics & + ~(VAL32((IMAGE_SCN_CNT_CODE | + IMAGE_SCN_CNT_INITIALIZED_DATA | + IMAGE_SCN_CNT_UNINITIALIZED_DATA| + IMAGE_SCN_MEM_DISCARDABLE | + IMAGE_SCN_MEM_NOT_CACHED | + IMAGE_SCN_MEM_NOT_PAGED | + IMAGE_SCN_MEM_EXECUTE | + IMAGE_SCN_MEM_READ | + IMAGE_SCN_MEM_WRITE | + // allow shared sections for all images for now. + // we'll constrain this in CheckILOnly + IMAGE_SCN_MEM_SHARED))))); + + // we should not allow writable code sections, check if both flags are set + CHECK((section->Characteristics & VAL32((IMAGE_SCN_CNT_CODE|IMAGE_SCN_MEM_WRITE))) != + VAL32((IMAGE_SCN_CNT_CODE|IMAGE_SCN_MEM_WRITE))); + + CHECK(CheckSection(currentAddress, VAL32(section->VirtualAddress), VAL32(section->Misc.VirtualSize), + currentOffset, VAL32(section->PointerToRawData), VAL32(section->SizeOfRawData))); + + currentAddress = VAL32(section->VirtualAddress) + + AlignUp((UINT)VAL32(section->Misc.VirtualSize), (UINT)VAL32(pNT->OptionalHeader.SectionAlignment)); + currentOffset = VAL32(section->PointerToRawData) + VAL32(section->SizeOfRawData); + + section++; + } + + // Now check that the COR data directory is either NULL, or exists entirely in one section. + { + PTR_IMAGE_DATA_DIRECTORY pCORDataDir = pDataDirectories + IMAGE_DIRECTORY_ENTRY_COMHEADER; + CHECK(CheckRva(VAL32(pCORDataDir->VirtualAddress), VAL32(pCORDataDir->Size), 0, NULL_OK)); + } + + // @todo: verify directory entries + + const_cast<PEDecoder *>(this)->m_flags |= FLAG_NT_CHECKED; + + CHECK_OK; +} + +CHECK PEDecoder::CheckSection(COUNT_T previousAddressEnd, COUNT_T addressStart, COUNT_T addressSize, + COUNT_T previousOffsetEnd, COUNT_T offsetStart, COUNT_T offsetSize) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(HasNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + + // Fetch the NT header + IMAGE_NT_HEADERS *pNT = FindNTHeaders(); + + // OS will zero pad a mapped file up to file alignment size - some images rely on this + // COUNT_T alignedSize = AlignUp(m_size, VAL32(pNT->OptionalHeader.FileAlignment)); + COUNT_T alignedSize = IsMapped() ? AlignUp(m_size, VAL32(pNT->OptionalHeader.FileAlignment)) : m_size; + + // Check to make sure that our memory is big enough to cover the stated range. + // Note that this check is only required if we have a non-flat image. + if (IsMapped()) + CHECK(alignedSize >= VAL32(pNT->OptionalHeader.SizeOfImage)); + + // Check expected alignments + CHECK(CheckAligned(addressStart, VAL32(pNT->OptionalHeader.SectionAlignment))); + CHECK(CheckAligned(offsetStart, VAL32(pNT->OptionalHeader.FileAlignment))); + CHECK(CheckAligned(offsetSize, VAL32(pNT->OptionalHeader.FileAlignment))); + + // addressSize is typically not aligned, so we align it for purposes of checks. + COUNT_T alignedAddressSize = AlignUp(addressSize, VAL32(pNT->OptionalHeader.SectionAlignment)); + CHECK(addressSize <= alignedAddressSize); + + // Check overflow + CHECK(CheckOverflow(addressStart, alignedAddressSize)); + CHECK(CheckOverflow(offsetStart, offsetSize)); + + // Make sure we don't overlap the previous section + CHECK(addressStart >= previousAddressEnd + && (offsetSize == 0 + || offsetStart >= previousOffsetEnd)); + + // Make sure we don't overrun the end of the mapped image + CHECK(addressStart + alignedAddressSize <= VAL32(pNT->OptionalHeader.SizeOfImage)); + + // Make sure we don't overrun the end of the file (only relevant if we're not mapped, otherwise + // we don't know the file size, as it's not declared in the headers.) + if (!IsMapped()) + CHECK(offsetStart + offsetSize <= alignedSize); + + // Make sure the data doesn't overrun the virtual address space + CHECK(offsetSize <= alignedAddressSize); + + CHECK_OK; +} + +BOOL PEDecoder::HasWriteableSections() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckFormat()); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + + PTR_IMAGE_SECTION_HEADER pSection = FindFirstSection(); + _ASSERTE(pSection != NULL); + + PTR_IMAGE_SECTION_HEADER pSectionEnd = pSection + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + + while (pSection < pSectionEnd) + { + if ((pSection->Characteristics & VAL32(IMAGE_SCN_MEM_WRITE)) != 0) + { + return TRUE; + } + + pSection++; + } + + return FALSE; +} + +CHECK PEDecoder::CheckDirectoryEntry(int entry, int forbiddenFlags, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(entry < IMAGE_NUMBEROF_DIRECTORY_ENTRIES); + PRECONDITION(HasDirectoryEntry(entry)); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + CHECK(CheckDirectory(GetDirectoryEntry(entry), forbiddenFlags, ok)); + + CHECK_OK; +} + +CHECK PEDecoder::CheckDirectory(IMAGE_DATA_DIRECTORY *pDir, int forbiddenFlags, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckPointer(pDir)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + + CHECK(CheckRva(VAL32(pDir->VirtualAddress), VAL32(pDir->Size), forbiddenFlags, ok)); + + CHECK_OK; +} + +CHECK PEDecoder::CheckRva(RVA rva, COUNT_T size, int forbiddenFlags, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + + if (rva == 0) + { + CHECK_MSG(ok == NULL_OK, "Zero RVA illegal"); + CHECK(size == 0); + } + else + { + IMAGE_SECTION_HEADER *section = RvaToSection(rva); + + CHECK(section != NULL); + + CHECK(CheckBounds(VAL32(section->VirtualAddress), + // AlignUp((UINT)VAL32(section->Misc.VirtualSize), (UINT)VAL32(FindNTHeaders()->OptionalHeader.SectionAlignment)), + (UINT)VAL32(section->Misc.VirtualSize), + rva, size)); + if(!IsMapped()) + { + CHECK(CheckBounds(VAL32(section->VirtualAddress), VAL32(section->SizeOfRawData), rva, size)); + } + + if (forbiddenFlags!=0) + CHECK((section->Characteristics & VAL32(forbiddenFlags))==0); + } + + CHECK_OK; +} + +CHECK PEDecoder::CheckRva(RVA rva, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + + if (rva == 0) + CHECK_MSG(ok == NULL_OK, "Zero RVA illegal"); + else + CHECK(RvaToSection(rva) != NULL); + + CHECK_OK; +} + +CHECK PEDecoder::CheckOffset(COUNT_T fileOffset, COUNT_T size, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (fileOffset == 0) + { + CHECK_MSG(ok == NULL_OK, "zero fileOffset illegal"); + CHECK(size == 0); + } + else + { + IMAGE_SECTION_HEADER *section = OffsetToSection(fileOffset); + + CHECK(section != NULL); + + CHECK(CheckBounds(section->PointerToRawData, section->SizeOfRawData, + fileOffset, size)); + } + + CHECK_OK; +} + +CHECK PEDecoder::CheckOffset(COUNT_T fileOffset, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (fileOffset == NULL) + CHECK_MSG(ok == NULL_OK, "Null pointer illegal"); + else + { + CHECK(OffsetToSection(fileOffset) != NULL); + } + + CHECK_OK; +} + +CHECK PEDecoder::CheckData(const void *data, COUNT_T size, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (data == NULL) + { + CHECK_MSG(ok == NULL_OK, "NULL pointer illegal"); + CHECK(size == 0); + } + else + { + CHECK(CheckUnderflow(data, m_base)); + CHECK((UINT_PTR) (((BYTE *) data) - ((BYTE *) m_base)) <= COUNT_T_MAX); + + if (IsMapped()) + CHECK(CheckRva((COUNT_T) ((BYTE *) data - (BYTE *) m_base), size)); + else + CHECK(CheckOffset((COUNT_T) ((BYTE *) data - (BYTE *) m_base), size)); + } + + CHECK_OK; +} + +CHECK PEDecoder::CheckData(const void *data, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (data == NULL) + CHECK_MSG(ok == NULL_OK, "Null pointer illegal"); + else + { + CHECK(CheckUnderflow(data, m_base)); + CHECK((UINT_PTR) (((BYTE *) data) - ((BYTE *) m_base)) <= COUNT_T_MAX); + + if (IsMapped()) + CHECK(CheckRva((COUNT_T) ((BYTE *) data - (BYTE *) m_base))); + else + CHECK(CheckOffset((COUNT_T) ((BYTE *) data - (BYTE *) m_base))); + } + + CHECK_OK; +} + +CHECK PEDecoder::CheckInternalAddress(SIZE_T address, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (address == 0) + CHECK_MSG(ok == NULL_OK, "Zero RVA illegal"); + else + CHECK(RvaToSection(InternalAddressToRva(address)) != NULL); + + CHECK_OK; +} + +CHECK PEDecoder::CheckInternalAddress(SIZE_T address, COUNT_T size, IsNullOK ok) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (address == 0) + { + CHECK_MSG(ok == NULL_OK, "Zero RVA illegal"); + CHECK(size == 0); + } + else + { + CHECK(CheckRva(InternalAddressToRva(address), size)); + } + + CHECK_OK; +} + +RVA PEDecoder::InternalAddressToRva(SIZE_T address) const +{ + CONTRACT(RVA) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + POSTCONDITION(CheckRva(RETVAL)); + } + CONTRACT_END; + + if (m_flags & FLAG_RELOCATED) + { + // Address has been fixed up + RETURN (RVA) ((BYTE *) address - (BYTE *) m_base); + } + else + { + // Address has not been fixed up + RETURN (RVA) (address - (SIZE_T) GetPreferredBase()); + } +} + +// Returns a pointer to the named section or NULL if not found. +// The name should include the starting "." as well. +IMAGE_SECTION_HEADER *PEDecoder::FindSection(LPCSTR sectionName) const +{ + CONTRACT(IMAGE_SECTION_HEADER *) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(sectionName != NULL); + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + } + CONTRACT_END; + + // Ensure that the section name length is valid + SIZE_T iSectionNameLength = strlen(sectionName); + if ((iSectionNameLength < 1) || (iSectionNameLength > IMAGE_SIZEOF_SHORT_NAME)) + { + _ASSERTE(!"Invalid section name!"); + RETURN NULL; + } + + // Get the start and ends of the sections + PTR_IMAGE_SECTION_HEADER pSection = FindFirstSection(FindNTHeaders()); + _ASSERTE(pSection != NULL); + PTR_IMAGE_SECTION_HEADER pSectionEnd = pSection + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + _ASSERTE(pSectionEnd != NULL); + + BOOL fFoundSection = FALSE; + + // Loop thru the sections and see if we got the section we are interested in + while (pSection < pSectionEnd) + { + // Is this the section we are looking for? + if (strncmp(sectionName, (char*)pSection->Name, iSectionNameLength) == 0) + { + // We found our section - break out of the loop + fFoundSection = TRUE; + break; + } + + // Move to the next section + pSection++; + } + + if (TRUE == fFoundSection) + RETURN pSection; + else + RETURN NULL; +} + +IMAGE_SECTION_HEADER *PEDecoder::RvaToSection(RVA rva) const +{ + CONTRACT(IMAGE_SECTION_HEADER *) + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + SUPPORTS_DAC; + } + CONTRACT_END; + + PTR_IMAGE_SECTION_HEADER section = dac_cast<PTR_IMAGE_SECTION_HEADER>(FindFirstSection(FindNTHeaders())); + PTR_IMAGE_SECTION_HEADER sectionEnd = section + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + + while (section < sectionEnd) + { + if (rva < (VAL32(section->VirtualAddress) + + AlignUp((UINT)VAL32(section->Misc.VirtualSize), (UINT)VAL32(FindNTHeaders()->OptionalHeader.SectionAlignment)))) + { + if (rva < VAL32(section->VirtualAddress)) + RETURN NULL; + else + { + RETURN section; + } + } + + section++; + } + + RETURN NULL; +} + +IMAGE_SECTION_HEADER *PEDecoder::OffsetToSection(COUNT_T fileOffset) const +{ + CONTRACT(IMAGE_SECTION_HEADER *) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + SUPPORTS_DAC; + } + CONTRACT_END; + + PTR_IMAGE_SECTION_HEADER section = dac_cast<PTR_IMAGE_SECTION_HEADER>(FindFirstSection(FindNTHeaders())); + PTR_IMAGE_SECTION_HEADER sectionEnd = section + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + + while (section < sectionEnd) + { + if (fileOffset < section->PointerToRawData + section->SizeOfRawData) + { + if (fileOffset < section->PointerToRawData) + RETURN NULL; + else + RETURN section; + } + + section++; + } + + RETURN NULL; +} + +TADDR PEDecoder::GetRvaData(RVA rva, IsNullOK ok /*= NULL_NOT_OK*/) const +{ + CONTRACT(TADDR) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckRva(rva, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + SUPPORTS_DAC; + } + CONTRACT_END; + + if ((rva == 0)&&(ok == NULL_NOT_OK)) + RETURN NULL; + + RVA offset; + if (IsMapped()) + offset = rva; + else + { + // !!! check for case where rva is in padded portion of segment + offset = RvaToOffset(rva); + } + + RETURN( m_base + offset ); +} + +RVA PEDecoder::GetDataRva(const TADDR data) const +{ + CONTRACT(RVA) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckData((void *)data, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + if (data == NULL) + RETURN 0; + + COUNT_T offset = (COUNT_T) (data - m_base); + if (IsMapped()) + RETURN offset; + else + RETURN OffsetToRva(offset); +} + +BOOL PEDecoder::PointerInPE(PTR_CVOID data) const +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + SUPPORTS_DAC; + } + CONTRACTL_END; + + TADDR taddrData = dac_cast<TADDR>(data); + TADDR taddrBase = dac_cast<TADDR>(m_base); + + if (this->IsMapped()) + { + return taddrBase <= taddrData && taddrData < taddrBase + GetVirtualSize(); + } + else + { + return taddrBase <= taddrData && taddrData < taddrBase + GetSize(); + } +} + +TADDR PEDecoder::GetOffsetData(COUNT_T fileOffset, IsNullOK ok /*= NULL_NOT_OK*/) const +{ + CONTRACT(TADDR) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + PRECONDITION(CheckOffset(fileOffset, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + if ((fileOffset == 0)&&(ok == NULL_NOT_OK)) + RETURN NULL; + + RETURN GetRvaData(OffsetToRva(fileOffset)); +} + +//------------------------------------------------------------------------------- +// Lifted from "..\md\inc\mdfileformat.h" +// (cannot #include it here because it references lot of other stuff) +#define STORAGE_MAGIC_SIG 0x424A5342 // BSJB +struct STORAGESIGNATURE +{ + ULONG lSignature; // "Magic" signature. + USHORT iMajorVer; // Major file version. + USHORT iMinorVer; // Minor file version. + ULONG iExtraData; // Offset to next structure of information + ULONG iVersionString; // Length of version string +}; +typedef STORAGESIGNATURE UNALIGNED * PSTORAGESIGNATURE; +typedef DPTR(STORAGESIGNATURE UNALIGNED) PTR_STORAGESIGNATURE; + + +struct STORAGEHEADER +{ + BYTE fFlags; // STGHDR_xxx flags. + BYTE pad; + USHORT iStreams; // How many streams are there. +}; +typedef STORAGEHEADER UNALIGNED * PSTORAGEHEADER; +typedef DPTR(STORAGEHEADER UNALIGNED) PTR_STORAGEHEADER; + + +struct STORAGESTREAM +{ + ULONG iOffset; // Offset in file for this stream. + ULONG iSize; // Size of the file. + char rcName[32]; // Start of name, null terminated. +}; +typedef STORAGESTREAM UNALIGNED * PSTORAGESTREAM; +typedef DPTR(STORAGESTREAM UNALIGNED) PTR_STORAGESTREAM; + + +// if the stream's name is shorter than 32 bytes (incl.zero terminator), +// the size of storage stream header is less than sizeof(STORAGESTREAM) +// and is padded to 4-byte alignment +inline PTR_STORAGESTREAM NextStorageStream(PTR_STORAGESTREAM pSS) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + SUPPORTS_DAC; + TADDR pc = dac_cast<TADDR>(pSS); + pc += (sizeof(STORAGESTREAM) - 32 /*sizeof(STORAGESTREAM::rcName)*/ + strlen(pSS->rcName)+1+3)&~3; + return PTR_STORAGESTREAM(pc); +} +//------------------------------------------------------------------------------- + + +CHECK PEDecoder::CheckCorHeader() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + + if (m_flags & FLAG_COR_CHECKED) + CHECK_OK; + + CHECK(CheckNTHeaders()); + + CHECK(HasCorHeader()); + + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_COMHEADER); + + CHECK(CheckDirectory(pDir, IMAGE_SCN_MEM_WRITE, NULL_NOT_OK)); + + CHECK(VAL32(pDir->Size) >= sizeof(IMAGE_COR20_HEADER)); + + IMAGE_SECTION_HEADER *section = RvaToSection(VAL32(pDir->VirtualAddress)); + CHECK(section != NULL); + CHECK((section->Characteristics & VAL32(IMAGE_SCN_MEM_READ))!=0); + + CHECK(CheckRva(VAL32(pDir->VirtualAddress), sizeof(IMAGE_COR20_HEADER))); + + IMAGE_COR20_HEADER *pCor = GetCorHeader(); + + //CHECK(((ULONGLONG)pCor & 0x3)==0); + + // If the file is COM+ 1.0, which by definition has nothing the runtime can + // use, or if the file requires a newer version of this engine than us, + // it cannot be run by this engine. + CHECK(VAL16(pCor->MajorRuntimeVersion) > 1 && VAL16(pCor->MajorRuntimeVersion) <= COR_VERSION_MAJOR); + + CHECK(CheckDirectory(&pCor->MetaData, IMAGE_SCN_MEM_WRITE, HasNativeHeader() ? NULL_OK : NULL_NOT_OK)); + CHECK(CheckDirectory(&pCor->Resources, IMAGE_SCN_MEM_WRITE, NULL_OK)); + CHECK(CheckDirectory(&pCor->StrongNameSignature, IMAGE_SCN_MEM_WRITE, NULL_OK)); + CHECK(CheckDirectory(&pCor->CodeManagerTable, IMAGE_SCN_MEM_WRITE, NULL_OK)); + CHECK(CheckDirectory(&pCor->VTableFixups, 0, NULL_OK)); + CHECK(CheckDirectory(&pCor->ExportAddressTableJumps, 0, NULL_OK)); + CHECK(CheckDirectory(&pCor->ManagedNativeHeader, 0, NULL_OK)); + + CHECK(VAL32(pCor->cb) >= offsetof(IMAGE_COR20_HEADER, ManagedNativeHeader) + sizeof(IMAGE_DATA_DIRECTORY)); + + DWORD validBits = COMIMAGE_FLAGS_ILONLY + | COMIMAGE_FLAGS_32BITREQUIRED + | COMIMAGE_FLAGS_TRACKDEBUGDATA + | COMIMAGE_FLAGS_STRONGNAMESIGNED + | COMIMAGE_FLAGS_NATIVE_ENTRYPOINT + | COMIMAGE_FLAGS_IL_LIBRARY + | COMIMAGE_FLAGS_32BITPREFERRED; + + CHECK((pCor->Flags&VAL32(~validBits)) == 0); + + // Pure IL images should not have VTable fixups or EAT jumps + if (IsILOnly()) + { + CHECK(pCor->VTableFixups.Size == VAL32(0)); + CHECK(pCor->ExportAddressTableJumps.Size == VAL32(0)); + CHECK(!(pCor->Flags & VAL32(COMIMAGE_FLAGS_NATIVE_ENTRYPOINT))); + //@TODO: If not an exe, check that EntryPointToken is mdNil + } + else + { + if (pCor->Flags & VAL32(COMIMAGE_FLAGS_NATIVE_ENTRYPOINT)) + { + CHECK(CheckRva(VAL32(IMAGE_COR20_HEADER_FIELD(*pCor,EntryPointToken)))); + } + } + + // Strong name signed images should have a signature + if (IsStrongNameSigned()) + CHECK(HasStrongNameSignature()); + + // IL library files (really a misnomer - these are native images or ReadyToRun images) + // only they can have a native image header + if ((pCor->Flags&VAL32(COMIMAGE_FLAGS_IL_LIBRARY)) == 0) + { + CHECK(VAL32(pCor->ManagedNativeHeader.Size) == 0); + } + + // Metadata header checks + IMAGE_DATA_DIRECTORY *pDirMD = &pCor->MetaData; + COUNT_T ctMD = (COUNT_T)VAL32(pDirMD->Size); + TADDR pcMD = (TADDR)GetDirectoryData(pDirMD); + + if(pcMD != NULL) + { + // Storage signature checks + CHECK(ctMD >= sizeof(STORAGESIGNATURE)); + PTR_STORAGESIGNATURE pStorageSig = PTR_STORAGESIGNATURE((TADDR)pcMD); + COUNT_T ctMDStreamSize = ctMD; // Store MetaData stream size for later usage + + + CHECK(VAL32(pStorageSig->lSignature) == STORAGE_MAGIC_SIG); + COUNT_T ctSSig; + CHECK(ClrSafeInt<COUNT_T>::addition(sizeof(STORAGESIGNATURE), (COUNT_T)VAL32(pStorageSig->iVersionString), ctSSig)); + CHECK(ctMD > ctSSig); + + // Storage header checks + pcMD += ctSSig; + + PTR_STORAGEHEADER pSHdr = PTR_STORAGEHEADER((TADDR)pcMD); + + + ctMD -= ctSSig; + CHECK(ctMD >= sizeof(STORAGEHEADER)); + pcMD = dac_cast<TADDR>(pSHdr) + sizeof(STORAGEHEADER); + ctMD -= sizeof(STORAGEHEADER); + WORD nStreams = VAL16(pSHdr->iStreams); + + // Storage streams checks (pcMD is a target pointer, so watch out) + PTR_STORAGESTREAM pStr = PTR_STORAGESTREAM((TADDR)pcMD); + PTR_STORAGESTREAM pSSOutOfRange = + PTR_STORAGESTREAM((TADDR)(pcMD + ctMD)); + size_t namelen; + WORD iStr; + PTR_STORAGESTREAM pSS; + for(iStr = 1, pSS = pStr; iStr <= nStreams; iStr++) + { + CHECK(pSS < pSSOutOfRange); + CHECK(pSS + 1 <= pSSOutOfRange); + + for(namelen=0; (namelen<32)&&(pSS->rcName[namelen]!=0); namelen++); + CHECK((0 < namelen)&&(namelen < 32)); + + // Is it ngen image? + if (!HasNativeHeader()) + { + // Forbid HOT_MODEL_STREAM for non-ngen images + CHECK(strcmp(pSS->rcName, HOT_MODEL_STREAM_A) != 0); + } + + pcMD = dac_cast<TADDR>(NextStorageStream(pSS)); + ctMD -= (COUNT_T)(pcMD - dac_cast<TADDR>(pSS)); + + pSS = PTR_STORAGESTREAM((TADDR)pcMD); + } + + // At this moment, pcMD is pointing past the last stream header + // and ctMD contains total size left for streams per se + // Now, check the offsets and sizes of streams + COUNT_T ctStreamsBegin = (COUNT_T)(pcMD - dac_cast<TADDR>(pStorageSig)); // min.possible offset + COUNT_T ctSS, ctSSbegin, ctSSend = 0; + for(iStr = 1, pSS = pStr; iStr <= nStreams; iStr++,pSS = NextStorageStream(pSS)) + { + ctSSbegin = (COUNT_T)VAL32(pSS->iOffset); + CHECK(ctStreamsBegin <= ctSSbegin); + CHECK(ctSSbegin < ctMDStreamSize); + + ctSS = (COUNT_T)VAL32(pSS->iSize); + CHECK(ctMD >= ctSS); + CHECK(ClrSafeInt<COUNT_T>::addition(ctSSbegin, ctSS, ctSSend)); + CHECK(ctSSend <= ctMDStreamSize); + ctMD -= ctSS; + + // Check stream overlap + PTR_STORAGESTREAM pSSprior; + for(pSSprior=pStr; pSSprior < pSS; pSSprior = NextStorageStream(pSSprior)) + { + COUNT_T ctSSprior_end = 0; + CHECK(ClrSafeInt<COUNT_T>::addition((COUNT_T)VAL32(pSSprior->iOffset), (COUNT_T)VAL32(pSSprior->iSize), ctSSprior_end)); + CHECK((ctSSbegin >= ctSSprior_end)||(ctSSend <= (COUNT_T)VAL32(pSSprior->iOffset))); + } + } + } //end if(pcMD != NULL) + + const_cast<PEDecoder *>(this)->m_flags |= FLAG_COR_CHECKED; + + CHECK_OK; +} + + + +// This function exists to provide compatibility between two different native image +// (NGEN) formats. In particular, the manifest metadata blob and the full metadata +// blob swapped locations from 3.5RTM to 3.5SP1. The logic here is to look at the +// runtime version embedded in the native image, to determine which format it is. +IMAGE_DATA_DIRECTORY *PEDecoder::GetMetaDataHelper(METADATA_SECTION_TYPE type) const +{ + CONTRACT(IMAGE_DATA_DIRECTORY *) + { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); +#ifdef FEATURE_PREJIT + PRECONDITION(type == METADATA_SECTION_FULL || type == METADATA_SECTION_MANIFEST); + PRECONDITION(type != METADATA_SECTION_MANIFEST || HasNativeHeader()); +#else // FEATURE_PREJIT + PRECONDITION(type == METADATA_SECTION_FULL); +#endif // FEATURE_PREJIT + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDirRet = &GetCorHeader()->MetaData; + +#ifdef FEATURE_PREJIT + // For backward compatibility reasons, we must be able to locate the metadata in all v2 native images as + // well as current version of native images. This is needed by mdbg.exe for SxS debugging scenarios. + // Specifically, mdbg.exe can be used to debug v2 managed applications, and need to be able to find + // metadata in v2 native images. Therefore, the location of the data we need to locate the metadata must + // never be moved. Here are some asserts to ensure that. + // IMAGE_COR20_HEADER should be stable since it is defined in ECMA. Verify a coupld of fields we use: + _ASSERTE(offsetof(IMAGE_COR20_HEADER, MetaData) == 8); + _ASSERTE(offsetof(IMAGE_COR20_HEADER, ManagedNativeHeader) == 64); + // We use a couple of fields in CORCOMPILE_HEADER. + _ASSERTE(offsetof(CORCOMPILE_HEADER, VersionInfo) == 40); + _ASSERTE(offsetof(CORCOMPILE_HEADER, ManifestMetaData) == 88); + // And we use four version fields in CORCOMPILE_VERSION_INFO. + _ASSERTE(offsetof(CORCOMPILE_VERSION_INFO, wVersionMajor) == 4); + _ASSERTE(offsetof(CORCOMPILE_VERSION_INFO, wVersionMinor) == 6); + _ASSERTE(offsetof(CORCOMPILE_VERSION_INFO, wVersionBuildNumber) == 8); + _ASSERTE(offsetof(CORCOMPILE_VERSION_INFO, wVersionPrivateBuildNumber) == 10); + + // Visual Studio took dependency on crossgen /CreatePDB returning COR_E_NI_AND_RUNTIME_VERSION_MISMATCH + // when crossgen and the native image come from different runtimes. In order to reach error path that returns + // COR_E_NI_AND_RUNTIME_VERSION_MISMATCH in this case, size of CORCOMPILE_HEADER has to remain constant, + // and the offset of PEKind and Machine fields inside CORCOMPILE_HEADER also have to remain constant, to pass earlier + // checks that lead to different error codes. See Windows Phone Blue Bug #45406 for details. + _ASSERTE(sizeof(CORCOMPILE_HEADER) == 160 + sizeof(TADDR)); + _ASSERTE(offsetof(CORCOMPILE_HEADER, PEKind) == 108 + sizeof(TADDR)); + _ASSERTE(offsetof(CORCOMPILE_HEADER, Machine) == 116 + sizeof(TADDR)); + + // Handle NGEN format; otherwise, there is only one MetaData section in the + // COR_HEADER and so the value of pDirRet is correct + if (HasNativeHeader()) + { + + if (type == METADATA_SECTION_MANIFEST) + pDirRet = &GetNativeHeader()->ManifestMetaData; + + + } + +#endif // FEATURE_PREJIT + + RETURN pDirRet; +} + +PTR_CVOID PEDecoder::GetMetadata(COUNT_T *pSize) const +{ + CONTRACT(PTR_CVOID) + { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = GetMetaDataHelper(METADATA_SECTION_FULL); + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN dac_cast<PTR_VOID>(GetDirectoryData(pDir)); +} + +const void *PEDecoder::GetResources(COUNT_T *pSize) const +{ + CONTRACT(const void *) + { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->Resources; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN (void *)GetDirectoryData(pDir); +} + +CHECK PEDecoder::CheckResource(COUNT_T offset) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckCorHeader()); + } + CONTRACT_CHECK_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->Resources; + + CHECK(CheckOverflow(VAL32(pDir->VirtualAddress), offset)); + + RVA rva = VAL32(pDir->VirtualAddress) + offset; + + // Make sure we have at least enough data for a length + CHECK(CheckRva(rva, sizeof(DWORD))); + + // Make sure resource is within resource section + CHECK(CheckBounds(VAL32(pDir->VirtualAddress), VAL32(pDir->Size), + rva + sizeof(DWORD), GET_UNALIGNED_VAL32((LPVOID)GetRvaData(rva)))); + + CHECK_OK; +} + +const void *PEDecoder::GetResource(COUNT_T offset, COUNT_T *pSize) const +{ + CONTRACT(const void *) + { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->Resources; + + // 403571: Prefix complained correctly about need to always perform rva check + if (CheckResource(offset) == FALSE) + return NULL; + + void * resourceBlob = (void *)GetRvaData(VAL32(pDir->VirtualAddress) + offset); + // Holds if CheckResource(offset) == TRUE + PREFIX_ASSUME(resourceBlob != NULL); + + if (pSize != NULL) + *pSize = GET_UNALIGNED_VAL32(resourceBlob); + + RETURN (const void *) ((BYTE*)resourceBlob+sizeof(DWORD)); +} + +BOOL PEDecoder::HasManagedEntryPoint() const +{ + CONTRACTL { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + ULONG flags = GetCorHeader()->Flags; + return (!(flags & VAL32(COMIMAGE_FLAGS_NATIVE_ENTRYPOINT)) && + (!IsNilToken(GetEntryPointToken()))); +} + +ULONG PEDecoder::GetEntryPointToken() const +{ + CONTRACT(ULONG) + { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + RETURN VAL32(IMAGE_COR20_HEADER_FIELD(*GetCorHeader(), EntryPointToken)); +} + +IMAGE_COR_VTABLEFIXUP *PEDecoder::GetVTableFixups(COUNT_T *pCount) const +{ + CONTRACT(IMAGE_COR_VTABLEFIXUP *) + { + INSTANCE_CHECK; + PRECONDITION(CheckCorHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->VTableFixups; + + if (pCount != NULL) + *pCount = VAL32(pDir->Size)/sizeof(IMAGE_COR_VTABLEFIXUP); + + RETURN PTR_IMAGE_COR_VTABLEFIXUP(GetDirectoryData(pDir)); +} + +CHECK PEDecoder::CheckILOnly() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (m_flags & FLAG_IL_ONLY_CHECKED) + CHECK_OK; + + CHECK(CheckCorHeader()); + + if (HasReadyToRunHeader()) + { + // Pretend R2R images are IL-only + const_cast<PEDecoder *>(this)->m_flags |= FLAG_IL_ONLY_CHECKED; + CHECK_OK; + } + + // Allow only verifiable directories. + + static int s_allowedBitmap = + ((1 << (IMAGE_DIRECTORY_ENTRY_IMPORT )) | + (1 << (IMAGE_DIRECTORY_ENTRY_RESOURCE )) | + (1 << (IMAGE_DIRECTORY_ENTRY_SECURITY )) | + (1 << (IMAGE_DIRECTORY_ENTRY_BASERELOC)) | + (1 << (IMAGE_DIRECTORY_ENTRY_DEBUG )) | + (1 << (IMAGE_DIRECTORY_ENTRY_IAT )) | + (1 << (IMAGE_DIRECTORY_ENTRY_COMHEADER))); + + + + + for (UINT32 entry=0; entry<GetNumberOfRvaAndSizes(); ++entry) + { + if (Has32BitNTHeaders()) + CheckBounds(dac_cast<PTR_CVOID>(&GetNTHeaders32()->OptionalHeader), + GetNTHeaders32()->FileHeader.SizeOfOptionalHeader, + dac_cast<PTR_CVOID>(GetNTHeaders32()->OptionalHeader.DataDirectory + entry), + sizeof(IMAGE_DATA_DIRECTORY)); + else + CheckBounds(dac_cast<PTR_CVOID>(&GetNTHeaders64()->OptionalHeader), + GetNTHeaders32()->FileHeader.SizeOfOptionalHeader, + dac_cast<PTR_CVOID>(GetNTHeaders64()->OptionalHeader.DataDirectory + entry), + sizeof(IMAGE_DATA_DIRECTORY)); + + if (HasDirectoryEntry(entry)) + { + CHECK((s_allowedBitmap & (1 << entry)) != 0); + if (entry!=IMAGE_DIRECTORY_ENTRY_SECURITY) //ignored by OS loader + CHECK(CheckDirectoryEntry(entry,IMAGE_SCN_MEM_SHARED,NULL_NOT_OK)); + } + } + if (HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT) || + HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_BASERELOC) || + FindNTHeaders()->OptionalHeader.AddressOfEntryPoint != 0) + { + // When the image is LoadLibrary'd, we whack the import, IAT directories and the entrypoint. We have to relax + // the verification for mapped images. Ideally, we would only do it for a post-LoadLibrary image. + if (!IsMapped() || (HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT) || HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_BASERELOC))) + { + CHECK(CheckILOnlyImportDlls()); + CHECK(CheckILOnlyBaseRelocations()); + } + +#ifdef TARGET_X86 + if (!IsMapped()) + { + CHECK(CheckILOnlyEntryPoint()); + } +#endif + } + + // Check some section characteristics + IMAGE_NT_HEADERS *pNT = FindNTHeaders(); + IMAGE_SECTION_HEADER *section = FindFirstSection(pNT); + IMAGE_SECTION_HEADER *sectionEnd = section + VAL16(pNT->FileHeader.NumberOfSections); + while (section < sectionEnd) + { + // Don't allow shared sections for IL-only images + CHECK(!(section->Characteristics & IMAGE_SCN_MEM_SHARED)); + + // Be sure that we have some access to the section. Note that this test assumes that + // execute or write permissions will also let us read the section. If that is found to be an + // incorrect assumption, this will need to be modified. + CHECK((section->Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) != 0); + section++; + } + + // For EXE, check that OptionalHeader.Win32VersionValue is zero. When this value is non-zero, GetVersionEx + // returns PE supplied values, rather than native OS values; the runtime relies on knowing the actual + // OS version. + if (!IsDll()) + { + CHECK(GetWin32VersionValue() == 0); + } + + + const_cast<PEDecoder *>(this)->m_flags |= FLAG_IL_ONLY_CHECKED; + + CHECK_OK; +} + + +CHECK PEDecoder::CheckILOnlyImportDlls() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + // The only allowed DLL Imports are MscorEE.dll:_CorExeMain,_CorDllMain + +#ifdef HOST_64BIT + // On win64, when the image is LoadLibrary'd, we whack the import and IAT directories. We have to relax + // the verification for mapped images. Ideally, we would only do it for a post-LoadLibrary image. + if (IsMapped() && !HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT)) + CHECK_OK; +#endif + + CHECK(HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT)); + CHECK(CheckDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_SCN_MEM_WRITE)); + + // Get the import directory entry + PIMAGE_DATA_DIRECTORY pDirEntryImport = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_IMPORT); + CHECK(pDirEntryImport != NULL); + PREFIX_ASSUME(pDirEntryImport != NULL); + + // There should be space for 2 entries. (mscoree and NULL) + CHECK(VAL32(pDirEntryImport->Size) >= (2 * sizeof(IMAGE_IMPORT_DESCRIPTOR))); + + // Get the import data + PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR) GetDirectoryData(pDirEntryImport); + CHECK(pID != NULL); + PREFIX_ASSUME(pID != NULL); + + // Entry 0: ILT, Name, IAT must be be non-null. Forwarder, DateTime should be NULL. + CHECK( IMAGE_IMPORT_DESC_FIELD(pID[0], Characteristics) != 0 + && pID[0].TimeDateStamp == 0 + && (pID[0].ForwarderChain == 0 || pID[0].ForwarderChain == static_cast<ULONG>(-1)) + && pID[0].Name != 0 + && pID[0].FirstThunk != 0); + + // Entry 1: must be all nulls. + CHECK( IMAGE_IMPORT_DESC_FIELD(pID[1], Characteristics) == 0 + && pID[1].TimeDateStamp == 0 + && pID[1].ForwarderChain == 0 + && pID[1].Name == 0 + && pID[1].FirstThunk == 0); + + // Ensure the RVA of the name plus its length is valid for this image + UINT nameRVA = VAL32(pID[0].Name); + CHECK(CheckRva(nameRVA, (COUNT_T) sizeof("mscoree.dll"))); + + // Make sure the name is equal to mscoree + CHECK(SString::_stricmp( (char *)GetRvaData(nameRVA), "mscoree.dll") == 0); + + // Check the Hint/Name table. + CHECK(CheckILOnlyImportByNameTable(VAL32(IMAGE_IMPORT_DESC_FIELD(pID[0], OriginalFirstThunk)))); + + // The IAT needs to be checked only for size. + CHECK(CheckRva(VAL32(pID[0].FirstThunk), 2*sizeof(UINT32))); + + CHECK_OK; +} + +CHECK PEDecoder::CheckILOnlyImportByNameTable(RVA rva) const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + // Check if we have enough space to hold 2 DWORDS + CHECK(CheckRva(rva, 2*sizeof(UINT32))); + + UINT32 UNALIGNED *pImportArray = (UINT32 UNALIGNED *) GetRvaData(rva); + + CHECK(GET_UNALIGNED_VAL32(&pImportArray[0]) != 0); + CHECK(GET_UNALIGNED_VAL32(&pImportArray[1]) == 0); + + UINT32 importRVA = GET_UNALIGNED_VAL32(&pImportArray[0]); + + // First bit Set implies Ordinal lookup + CHECK((importRVA & 0x80000000) == 0); + +#define DLL_NAME "_CorDllMain" +#define EXE_NAME "_CorExeMain" + + static_assert_no_msg(sizeof(DLL_NAME) == sizeof(EXE_NAME)); + + // Check if we have enough space to hold 2 bytes + + // _CorExeMain or _CorDllMain and a NULL char + CHECK(CheckRva(importRVA, offsetof(IMAGE_IMPORT_BY_NAME, Name) + sizeof(DLL_NAME))); + + IMAGE_IMPORT_BY_NAME *import = (IMAGE_IMPORT_BY_NAME*) GetRvaData(importRVA); + + CHECK(SString::_stricmp((char *) import->Name, DLL_NAME) == 0 || _stricmp((char *) import->Name, EXE_NAME) == 0); + + CHECK_OK; +} + +#ifdef TARGET_X86 +// jmp dword ptr ds:[XXXX] +#define JMP_DWORD_PTR_DS_OPCODE { 0xFF, 0x25 } +#define JMP_DWORD_PTR_DS_OPCODE_SIZE 2 // Size of opcode +#define JMP_SIZE 6 // Size of opcode + operand +#endif + +CHECK PEDecoder::CheckILOnlyBaseRelocations() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_BASERELOC)) + { + // We require base relocs for dlls. + CHECK(!IsDll()); + + CHECK((FindNTHeaders()->FileHeader.Characteristics & VAL16(IMAGE_FILE_RELOCS_STRIPPED)) != 0); + } + else + { + CHECK((FindNTHeaders()->FileHeader.Characteristics & VAL16(IMAGE_FILE_RELOCS_STRIPPED)) == 0); + + CHECK(CheckDirectoryEntry(IMAGE_DIRECTORY_ENTRY_BASERELOC, IMAGE_SCN_MEM_WRITE)); + + IMAGE_DATA_DIRECTORY *pRelocDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_BASERELOC); + + IMAGE_SECTION_HEADER *section = RvaToSection(VAL32(pRelocDir->VirtualAddress)); + CHECK(section != NULL); + CHECK((section->Characteristics & VAL32(IMAGE_SCN_MEM_READ))!=0); + + IMAGE_BASE_RELOCATION *pReloc = (IMAGE_BASE_RELOCATION *) + GetRvaData(VAL32(pRelocDir->VirtualAddress)); + + // 403569: PREfix correctly complained about pReloc being possibly NULL + CHECK(pReloc != NULL); + CHECK(VAL32(pReloc->SizeOfBlock) == VAL32(pRelocDir->Size)); + + UINT16 *pRelocEntry = (UINT16 *) (pReloc + 1); + UINT16 *pRelocEntryEnd = (UINT16 *) ((BYTE *) pReloc + VAL32(pReloc->SizeOfBlock)); + if(FindNTHeaders()->FileHeader.Machine == VAL16(IMAGE_FILE_MACHINE_IA64)) + { + // Exactly 2 Reloc records, both IMAGE_REL_BASED_DIR64 + CHECK(VAL32(pReloc->SizeOfBlock) >= (sizeof(IMAGE_BASE_RELOCATION)+2*sizeof(UINT16))); + CHECK((VAL16(pRelocEntry[0]) & 0xF000) == (IMAGE_REL_BASED_DIR64 << 12)); + pRelocEntry++; + CHECK((VAL16(pRelocEntry[0]) & 0xF000) == (IMAGE_REL_BASED_DIR64 << 12)); + } + else + { + // Only one Reloc record is expected + CHECK(VAL32(pReloc->SizeOfBlock) >= (sizeof(IMAGE_BASE_RELOCATION)+sizeof(UINT16))); + if(FindNTHeaders()->FileHeader.Machine == VAL16(IMAGE_FILE_MACHINE_AMD64)) + CHECK((VAL16(pRelocEntry[0]) & 0xF000) == (IMAGE_REL_BASED_DIR64 << 12)); + else + CHECK((VAL16(pRelocEntry[0]) & 0xF000) == (IMAGE_REL_BASED_HIGHLOW << 12)); + } + + while (++pRelocEntry < pRelocEntryEnd) + { + // NULL padding entries are allowed + CHECK((VAL16(pRelocEntry[0]) & 0xF000) == IMAGE_REL_BASED_ABSOLUTE); + } + } + + CHECK_OK; +} + +#ifdef TARGET_X86 +CHECK PEDecoder::CheckILOnlyEntryPoint() const +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + CHECK(FindNTHeaders()->OptionalHeader.AddressOfEntryPoint != 0); + + if(FindNTHeaders()->FileHeader.Machine == VAL16(IMAGE_FILE_MACHINE_I386)) + { + // EntryPoint should be a jmp dword ptr ds:[XXXX] instruction. + // XXXX should be RVA of the first and only entry in the IAT. + + CHECK(CheckRva(VAL32(FindNTHeaders()->OptionalHeader.AddressOfEntryPoint), JMP_SIZE)); + + BYTE *stub = (BYTE *) GetRvaData(VAL32(FindNTHeaders()->OptionalHeader.AddressOfEntryPoint)); + + static const BYTE s_DllOrExeMain[] = JMP_DWORD_PTR_DS_OPCODE; + + // 403570: prefix complained about stub being possibly NULL. + // Unsure here. PREFIX_ASSUME might be also correct as indices are + // verified in the above CHECK statement. + CHECK(stub != NULL); + CHECK(memcmp(stub, s_DllOrExeMain, JMP_DWORD_PTR_DS_OPCODE_SIZE) == 0); + + // Verify target of jump - it should be first entry in the IAT. + + PIMAGE_IMPORT_DESCRIPTOR pID = + (PIMAGE_IMPORT_DESCRIPTOR) GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_IMPORT); + + UINT32 va = * (UINT32 *) (stub + JMP_DWORD_PTR_DS_OPCODE_SIZE); + + CHECK(VAL32(pID[0].FirstThunk) == (va - (SIZE_T) GetPreferredBase())); + } + + CHECK_OK; +} +#endif // TARGET_X86 + +#ifndef DACCESS_COMPILE + +void PEDecoder::LayoutILOnly(void *base, BOOL allowFullPE) const +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(allowFullPE || CheckILOnlyFormat()); + PRECONDITION(CheckZeroedMemory(base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfImage))); + // Ideally we would require the layout address to honor the section alignment constraints. + // However, we do have 8K aligned IL only images which we load on 32 bit platforms. In this + // case, we can only guarantee OS page alignment (which after all, is good enough.) + PRECONDITION(CheckAligned((SIZE_T)base, GetOsPageSize())); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + // We're going to copy everything first, and write protect what we need to later. + + // First, copy headers + CopyMemory(base, (void *)m_base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfHeaders)); + + // Now, copy all sections to appropriate virtual address + + IMAGE_SECTION_HEADER *sectionStart = IMAGE_FIRST_SECTION(FindNTHeaders()); + IMAGE_SECTION_HEADER *sectionEnd = sectionStart + VAL16(FindNTHeaders()->FileHeader.NumberOfSections); + + IMAGE_SECTION_HEADER *section = sectionStart; + while (section < sectionEnd) + { + // Raw data may be less than section size if tail is zero, but may be more since VirtualSize is + // not padded. + DWORD size = min(VAL32(section->SizeOfRawData), VAL32(section->Misc.VirtualSize)); + + CopyMemory((BYTE *) base + VAL32(section->VirtualAddress), (BYTE *) m_base + VAL32(section->PointerToRawData), size); + + // Note that our memory is zeroed already, so no need to initialize any tail. + + section++; + } + + // Apply write protection to copied headers + DWORD oldProtection; + if (!ClrVirtualProtect((void *) base, VAL32(FindNTHeaders()->OptionalHeader.SizeOfHeaders), + PAGE_READONLY, &oldProtection)) + ThrowLastError(); + + // Finally, apply proper protection to copied sections + for (section = sectionStart; section < sectionEnd; section++) + { + // Add appropriate page protection. +#if defined(CROSSGEN_COMPILE) || defined(TARGET_UNIX) + if (section->Characteristics & IMAGE_SCN_MEM_WRITE) + continue; + + DWORD newProtection = PAGE_READONLY; +#else + DWORD newProtection = section->Characteristics & IMAGE_SCN_MEM_EXECUTE ? + PAGE_EXECUTE_READ : + section->Characteristics & IMAGE_SCN_MEM_WRITE ? + PAGE_READWRITE : + PAGE_READONLY; +#endif + + if (!ClrVirtualProtect((void*)((BYTE*)base + VAL32(section->VirtualAddress)), + VAL32(section->Misc.VirtualSize), + newProtection, &oldProtection)) + { + ThrowLastError(); + } + } + + RETURN; +} + +#endif // #ifndef DACCESS_COMPILE + +bool ReadResourceDirectoryHeader(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, IMAGE_RESOURCE_DIRECTORY_ENTRY** ppDirectoryEntries, IMAGE_RESOURCE_DIRECTORY **ppResourceDirectory) +{ + if (!pDecoder->CheckRva(rva, sizeof(IMAGE_RESOURCE_DIRECTORY))) + { + return false; + } + + *ppResourceDirectory = (IMAGE_RESOURCE_DIRECTORY *)pDecoder->GetRvaData(rva); + + // Check to see if entire resource directory is accessible + if (!pDecoder->CheckRva(rva + sizeof(IMAGE_RESOURCE_DIRECTORY), + (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * (*ppResourceDirectory)->NumberOfNamedEntries) + + (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * (*ppResourceDirectory)->NumberOfIdEntries))) + { + return false; + } + + *ppDirectoryEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)pDecoder->GetRvaData(rva + sizeof(IMAGE_RESOURCE_DIRECTORY)); + return true; +} + +bool ReadNameFromResourceDirectoryEntry(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries, DWORD iEntry, DWORD *pNameUInt, WCHAR **pNameStr) +{ + *pNameStr = NULL; + *pNameUInt = 0; + + if (!IS_INTRESOURCE(pDirectoryEntries[iEntry].Name)) + { + DWORD entryName = pDirectoryEntries[iEntry].Name; + if (!(entryName & IMAGE_RESOURCE_NAME_IS_STRING)) + return false; + DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection; + + if (!pDecoder->CheckRva(entryNameRva, sizeof(WORD))) + return false; + + size_t entryNameLen = *(WORD*)pDecoder->GetRvaData(entryNameRva); + if (!pDecoder->CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen)))) + return false; + *pNameStr = new(nothrow) WCHAR[entryNameLen + 1]; + if ((*pNameStr) == NULL) + return false; + memcpy((*pNameStr), (WCHAR*)pDecoder->GetRvaData(entryNameRva + sizeof(WORD)), entryNameLen * sizeof(WCHAR)); + (*pNameStr)[entryNameLen] = 0; + } + else + { + DWORD name = pDirectoryEntries[iEntry].Name; + if (!IS_INTRESOURCE(name)) + return false; + + *pNameUInt = name; + } + + return true; +} + +DWORD ReadResourceDirectory(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pisDirectory) +{ + *pisDirectory = FALSE; + + IMAGE_RESOURCE_DIRECTORY* pResourceDirectory; + IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries; + if (!ReadResourceDirectoryHeader(pDecoder, rvaOfResourceSection, rva, &pDirectoryEntries, &pResourceDirectory)) + { + return 0; + } + + // A fast implementation of resource lookup uses a binary search, but our needs are simple, and a linear search + // is easier to prove correct, so do that instead. + DWORD iEntryCount = (DWORD)pResourceDirectory->NumberOfNamedEntries + (DWORD)pResourceDirectory->NumberOfIdEntries; + + for (DWORD iEntry = 0; iEntry < iEntryCount; iEntry++) + { + BOOL foundEntry = FALSE; + + if (IS_INTRESOURCE(name)) + { + // name is id + if (pDirectoryEntries[iEntry].Name == (DWORD)(SIZE_T)name) + foundEntry = TRUE; + } + else + { + // name is string + DWORD entryName = pDirectoryEntries[iEntry].Name; + if (!(entryName & IMAGE_RESOURCE_NAME_IS_STRING)) + continue; + + DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection; + + if (!pDecoder->CheckRva(entryNameRva, sizeof(WORD))) + return 0; + + size_t entryNameLen = *(WORD*)pDecoder->GetRvaData(entryNameRva); + if (wcslen(name) != entryNameLen) + continue; + + if (!pDecoder->CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen)))) + return 0; + + if (memcmp((WCHAR*)pDecoder->GetRvaData(entryNameRva + sizeof(WORD)), name, entryNameLen * sizeof(WCHAR)) == 0) + foundEntry = TRUE; + } + + if (!foundEntry) + continue; + + *pisDirectory = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY); + DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + DWORD dataRva = rvaOfResourceSection + offsetToData; + return dataRva; + } + + return 0; +} + +DWORD ReadResourceDataEntry(const PEDecoder *pDecoder, DWORD rva, COUNT_T *pSize) +{ + *pSize = 0; + + if (!pDecoder->CheckRva(rva, sizeof(IMAGE_RESOURCE_DATA_ENTRY))) + { + return 0; + } + + IMAGE_RESOURCE_DATA_ENTRY *pDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *)pDecoder->GetRvaData(rva); + *pSize = pDataEntry->Size; + return pDataEntry->OffsetToData; +} + +void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSize /*=NULL*/) const +{ + CONTRACTL { + INSTANCE_CHECK; + PRECONDITION(IsMapped()); + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + COUNT_T sizeUnused = 0; // Use this variable if pSize is null + if (pSize == NULL) + pSize = &sizeUnused; + + *pSize = 0; + + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return NULL; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return NULL; + + BOOL isDirectory = FALSE; + DWORD nameTableRva = ReadResourceDirectory(this, pDir->VirtualAddress, pDir->VirtualAddress, lpType, &isDirectory); + + if (!isDirectory) + return NULL; + + if (nameTableRva == 0) + return NULL; + + DWORD languageTableRva = ReadResourceDirectory(this, pDir->VirtualAddress, nameTableRva, lpName, &isDirectory); + if (!isDirectory) + return NULL; + + if (languageTableRva == 0) + return NULL; + + // This api is designed to find resources with LANGID = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) + // This translates to LANGID 0 as the initial lookup point, which is sufficient for the needs of this api at this time + // (FindResource in the Windows api implements a large number of fallback paths which this api does not implement) + + DWORD resourceDataEntryRva = ReadResourceDirectory(this, pDir->VirtualAddress, languageTableRva, 0, &isDirectory); + if (isDirectory) // This must not be a resource directory itself + return NULL; + + if (resourceDataEntryRva == 0) + return NULL; + + DWORD resourceDataRva = ReadResourceDataEntry(this, resourceDataEntryRva, pSize); + if (!CheckRva(resourceDataRva, *pSize)) + { + *pSize = 0; + return NULL; + } + + return (void*)GetRvaData(resourceDataRva); +} + +typedef bool (*PEDecoder_EnumerateResourceTableFunction)(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context); + +struct ResourceEnumerateNamesState +{ + PEDecoder_ResourceNamesCallbackFunction namesCallback; + PEDecoder_ResourceCallbackFunction langIDcallback; + void *context; + LPCWSTR nameType; + LPCWSTR nameName; + PEDecoder_EnumerateResourceTableFunction callbackPerName; + PEDecoder_EnumerateResourceTableFunction callbackPerLangID; +}; + +struct ResourceEnumerateTypesState +{ + PEDecoder_ResourceTypesCallbackFunction callback; + void *context; + LPCWSTR nameType; +}; + +bool EnumerateWin32ResourceTable(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, DWORD rvaOfResourceTable, PEDecoder_EnumerateResourceTableFunction resourceTableEnumerator, void *context) +{ + IMAGE_RESOURCE_DIRECTORY* pResourceDirectory; + IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries; + if (!ReadResourceDirectoryHeader(pDecoder, rvaOfResourceSection, rvaOfResourceTable, &pDirectoryEntries, &pResourceDirectory)) + { + return false; + } + + DWORD iEntryCount = (DWORD)pResourceDirectory->NumberOfNamedEntries + (DWORD)pResourceDirectory->NumberOfIdEntries; + + for (DWORD iEntry = 0; iEntry < iEntryCount; iEntry++) + { + DWORD nameUInt; + NewArrayHolder<WCHAR> nameString; + if (!ReadNameFromResourceDirectoryEntry(pDecoder, rvaOfResourceSection, pDirectoryEntries, iEntry, &nameUInt, &nameString)) + return false; + + LPCWSTR name = MAKEINTRESOURCEW(nameUInt); + if (nameString != NULL) + name = &nameString[0]; + + bool isDirectory = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY); + DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + DWORD dataRva = rvaOfResourceSection + offsetToData; + + if (!resourceTableEnumerator(pDecoder, rvaOfResourceSection, isDirectory, name, dataRva, context)) + return false; + } + + return true; +} + +bool DoesResourceNameMatch(LPCWSTR nameA, LPCWSTR nameB) +{ + bool foundEntry = false; + + if (IS_INTRESOURCE(nameA)) + { + // name is id + if (nameA == nameB) + foundEntry = true; + } + else + { + // name is a string. + + // Check for name enumerated is an id. If so, it doesn't match, skip to next. + if (IS_INTRESOURCE(nameB)) + return false; + else + foundEntry = !wcscmp(nameB, nameA); + } + + return foundEntry; +} + +bool EnumerateLangIDs(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (isDirectory) + return false; + + // Only LangIDs are permitted here + if (!IS_INTRESOURCE(name)) + return false; + + if (dataRVA == 0) + return false; + + COUNT_T cbData; + DWORD resourceDataRva = ReadResourceDataEntry(pDecoder, dataRVA, &cbData); + if (!pDecoder->CheckRva(resourceDataRva, cbData)) + { + return false; + } + + BYTE *pData = (BYTE*)pDecoder->GetRvaData(resourceDataRva); + + return state->langIDcallback(state->nameName, state->nameType, (DWORD)(uintptr_t)name, pData, cbData, state->context); +} + + +bool EnumerateNames(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (!isDirectory) + return false; + + state->nameName = name; + return state->namesCallback(state->nameName, state->nameType, state->context); +} + +bool EnumerateNamesForLangID(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (!isDirectory) + return false; + + bool foundEntry = DoesResourceNameMatch(state->nameName, name); + + if (foundEntry) + return EnumerateWin32ResourceTable(pDecoder, rvaOfResourceSection, dataRVA, state->callbackPerLangID, context); + else + return true; // Keep scanning +} + + +bool EnumerateTypes(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateTypesState *state = (ResourceEnumerateTypesState*)context; + if (!isDirectory) + return false; + + state->nameType = name; + return state->callback(name, state->context); +} + +bool EnumerateTypesForNames(const PEDecoder *pDecoder, DWORD rvaOfResourceSection, bool isDirectory, LPCWSTR name, DWORD dataRVA, void *context) +{ + ResourceEnumerateNamesState *state = (ResourceEnumerateNamesState*)context; + if (!isDirectory) + return false; + + bool foundEntry = DoesResourceNameMatch(state->nameType, name); + + if (foundEntry) + return EnumerateWin32ResourceTable(pDecoder, rvaOfResourceSection, dataRVA, state->callbackPerName, context); + else + return true; // Keep scanning +} + + +bool PEDecoder::EnumerateWin32ResourceTypes(PEDecoder_ResourceTypesCallbackFunction callback, void* context) const +{ + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return true; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return true; + + DWORD rvaOfResourceSection = pDir->VirtualAddress; + + ResourceEnumerateTypesState state; + state.context = context; + state.callback = callback; + + return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypes, &state); +} + +bool PEDecoder::EnumerateWin32ResourceNames(LPCWSTR lpType, PEDecoder_ResourceNamesCallbackFunction callback, void* context) const +{ + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return true; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return true; + + DWORD rvaOfResourceSection = pDir->VirtualAddress; + + ResourceEnumerateNamesState state; + state.context = context; + state.namesCallback = callback; + state.langIDcallback = NULL; + state.nameType = lpType; + state.nameName = NULL; + state.callbackPerName = EnumerateNames; + state.callbackPerLangID = NULL; + + return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypesForNames, &state); +} + +bool PEDecoder::EnumerateWin32Resources(LPCWSTR lpName, LPCWSTR lpType, PEDecoder_ResourceCallbackFunction callback, void* context) const +{ + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return true; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); + + if (pDir->VirtualAddress == 0) + return true; + + DWORD rvaOfResourceSection = pDir->VirtualAddress; + + ResourceEnumerateNamesState state; + state.context = context; + state.namesCallback = NULL; + state.langIDcallback = callback; + state.nameType = lpType; + state.nameName = lpName; + state.callbackPerName = EnumerateNamesForLangID; + state.callbackPerLangID = EnumerateLangIDs; + + return EnumerateWin32ResourceTable(this, rvaOfResourceSection, rvaOfResourceSection, EnumerateTypesForNames, &state); +} + +BOOL PEDecoder::HasNativeHeader() const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + +#ifdef FEATURE_PREJIT + // Pretend that ready-to-run images do not have native header + RETURN (GetCorHeader() && ((GetCorHeader()->Flags & VAL32(COMIMAGE_FLAGS_IL_LIBRARY)) != 0) && !HasReadyToRunHeader()); +#else + RETURN FALSE; +#endif +} + +CHECK PEDecoder::CheckNativeHeader() const +{ + CONTRACT_CHECK + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_CHECK_END; + +#ifdef FEATURE_PREJIT + if (m_flags & FLAG_NATIVE_CHECKED) + CHECK_OK; + + CHECK(CheckCorHeader()); + + CHECK(HasNativeHeader()); + + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->ManagedNativeHeader; + + CHECK(CheckDirectory(pDir)); + CHECK(VAL32(pDir->Size) == sizeof(CORCOMPILE_HEADER)); + +#if 0 + // We want to be sure to not trigger these checks when loading a native + // image in a retail build + + // And we do not want to trigger these checks in debug builds either to avoid debug/retail behavior + // differences. + + PTR_CORCOMPILE_HEADER pHeader = PTR_CORCOMPILE_HEADER((TADDR)GetDirectoryData(pDir)); + + CHECK(CheckDirectory(&pHeader->EEInfoTable)); + CHECK(pHeader->EEInfoTable.Size == sizeof(CORCOMPILE_EE_INFO_TABLE)); + + CHECK(CheckDirectory(&pHeader->HelperTable, 0, NULL_OK)); + // @todo: verify helper table size + + CHECK(CheckDirectory(&pHeader->ImportSections, 0, NULL_OK)); + // @todo verify import sections + + CHECK(CheckDirectory(&pHeader->ImportTable, 0, NULL_OK)); + // @todo verify import table + + CHECK(CheckDirectory(&pHeader->VersionInfo, 0, NULL_OK)); // no version header for precompiled netmodules + CHECK(pHeader->VersionInfo.Size == 0 + || (pHeader->VersionInfo.Size == sizeof(CORCOMPILE_VERSION_INFO) && + // Sanity check that we are not just pointing to zeroed-out memory + ((CORCOMPILE_VERSION_INFO*)PTR_READ(GetDirectoryData(&pHeader->VersionInfo), sizeof(CORCOMPILE_VERSION_INFO)))->wOSMajorVersion != 0)); + + CHECK(CheckDirectory(&pHeader->Dependencies, 0, NULL_OK)); // no version header for precompiled netmodules + CHECK(pHeader->Dependencies.Size % sizeof(CORCOMPILE_DEPENDENCY) == 0); + + CHECK(CheckDirectory(&pHeader->DebugMap, 0, NULL_OK)); + CHECK(pHeader->DebugMap.Size % sizeof(CORCOMPILE_DEBUG_RID_ENTRY) == 0); + + // GetPersistedModuleImage() + CHECK(CheckDirectory(&pHeader->ModuleImage)); + CHECK(pHeader->ModuleImage.Size > 0); // sizeof(Module) if we knew it here + + CHECK(CheckDirectory(&pHeader->CodeManagerTable)); + CHECK(pHeader->CodeManagerTable.Size == sizeof(CORCOMPILE_CODE_MANAGER_ENTRY)); + + PTR_CORCOMPILE_CODE_MANAGER_ENTRY pEntry = PTR_CORCOMPILE_CODE_MANAGER_ENTRY((TADDR)GetDirectoryData(&pHeader->CodeManagerTable)); + CHECK(CheckDirectory(&pEntry->HotCode, IMAGE_SCN_MEM_WRITE, NULL_OK)); + CHECK(CheckDirectory(&pEntry->Code, IMAGE_SCN_MEM_WRITE, NULL_OK)); + CHECK(CheckDirectory(&pEntry->ColdCode, IMAGE_SCN_MEM_WRITE, NULL_OK)); + + CHECK(CheckDirectory(&pHeader->ProfileDataList, 0, NULL_OK)); + CHECK(pHeader->ProfileDataList.Size >= sizeof(CORCOMPILE_METHOD_PROFILE_LIST) + || pHeader->ProfileDataList.Size == 0); + +#endif + + const_cast<PEDecoder *>(this)->m_flags |= FLAG_NATIVE_CHECKED; + +#else // FEATURE_PREJIT + CHECK(false); +#endif // FEATURE_PREJIT + + CHECK_OK; +} + +READYTORUN_HEADER * PEDecoder::FindReadyToRunHeader() const +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->ManagedNativeHeader; + + if (VAL32(pDir->Size) >= sizeof(READYTORUN_HEADER) && CheckDirectory(pDir)) + { + PTR_READYTORUN_HEADER pHeader = PTR_READYTORUN_HEADER((TADDR)GetDirectoryData(pDir)); + if (pHeader->Signature == READYTORUN_SIGNATURE) + { + const_cast<PEDecoder*>(this)->m_pReadyToRunHeader = pHeader; + return pHeader; + } + } + + const_cast<PEDecoder *>(this)->m_flags |= FLAG_HAS_NO_READYTORUN_HEADER; + return NULL; +} + +#ifndef DACCESS_COMPILE +void *PEDecoder::GetExport(LPCSTR exportName) const +{ + // Get the export directory entry + PIMAGE_DATA_DIRECTORY pExportDirectoryEntry = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT); + if (pExportDirectoryEntry->VirtualAddress == 0 || pExportDirectoryEntry->Size == 0) + { + return NULL; + } + + uint8_t *imageBase = (uint8_t *)GetBase(); + const IMAGE_EXPORT_DIRECTORY *pExportDir = (const IMAGE_EXPORT_DIRECTORY *)GetDirectoryData(pExportDirectoryEntry); + + uint32_t namePointerCount = VAL32(pExportDir->NumberOfNames); + uint32_t addressTableRVA = VAL32(pExportDir->AddressOfFunctions); + uint32_t namePointersRVA = VAL32(pExportDir->AddressOfNames); + + for (uint32_t nameIndex = 0; nameIndex < namePointerCount; nameIndex++) + { + uint32_t namePointerRVA = VAL32(*(const uint32_t *)&imageBase[namePointersRVA + sizeof(uint32_t) * nameIndex]); + if (namePointerRVA != 0) + { + const char *namePointer = (const char *)&imageBase[namePointerRVA]; + if (!strcmp(namePointer, exportName)) + { + uint32_t exportRVA = VAL32(*(const uint32_t *)&imageBase[addressTableRVA + sizeof(uint32_t) * nameIndex]); + return &imageBase[exportRVA]; + } + } + } + + return NULL; +} +#endif + +// +// code:PEDecoder::CheckILMethod and code:PEDecoder::ComputeILMethodSize really belong to +// file:..\inc\corhlpr.cpp. Unfortunately, corhlpr.cpp is public header file that cannot be +// properly DACized and have other dependencies on the rest of the CLR. +// + +typedef DPTR(COR_ILMETHOD_TINY) PTR_COR_ILMETHOD_TINY; +typedef DPTR(COR_ILMETHOD_FAT) PTR_COR_ILMETHOD_FAT; +typedef DPTR(COR_ILMETHOD_SECT_SMALL) PTR_COR_ILMETHOD_SECT_SMALL; +typedef DPTR(COR_ILMETHOD_SECT_FAT) PTR_COR_ILMETHOD_SECT_FAT; + +CHECK PEDecoder::CheckILMethod(RVA rva) +{ + CONTRACT_CHECK + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + // + // Incrementaly validate that the entire IL method body is within the bounds of the image + // + + // We need to have at least the tiny header + CHECK(CheckRva(rva, sizeof(IMAGE_COR_ILMETHOD_TINY))); + + TADDR pIL = GetRvaData(rva); + + PTR_COR_ILMETHOD_TINY pMethodTiny = PTR_COR_ILMETHOD_TINY(pIL); + + if (pMethodTiny->IsTiny()) + { + // Tiny header has no optional sections - we are done. + CHECK(CheckRva(rva, sizeof(IMAGE_COR_ILMETHOD_TINY) + pMethodTiny->GetCodeSize())); + CHECK_OK; + } + + // + // Fat header + // + + CHECK(CheckRva(rva, sizeof(IMAGE_COR_ILMETHOD_FAT))); + + PTR_COR_ILMETHOD_FAT pMethodFat = PTR_COR_ILMETHOD_FAT(pIL); + + CHECK(pMethodFat->IsFat()); + + S_UINT32 codeEnd = S_UINT32(4) * S_UINT32(pMethodFat->GetSize()) + S_UINT32(pMethodFat->GetCodeSize()); + CHECK(!codeEnd.IsOverflow()); + + // Check minimal size of the header + CHECK(pMethodFat->GetSize() >= (sizeof(COR_ILMETHOD_FAT) / 4)); + + CHECK(CheckRva(rva, codeEnd.Value())); + + if (!pMethodFat->More()) + { + CHECK_OK; + } + + // DACized copy of code:COR_ILMETHOD_FAT::GetSect + TADDR pSect = AlignUp(pIL + codeEnd.Value(), 4); + + // + // Optional sections following the code + // + + for (;;) + { + CHECK(CheckRva(rva, UINT32(pSect - pIL) + sizeof(IMAGE_COR_ILMETHOD_SECT_SMALL))); + + PTR_COR_ILMETHOD_SECT_SMALL pSectSmall = PTR_COR_ILMETHOD_SECT_SMALL(pSect); + + UINT32 sectSize; + + if (pSectSmall->IsSmall()) + { + sectSize = pSectSmall->DataSize; + + // Workaround for bug in shipped compilers - see comment in code:COR_ILMETHOD_SECT::DataSize + if ((pSectSmall->Kind & CorILMethod_Sect_KindMask) == CorILMethod_Sect_EHTable) + sectSize = COR_ILMETHOD_SECT_EH_SMALL::Size(sectSize / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL)); + } + else + { + CHECK(CheckRva(rva, UINT32(pSect - pIL) + sizeof(IMAGE_COR_ILMETHOD_SECT_FAT))); + + PTR_COR_ILMETHOD_SECT_FAT pSectFat = PTR_COR_ILMETHOD_SECT_FAT(pSect); + + sectSize = pSectFat->GetDataSize(); + + // Workaround for bug in shipped compilers - see comment in code:COR_ILMETHOD_SECT::DataSize + if ((pSectSmall->Kind & CorILMethod_Sect_KindMask) == CorILMethod_Sect_EHTable) + sectSize = COR_ILMETHOD_SECT_EH_FAT::Size(sectSize / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT)); + } + + // Section has to be non-empty to avoid infinite loop below + CHECK(sectSize > 0); + + S_UINT32 sectEnd = S_UINT32(UINT32(pSect - pIL)) + S_UINT32(sectSize); + CHECK(!sectEnd.IsOverflow()); + + CHECK(CheckRva(rva, sectEnd.Value())); + + if (!pSectSmall->More()) + { + CHECK_OK; + } + + // DACized copy of code:COR_ILMETHOD_FAT::Next + pSect = AlignUp(pIL + sectEnd.Value(), 4); + } +} + +// +// Compute size of IL blob. Assumes that the IL is within the bounds of the image - make sure +// to call code:PEDecoder::CheckILMethod before calling this method. +// +// code:PEDecoder::ComputeILMethodSize is DACized duplicate of code:COR_ILMETHOD_DECODER::GetOnDiskSize. +// code:MethodDesc::GetILHeader contains debug-only check that ensures that both implementations +// are in sync. +// + +SIZE_T PEDecoder::ComputeILMethodSize(TADDR pIL) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } CONTRACTL_END; + + // + // Mirror flow of code:PEDecoder::CheckILMethod, except for the range checks + // + + PTR_COR_ILMETHOD_TINY pMethodTiny = PTR_COR_ILMETHOD_TINY(pIL); + + if (pMethodTiny->IsTiny()) + { + return sizeof(IMAGE_COR_ILMETHOD_TINY) + pMethodTiny->GetCodeSize(); + } + + PTR_COR_ILMETHOD_FAT pMethodFat = PTR_COR_ILMETHOD_FAT(pIL); + + UINT32 codeEnd = 4 * pMethodFat->GetSize() + pMethodFat->GetCodeSize(); + + if (!pMethodFat->More()) + { + return codeEnd; + } + + // DACized copy of code:COR_ILMETHOD_FAT::GetSect + TADDR pSect = AlignUp(pIL + codeEnd, 4); + + for (;;) + { + PTR_COR_ILMETHOD_SECT_SMALL pSectSmall = PTR_COR_ILMETHOD_SECT_SMALL(pSect); + + UINT32 sectSize; + + if (pSectSmall->IsSmall()) + { + sectSize = pSectSmall->DataSize; + + // Workaround for bug in shipped compilers - see comment in code:COR_ILMETHOD_SECT::DataSize + if ((pSectSmall->Kind & CorILMethod_Sect_KindMask) == CorILMethod_Sect_EHTable) + sectSize = COR_ILMETHOD_SECT_EH_SMALL::Size(sectSize / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_SMALL)); + } + else + { + PTR_COR_ILMETHOD_SECT_FAT pSectFat = PTR_COR_ILMETHOD_SECT_FAT(pSect); + + sectSize = pSectFat->GetDataSize(); + + // Workaround for bug in shipped compilers - see comment in code:COR_ILMETHOD_SECT::DataSize + if ((pSectSmall->Kind & CorILMethod_Sect_KindMask) == CorILMethod_Sect_EHTable) + sectSize = COR_ILMETHOD_SECT_EH_FAT::Size(sectSize / sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT)); + } + + UINT32 sectEnd = UINT32(pSect - pIL) + sectSize; + + if (!pSectSmall->More() || (sectSize == 0)) + { + return sectEnd; + } + + // DACized copy of code:COR_ILMETHOD_FAT::Next + pSect = AlignUp(pIL + sectEnd, 4); + } +} + +// +// GetDebugDirectoryEntry - return the debug directory entry at the specified index +// +// Arguments: +// index The 0-based index of the entry to return. Usually this is just 0, +// but there can be multiple debug directory entries in a PE file. +// +// Return value: +// A pointer to the IMAGE_DEBUG_DIRECTORY in the PE file for the specified index, +// or NULL if it doesn't exist. +// +// Note that callers on untrusted input are required to validate the debug directory +// first by calling CheckDirectoryEntry(IMAGE_DIRECTORY_ENTRY_DEBUG) (possibly +// indirectly via one of the CheckILOnly* functions). +// +PTR_IMAGE_DEBUG_DIRECTORY PEDecoder::GetDebugDirectoryEntry(UINT index) const +{ + CONTRACT(PTR_IMAGE_DEBUG_DIRECTORY) + { + INSTANCE_CHECK; + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_DEBUG)) + { + RETURN NULL; + } + + // Get a pointer to the contents and size of the debug directory + // Also validates (in CHK builds) that this is all within one section, which the + // caller should have already validated if they don't trust the context of this PE file. + COUNT_T cbDebugDir; + TADDR taDebugDir = GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_DEBUG, &cbDebugDir); + + // Check if the specified directory entry exists (based on the size of the directory) + // Note that the directory size should be an even multiple of the entry size, but we + // just round-down because we need to be resiliant (without asserting) to corrupted / + // fuzzed PE files. + UINT cNumEntries = cbDebugDir / sizeof(IMAGE_DEBUG_DIRECTORY); + if (index >= cNumEntries) + { + RETURN NULL; // index out of range + } + + // Get the debug directory entry at the specified index. + PTR_IMAGE_DEBUG_DIRECTORY pDebugEntry = dac_cast<PTR_IMAGE_DEBUG_DIRECTORY>(taDebugDir); + pDebugEntry += index; // offset from the first entry to the requested entry + RETURN pDebugEntry; +} + + +#ifdef FEATURE_PREJIT + +CORCOMPILE_EE_INFO_TABLE *PEDecoder::GetNativeEEInfoTable() const +{ + CONTRACT(CORCOMPILE_EE_INFO_TABLE *) + { + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + POSTCONDITION(CheckPointer(RETVAL)); + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->EEInfoTable; + + // 403523: PREFIX correctly complained here. Fixed GetDirectoryData. + RETURN PTR_CORCOMPILE_EE_INFO_TABLE(GetDirectoryData(pDir)); +} + + +void *PEDecoder::GetNativeHelperTable(COUNT_T *pSize) const +{ + CONTRACT(void *) + { + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->HelperTable; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN (void *)GetDirectoryData(pDir); +} + +CORCOMPILE_VERSION_INFO *PEDecoder::GetNativeVersionInfoMaybeNull(bool skipCheckNativeHeader) const +{ + CONTRACT(CORCOMPILE_VERSION_INFO *) + { + PRECONDITION(skipCheckNativeHeader || CheckNativeHeader()); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->VersionInfo; + + RETURN PTR_CORCOMPILE_VERSION_INFO(GetDirectoryData(pDir)); +} + +CORCOMPILE_VERSION_INFO *PEDecoder::GetNativeVersionInfo() const +{ + CONTRACT(CORCOMPILE_VERSION_INFO *) + { + POSTCONDITION(CheckPointer(RETVAL)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + RETURN GetNativeVersionInfoMaybeNull(); +} + +BOOL PEDecoder::HasNativeDebugMap() const +{ + CONTRACT(BOOL) + { + PRECONDITION(CheckNativeHeader()); + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + // 403522: Prefix complained correctly here. + CORCOMPILE_HEADER *pNativeHeader = GetNativeHeader(); + if (!pNativeHeader) + RETURN FALSE; + else + RETURN (VAL32(pNativeHeader->DebugMap.VirtualAddress) != 0); +} + +TADDR PEDecoder::GetNativeDebugMap(COUNT_T *pSize) const +{ + CONTRACT(TADDR) + { + PRECONDITION(CheckNativeHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->DebugMap; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN (GetDirectoryData(pDir)); +} + +Module *PEDecoder::GetPersistedModuleImage(COUNT_T *pSize) const +{ + CONTRACT(Module *) + { + PRECONDITION(CheckNativeHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + POSTCONDITION(CheckPointer(RETVAL)); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->ModuleImage; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN (Module *) GetDirectoryData(pDir); +} + +CHECK PEDecoder::CheckNativeHeaderVersion() const +{ + CONTRACT_CHECK + { + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->ManagedNativeHeader; + CHECK(VAL32(pDir->Size) == sizeof(CORCOMPILE_HEADER)); + + CORCOMPILE_HEADER *pNativeHeader = GetNativeHeader(); + CHECK(pNativeHeader->Signature == CORCOMPILE_SIGNATURE); + CHECK(pNativeHeader->MajorVersion == CORCOMPILE_MAJOR_VERSION); + CHECK(pNativeHeader->MinorVersion == CORCOMPILE_MINOR_VERSION); + + CHECK_OK; +} + +CORCOMPILE_CODE_MANAGER_ENTRY *PEDecoder::GetNativeCodeManagerTable() const +{ + CONTRACT(CORCOMPILE_CODE_MANAGER_ENTRY *) + { + PRECONDITION(CheckNativeHeader()); + POSTCONDITION(CheckPointer(RETVAL)); + SUPPORTS_DAC; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->CodeManagerTable; + + RETURN PTR_CORCOMPILE_CODE_MANAGER_ENTRY(GetDirectoryData(pDir)); +} + +PCODE PEDecoder::GetNativeHotCode(COUNT_T * pSize) const +{ + CONTRACT(PCODE) + { + PRECONDITION(CheckNativeHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + SUPPORTS_DAC; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeCodeManagerTable()->HotCode; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN GetDirectoryData(pDir); +} + +PCODE PEDecoder::GetNativeCode(COUNT_T * pSize) const +{ + CONTRACT(PCODE) + { + PRECONDITION(CheckNativeHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + SUPPORTS_DAC; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeCodeManagerTable()->Code; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN GetDirectoryData(pDir); +} + +PCODE PEDecoder::GetNativeColdCode(COUNT_T * pSize) const +{ + CONTRACT(PCODE) + { + PRECONDITION(CheckNativeHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeCodeManagerTable()->ColdCode; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN GetDirectoryData(pDir); +} + + +CORCOMPILE_METHOD_PROFILE_LIST *PEDecoder::GetNativeProfileDataList(COUNT_T * pSize) const +{ + CONTRACT(CORCOMPILE_METHOD_PROFILE_LIST *) + { + PRECONDITION(CheckNativeHeader()); + PRECONDITION(CheckPointer(pSize, NULL_OK)); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->ProfileDataList; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN PTR_CORCOMPILE_METHOD_PROFILE_LIST(GetDirectoryData(pDir)); +} + +#endif // FEATURE_PREJIT + +PTR_CVOID PEDecoder::GetNativeManifestMetadata(COUNT_T *pSize) const +{ + CONTRACT(PTR_CVOID) + { + INSTANCE_CHECK; + PRECONDITION(HasReadyToRunHeader() || CheckNativeHeader()); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); // TBD - may not store metadata for IJW + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = NULL; +#ifdef FEATURE_PREJIT + if (!HasReadyToRunHeader()) + { + pDir = GetMetaDataHelper(METADATA_SECTION_MANIFEST); + } + else +#endif + { + READYTORUN_HEADER * pHeader = GetReadyToRunHeader(); + + PTR_READYTORUN_SECTION pSections = dac_cast<PTR_READYTORUN_SECTION>(dac_cast<TADDR>(pHeader) + sizeof(READYTORUN_HEADER)); + for (DWORD i = 0; i < pHeader->CoreHeader.NumberOfSections; i++) + { + // Verify that section types are sorted + _ASSERTE(i == 0 || (pSections[i - 1].Type < pSections[i].Type)); + + READYTORUN_SECTION * pSection = pSections + i; + if (pSection->Type == ReadyToRunSectionType::ManifestMetadata) + { + // Set pDir to the address of the manifest metadata section + pDir = &pSection->Section; + break; + } + } + + // ReadyToRun file without large version bubble support doesn't have the ManifestMetadata + if (pDir == NULL) + { + if (pSize != NULL) + { + *pSize = 0; + } + + RETURN NULL; + } + } + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + RETURN dac_cast<PTR_VOID>(GetDirectoryData(pDir)); +} + +#ifdef FEATURE_PREJIT + +PTR_CORCOMPILE_IMPORT_SECTION PEDecoder::GetNativeImportSections(COUNT_T *pCount) const +{ + CONTRACT(PTR_CORCOMPILE_IMPORT_SECTION) + { + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->ImportSections; + + if (pCount != NULL) + *pCount = VAL32(pDir->Size) / sizeof(CORCOMPILE_IMPORT_SECTION); + + RETURN dac_cast<PTR_CORCOMPILE_IMPORT_SECTION>(GetDirectoryData(pDir)); +} + +PTR_CORCOMPILE_IMPORT_SECTION PEDecoder::GetNativeImportSectionFromIndex(COUNT_T index) const +{ + CONTRACT(PTR_CORCOMPILE_IMPORT_SECTION) + { + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->ImportSections; + + _ASSERTE(VAL32(pDir->Size) % sizeof(CORCOMPILE_IMPORT_SECTION) == 0); + _ASSERTE(index * sizeof(CORCOMPILE_IMPORT_SECTION) < VAL32(pDir->Size)); + + RETURN dac_cast<PTR_CORCOMPILE_IMPORT_SECTION>(GetDirectoryData(pDir)) + index; +} + +PTR_CORCOMPILE_IMPORT_SECTION PEDecoder::GetNativeImportSectionForRVA(RVA rva) const +{ + CONTRACT(PTR_CORCOMPILE_IMPORT_SECTION) + { + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->ImportSections; + + _ASSERTE(VAL32(pDir->Size) % sizeof(CORCOMPILE_IMPORT_SECTION) == 0); + + PTR_CORCOMPILE_IMPORT_SECTION pSections = dac_cast<PTR_CORCOMPILE_IMPORT_SECTION>(GetDirectoryData(pDir)); + PTR_CORCOMPILE_IMPORT_SECTION pEnd = dac_cast<PTR_CORCOMPILE_IMPORT_SECTION>(dac_cast<TADDR>(pSections) + VAL32(pDir->Size)); + + for (PTR_CORCOMPILE_IMPORT_SECTION pSection = pSections; pSection < pEnd; pSection++) + { + if (rva >= VAL32(pSection->Section.VirtualAddress) && rva < VAL32(pSection->Section.VirtualAddress) + VAL32(pSection->Section.Size)) + RETURN pSection; + } + + RETURN NULL; +} + +TADDR PEDecoder::GetStubsTable(COUNT_T *pSize) const +{ + CONTRACTL { + INSTANCE_CHECK; + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->StubsData; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + return (GetDirectoryData(pDir)); +} + +TADDR PEDecoder::GetVirtualSectionsTable(COUNT_T *pSize) const +{ + CONTRACTL { + INSTANCE_CHECK; + PRECONDITION(CheckNativeHeader()); + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + IMAGE_DATA_DIRECTORY *pDir = &GetNativeHeader()->VirtualSectionsTable; + + if (pSize != NULL) + *pSize = VAL32(pDir->Size); + + return (GetDirectoryData(pDir)); +} + +#endif // FEATURE_PREJIT + +// Get the SizeOfStackReserve and SizeOfStackCommit from the PE file that was used to create +// the calling process (.exe file). +void PEDecoder::GetEXEStackSizes(SIZE_T *PE_SizeOfStackReserve, SIZE_T *PE_SizeOfStackCommit) const +{ + CONTRACTL { + PRECONDITION(!IsDll()); // This routine should only be called for EXE files. + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + * PE_SizeOfStackReserve = GetSizeOfStackReserve(); + * PE_SizeOfStackCommit = GetSizeOfStackCommit(); +} + +CHECK PEDecoder::CheckWillCreateGuardPage() const +{ + CONTRACT_CHECK + { + PRECONDITION(CheckNTHeaders()); + NOTHROW; + GC_NOTRIGGER; + } + CONTRACT_CHECK_END; + + if (!IsDll()) + { + SIZE_T sizeReservedStack = 0; + SIZE_T sizeCommitedStack = 0; + + GetEXEStackSizes(&sizeReservedStack, &sizeCommitedStack); + + CHECK(ThreadWillCreateGuardPage(sizeReservedStack, sizeCommitedStack)); + + } + + CHECK_OK; +} + +BOOL PEDecoder::HasNativeEntryPoint() const +{ + CONTRACTL { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckCorHeader()); + } CONTRACTL_END; + + ULONG flags = GetCorHeader()->Flags; + return ((flags & VAL32(COMIMAGE_FLAGS_NATIVE_ENTRYPOINT)) && + (IMAGE_COR20_HEADER_FIELD(*GetCorHeader(), EntryPointToken) != VAL32(0))); +} + +void *PEDecoder::GetNativeEntryPoint() const +{ + CONTRACT (void *) { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckCorHeader()); + PRECONDITION(HasNativeEntryPoint()); + POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); + } CONTRACT_END; + + RETURN ((void *) GetRvaData((RVA)VAL32(IMAGE_COR20_HEADER_FIELD(*GetCorHeader(), EntryPointToken)))); +} + +#ifdef DACCESS_COMPILE + +void +PEDecoder::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, + bool enumThis) +{ + SUPPORTS_DAC; + if (enumThis) + { + DAC_ENUM_DTHIS(); + } + + DacEnumMemoryRegion((TADDR)m_base, sizeof(IMAGE_DOS_HEADER)); + m_pNTHeaders.EnumMem(); + m_pCorHeader.EnumMem(); + m_pNativeHeader.EnumMem(); + m_pReadyToRunHeader.EnumMem(); + + if (HasNTHeaders()) + { + // resource file does not have NT Header. + // + // we also need to write out section header. + DacEnumMemoryRegion(dac_cast<TADDR>(FindFirstSection()), sizeof(IMAGE_SECTION_HEADER) * GetNumberOfSections()); + } +} + +#endif // #ifdef DACCESS_COMPILE + +// -------------------------------------------------------------------------------- + +#ifdef _DEBUG + +// This is a stress mode to force DLLs to be relocated. +// This is particularly useful for hardbinding of ngen images as we +// embed pointers into other hardbound ngen dependencies. + +BOOL PEDecoder::GetForceRelocs() +{ + WRAPPER_NO_CONTRACT; + + static ConfigDWORD forceRelocs; + return (forceRelocs.val(CLRConfig::INTERNAL_ForceRelocs) != 0); +} + +BOOL PEDecoder::ForceRelocForDLL(LPCWSTR lpFileName) +{ +#ifdef _DEBUG + STATIC_CONTRACT_NOTHROW; \ + ANNOTATION_DEBUG_ONLY; \ + STATIC_CONTRACT_CANNOT_TAKE_LOCK; +#endif + +#if defined(DACCESS_COMPILE) || defined(TARGET_UNIX) + return TRUE; +#else + + if (!GetForceRelocs()) + return TRUE; + + BOOL fSuccess = FALSE; + PBYTE hndle = NULL; + PEDecoder pe; + void* pPreferredBase; + COUNT_T nVirtualSize; + + HANDLE hFile = WszCreateFile(lpFileName, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + goto ErrExit; + + HANDLE hMap = WszCreateFileMapping(hFile, + NULL, + SEC_IMAGE | PAGE_READONLY, + 0, + 0, + NULL); + CloseHandle(hFile); + + if (hMap == NULL) + goto ErrExit; + + hndle = (PBYTE)MapViewOfFile(hMap, + FILE_MAP_READ, + 0, + 0, + 0); + CloseHandle(hMap); + + if (!hndle) + goto ErrExit; + + pe.Init(hndle); + + pPreferredBase = (void*)pe.GetPreferredBase(); + nVirtualSize = pe.GetVirtualSize(); + + UnmapViewOfFile(hndle); + hndle = NULL; + + // Reserve the space so nobody can use it. A potential bug is likely to + // result in a plain AV this way. It is not a good idea to use the original + // mapping for the reservation since since it would lock the file on the disk. + if (!ClrVirtualAlloc(pPreferredBase, nVirtualSize, MEM_RESERVE, PAGE_NOACCESS)) + goto ErrExit; + + fSuccess = TRUE; + +ErrExit: + if (hndle != NULL) + UnmapViewOfFile(hndle); + + return fSuccess; + +#endif // DACCESS_COMPILE || TARGET_UNIX +} + +#endif // _DEBUG + +// +// MethodSectionIterator class is used to iterate hot (or) cold method section in an ngen image. +// Also used to iterate over jitted methods in the code heap +// +MethodSectionIterator::MethodSectionIterator(const void *code, SIZE_T codeSize, + const void *codeTable, SIZE_T codeTableSize) +{ + //For DAC builds,we'll read the table one DWORD at a time. Note that m_code IS + //NOT a host pointer. + m_codeTableStart = PTR_DWORD(TADDR(codeTable)); + m_codeTable = m_codeTableStart; + _ASSERTE((codeTableSize % sizeof(DWORD)) == 0); + m_codeTableEnd = m_codeTableStart + (codeTableSize / sizeof(DWORD)); + m_code = (BYTE *) code; + m_current = NULL; + + + if (m_codeTable < m_codeTableEnd) + { + m_dword = *m_codeTable++; + m_index = 0; + } + else + { + m_index = NIBBLES_PER_DWORD; + } +} + +BOOL MethodSectionIterator::Next() +{ + while (m_codeTable < m_codeTableEnd || m_index < (int)NIBBLES_PER_DWORD) + { + while (m_index++ < (int)NIBBLES_PER_DWORD) + { + int nibble = (m_dword & HIGHEST_NIBBLE_MASK)>>HIGHEST_NIBBLE_BIT; + m_dword <<= NIBBLE_SIZE; + + if (nibble != 0) + { + // We have found a method start + m_current = m_code + ((nibble-1)*CODE_ALIGN); + m_code += BYTES_PER_BUCKET; + return TRUE; + } + + m_code += BYTES_PER_BUCKET; + } + + if (m_codeTable < m_codeTableEnd) + { + m_dword = *m_codeTable++; + m_index = 0; + } + } + return FALSE; +} diff --git a/src/coreclr/utilcode/peinformation.cpp b/src/coreclr/utilcode/peinformation.cpp new file mode 100644 index 00000000000..78e41b11196 --- /dev/null +++ b/src/coreclr/utilcode/peinformation.cpp @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// -------------------------------------------------------------------------------- +// PEInformation.cpp +// + +// -------------------------------------------------------------------------------- + +#include "stdafx.h" +#include "utilcode.h" +#include "peinformation.h" + + +HRESULT TranslatePEToArchitectureType(CorPEKind CLRPeKind, DWORD dwImageType, PEKIND * pPeKind) +{ + return TranslatePEToArchitectureType(CLRPeKind, dwImageType, 0, pPeKind); +} + +HRESULT TranslatePEToArchitectureType(CorPEKind CLRPeKind, DWORD dwImageType, DWORD dwAssemblyFlags, PEKIND * pPeKind) +{ + HRESULT hr = S_OK; + + _ASSERTE(pPeKind != NULL); + + if (CLRPeKind == peNot) + { // Not a PE. Shouldn't ever get here. + *pPeKind = peInvalid; + hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT); + goto Exit; + } + else if (IsAfPA_NoPlatform(dwAssemblyFlags)) + { + *pPeKind = peNone; + goto Exit; + } + else + { + if ((CLRPeKind & peILonly) && + !(CLRPeKind & pe32Plus) && + !(CLRPeKind & pe32BitRequired) && + (dwImageType == IMAGE_FILE_MACHINE_I386)) + { + // Processor-agnostic (MSIL) + *pPeKind = peMSIL; + } + else if (CLRPeKind & pe32Plus) + { + // 64-bit + + if (CLRPeKind & pe32BitRequired) + { + *pPeKind = peInvalid; + hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT); + goto Exit; + } + + // Regardless of whether ILONLY is set or not, the architecture + // is the machine type. + + if (dwImageType == IMAGE_FILE_MACHINE_IA64) + { + *pPeKind = peIA64; + } + else if (dwImageType == IMAGE_FILE_MACHINE_AMD64) + { + *pPeKind = peAMD64; + } + else + { // We don't support other architectures + *pPeKind = peInvalid; + hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT); + goto Exit; + } + } + else + { + // 32-bit, non-agnostic + + if (dwImageType == IMAGE_FILE_MACHINE_I386) + { + *pPeKind = peI386; + } + else if (dwImageType == IMAGE_FILE_MACHINE_ARMNT) + { + *pPeKind = peARM; + } + else + { // Not supported + *pPeKind = peInvalid; + hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT); + goto Exit; + } + } + } + +Exit: + return hr; +} diff --git a/src/coreclr/utilcode/posterror.cpp b/src/coreclr/utilcode/posterror.cpp new file mode 100644 index 00000000000..c71d1b82e6c --- /dev/null +++ b/src/coreclr/utilcode/posterror.cpp @@ -0,0 +1,339 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// PostErrors.cpp +// +// This module contains the error handling/posting code for the engine. It +// is assumed that all methods may be called by a dispatch client, and therefore +// errors are always posted using IErrorInfo. +// + +//***************************************************************************** +#include "stdafx.h" // Standard header. + +#ifndef FEATURE_UTILCODE_NO_DEPENDENCIES + +#include <utilcode.h> // Utility helpers. +#include <corerror.h> +#include "../dlls/mscorrc/resource.h" +#include "ex.h" + +#include <posterror.h> + +#if !defined(lengthof) +#define lengthof(x) (sizeof(x)/sizeof(x[0])) +#endif + +// Local prototypes. +HRESULT FillErrorInfo(LPCWSTR szMsg, DWORD dwHelpContext); + +void GetResourceCultureCallbacks( + FPGETTHREADUICULTURENAMES* fpGetThreadUICultureNames, + FPGETTHREADUICULTUREID* fpGetThreadUICultureId) +{ + WRAPPER_NO_CONTRACT; + CCompRC::GetDefaultCallbacks( + fpGetThreadUICultureNames, + fpGetThreadUICultureId + ); +} +//***************************************************************************** +// Set callbacks to get culture info +//***************************************************************************** +void SetResourceCultureCallbacks( + FPGETTHREADUICULTURENAMES fpGetThreadUICultureNames, + FPGETTHREADUICULTUREID fpGetThreadUICultureId // TODO: Don't rely on the LCID, only the name +) +{ + WRAPPER_NO_CONTRACT; + CCompRC::SetDefaultCallbacks( + fpGetThreadUICultureNames, + fpGetThreadUICultureId + ); + +} + +//***************************************************************************** +// Public function to load a resource string +//***************************************************************************** +STDAPI UtilLoadStringRC( + UINT iResourceID, + __out_ecount(iMax) LPWSTR szBuffer, + int iMax, + int bQuiet +) +{ + WRAPPER_NO_CONTRACT; + return UtilLoadResourceString(bQuiet? CCompRC::Optional : CCompRC::Required,iResourceID, szBuffer, iMax); +} + +HRESULT UtilLoadResourceString(CCompRC::ResourceCategory eCategory, UINT iResourceID, __out_ecount (iMax) LPWSTR szBuffer, int iMax) +{ + CONTRACTL + { + DISABLED(NOTHROW); + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT retVal = E_OUTOFMEMORY; + + SString::Startup(); + EX_TRY + { + CCompRC *pResourceDLL = CCompRC::GetDefaultResourceDll(); + + if (pResourceDLL != NULL) + { + retVal = pResourceDLL->LoadString(eCategory, iResourceID, szBuffer, iMax); + } + } + EX_CATCH + { + // Catch any errors and return E_OUTOFMEMORY; + retVal = E_OUTOFMEMORY; + } + EX_END_CATCH(SwallowAllExceptions); + + return retVal; +} + +//***************************************************************************** +// Format a Runtime Error message. +//***************************************************************************** +HRESULT __cdecl FormatRuntimeErrorVa( + __inout_ecount(cchMsg) WCHAR *rcMsg, // Buffer into which to format. + ULONG cchMsg, // Size of buffer, characters. + HRESULT hrRpt, // The HR to report. + va_list marker) // Optional args. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + WCHAR rcBuf[512]; // Resource string. + HRESULT hr; + + // Ensure nul termination. + *rcMsg = W('\0'); + + // If this is one of our errors or if it is simply a resource ID, then grab the error from the rc file. + if ((HRESULT_FACILITY(hrRpt) == FACILITY_URT) || (HIWORD(hrRpt) == 0)) + { + hr = UtilLoadStringRC(LOWORD(hrRpt), rcBuf, NumItems(rcBuf), true); + if (hr == S_OK) + { + _vsnwprintf_s(rcMsg, cchMsg, _TRUNCATE, rcBuf, marker); + } + } + // Otherwise it isn't one of ours, so we need to see if the system can + // find the text for it. + else + { + if (WszFormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + 0, hrRpt, 0, + rcMsg, cchMsg, 0/*<TODO>@todo: marker</TODO>*/)) + { + hr = S_OK; + + // System messages contain a trailing \r\n, which we don't want normally. + size_t iLen = wcslen(rcMsg); + if (iLen > 3 && rcMsg[iLen - 2] == '\r' && rcMsg[iLen - 1] == '\n') + rcMsg[iLen - 2] = '\0'; + } + else + hr = HRESULT_FROM_GetLastError(); + } + + // If we failed to find the message anywhere, then issue a hard coded message. + if (FAILED(hr)) + { + _snwprintf_s(rcMsg, cchMsg, _TRUNCATE, W("Common Language Runtime Internal error: 0x%08x"), hrRpt); + DEBUG_STMT(DbgWriteEx(rcMsg)); + } + + return hrRpt; +} // FormatRuntimeErrorVa + +//***************************************************************************** +// Format a Runtime Error message, varargs. +//***************************************************************************** +HRESULT __cdecl FormatRuntimeError( + __out_ecount(cchMsg) WCHAR *rcMsg, // Buffer into which to format. + ULONG cchMsg, // Size of buffer, characters. + HRESULT hrRpt, // The HR to report. + ...) // Optional args. +{ + WRAPPER_NO_CONTRACT; + va_list marker; // User text. + va_start(marker, hrRpt); + hrRpt = FormatRuntimeErrorVa(rcMsg, cchMsg, hrRpt, marker); + va_end(marker); + return hrRpt; +} + +#ifdef FEATURE_COMINTEROP +//***************************************************************************** +// Create, fill out and set an error info object. Note that this does not fill +// out the IID for the error object; that is done elsewhere. +//***************************************************************************** +HRESULT FillErrorInfo( // Return status. + LPCWSTR szMsg, // Error message. + DWORD dwHelpContext) // Help context. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + ICreateErrorInfo *pICreateErr = NULL; // Error info creation Iface pointer. + IErrorInfo *pIErrInfo = NULL; // The IErrorInfo interface. + HRESULT hr; // Return status. + + // Get the ICreateErrorInfo pointer. + hr = S_OK; + EX_TRY + { + hr = CreateErrorInfo(&pICreateErr); + } + EX_CATCH + { + hr = GET_EXCEPTION()->GetHR(); + } + EX_END_CATCH(SwallowAllExceptions); + + if (FAILED(hr)) + return (hr); + + // Set message text description. + if (FAILED(hr = pICreateErr->SetDescription((LPWSTR) szMsg))) + goto Exit1; + + // suppress PreFast warning about passing literal string to non-const API. + // This API (ICreateErrorInfo::SetHelpFile) is documented to take a const argument, but + // we can't put const in the signature because it would break existing implementors of + // the API. +#ifdef _PREFAST_ +#pragma prefast(push) +#pragma warning(disable:6298) +#endif + + // Set the help file and help context. + //<TODO>@todo: we don't have a help file yet.</TODO> + if (FAILED(hr = pICreateErr->SetHelpFile(const_cast<WCHAR*>(W("complib.hlp")))) || + FAILED(hr = pICreateErr->SetHelpContext(dwHelpContext))) + goto Exit1; + +#ifdef _PREFAST_ +#pragma prefast(pop) +#endif + + // Get the IErrorInfo pointer. + if (FAILED(hr = pICreateErr->QueryInterface(IID_IErrorInfo, (PVOID *) &pIErrInfo))) + goto Exit1; + + // Save the error and release our local pointers. + { + // If we get here, we have loaded oleaut32.dll. + CONTRACT_VIOLATION(ThrowsViolation); + SetErrorInfo(0L, pIErrInfo); + } + +Exit1: + pICreateErr->Release(); + if (pIErrInfo) { + pIErrInfo->Release(); + } + return hr; +} +#endif // FEATURE_COMINTEROP + +//***************************************************************************** +// This function will post an error for the client. If the LOWORD(hrRpt) can +// be found as a valid error message, then it is loaded and formatted with +// the arguments passed in. If it cannot be found, then the error is checked +// against FormatMessage to see if it is a system error. System errors are +// not formatted so no add'l parameters are required. If any errors in this +// process occur, hrRpt is returned for the client with no error posted. +//***************************************************************************** +extern "C" +HRESULT __cdecl PostErrorVA( // Returned error. + HRESULT hrRpt, // Reported error. + va_list marker) // Error arguments. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + ENTRY_POINT; + } + CONTRACTL_END; + +#ifdef FEATURE_COMINTEROP + + const DWORD cchMsg = 4096; + WCHAR *rcMsg = (WCHAR*)alloca(cchMsg * sizeof(WCHAR)); // Error message. + HRESULT hr; + + BEGIN_ENTRYPOINT_NOTHROW; + + // Return warnings without text. + if (!FAILED(hrRpt)) + goto ErrExit; + + // If we are already out of memory or out of stack or the thread is in some bad state, + // we don't want throw gasoline on the fire by calling ErrorInfo stuff below (which can + // trigger a delayload of oleaut32.dll). We don't need to embellish transient errors + // so just return this without text. + if (Exception::IsTransient(hrRpt)) + { + goto ErrExit; + } + + // Format the error. + FormatRuntimeErrorVa(rcMsg, cchMsg, hrRpt, marker); + + // Turn the error into a posted error message. If this fails, we still + // return the original error message since a message caused by our error + // handling system isn't going to give you a clue about the original error. + hr = FillErrorInfo(rcMsg, LOWORD(hrRpt)); + _ASSERTE(hr == S_OK); + +ErrExit: + + END_ENTRYPOINT_NOTHROW; + +#endif // FEATURE_COMINTEROP + + return (hrRpt); +} // PostErrorVA + +#endif //!FEATURE_UTILCODE_NO_DEPENDENCIES + +//***************************************************************************** +// This function will post an error for the client. If the LOWORD(hrRpt) can +// be found as a valid error message, then it is loaded and formatted with +// the arguments passed in. If it cannot be found, then the error is checked +// against FormatMessage to see if it is a system error. System errors are +// not formatted so no add'l parameters are required. If any errors in this +// process occur, hrRpt is returned for the client with no error posted. +//***************************************************************************** +extern "C" +HRESULT __cdecl PostError( + HRESULT hrRpt, // Reported error. + ...) // Error arguments. +{ +#ifndef FEATURE_UTILCODE_NO_DEPENDENCIES + WRAPPER_NO_CONTRACT; + va_list marker; // User text. + va_start(marker, hrRpt); + hrRpt = PostErrorVA(hrRpt, marker); + va_end(marker); +#endif //!FEATURE_UTILCODE_NO_DEPENDENCIES + return hrRpt; +} diff --git a/src/coreclr/utilcode/prettyprintsig.cpp b/src/coreclr/utilcode/prettyprintsig.cpp new file mode 100644 index 00000000000..e902a5fc001 --- /dev/null +++ b/src/coreclr/utilcode/prettyprintsig.cpp @@ -0,0 +1,1027 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +//***************************************************************************** +// This code supports formatting a method and it's signature in a friendly +// and consistent format. +// +//***************************************************************************** +#include "stdafx.h" +#include "prettyprintsig.h" +#include "utilcode.h" +#include "metadata.h" +#include "corpriv.h" + +/***********************************************************************/ +// Null-terminates the string held in "out" + +static WCHAR* asStringW(CQuickBytes *out) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return NULL;); + } + CONTRACTL_END + + SIZE_T oldSize = out->Size(); + if (FAILED(out->ReSizeNoThrow(oldSize + 1))) + return 0; + WCHAR * cur = (WCHAR *) ((BYTE *) out->Ptr() + oldSize); + *cur = 0; + return((WCHAR*) out->Ptr()); +} // static WCHAR* asStringW() + +// Null-terminates the string held in "out" + +static CHAR* asStringA(CQuickBytes *out) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return NULL;); + } + CONTRACTL_END + + SIZE_T oldSize = out->Size(); + if (FAILED(out->ReSizeNoThrow(oldSize + 1))) + return 0; + CHAR * cur = (CHAR *) ((BYTE *) out->Ptr() + oldSize); + *cur = 0; + return((CHAR*) out->Ptr()); +} // static CHAR* asStringA() + +/***********************************************************************/ +// Appends the str to "out" +// The string held in "out" is not NULL-terminated. asStringW() needs to +// be called for the NULL-termination + +static HRESULT appendStrW(CQuickBytes *out, const WCHAR* str) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + SIZE_T len = wcslen(str) * sizeof(WCHAR); + SIZE_T oldSize = out->Size(); + if (FAILED(out->ReSizeNoThrow(oldSize + len))) + return E_OUTOFMEMORY; + WCHAR * cur = (WCHAR *) ((BYTE *) out->Ptr() + oldSize); + memcpy(cur, str, len); + // Note no trailing null! + return S_OK; +} // static HRESULT appendStrW() + +// Appends the str to "out" +// The string held in "out" is not NULL-terminated. asStringA() needs to +// be called for the NULL-termination + +static HRESULT appendStrA(CQuickBytes *out, const CHAR* str) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + SIZE_T len = strlen(str) * sizeof(CHAR); + SIZE_T oldSize = out->Size(); + if (FAILED(out->ReSizeNoThrow(oldSize + len))) + return E_OUTOFMEMORY; + CHAR * cur = (CHAR *) ((BYTE *) out->Ptr() + oldSize); + memcpy(cur, str, len); + // Note no trailing null! + return S_OK; +} // static HRESULT appendStrA() + + +static HRESULT appendStrNumW(CQuickBytes *out, int num) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + WCHAR buff[32]; + swprintf_s(buff, 32, W("%d"), num); + return appendStrW(out, buff); +} // static HRESULT appendStrNumW() + +static HRESULT appendStrNumA(CQuickBytes *out, int num) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + CHAR buff[32]; + sprintf_s(buff, 32, "%d", num); + return appendStrA(out, buff); +} // static HRESULT appendStrNumA() + +static HRESULT appendStrHexW(CQuickBytes *out, int num) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + WCHAR buff[32]; + swprintf_s(buff, 32, W("%08X"), num); + return appendStrW(out, buff); +} // static HRESULT appendStrHexW() + +static HRESULT appendStrHexA(CQuickBytes *out, int num) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + CHAR buff[32]; + sprintf_s(buff, 32, "%08X", num); + return appendStrA(out, buff); +} // static HRESULT appendStrHexA() + +/***********************************************************************/ + +LPCWSTR PrettyPrintSigWorker( + PCCOR_SIGNATURE & typePtr, // type to convert, + size_t typeLen, // length of type + const WCHAR * name, // can be "", the name of the method for this sig + CQuickBytes * out, // where to put the pretty printed string + IMetaDataImport * pIMDI); // Import api to use. + +//***************************************************************************** +//***************************************************************************** +// pretty prints 'type' to the buffer 'out' returns a pointer to the next type, +// or 0 on a format failure + +static PCCOR_SIGNATURE PrettyPrintType( + PCCOR_SIGNATURE typePtr, // type to convert, + size_t typeLen, // Maximum length of the type + CQuickBytes * out, // where to put the pretty printed string + IMetaDataImport * pIMDI) // ptr to IMDInternal class with ComSig +{ + mdToken tk; + const WCHAR * str; + WCHAR rcname[MAX_CLASS_NAME]; + HRESULT hr; + unsigned __int8 elt = *typePtr++; + PCCOR_SIGNATURE typeEnd = typePtr + typeLen; + + switch(elt) + { + case ELEMENT_TYPE_VOID: + str = W("void"); + goto APPEND; + + case ELEMENT_TYPE_BOOLEAN: + str = W("bool"); + goto APPEND; + + case ELEMENT_TYPE_CHAR: + str = W("wchar"); + goto APPEND; + + case ELEMENT_TYPE_I1: + str = W("int8"); + goto APPEND; + + case ELEMENT_TYPE_U1: + str = W("unsigned int8"); + goto APPEND; + + case ELEMENT_TYPE_I2: + str = W("int16"); + goto APPEND; + + case ELEMENT_TYPE_U2: + str = W("unsigned int16"); + goto APPEND; + + case ELEMENT_TYPE_I4: + str = W("int32"); + goto APPEND; + + case ELEMENT_TYPE_U4: + str = W("unsigned int32"); + goto APPEND; + + case ELEMENT_TYPE_I8: + str = W("int64"); + goto APPEND; + + case ELEMENT_TYPE_U8: + str = W("unsigned int64"); + goto APPEND; + + case ELEMENT_TYPE_R4: + str = W("float32"); + goto APPEND; + + case ELEMENT_TYPE_R8: + str = W("float64"); + goto APPEND; + + case ELEMENT_TYPE_U: + str = W("unsigned int"); + goto APPEND; + + case ELEMENT_TYPE_I: + str = W("int"); + goto APPEND; + + case ELEMENT_TYPE_OBJECT: + str = W("class System.Object"); + goto APPEND; + + case ELEMENT_TYPE_STRING: + str = W("class System.String"); + goto APPEND; + + case ELEMENT_TYPE_CANON_ZAPSIG: + str = W("class System.__Canon"); + goto APPEND; + + case ELEMENT_TYPE_TYPEDBYREF: + str = W("refany"); + goto APPEND; + + APPEND: + appendStrW(out, str); + break; + + case ELEMENT_TYPE_VALUETYPE: + str = W("value class "); + goto DO_CLASS; + + case ELEMENT_TYPE_CLASS: + str = W("class "); + goto DO_CLASS; + + DO_CLASS: + typePtr += CorSigUncompressToken(typePtr, &tk); + appendStrW(out, str); + rcname[0] = 0; + str = rcname; + + if (TypeFromToken(tk) == mdtTypeRef) + { + hr = pIMDI->GetTypeRefProps(tk, 0, rcname, NumItems(rcname), 0); + } + else if (TypeFromToken(tk) == mdtTypeDef) + { + hr = pIMDI->GetTypeDefProps(tk, rcname, NumItems(rcname), 0, 0, 0); + } + else + { + _ASSERTE(!"Unknown token type encountered in signature."); + str = W("<UNKNOWN>"); + } + + appendStrW(out, str); + break; + + case ELEMENT_TYPE_SZARRAY: + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + appendStrW(out, W("[]")); + break; + + case ELEMENT_TYPE_ARRAY: + { + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + unsigned rank = CorSigUncompressData(typePtr); + PREFIX_ASSUME(rank <= 0xffffff); + + // <TODO>TODO what is the syntax for the rank 0 case? </TODO> + if (rank == 0) + { + appendStrW(out, W("[??]")); + } + else + { + _ASSERTE(rank != 0); + int* lowerBounds = (int*) _alloca(sizeof(int)*2*rank); + int* sizes = &lowerBounds[rank]; + memset(lowerBounds, 0, sizeof(int)*2*rank); + + unsigned numSizes = CorSigUncompressData(typePtr); + _ASSERTE(numSizes <= rank); + unsigned int i; + for(i =0; i < numSizes; i++) + sizes[i] = CorSigUncompressData(typePtr); + + unsigned numLowBounds = CorSigUncompressData(typePtr); + _ASSERTE(numLowBounds <= rank); + for(i = 0; i < numLowBounds; i++) + lowerBounds[i] = CorSigUncompressData(typePtr); + + appendStrW(out, W("[")); + for(i = 0; i < rank; i++) + { + if (sizes[i] != 0 && lowerBounds[i] != 0) + { + appendStrNumW(out, lowerBounds[i]); + appendStrW(out, W("...")); + appendStrNumW(out, lowerBounds[i] + sizes[i] + 1); + } + if (i < rank-1) + appendStrW(out, W(",")); + } + appendStrW(out, W("]")); + } + } + break; + + case ELEMENT_TYPE_MVAR: + appendStrW(out, W("!!")); + appendStrNumW(out, CorSigUncompressData(typePtr)); + break; + + case ELEMENT_TYPE_VAR: + appendStrW(out, W("!")); + appendStrNumW(out, CorSigUncompressData(typePtr)); + break; + + case ELEMENT_TYPE_GENERICINST: + { + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + unsigned ntypars = CorSigUncompressData(typePtr); + appendStrW(out, W("<")); + for (unsigned i = 0; i < ntypars; i++) + { + if (i > 0) + appendStrW(out, W(",")); + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + } + appendStrW(out, W(">")); + } + break; + + case ELEMENT_TYPE_MODULE_ZAPSIG: + appendStrW(out, W("[module#")); + appendStrNumW(out, CorSigUncompressData(typePtr)); + appendStrW(out, W(", token#")); + typePtr += CorSigUncompressToken(typePtr, &tk); + appendStrHexW(out, tk); + appendStrW(out, W("]")); + break; + + case ELEMENT_TYPE_FNPTR: + appendStrW(out, W("fnptr ")); + PrettyPrintSigWorker(typePtr, (typeEnd - typePtr), W(""), out, pIMDI); + break; + + case ELEMENT_TYPE_NATIVE_VALUETYPE_ZAPSIG: + appendStrW(out, W("native ")); + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + break; + + // Modifiers or depedant types + case ELEMENT_TYPE_PINNED: + str = W(" pinned"); + goto MODIFIER; + + case ELEMENT_TYPE_PTR: + str = W("*"); + goto MODIFIER; + + case ELEMENT_TYPE_BYREF: + str = W("&"); + goto MODIFIER; + + MODIFIER: + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + appendStrW(out, str); + break; + + default: + case ELEMENT_TYPE_SENTINEL: + case ELEMENT_TYPE_END: + _ASSERTE(!"Unknown Type"); + return(typePtr); + break; + } + return(typePtr); +} // static PCCOR_SIGNATURE PrettyPrintType() + +//***************************************************************************** +// Converts a com signature to a text signature. +// +// Note that this function DOES NULL terminate the result signature string. +//***************************************************************************** +LPCWSTR PrettyPrintSigLegacy( + PCCOR_SIGNATURE typePtr, // type to convert, + unsigned typeLen, // length of type + const WCHAR * name, // can be "", the name of the method for this sig + CQuickBytes * out, // where to put the pretty printed string + IMetaDataImport * pIMDI) // Import api to use. +{ + return PrettyPrintSigWorker(typePtr, typeLen, name, out, pIMDI); +} // LPCWSTR PrettyPrintSigLegacy() + +LPCWSTR PrettyPrintSigWorker( + PCCOR_SIGNATURE & typePtr, // type to convert, + size_t typeLen, // length of type + const WCHAR * name, // can be "", the name of the method for this sig + CQuickBytes * out, // where to put the pretty printed string + IMetaDataImport * pIMDI) // Import api to use. +{ + out->Shrink(0); + unsigned numTyArgs = 0; + unsigned numArgs; + PCCOR_SIGNATURE typeEnd = typePtr + typeLen; // End of the signature. + + if (name != 0) // 0 means a local var sig + { + // get the calling convention out + unsigned callConv = CorSigUncompressData(typePtr); + + if (isCallConv(callConv, IMAGE_CEE_CS_CALLCONV_FIELD)) + { + PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + if (name != 0 && *name != 0) + { + appendStrW(out, W(" ")); + appendStrW(out, name); + } + return(asStringW(out)); + } + + if (callConv & IMAGE_CEE_CS_CALLCONV_HASTHIS) + appendStrW(out, W("instance ")); + + if (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + appendStrW(out, W("generic ")); + numTyArgs = CorSigUncompressData(typePtr); + } + + static const WCHAR * const callConvNames[IMAGE_CEE_CS_CALLCONV_MAX] = + { + W(""), + W("unmanaged cdecl "), + W("unmanaged stdcall "), + W("unmanaged thiscall "), + W("unmanaged fastcall "), + W("vararg "), + W("<error> "), + W("<error> "), + W(""), + W(""), + W(""), + W("native vararg ") + }; + + if ((callConv & IMAGE_CEE_CS_CALLCONV_MASK) < IMAGE_CEE_CS_CALLCONV_MAX) + { + appendStrW(out, callConvNames[callConv & IMAGE_CEE_CS_CALLCONV_MASK]); + } + + + numArgs = CorSigUncompressData(typePtr); + // do return type + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + + } + else + { + numArgs = CorSigUncompressData(typePtr); + } + + if (name != 0 && *name != 0) + { + appendStrW(out, W(" ")); + appendStrW(out, name); + } + appendStrW(out, W("(")); + + bool needComma = false; + + while (numArgs) + { + if (typePtr >= typeEnd) + break; + + if (*typePtr == ELEMENT_TYPE_SENTINEL) + { + if (needComma) + appendStrW(out, W(",")); + appendStrW(out, W("...")); + typePtr++; + } + else + { + if (numArgs <= 0) + break; + if (needComma) + appendStrW(out, W(",")); + typePtr = PrettyPrintType(typePtr, (typeEnd - typePtr), out, pIMDI); + --numArgs; + } + needComma = true; + } + appendStrW(out, W(")")); + return (asStringW(out)); +} // LPCWSTR PrettyPrintSigWorker() + + +// Internal implementation of PrettyPrintSig(). + +HRESULT PrettyPrintSigWorkerInternal( + PCCOR_SIGNATURE & typePtr, // type to convert, + size_t typeLen, // length of type + const CHAR * name, // can be "", the name of the method for this sig + CQuickBytes * out, // where to put the pretty printed string + IMDInternalImport * pIMDI); // Import api to use. + +static HRESULT PrettyPrintClass( + PCCOR_SIGNATURE &typePtr, // type to convert + PCCOR_SIGNATURE typeEnd, // end of the signature. + CQuickBytes *out, // where to put the pretty printed string + IMDInternalImport *pIMDI); // ptr to IMDInternal class with ComSig + + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +//***************************************************************************** +//***************************************************************************** +// pretty prints 'type' to the buffer 'out' returns a pointer to the next type, +// or 0 on a format failure + +__checkReturn +static HRESULT PrettyPrintTypeA( + PCCOR_SIGNATURE &typePtr, // type to convert, + size_t typeLen, // Maximum length of the type. + CQuickBytes *out, // where to put the pretty printed string + IMDInternalImport *pIMDI) // ptr to IMDInternal class with ComSig +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + mdToken tk; // A type's token. + const CHAR *str; // Temporary string. + HRESULT hr; // A result. + + PCCOR_SIGNATURE typeEnd = typePtr + typeLen; // End of the signature. + unsigned __int8 elt = *typePtr++; + + switch(elt) { + case ELEMENT_TYPE_VOID: + str = "void"; + goto APPEND; + + case ELEMENT_TYPE_BOOLEAN: + str = "bool"; + goto APPEND; + + case ELEMENT_TYPE_CHAR: + str = "wchar"; + goto APPEND; + + case ELEMENT_TYPE_I1: + str = "int8"; + goto APPEND; + + case ELEMENT_TYPE_U1: + str = "unsigned int8"; + goto APPEND; + + case ELEMENT_TYPE_I2: + str = "int16"; + goto APPEND; + + case ELEMENT_TYPE_U2: + str = "unsigned int16"; + goto APPEND; + + case ELEMENT_TYPE_I4: + str = "int32"; + goto APPEND; + + case ELEMENT_TYPE_U4: + str = "unsigned int32"; + goto APPEND; + + case ELEMENT_TYPE_I8: + str = "int64"; + goto APPEND; + + case ELEMENT_TYPE_U8: + str = "unsigned int64"; + goto APPEND; + + case ELEMENT_TYPE_R4: + str = "float32"; + goto APPEND; + + case ELEMENT_TYPE_R8: + str = "float64"; + goto APPEND; + + case ELEMENT_TYPE_U: + str = "unsigned int"; + goto APPEND; + + case ELEMENT_TYPE_I: + str = "int"; + goto APPEND; + + case ELEMENT_TYPE_OBJECT: + str = "class System.Object"; + goto APPEND; + + case ELEMENT_TYPE_STRING: + str = "class System.String"; + goto APPEND; + + case ELEMENT_TYPE_CANON_ZAPSIG: + str = "class System.__Canon"; + goto APPEND; + + case ELEMENT_TYPE_TYPEDBYREF: + str = "refany"; + goto APPEND; + + APPEND: + IfFailGo(appendStrA(out, str)); + break; + + case ELEMENT_TYPE_INTERNAL: + void* pMT; + pMT = *((void* UNALIGNED *)typePtr); + typePtr += sizeof(void*); + CHAR tempBuffer[64]; + sprintf_s(tempBuffer, 64, "pMT: %p", pMT); + IfFailGo(appendStrA(out, tempBuffer)); + break; + + case ELEMENT_TYPE_VALUETYPE: + str = "value class "; + goto DO_CLASS; + + case ELEMENT_TYPE_CLASS: + str = "class "; + goto DO_CLASS; + + DO_CLASS: + IfFailGo(appendStrA(out, str)); + IfFailGo(PrettyPrintClass(typePtr, typeEnd, out, pIMDI)); + break; + + case ELEMENT_TYPE_CMOD_REQD: + str = "required_modifier "; + goto CMOD; + + case ELEMENT_TYPE_CMOD_OPT: + str = "optional_modifier "; + goto CMOD; + + CMOD: + IfFailGo(appendStrA(out, str)); + IfFailGo(PrettyPrintClass(typePtr, typeEnd, out, pIMDI)); + IfFailGo(appendStrA(out, " ")); + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + break; + + case ELEMENT_TYPE_SZARRAY: + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + IfFailGo(appendStrA(out, "[]")); + break; + + case ELEMENT_TYPE_ARRAY: + { + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + unsigned rank = CorSigUncompressData(typePtr); + PREFIX_ASSUME(rank <= 0xffffff); + // <TODO>TODO what is the syntax for the rank 0 case? </TODO> + if (rank == 0) + { + IfFailGo(appendStrA(out, "[??]")); + } + else + { + _ASSERTE(rank != 0); + int* lowerBounds = (int*) _alloca(sizeof(int)*2*rank); + int* sizes = &lowerBounds[rank]; + memset(lowerBounds, 0, sizeof(int)*2*rank); + + unsigned numSizes = CorSigUncompressData(typePtr); + _ASSERTE(numSizes <= rank); + unsigned int i; + for(i =0; i < numSizes; i++) + sizes[i] = CorSigUncompressData(typePtr); + + unsigned numLowBounds = CorSigUncompressData(typePtr); + _ASSERTE(numLowBounds <= rank); + for(i = 0; i < numLowBounds; i++) + lowerBounds[i] = CorSigUncompressData(typePtr); + + IfFailGo(appendStrA(out, "[")); + for(i = 0; i < rank; i++) + { + if (sizes[i] != 0 && lowerBounds[i] != 0) + { + appendStrNumA(out, lowerBounds[i]); + IfFailGo(appendStrA(out, "...")); + appendStrNumA(out, lowerBounds[i] + sizes[i] + 1); + } + if (i < rank-1) + IfFailGo(appendStrA(out, ",")); + } + IfFailGo(appendStrA(out, "]")); + } + } + break; + + case ELEMENT_TYPE_MVAR: + IfFailGo(appendStrA(out, "!!")); + appendStrNumA(out, CorSigUncompressData(typePtr)); + break; + + case ELEMENT_TYPE_VAR: + IfFailGo(appendStrA(out, "!")); + appendStrNumA(out, CorSigUncompressData(typePtr)); + break; + + case ELEMENT_TYPE_GENERICINST: + { + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + unsigned ntypars = CorSigUncompressData(typePtr); + IfFailGo(appendStrA(out, "<")); + for (unsigned i = 0; i < ntypars; i++) + { + if (i > 0) + IfFailGo(appendStrA(out, ",")); + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + } + IfFailGo(appendStrA(out, ">")); + } + break; + + case ELEMENT_TYPE_MODULE_ZAPSIG: + IfFailGo(appendStrA(out, "[module#")); + appendStrNumA(out, CorSigUncompressData(typePtr)); + IfFailGo(appendStrA(out, ", token#")); + typePtr += CorSigUncompressToken(typePtr, &tk); + IfFailGo(appendStrHexA(out, tk)); + IfFailGo(appendStrA(out, "]")); + break; + + case ELEMENT_TYPE_FNPTR: + IfFailGo(appendStrA(out, "fnptr ")); + IfFailGo(PrettyPrintSigWorkerInternal(typePtr, (typeEnd - typePtr), "", out,pIMDI)); + break; + + case ELEMENT_TYPE_NATIVE_VALUETYPE_ZAPSIG: + IfFailGo(appendStrA(out, "native ")); + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + break; + + // Modifiers or dependent types + case ELEMENT_TYPE_PINNED: + str = " pinned"; + goto MODIFIER; + + case ELEMENT_TYPE_PTR: + str = "*"; + goto MODIFIER; + + case ELEMENT_TYPE_BYREF: + str = "&"; + goto MODIFIER; + + MODIFIER: + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + IfFailGo(appendStrA(out, str)); + break; + + default: + case ELEMENT_TYPE_SENTINEL: + case ELEMENT_TYPE_END: + hr = E_INVALIDARG; + break; + } + ErrExit: + return hr; +} // PrettyPrintTypeA +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +// pretty prints the class 'type' to the buffer 'out' +static HRESULT PrettyPrintClass( + PCCOR_SIGNATURE &typePtr, // type to convert + PCCOR_SIGNATURE typeEnd, // end of the signature. + CQuickBytes *out, // where to put the pretty printed string + IMDInternalImport *pIMDI) // ptr to IMDInternal class with ComSig +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + mdToken tk; + const CHAR *str; // type's token. + LPCUTF8 pNS; // type's namespace. + LPCUTF8 pN; // type's name. + HRESULT hr; // result + + IfFailGo(CorSigUncompressToken_EndPtr(typePtr, typeEnd, &tk)); + str = "<UNKNOWN>"; + + if (TypeFromToken(tk) == mdtTypeSpec) + { + ULONG cSig; + PCCOR_SIGNATURE sig; + IfFailGo(pIMDI->GetSigFromToken(tk, &cSig, &sig)); + IfFailGo(PrettyPrintTypeA(sig, cSig, out, pIMDI)); + } + else + { + if (TypeFromToken(tk) == mdtTypeRef) + { + //<TODO>@consider: assembly name?</TODO> + if (FAILED(pIMDI->GetNameOfTypeRef(tk, &pNS, &pN))) + { + pNS = pN = "Invalid TypeRef record"; + } + } + else + { + _ASSERTE(TypeFromToken(tk) == mdtTypeDef); + if (FAILED(pIMDI->GetNameOfTypeDef(tk, &pN, &pNS))) + { + pNS = pN = "Invalid TypeDef record"; + } + } + + if (pNS && *pNS) + { + IfFailGo(appendStrA(out, pNS)); + IfFailGo(appendStrA(out, NAMESPACE_SEPARATOR_STR)); + } + IfFailGo(appendStrA(out, pN)); + } + return S_OK; + +ErrExit: + return hr; +} // static HRESULT PrettyPrintClass() + +//***************************************************************************** +// Converts a com signature to a text signature. +// +// Note that this function DOES NULL terminate the result signature string. +//***************************************************************************** +HRESULT PrettyPrintSigInternalLegacy( + PCCOR_SIGNATURE typePtr, // type to convert, + unsigned typeLen, // length of type + const CHAR * name, // can be "", the name of the method for this sig + CQuickBytes * out, // where to put the pretty printed string + IMDInternalImport * pIMDI) // Import api to use. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + return PrettyPrintSigWorkerInternal(typePtr, typeLen, name, out, pIMDI); +} // HRESULT PrettyPrintSigInternalLegacy() + +HRESULT PrettyPrintSigWorkerInternal( + PCCOR_SIGNATURE & typePtr, // type to convert, + size_t typeLen, // length of type + const CHAR * name, // can be "", the name of the method for this sig + CQuickBytes * out, // where to put the pretty printed string + IMDInternalImport * pIMDI) // Import api to use. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr = S_OK; + unsigned numArgs; // Count of arugments to function, or count of local vars. + unsigned numTyArgs = 0; + PCCOR_SIGNATURE typeEnd = typePtr + typeLen; + bool needComma = false; + + out->Shrink(0); + + if (name != 0) // 0 means a local var sig + { + // get the calling convention out + unsigned callConv = CorSigUncompressData(typePtr); + + if (isCallConv(callConv, IMAGE_CEE_CS_CALLCONV_FIELD)) + { + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + if (name != 0 && *name != 0) + + { + IfFailGo(appendStrA(out, " ")); + IfFailGo(appendStrA(out, name)); + } + goto ErrExit; + } + + if (callConv & IMAGE_CEE_CS_CALLCONV_HASTHIS) + IfFailGo(appendStrA(out, "instance ")); + + if (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + IfFailGo(appendStrA(out, "generic ")); + numTyArgs = CorSigUncompressData(typePtr); + } + + static const CHAR* const callConvNames[IMAGE_CEE_CS_CALLCONV_MAX] = + { + "", + "unmanaged cdecl ", + "unmanaged stdcall ", + "unmanaged thiscall ", + "unmanaged fastcall ", + "vararg ", + "<error> ", + "<error> ", + "", + "", + "", + "native vararg " + }; + + if ((callConv & IMAGE_CEE_CS_CALLCONV_MASK) < IMAGE_CEE_CS_CALLCONV_MAX) + { + appendStrA(out, callConvNames[callConv & IMAGE_CEE_CS_CALLCONV_MASK]); + } + + numArgs = CorSigUncompressData(typePtr); + // do return type + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + } + else + { + numArgs = CorSigUncompressData(typePtr); + } + + if (name != 0 && *name != 0) + { + IfFailGo(appendStrA(out, " ")); + IfFailGo(appendStrA(out, name)); + } + IfFailGo(appendStrA(out, "(")); + + while (numArgs) + { + if (typePtr >= typeEnd) + break; + + if (*typePtr == ELEMENT_TYPE_SENTINEL) + { + if (needComma) + IfFailGo(appendStrA(out, ",")); + IfFailGo(appendStrA(out, "...")); + ++typePtr; + } + else + { + if (needComma) + IfFailGo(appendStrA(out, ",")); + IfFailGo(PrettyPrintTypeA(typePtr, (typeEnd - typePtr), out, pIMDI)); + --numArgs; + } + needComma = true; + } + IfFailGo(appendStrA(out, ")")); + if (asStringA(out) == 0) + IfFailGo(E_OUTOFMEMORY); + + ErrExit: + return hr; +} // HRESULT PrettyPrintSigWorkerInternal() diff --git a/src/coreclr/utilcode/regutil.cpp b/src/coreclr/utilcode/regutil.cpp new file mode 100644 index 00000000000..fcfbe6eb7c7 --- /dev/null +++ b/src/coreclr/utilcode/regutil.cpp @@ -0,0 +1,759 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// regutil.cpp +// + +// +// This module contains a set of functions that can be used to access the +// registry. +// +//***************************************************************************** + + +#include "stdafx.h" +#include "utilcode.h" +#include "mscoree.h" +#include "sstring.h" +#include "ex.h" + +#define COMPLUS_PREFIX W("COMPlus_") +#define LEN_OF_COMPLUS_PREFIX 8 + +#if (!defined(FEATURE_UTILCODE_NO_DEPENDENCIES) || defined(DEBUG)) && !defined(TARGET_UNIX) +#define ALLOW_REGISTRY +#endif + +#undef WszRegCreateKeyEx +#undef WszRegOpenKeyEx +#undef WszRegOpenKey +#define WszRegCreateKeyEx RegCreateKeyExW +#define WszRegOpenKeyEx RegOpenKeyExW +#define WszRegOpenKey(hKey, wszSubKey, phkRes) RegOpenKeyExW(hKey, wszSubKey, 0, KEY_ALL_ACCESS, phkRes) + +//***************************************************************************** +// Reads from the environment setting +//***************************************************************************** +LPWSTR REGUTIL::EnvGetString(LPCWSTR name, BOOL fPrependCOMPLUS) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + WCHAR buff[64]; + + if(wcslen(name) > (size_t)(64 - 1 - (fPrependCOMPLUS ? LEN_OF_COMPLUS_PREFIX : 0))) + { + return NULL; + } + + if (fPrependCOMPLUS) + { +#ifdef ALLOW_REGISTRY + if (!EnvCacheValueNameSeenPerhaps(name)) + return NULL; +#endif // ALLOW_REGISTRY + wcscpy_s(buff, _countof(buff), COMPLUS_PREFIX); + } + else + { + *buff = 0; + } + + wcscat_s(buff, _countof(buff), name); + + FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value. + + + NewArrayHolder<WCHAR> ret = NULL; + HRESULT hr = S_OK; + DWORD Len; + EX_TRY + { + PathString temp; + + Len = WszGetEnvironmentVariable(buff, temp); + if (Len != 0) + { + ret = temp.GetCopyOfUnicodeString(); + } + + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + } + + if(ret != NULL) + { + return ret.Extract(); + } + + return NULL; + + +} + +//***************************************************************************** +// Reads a DWORD from the COR configuration according to the level specified +// Returns back defValue if the key cannot be found +//***************************************************************************** +DWORD REGUTIL::GetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue, CORConfigLevel level, BOOL fPrependCOMPLUS) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + SUPPORTS_DAC_HOST_ONLY; + + ULONGLONG result; + GetConfigInteger(name, defValue, &result, TRUE, level, fPrependCOMPLUS); + + return (DWORD)result; +} + +#define uniwcst(val, endptr, base) (fGetDWORD ? wcstoul(val, endptr, base) : _wcstoui64(val, endptr, base)) + +// +// Look up a dword config value, and write the result to the DWORD passed in by reference. +// +// Return value: +// * E_FAIL if the value is not found. (result is assigned the default value) +// * S_OK if the value is found. (result is assigned the value that was found) +// +// Arguments: +// * info - see file:../inc/CLRConfig.h for details +// * result - Pointer to the output DWORD. +// +// static +HRESULT REGUTIL::GetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue, __out DWORD * result, CORConfigLevel level, BOOL fPrependCOMPLUS) +{ + ULONGLONG ullResult; + HRESULT hr = GetConfigInteger(name, defValue, &ullResult, TRUE, level, fPrependCOMPLUS); + *result = (DWORD)ullResult; + return hr; +} + +ULONGLONG REGUTIL::GetConfigULONGLONG_DontUse_(LPCWSTR name, ULONGLONG defValue, CORConfigLevel level, BOOL fPrependCOMPLUS) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + SUPPORTS_DAC_HOST_ONLY; + + ULONGLONG result; + GetConfigInteger(name, defValue, &result, FALSE, level, fPrependCOMPLUS); + + return result; +} + +// This function should really be refactored to return the string from the environment and let the caller decide +// what to convert it to; and return the buffer read from the reg call. +// Note for PAL: right now PAL does not have a _wcstoui64 API, so I am temporarily reading in all numbers as +// a 32-bit number. When we have the _wcstoui64 API on MAC we will use uniwcst instead of wcstoul. +HRESULT REGUTIL::GetConfigInteger(LPCWSTR name, ULONGLONG defValue, __out ULONGLONG * result, BOOL fGetDWORD, CORConfigLevel level, BOOL fPrependCOMPLUS) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + SUPPORTS_DAC_HOST_ONLY; + + ULONGLONG rtn; + ULONGLONG ret = 0; + DWORD type = 0; + DWORD size = 4; + + FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value. + + if (level & COR_CONFIG_ENV) + { + WCHAR* val = EnvGetString(name, fPrependCOMPLUS); // try getting it from the environement first + if (val != 0) { + errno = 0; + LPWSTR endPtr; + rtn = uniwcst(val, &endPtr, 16); // treat it has hex + BOOL fSuccess = ((errno != ERANGE) && (endPtr != val)); + delete[] val; + + if (fSuccess) // success + { + *result = rtn; + return (S_OK); + } + } + } + + // Early out if no registry access, simplifies following code. + // + if (!(level & COR_CONFIG_REGISTRY)) + { + *result = defValue; + return (E_FAIL); + } + +#ifdef ALLOW_REGISTRY + // Probe the config cache to see if there is any point + // probing the registry; if not, don't bother. + // + if (!RegCacheValueNameSeenPerhaps(name)) + { + *result = defValue; + return (E_FAIL); + } +#endif // ALLOW_REGISTRY + + if (level & COR_CONFIG_USER) + { +#ifdef ALLOW_REGISTRY + { + LONG retVal = ERROR_SUCCESS; + BOOL bCloseHandle = FALSE; + HKEY userKey = s_hUserFrameworkKey; + + if (userKey == INVALID_HANDLE_VALUE) + { + retVal = WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &userKey); + bCloseHandle = TRUE; + } + + if (retVal == ERROR_SUCCESS) + { + rtn = WszRegQueryValueEx(userKey, name, 0, &type, (LPBYTE)&ret, &size); + + if (bCloseHandle) + VERIFY(!RegCloseKey(userKey)); + + if (rtn == ERROR_SUCCESS && (type == REG_DWORD || (!fGetDWORD && type == REG_QWORD))) + { + *result = ret; + return (S_OK); + } + } + } +#endif // ALLOW_REGISTRY + } + + if (level & COR_CONFIG_MACHINE) + { +#ifdef ALLOW_REGISTRY + { + LONG retVal = ERROR_SUCCESS; + BOOL bCloseHandle = FALSE; + HKEY machineKey = s_hMachineFrameworkKey; + + if (machineKey == INVALID_HANDLE_VALUE) + { + retVal = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &machineKey); + bCloseHandle = TRUE; + } + + if (retVal == ERROR_SUCCESS) + { + rtn = WszRegQueryValueEx(machineKey, name, 0, &type, (LPBYTE)&ret, &size); + + if (bCloseHandle) + VERIFY(!RegCloseKey(machineKey)); + + if (rtn == ERROR_SUCCESS && (type == REG_DWORD || (!fGetDWORD && type == REG_QWORD))) + { + *result = ret; + return (S_OK); + } + } + } +#endif // ALLOW_REGISTRY + } + + *result = defValue; + return (E_FAIL); +} + +//***************************************************************************** +// Reads a string from the COR configuration according to the level specified +// The caller is responsible for deallocating the returned string by +// calling code:REGUTIL::FreeConfigString or using a code:ConfigStringHolder +//***************************************************************************** + +LPWSTR REGUTIL::GetConfigString_DontUse_(LPCWSTR name, BOOL fPrependCOMPLUS, CORConfigLevel level, BOOL fUsePerfCache) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + } + CONTRACTL_END; + +#ifdef ALLOW_REGISTRY + HRESULT lResult; + RegKeyHolder userKey = NULL; + RegKeyHolder machineKey = NULL; + RegKeyHolder fusionKey = NULL; + DWORD type; + DWORD size; +#endif // ALLOW_REGISTRY + NewArrayHolder<WCHAR> ret(NULL); + + FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value. + + if (level & COR_CONFIG_ENV) + { + ret = EnvGetString(name, fPrependCOMPLUS); // try getting it from the environement first + if (ret != 0) { + if (*ret != 0) + { + ret.SuppressRelease(); + return(ret); + } + ret.Clear(); + } + } + + // Early out if no registry access, simplifies following code. + // + if (!(level & COR_CONFIG_REGISTRY)) + { + return(ret); + } + +#ifdef ALLOW_REGISTRY + // Probe the config cache to see if there is any point + // probing the registry; if not, don't bother. + // + if (fUsePerfCache && !RegCacheValueNameSeenPerhaps(name)) + return ret; +#endif // ALLOW_REGISTRY + + if (level & COR_CONFIG_USER) + { +#ifdef ALLOW_REGISTRY + BOOL bUsingCachedKey = FALSE; + + if (s_hUserFrameworkKey != INVALID_HANDLE_VALUE) + { + bUsingCachedKey = TRUE; + userKey = s_hUserFrameworkKey; + } + + if (bUsingCachedKey || WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &userKey) == ERROR_SUCCESS) + { + BOOL bReturn = FALSE; + if (WszRegQueryValueEx(userKey, name, 0, &type, 0, &size) == ERROR_SUCCESS && + type == REG_SZ) + { + ret = (LPWSTR) new (nothrow) BYTE [size]; + if (ret) + { + ret[0] = W('\0'); + lResult = WszRegQueryValueEx(userKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size); + _ASSERTE(lResult == ERROR_SUCCESS); + { + ret.SuppressRelease(); + } + } + bReturn = TRUE; + } + + if (bUsingCachedKey) + userKey.SuppressRelease(); + + if (bReturn) + return ret; + } + +#endif // ALLOW_REGISTRY + } + + if (level & COR_CONFIG_MACHINE) + { +#ifdef ALLOW_REGISTRY + BOOL bUsingCachedKey = FALSE; + + if (s_hMachineFrameworkKey != INVALID_HANDLE_VALUE) + { + bUsingCachedKey = TRUE; + machineKey = s_hMachineFrameworkKey; + } + + if (bUsingCachedKey || WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &machineKey) == ERROR_SUCCESS) + { + BOOL bReturn = FALSE; + if (WszRegQueryValueEx(machineKey, name, 0, &type, 0, &size) == ERROR_SUCCESS && + type == REG_SZ) + { + ret = (LPWSTR) new (nothrow) BYTE [size]; + if (ret) + { + ret[0] = W('\0'); + lResult = WszRegQueryValueEx(machineKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size); + _ASSERTE(lResult == ERROR_SUCCESS); + { + ret.SuppressRelease(); + } + } + bReturn = TRUE; + } + + if (bUsingCachedKey) + machineKey.SuppressRelease(); + + if (bReturn) + return ret; + } + +#endif // ALLOW_REGISTRY + } + + return NULL; +} + +//***************************************************************************** +// Deallocation function for code:REGUTIL::GetConfigString_DontUse_ +// +// Notes: +// Use a code:ConfigStringHolder to automatically call this. +//***************************************************************************** +void REGUTIL::FreeConfigString(__in_z LPWSTR str) +{ + LIMITED_METHOD_CONTRACT; + + delete [] str; +} + +//***************************************************************************** +// Reads a BIT flag from the COR configuration according to the level specified +// Returns back defValue if the key cannot be found +//***************************************************************************** +DWORD REGUTIL::GetConfigFlag_DontUse_(LPCWSTR name, DWORD bitToSet, BOOL defValue) +{ + WRAPPER_NO_CONTRACT; + + return(GetConfigDWORD_DontUse_(name, defValue) != 0 ? bitToSet : 0); +} + + +#ifdef ALLOW_REGISTRY + + + +// +// ProbabilisticNameSet: +// +// (Used by ConfigCache, below. If used elsewhere, might justify +// promotion to a standalone header file.) +// +// Represent a set of names in a small, fixed amount of storage. +// We turn a name into a small integer, then add the integer to a bitvector. +// An old trick we used in VC++4 minimal rebuild. +// +// For best results, the number of elements should be a fraction of +// the total number of bits in 'bits'. +// +// Note, only the const methods are thread-safe. +// Callers are responsible for providing their own synchronization when +// constructing and Add'ing names to the set. +// +class ProbabilisticNameSet { +public: + ProbabilisticNameSet() + { + WRAPPER_NO_CONTRACT; + + memset(bits, 0, sizeof(bits)); + } + + // Add a name to the set. + // + void Add(LPCWSTR name) + { + WRAPPER_NO_CONTRACT; + + unsigned i, mask; + GetBitIndex(name, 0, &i, &mask); + bits[i] |= mask; + } + + void Add(LPCWSTR name, DWORD count) + { + WRAPPER_NO_CONTRACT; + + unsigned i, mask; + GetBitIndex(name, count, &i, &mask); + bits[i] |= mask; + } + + // Return TRUE if a name *may have* been added to the set; + // return FALSE if the name *definitely* was NOT ever added to the set. + // + BOOL MayContain(LPCWSTR name) const + { + WRAPPER_NO_CONTRACT; + + unsigned i, mask; + GetBitIndex(name, 0, &i, &mask); + return !!(bits[i] & mask); + } + +private: + static const unsigned cbitSet = 256U; + static const unsigned cbitWord = 8U*sizeof(unsigned); + unsigned bits[cbitSet/cbitWord]; + + // Return the word index and bit mask corresponding to the bitvector member + // addressed by the *case-insensitive* hash of the given name. + // + void GetBitIndex(LPCWSTR name, DWORD count, unsigned* pi, unsigned* pmask) const + { + LIMITED_METHOD_CONTRACT; + unsigned hash; + if (count > 0) + hash = HashiStringNKnownLower80(name, count) % cbitSet; + else + hash = HashiStringKnownLower80(name) % cbitSet; + *pi = hash / cbitWord; + *pmask = (1U << (hash % cbitWord)); + } + +}; + + +// From the Win32 SDK docs: +// Registry Element Size Limits +// ... +// The maximum size of a value name is as follows: +// Windows Server 2003 and Windows XP: 16,383 characters +// Windows 2000: 260 ANSI characters or 16,383 Unicode characters. +// Windows Me/98/95: 255 characters +// Despite that, we only cache value names of 80 characters or less -- +// longer names don't make sense as configuration settings names. +// +static const unsigned cchRegValueNameMax = 80; + +BOOL REGUTIL::s_fUseRegCache = FALSE; +BOOL REGUTIL::s_fUseEnvCache = FALSE; +HKEY REGUTIL::s_hMachineFrameworkKey = (HKEY) INVALID_HANDLE_VALUE; +HKEY REGUTIL::s_hUserFrameworkKey = (HKEY) INVALID_HANDLE_VALUE; +static ProbabilisticNameSet regNames; // set of registry value names seen; should be + // a static field of REGUTIL but I don't + // want to expose ProbabilisticNameSet. +static ProbabilisticNameSet envNames; // set of environment value names seen; + +// "Registry Configuration Cache" +// +// Initialize the (optional) registry config cache. +// +// The purpose of the cache is to avoid hundreds of registry probes +// otherwise incurred by calls to GetConfigDWORD_DontUse_ and GetConfigString_DontUse_. +// +// We accomplish this by enumerating the relevant registry keys and +// remembering the extant value names; and then by avoiding probing +// for a name that was not seen in the enumeration (initialization) phase. +// +// It is optional in the sense that REGUTIL facilities like +// GetConfigDWORD_DontUse_ and GetConfigString_DontUse_ will work fine if the cache +// is never initialized; however, each config access then will hit +// the registry (typically multiple times to search HKCU and HKLM). +// +// +// Initialization: Enumerate these registry keys +// HKCU Software\Microsoft\.NetFramework +// HKLM Software\Microsoft\.NetFramework +// for value names, and "remember" them in the ProbalisticNameSet 'names'. +// +// If we ever find a reg value named DisableConfigCache under any of these +// three keys, the feature is disabled. +// +// This method is not thread-safe. It should only be called once. +// +// Perf Optimization for VSWhidbey:113373. +// +void REGUTIL::InitOptionalConfigCache() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + static const HKEY roots[] = { HKEY_CURRENT_USER, + HKEY_LOCAL_MACHINE}; + + LONG l = ERROR_SUCCESS; // general Win32 API error return code + HKEY hkey = NULL; + + // No caching if the environment variable COMPlus_DisableConfigCache is set + // + if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DisableConfigCache) != 0) + goto failure; + + // Enumerate each root + // + for (int i = 0; i < NumItems(roots); i++) { + hkey = NULL; // defensive + l = WszRegOpenKeyEx(roots[i], FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &hkey); + if (l == ERROR_FILE_NOT_FOUND) { + // That registry key is not present. + // For example, installation with no HKCU\...\.NETFramework. + // Should be OK to proceed. + continue; + } + else if (l == ERROR_ACCESS_DENIED) { + // If we encounter access denied for the current key, ignore + // the failure and continue to cache the rest. Effectively this means + // we are caching that key as containing no values, which is correct + // because in the unlikely event there are values hiding underneath + // later attempts to access them (open the key) would also hit access + // denied and continue on probing other locations. + continue; + } + else if (l != ERROR_SUCCESS) { + // Something else went wrong. To be safe, don't enable the cache. + goto failure; + } + + // Enumerate every value name under this key. + // + for (int j = 0; ; j++) { + WCHAR wszValue[cchRegValueNameMax + 1]; + DWORD dwValueSize = NumItems(wszValue); + l = WszRegEnumValue(hkey, j, wszValue, &dwValueSize, + NULL, NULL, NULL, NULL); + + if (l == ERROR_SUCCESS) { + // Add value name to the names cache. + regNames.Add(wszValue); + } + else if (l == ERROR_NO_MORE_ITEMS) { + // Expected case: we've considered every value under this key. + break; + } + else if ((l == ERROR_INSUFFICIENT_BUFFER || l == ERROR_MORE_DATA) && + (dwValueSize > cchRegValueNameMax)) { + // Name is too long. That's OK, we don't cache such names. + continue; + } + else if (l == ERROR_ACCESS_DENIED) { + // As above, ignore access denied and continue on trying to cache + continue; + } + else { + // WszRegEnumValue failed OOM, or something else went wrong. + // To be safe, don't enable the cache. + goto failure; + } + } + + // Save the handles to framework regkeys so that future reads dont have to + // open it again + if (roots[i] == HKEY_CURRENT_USER) + s_hUserFrameworkKey = hkey; + else if (roots[i] == HKEY_LOCAL_MACHINE) + s_hMachineFrameworkKey = hkey; + else + RegCloseKey(hkey); + + hkey = NULL; + } + + // Success. We've enumerated all value names under the roots; + // enable the REGUTIL value name config cache. + // + s_fUseRegCache = TRUE; + + // Now create a cache of environment variables + if (WCHAR * wszStrings = WszGetEnvironmentStrings()) + { + // GetEnvironmentStrings returns pointer to a null terminated block containing + // null terminated strings + for(WCHAR *wszCurr = wszStrings; *wszCurr; wszCurr++) + { + WCHAR wch = towlower(*wszCurr); + + // Lets only cache env variables with the COMPlus prefix only + if (wch == W('c')) + { + WCHAR *wszName = wszCurr; + + // Look for the separator between name and value + while (*wszCurr && *wszCurr != W('=')) + wszCurr++; + + if (*wszCurr == W('=')) + { + // Check the prefix + if(!SString::_wcsnicmp(wszName, COMPLUS_PREFIX, LEN_OF_COMPLUS_PREFIX)) + { + wszName += LEN_OF_COMPLUS_PREFIX; + envNames.Add(wszName, (DWORD) (wszCurr - wszName)); + } + } + + } + // Look for current string termination + while (*wszCurr) + wszCurr++; + + } + + WszFreeEnvironmentStrings(wszStrings); + s_fUseEnvCache = TRUE; + + } + return; + +failure: + if (hkey != NULL) + RegCloseKey(hkey); +} + +// Return TRUE if the registry value name was seen (or might have been seen) +// in the registry at cache initialization time; +// return FALSE if it definitely was not seen at startup. +// +// If not using the config cache, return TRUE always. +// +// Perf Optimization for VSWhidbey:113373. +// +BOOL REGUTIL::RegCacheValueNameSeenPerhaps(LPCWSTR name) +{ + WRAPPER_NO_CONTRACT; + + return !s_fUseRegCache + || (wcslen(name) > cchRegValueNameMax) + || regNames.MayContain(name); +} + +BOOL REGUTIL::EnvCacheValueNameSeenPerhaps(LPCWSTR name) +{ + WRAPPER_NO_CONTRACT; + + return !s_fUseEnvCache + || envNames.MayContain(name); +} + +#endif // ALLOW_REGISTRY diff --git a/src/coreclr/utilcode/safewrap.cpp b/src/coreclr/utilcode/safewrap.cpp new file mode 100644 index 00000000000..20903862818 --- /dev/null +++ b/src/coreclr/utilcode/safewrap.cpp @@ -0,0 +1,336 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// SafeWrap.cpp +// + +// +// This file contains wrapper functions for Win32 API's that take SStrings +// and use CLR-safe holders. +// +// See guidelines in SafeWrap.h for writing these APIs. +//***************************************************************************** + +#include "stdafx.h" // Precompiled header key. +#include "safewrap.h" +#include "winwrap.h" // Header for macros and functions. +#include "utilcode.h" +#include "holder.h" +#include "sstring.h" +#include "ex.h" + +//----------------------------------------------------------------------------- +// Get the current directory. +// On success, returns true and sets 'Value' to unicode version of cur dir. +// Throws on all failures. This should mainly be oom. +//----------------------------------------------------------------------------- +void ClrGetCurrentDirectory(SString & value) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Get size needed + DWORD len = WszGetCurrentDirectory(value); + + + // An actual API failure in GetCurrentDirectory failure should be very rare, so we'll throw on those. + if (len == 0) + { + ThrowLastError(); + } +} + +//----------------------------------------------------------------------------- +// Reads an environment variable into the given SString. +// Returns true on success, false on failure (includes if the var does not exist). +// May throw on oom. +//----------------------------------------------------------------------------- +bool ClrGetEnvironmentVariable(LPCSTR szEnvVarName, SString & value) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + + PRECONDITION(szEnvVarName != NULL); + } + CONTRACTL_END; + + // First read it to get the needed length. + DWORD lenWithNull = GetEnvironmentVariableA(szEnvVarName, NULL, 0); + if (lenWithNull == 0) + { + return false; + } + + // Now read it for content. + char * pCharBuf = value.OpenANSIBuffer(lenWithNull); + DWORD lenWithoutNull = GetEnvironmentVariableA(szEnvVarName, pCharBuf, lenWithNull); + value.CloseBuffer(lenWithoutNull); + + if (lenWithoutNull != (lenWithNull - 1)) + { + // Env var must have changed underneath us. + return false; + } + return true; +} + +void ClrGetModuleFileName(HMODULE hModule, SString & value) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + WCHAR * pCharBuf = value.OpenUnicodeBuffer(_MAX_PATH); + DWORD numChars = GetModuleFileNameW(hModule, pCharBuf, _MAX_PATH); + value.CloseBuffer(numChars); +} + +ClrDirectoryEnumerator::ClrDirectoryEnumerator(LPCWSTR pBaseDirectory, LPCWSTR pMask /*= W("*")*/) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + StackSString strMask(pBaseDirectory); + SString s(SString::Literal, DIRECTORY_SEPARATOR_STR_W); + if (!strMask.EndsWith(s)) + { + strMask.Append(s); + } + strMask.Append(pMask); + dirHandle = WszFindFirstFile(strMask, &data); + + if (dirHandle == INVALID_HANDLE_VALUE) + { + DWORD dwLastError = GetLastError(); + + // We either ran out of files, or didnt encounter even a single file matching the + // search mask. If it is neither of these conditions, then convert the error to an exception + // and raise it. + if ((dwLastError != ERROR_FILE_NOT_FOUND) && (dwLastError != ERROR_NO_MORE_FILES)) + ThrowLastError(); + } + + fFindNext = FALSE; +} + +bool ClrDirectoryEnumerator::Next() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (dirHandle == INVALID_HANDLE_VALUE) + return FALSE; + + for (;;) + { + if (fFindNext) + { + if (!WszFindNextFile(dirHandle, &data)) + { + if (GetLastError() != ERROR_NO_MORE_FILES) + ThrowLastError(); + + return FALSE; + } + } + else + { + fFindNext = TRUE; + } + + // Skip junk + if (wcscmp(data.cFileName, W(".")) != 0 && wcscmp(data.cFileName, W("..")) != 0) + return TRUE; + } +} + +DWORD ClrReportEvent( + LPCWSTR pEventSource, + WORD wType, + WORD wCategory, + DWORD dwEventID, + PSID lpUserSid, + WORD wNumStrings, + LPCWSTR *lpStrings, + DWORD dwDataSize /*=0*/, + LPVOID lpRawData /*=NULL*/) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifndef TARGET_UNIX + HANDLE h = ::RegisterEventSourceW( + NULL, // uses local computer + pEventSource); + if (h == NULL) + { + // Return the error code to the caller so that + // appropriate asserts/logging can be done + // incase of errors like event log being full + return GetLastError(); + } + + // Every event id should have matching description in dlls\shim\eventmsg.mc. This allows + // event view to know how to display message. + _ASSERTE (dwEventID != 0); + + // Attempt to report the event to the event log. Note that if the operation fails + // (for example because of low memory conditions) it isn't fatal so we can safely ignore + // the return code from ReportEventW. + BOOL ret = ::ReportEventW( + h, // event log handle + wType, + wCategory, + dwEventID, + lpUserSid, + wNumStrings, + dwDataSize, + lpStrings, + lpRawData); + + DWORD dwRetStatus = GetLastError(); + + ::DeregisterEventSource(h); + + return (ret == TRUE)?ERROR_SUCCESS:dwRetStatus; +#else // TARGET_UNIX + // UNIXTODO: Report the event somewhere? + return ERROR_SUCCESS; +#endif // TARGET_UNIX +} + +// Returns ERROR_SUCCESS if succeessful in reporting to event log, or +// Windows error code to indicate the specific error. +DWORD ClrReportEvent( + LPCWSTR pEventSource, + WORD wType, + WORD wCategory, + DWORD dwEventID, + PSID lpUserSid, + LPCWSTR pMessage) +{ + return ClrReportEvent(pEventSource, wType, wCategory, dwEventID, lpUserSid, 1, &pMessage); +} + +#ifndef TARGET_UNIX +// Read a REG_SZ (null-terminated string) value from the registry. Throws. +// +// Arguments: +// hKey - key to registry hive. +// szValueName - null-terminated string for value name to lookup. +// If this is empty, this gets the (default) value in the registry hive. +// value - out parameter to hold registry value string contents. +// +// Returns: +// value is set on success. Throws on any failure, including if the value doesn't exist +// or if the value exists but is not a REG_SZ. +// +// Notes: +// REG_SZ is a single null-terminated string in the registry. +// This is only support on Windows because the registry is windows specific. +void ClrRegReadString(HKEY hKey, const SString & szValueName, SString & value) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DWORD type; + DWORD numBytesData; + + // Preemptively clear the string such that it's empty in any failure case. + value.Clear(); + + // + // Step 1: First call to find size of buffer and ensure data type is correct + // + LONG ret = WszRegQueryValueEx( + hKey, + szValueName.GetUnicode(), // NULL or "\0" represents the (default) key. + NULL, // reserved + &type, // should be REG_SZ + NULL, // not requesting data yet + &numBytesData + ); + + if (ret != ERROR_SUCCESS) + { + ThrowWin32(ret); + } + + if (type != REG_SZ) + { + // The registry value is not a string. + ThrowHR(E_INVALIDARG); + } + + // REG_SZ includes the null terminator. + DWORD numCharsIncludingNull = numBytesData / sizeof(WCHAR); + + // + // Step 2: Allocate buffer to hold final result + // + WCHAR * pData = value.OpenUnicodeBuffer(numCharsIncludingNull); + DWORD numBytesData2 = numBytesData; + + + // + // Step 3: Requery to get actual contents + // + ret = WszRegQueryValueEx( + hKey, + szValueName.GetUnicode(), + NULL, // reserved + &type, // should still be REG_SZ + (LPBYTE) pData, + &numBytesData2 + ); + + // This check should only fail if the registry was changed inbetween the first query + // and the second. In practice, that should never actually happen. + if ((numBytesData2 != numBytesData) || (type != REG_SZ)) + { + // On error, leave string empty. + value.CloseBuffer(0); + + ThrowHR(E_FAIL); + } + + if (ret != ERROR_SUCCESS) + { + // On error, leave string empty. + value.CloseBuffer(0); + ThrowWin32(ret); + } + + + // + // Step 4: Close the string buffer + // + COUNT_T numCharsNoNull = numCharsIncludingNull - 1; + value.CloseBuffer(numCharsNoNull); +} +#endif // TARGET_UNIX diff --git a/src/coreclr/utilcode/sbuffer.cpp b/src/coreclr/utilcode/sbuffer.cpp new file mode 100644 index 00000000000..215efefb199 --- /dev/null +++ b/src/coreclr/utilcode/sbuffer.cpp @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// --------------------------------------------------------------------------- + +// +// Buffer.cpp +// --------------------------------------------------------------------------- + +#include "stdafx.h" +#include "sbuffer.h" +#include "ex.h" +#include "holder.h" +#include "stdmacros.h" + +// Try to minimize the performance impact of contracts on CHK build. +#if defined(_MSC_VER) +#pragma inline_depth (20) +#endif + +const DWORD g_garbageFillBuffer[GARBAGE_FILL_BUFFER_ITEMS] = +{ + GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, + GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, + GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, + GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, GARBAGE_FILL_DWORD, +}; + +//---------------------------------------------------------------------------- +// ReallocateBuffer +// Low level buffer reallocate routine +//---------------------------------------------------------------------------- +void SBuffer::ReallocateBuffer(COUNT_T allocation, Preserve preserve) +{ + CONTRACT_VOID + { + PRECONDITION(CheckPointer(this)); + PRECONDITION(CheckBufferClosed()); + PRECONDITION(CheckAllocation(allocation)); + PRECONDITION(allocation >= m_size); + POSTCONDITION(m_allocation == allocation); + if (allocation > 0) THROWS; else NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + BYTE *newBuffer = NULL; + if (allocation > 0) + { + newBuffer = NewBuffer(allocation); + + if (preserve == PRESERVE) + { + // Copy the relevant contents of the old buffer over + DebugMoveBuffer(newBuffer, m_buffer, m_size); + } + } + + if (IsAllocated()) + DeleteBuffer(m_buffer, m_allocation); + + m_buffer = newBuffer; + m_allocation = allocation; + + if (allocation > 0) + SetAllocated(); + else + ClearAllocated(); + + ClearImmutable(); + + RETURN; +} + +void SBuffer::Replace(const Iterator &i, COUNT_T deleteSize, COUNT_T insertSize) +{ + CONTRACT_VOID + { + THROWS; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(this)); + PRECONDITION(CheckIteratorRange(i, deleteSize)); + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + COUNT_T startRange = (COUNT_T) (i.m_ptr - m_buffer); + // The PRECONDITION(CheckIterationRange(i, deleteSize)) should check this in + // contract-checking builds, but this ensures we don't integer overflow if someone + // passes in an egregious deleteSize by capping it to the remaining length in the + // buffer. + if ((COUNT_T)(m_buffer + m_size - i.m_ptr) < deleteSize) + { + _ASSERTE(!"Trying to replace more bytes than are remaining in the buffer."); + deleteSize = (COUNT_T)(m_buffer + m_size - i.m_ptr); + } + COUNT_T endRange = startRange + deleteSize; + COUNT_T end = m_size; + + SCOUNT_T delta = insertSize - deleteSize; + + if (delta < 0) + { + // Buffer is shrinking + + DebugDestructBuffer(i.m_ptr, deleteSize); + + DebugMoveBuffer(m_buffer + endRange + delta, + m_buffer + endRange, + end - endRange); + + Resize(m_size+delta, PRESERVE); + + i.Resync(this, m_buffer + startRange); + + } + else if (delta > 0) + { + // Buffer is growing + + ResizePadded(m_size+delta); + + i.Resync(this, m_buffer + startRange); + + DebugDestructBuffer(i.m_ptr, deleteSize); + + DebugMoveBuffer(m_buffer + endRange + delta, + m_buffer + endRange, + end - endRange); + + } + else + { + // Buffer stays the same size. We need to DebugDestruct it first to keep + // the invariant that the new space is clean. + + DebugDestructBuffer(i.m_ptr, insertSize); + } + + DebugConstructBuffer(i.m_ptr, insertSize); + + RETURN; +} + + diff --git a/src/coreclr/utilcode/securityutil.cpp b/src/coreclr/utilcode/securityutil.cpp new file mode 100644 index 00000000000..d3cab8ac1a4 --- /dev/null +++ b/src/coreclr/utilcode/securityutil.cpp @@ -0,0 +1,494 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +#include "stdafx.h" + +#include "securityutil.h" +#include "ex.h" + +#include "securitywrapper.h" + +// These are the right that we will give to the global section and global events used +// in communicating between debugger and debugee +// +// SECTION_ALL_ACCESS is needed for the IPC block. Unfortunately, we DACL our events and +// IPC block identically. Or this particular right does not need to bleed into here. +// +#ifndef CLR_IPC_GENERIC_RIGHT +#define CLR_IPC_GENERIC_RIGHT (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | STANDARD_RIGHTS_ALL | SECTION_ALL_ACCESS) +#endif + + +//***************************************************************** +// static helper function +// +// helper to form ACL that contains AllowedACE of users of current +// process and target process +// +// [IN] pid - target process id +// [OUT] ppACL - ACL for the process +// +// Clean up - +// Caller remember to call FreeACL() on *ppACL +//***************************************************************** +HRESULT SecurityUtil::GetACLOfPid(DWORD pid, PACL *ppACL) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + _ASSERTE(ppACL); + *ppACL = NULL; + + PSID pCurrentProcessSid = NULL; + PSID pTargetProcessSid = NULL; + PSID pTargetProcessAppContainerSid = NULL; + DWORD cSid = 0; + DWORD dwAclSize = 0; + + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetACLOfPid on pid : 0x%08x\n", + pid)); + + + SidBuffer sidCurrentProcess; + SidBuffer sidTargetProcess; + SidBuffer sidTargetProcessAppContainer; + + // Get sid for current process. + if (SUCCEEDED(sidCurrentProcess.InitFromProcessNoThrow(GetCurrentProcessId()))) + { + pCurrentProcessSid = sidCurrentProcess.GetSid().RawSid(); + cSid++; + } + + // Get sid for target process. + if (SUCCEEDED(sidTargetProcess.InitFromProcessNoThrow(pid))) + { + pTargetProcessSid = sidTargetProcess.GetSid().RawSid(); + cSid++; + } + + //FISHY: what is the scenario where only one of the above calls succeeds? + if (cSid == 0) + { + // failed to get any useful sid. Just return. + // need a better error. + // + hr = E_FAIL; + goto exit; + } + + hr = sidTargetProcessAppContainer.InitFromProcessAppContainerSidNoThrow(pid); + if (FAILED(hr)) + { + goto exit; + } + else if (hr == S_OK) + { + pTargetProcessAppContainerSid = sidTargetProcessAppContainer.GetSid().RawSid(); + cSid++; + } + else if(hr == S_FALSE) //not an app container, no sid to add + { + hr = S_OK; // don't leak S_FALSE + } + + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetACLOfPid number of sid : 0x%08x\n", + cSid)); + + // Now allocate space for ACL. First calculate the space is need to hold ACL + dwAclSize = sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)) * cSid; + if (pCurrentProcessSid) + { + dwAclSize += GetLengthSid(pCurrentProcessSid); + } + if (pTargetProcessSid) + { + dwAclSize += GetLengthSid(pTargetProcessSid); + } + if (pTargetProcessAppContainerSid) + { + dwAclSize += GetLengthSid(pTargetProcessAppContainerSid); + } + + *ppACL = (PACL) new (nothrow) char[dwAclSize]; + if (*ppACL == NULL) + { + hr = E_OUTOFMEMORY; + goto exit; + } + + // Initialize ACL + // add each sid to the allowed ace list + if (!InitializeAcl(*ppACL, dwAclSize, ACL_REVISION)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + + if (pCurrentProcessSid) + { + // add the current process's sid into ACL if we have it + if (!AddAccessAllowedAce(*ppACL, ACL_REVISION, CLR_IPC_GENERIC_RIGHT, pCurrentProcessSid)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + } + + if (pTargetProcessSid) + { + // add the target process's sid into ACL if we have it + if (!AddAccessAllowedAce(*ppACL, ACL_REVISION, CLR_IPC_GENERIC_RIGHT, pTargetProcessSid)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + } + + if (pTargetProcessAppContainerSid) + { + // add the target process's AppContainer's sid into ACL if we have it + if (!AddAccessAllowedAce(*ppACL, ACL_REVISION, CLR_IPC_GENERIC_RIGHT, pTargetProcessAppContainerSid)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + } + + // we better to form a valid ACL to return + _ASSERTE(IsValidAcl(*ppACL)); +exit: + if (FAILED(hr) && *ppACL) + { + delete [] (reinterpret_cast<char*>(ppACL)); + } + return hr; +} // SecurityUtil::GetACLOfPid + + +//***************************************************************** +// static helper function +// +// free the ACL allocated by SecurityUtil::GetACLOfPid +// +// [IN] pACL - ACL to be freed +// +//***************************************************************** +void SecurityUtil::FreeACL(PACL pACL) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + if (pACL) + { + delete [] (reinterpret_cast<char*>(pACL)); + } +} // SecurityUtil::FreeACL + + +//***************************************************************** +// constructor +// +// [IN] pACL - ACL that this instance of SecurityUtil will held on to +// +//***************************************************************** +SecurityUtil::SecurityUtil(PACL pACL) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + m_pACL = pACL; + m_pSacl = NULL; + m_fInitialized = false; +} + +//***************************************************************** +// destructor +// +// free the ACL that this instance of SecurityUtil helds on to +// +//***************************************************************** +SecurityUtil::~SecurityUtil() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + FreeACL(m_pACL); + FreeACL(m_pSacl); +} + +//***************************************************************** +// Initialization function +// +// form the SecurityDescriptor that will represent the m_pACL +// +//***************************************************************** +HRESULT SecurityUtil::Init() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + if (m_pACL) + { + if (!InitializeSecurityDescriptor(&m_SD, SECURITY_DESCRIPTOR_REVISION)) + { + hr = HRESULT_FROM_GetLastError(); + return hr; + } + if (!SetSecurityDescriptorDacl(&m_SD, TRUE, m_pACL, FALSE)) + { + hr = HRESULT_FROM_GetLastError(); + return hr; + } + + m_SA.nLength = sizeof(SECURITY_ATTRIBUTES); + m_SA.lpSecurityDescriptor = &m_SD; + m_SA.bInheritHandle = FALSE; + m_fInitialized = true; + } + return S_OK; +} + +// *************************************************************************** +// Initialization functions which will call the normal Init and add a +// mandatory label entry to the sacl +// +// Expects hProcess to be a valid handle to the process which has the desired +// mandatory label +// *************************************************************************** +HRESULT SecurityUtil::Init(HANDLE hProcess) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = Init(); + if (FAILED(hr)) + { + return hr; + } + + NewArrayHolder<BYTE> pLabel; + + hr = GetMandatoryLabelFromProcess(hProcess, &pLabel); + if (FAILED(hr)) + { + return hr; + } + + TOKEN_MANDATORY_LABEL * ptml = (TOKEN_MANDATORY_LABEL *) pLabel.GetValue(); + + hr = SetSecurityDescriptorMandatoryLabel(ptml->Label.Sid); + + return hr; +} + + +// *************************************************************************** +// Given a process, this will put the mandatory label into a buffer and point +// ppbLabel at the buffer. +// +// Caller must free ppbLabel via the array "delete []" operator +// *************************************************************************** +HRESULT SecurityUtil::GetMandatoryLabelFromProcess(HANDLE hProcess, LPBYTE * ppbLabel) +{ + *ppbLabel = NULL; + + DWORD dwSize = 0; + HandleHolder hToken; + DWORD err = 0; + + if(!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) + { + return HRESULT_FROM_GetLastError(); + } + + if(!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)TokenIntegrityLevel, NULL, 0, &dwSize)) + { + err = GetLastError(); + } + + // We need to make sure that GetTokenInformation failed in a predictable manner so we know that + // dwSize has the correct buffer size in it. + if (err != ERROR_INSUFFICIENT_BUFFER || dwSize == 0) + { + return HRESULT_FROM_WIN32(err); + } + + NewArrayHolder<BYTE> pLabel = new (nothrow) BYTE[dwSize]; + if (pLabel == NULL) + { + return E_OUTOFMEMORY; + } + + if(!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)TokenIntegrityLevel, pLabel, dwSize, &dwSize)) + { + return HRESULT_FROM_GetLastError(); + } + + // Our caller will be freeing the memory so use Extract + *ppbLabel = pLabel.Extract(); + + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Returns pointer inside the specified mandatory SID to the DWORD representing the +// integrity level of the process. This DWORD will be one of the +// SECURITY_MANDATORY_*_RID constants. +// +// Arguments: +// psidIntegrityLevelLabel - [in] PSID in which to find the integrity level. +// +// Return Value: +// Pointer to the RID stored in the specified SID. This RID represents the +// integrity level of the process +// + +// static +DWORD * SecurityUtil::GetIntegrityLevelFromMandatorySID(PSID psidIntegrityLevelLabel) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + return GetSidSubAuthority(psidIntegrityLevelLabel, (*GetSidSubAuthorityCount(psidIntegrityLevelLabel) - 1)); +} + +// Creates a mandatory label ace and sets it to be the entry in the +// security descriptor's sacl. This assumes there are no other entries +// in the sacl +HRESULT SecurityUtil::SetSecurityDescriptorMandatoryLabel(PSID psidIntegrityLevelLabel) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DWORD cbSid = GetLengthSid(psidIntegrityLevelLabel); + DWORD cbAceStart = offsetof(SYSTEM_MANDATORY_LABEL_ACE, SidStart); + // We are about allocate memory for a ACL and an ACE so we need space for: + // 1) the ACL: sizeof(ACL) + // 2) the entry: the sid is of variable size, so the SYSTEM_MANDATORY_LABEL_ACE + // structure has only the first DWORD of the sid in its definition, to get the + // appropriate size we get size without SidStart and add on the actual size of the sid + DWORD cbSacl = sizeof(ACL) + cbAceStart + cbSid; + + NewArrayHolder<BYTE> sacl = new (nothrow) BYTE[cbSacl]; + + m_pSacl = NULL; + + if (sacl == NULL) + { + return E_OUTOFMEMORY; + } + ZeroMemory(sacl.GetValue(), cbSacl); + PACL pSacl = reinterpret_cast<ACL *>(sacl.GetValue()); + SYSTEM_MANDATORY_LABEL_ACE * pLabelAce = reinterpret_cast<SYSTEM_MANDATORY_LABEL_ACE *>(sacl.GetValue() + sizeof(ACL)); + PSID psid = reinterpret_cast<SID *>(&pLabelAce->SidStart); + + // Our buffer looks like this now: (not drawn to scale) + // sacl pSacl pLabelAce psid + // - - + // | | + // | - - + // | | + // | | - + // | - | + // - - + + DWORD dwIntegrityLevel = *(GetIntegrityLevelFromMandatorySID(psidIntegrityLevelLabel)); + + if (dwIntegrityLevel >= SECURITY_MANDATORY_MEDIUM_RID) + { + // No need to set the integrity level unless it's lower than medium + return S_OK; + } + + if(!InitializeAcl(pSacl, cbSacl, ACL_REVISION)) + { + return HRESULT_FROM_GetLastError(); + } + + pSacl->AceCount = 1; + + pLabelAce->Header.AceType = SYSTEM_MANDATORY_LABEL_ACE_TYPE; + pLabelAce->Header.AceSize = WORD(cbAceStart + cbSid); + pLabelAce->Mask = SYSTEM_MANDATORY_LABEL_NO_WRITE_UP; + + memcpy(psid, psidIntegrityLevelLabel, cbSid); + + if(!SetSecurityDescriptorSacl(m_SA.lpSecurityDescriptor, TRUE, pSacl, FALSE)) + { + return HRESULT_FROM_GetLastError(); + } + + // No need to delete the sacl buffer, it will be deleted in the + // destructor of this class + m_pSacl = (PACL)sacl.Extract(); + return S_OK; +} + +//***************************************************************** +// Return SECURITY_ATTRIBUTES that we form in the Init function +// +// No clean up is needed after calling this function. The destructor of the +// instance will do the right thing. Note that this is designed such that +// we minimize memory allocation, ie the SECURITY_DESCRIPTOR and +// SECURITY_ATTRIBUTES are embedded in the SecurityUtil instance. +// +// Caller should not modify the returned SECURITY_ATTRIBUTES!!! +//***************************************************************** +HRESULT SecurityUtil::GetSA(SECURITY_ATTRIBUTES **ppSA) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(ppSA); + + if (m_fInitialized == false) + { + _ASSERTE(!"Bad code path!"); + *ppSA = NULL; + return E_FAIL; + } + + *ppSA = &m_SA; + return S_OK; +} diff --git a/src/coreclr/utilcode/securitywrapper.cpp b/src/coreclr/utilcode/securitywrapper.cpp new file mode 100644 index 00000000000..8f7ac38f444 --- /dev/null +++ b/src/coreclr/utilcode/securitywrapper.cpp @@ -0,0 +1,749 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// File: SecurityWrapper.cpp +// + +// +// Wrapper around Win32 Security functions +// +//***************************************************************************** + +#include "stdafx.h" + +#include "securitywrapper.h" +#include "ex.h" +#include "holder.h" + + +// For GetSidFromProcess* +#include <tlhelp32.h> +#include "wtsapi32.h" + + +//----------------------------------------------------------------------------- +// Constructor for Sid wrapper class. +// pSid - OS sid to wrap +//----------------------------------------------------------------------------- +Sid::Sid(PSID pSid) +{ + _ASSERTE(pSid != NULL); + m_pSid = pSid; +} + +//----------------------------------------------------------------------------- +// Aesthetic wrapper for Sid equality +//----------------------------------------------------------------------------- +bool Sid::Equals(PSID a, PSID b) +{ + return EqualSid(a, b) != 0; +} + +//----------------------------------------------------------------------------- +// Ctor for SidBuffer class +//----------------------------------------------------------------------------- +SidBuffer::SidBuffer() +{ + m_pBuffer = NULL; +} + +//----------------------------------------------------------------------------- +// Dtor for SidBuffer class. +//----------------------------------------------------------------------------- +SidBuffer::~SidBuffer() +{ + delete [] m_pBuffer; +} + +//----------------------------------------------------------------------------- +// Get the underlying sid +// Caller assumes SidBuffer has been initialized. +//----------------------------------------------------------------------------- +Sid SidBuffer::GetSid() +{ + _ASSERTE(m_pBuffer != NULL); + Sid s((PSID) m_pBuffer); + return s; +} + +// ---------------------------------------------------------------------------- +// Used by GetSidFromProcessWorker to determine which SID from the +// process token to use when initializing the SID +enum SidType +{ + // Use TokenOwner: the default owner SID used for newly created objects + kOwnerSid, + + // Use TokenUser: the user account from the token + kUserSid, +}; + +// ---------------------------------------------------------------------------- +// GetSidFromProcessWorker +// +// Description: +// Internal helper. Gets the SID for the given process and given sid type +// +// Arguments: +// * dwProcessId - [in] Process to get SID from +// * sidType - [in] Type of sid to get (owner or user) +// * ppSid - [out] SID found. Caller responsible for deleting this memory. +// +// Return Value: +// HRESULT indicating success / failure. +// +// Notes: +// * Caller owns deleting (*ppSid) when done with the SID +// + +HRESULT GetSidFromProcessWorker(DWORD dwProcessId, SidType sidType, PSID *ppSid) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + TOKEN_USER *pTokUser = NULL; + HANDLE hProc = INVALID_HANDLE_VALUE; + HANDLE hToken = INVALID_HANDLE_VALUE; + DWORD dwRetLength = 0; + LPVOID pvTokenInfo = NULL; + TOKEN_INFORMATION_CLASS tokenInfoClass; + PSID pSidFromTokenInfo = NULL; + DWORD cbSid; + PSID pSid = NULL; + + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetSidFromProcess: 0x%08x\n", + dwProcessId)); + + _ASSERTE(ppSid); + *ppSid = NULL; + + _ASSERTE((sidType == kOwnerSid) || (sidType == kUserSid)); + tokenInfoClass = (sidType == kOwnerSid) ? TokenOwner : TokenUser; + + hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId); + + if (hProc == NULL) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + if (!OpenProcessToken(hProc, TOKEN_QUERY, &hToken)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + + // figure out the length + GetTokenInformation(hToken, tokenInfoClass, NULL, 0, &dwRetLength); + _ASSERTE(dwRetLength); + + pvTokenInfo = new (nothrow) BYTE[dwRetLength]; + if (pvTokenInfo == NULL) + { + hr = E_OUTOFMEMORY; + goto exit; + } + + if (!GetTokenInformation(hToken, tokenInfoClass, pvTokenInfo, dwRetLength, &dwRetLength)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + + // Copy over the SID + pSidFromTokenInfo = + (sidType == kOwnerSid) ? + ((TOKEN_OWNER *) pvTokenInfo)->Owner : + ((TOKEN_USER *) pvTokenInfo)->User.Sid; + cbSid = GetLengthSid(pSidFromTokenInfo); + pSid = new (nothrow) BYTE[cbSid]; + if (pSid == NULL) + { + hr = E_OUTOFMEMORY; + } + else + { + if (!CopySid(cbSid, pSid, pSidFromTokenInfo)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + } + + *ppSid = pSid; + pSid = NULL; + +exit: + if (hToken != INVALID_HANDLE_VALUE) + { + CloseHandle(hToken); + } + if (hProc != INVALID_HANDLE_VALUE) + { + // clean up + CloseHandle(hProc); + } + if (pvTokenInfo) + { + delete [] (reinterpret_cast<BYTE*>(pvTokenInfo)); + } + + if (pSid) + { + delete [] (reinterpret_cast<BYTE*>(pSid)); + } + + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetSidFromProcess return hr : 0x%08x\n", + hr)); + + return hr; +} + +#ifndef FEATURE_CORESYSTEM +//----------------------------------------------------------------------------- +// get the sid of a given process id using WTSEnumerateProcesses +// @todo: Make this function fail when WTSEnumerateProcesses is not available +// Or is it always available on all of our platform? +// +// Caller remember to call delete on *ppSid +//----------------------------------------------------------------------------- +HRESULT GetSidFromProcessEXWorker(DWORD dwProcessId, PSID *ppSid) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(ppSid)); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + PWTS_PROCESS_INFOW rgProcessInfo = NULL; + DWORD dwNumProcesses; + DWORD iProc; + DWORD cbSid; + PSID pSid = NULL; + + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetSidFromProcessEx: 0x%08x\n", + dwProcessId)); + + + *ppSid = NULL; + if (!WTSEnumerateProcessesW(WTS_CURRENT_SERVER_HANDLE, // use local server + 0, // Reserved must be zero + 1, // version must be 1 + &rgProcessInfo, // Receives pointer to process list + &dwNumProcesses)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + + for (iProc = 0; iProc < dwNumProcesses; iProc++) + { + + if (rgProcessInfo[iProc].ProcessId == dwProcessId) + { + if (rgProcessInfo[iProc].pUserSid == NULL) + { + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetSidFromProcessEx is not able to retrieve SID\n")); + + // if there is no Sid for the user, don't call GetLengthSid. + // It will crash! It is ok to return E_FAIL as caller will ignore it. + hr = E_FAIL; + goto exit; + } + cbSid = GetLengthSid(rgProcessInfo[iProc].pUserSid); + pSid = new (nothrow) BYTE[cbSid]; + if (pSid == NULL) + { + hr = E_OUTOFMEMORY; + } + else + { + if (!CopySid(cbSid, pSid, rgProcessInfo[iProc].pUserSid)) + { + hr = HRESULT_FROM_GetLastError(); + } + else + { + // We are done. Go to exit + hr = S_OK; + } + } + + // we already find a match. Even if we fail from memory allocation of CopySid, still + // goto exit. + goto exit; + } + } + + // Walk the whole list and cannot find the matching PID + // Find a better error code. + hr = E_FAIL; + +exit: + + if (rgProcessInfo) + { + WTSFreeMemory(rgProcessInfo); + } + + if (FAILED(hr) && pSid) + { + delete [] (reinterpret_cast<BYTE*>(pSid)); + } + + if (SUCCEEDED(hr)) + { + _ASSERTE(pSid); + *ppSid = pSid; + } + LOG((LF_CORDB, LL_INFO10000, + "SecurityUtil::GetSidFromProcessEx return hr : 0x%08x\n", + hr)); + + + return hr; +} +#endif // !FEATURE_CORESYSTEM + +//----------------------------------------------------------------------------- +// The functions below initialize this SidBuffer instance with a Sid from +// the token of the specified process. The first pair use the OWNER sid from +// the process token if possible; else use the term serv API to find the +// USER sid from the process token. This seems a little inconsistent, but +// remains this way for backward compatibility. The second pair consistently +// use the USER sid (never the OWNER). +// +// While the USER and OWNER sid are often the same, they are not always the +// same. For example, running a process on win2k3 server as a member of the +// local admin group causes the USER sid to be the logged-on user, and the +// OWNER sid to be the local admins group. At least, that's how it was on +// Monday. Expect this to change randomly at unexpected times, as most +// security-related behavior does. +//----------------------------------------------------------------------------- + + +// ---------------------------------------------------------------------------- +// SidBuffer::InitFromProcessNoThrow +// +// Description: +// Initialize this SidBuffer instance with a Sid from the token of the specified +// process. Use the OWNER sid from the process token if possible; else use the term +// serv API to find the USER sid from the process token. This seems a little +// inconsistent, but remains this way for backward compatibility. +// +// Arguments: +// * pid - Process ID from which to grab the SID +// +// Return Value: +// HRESULT indicating success / failure +// + +HRESULT SidBuffer::InitFromProcessNoThrow(DWORD pid) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(m_pBuffer == NULL); + HRESULT hr = GetSidFromProcessWorker(pid, kOwnerSid, (PSID *) &m_pBuffer); +#ifndef FEATURE_CORESYSTEM + if (FAILED(hr)) + { + hr = GetSidFromProcessEXWorker(pid, (PSID *) &m_pBuffer); + } +#endif // !FEATURE_CORESYSTEM + if (FAILED(hr)) + { + return hr; + } + + _ASSERTE(m_pBuffer != NULL); + return S_OK; +} + +// See code:SidBuffer::InitFromProcessNoThrow. Throws if there's an error. +void SidBuffer::InitFromProcess(DWORD pid) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = InitFromProcessNoThrow(pid); + if (FAILED(hr)) + { + ThrowHR(hr); + } +} + +// ---------------------------------------------------------------------------- +// SidBuffer::InitFromProcessAppContainerSidNoThrow +// +// Description: +// Initialize this SidBuffer instance with the TokenAppContainerSid from +// the process token +// +// Arguments: +// * pid - Process ID from which to grab the SID +// +// Return Value: +// HRESULT indicating success / failure +// S_FALSE indicates the process isn't in an AppContainer +// +HRESULT SidBuffer::InitFromProcessAppContainerSidNoThrow(DWORD pid) +{ + HRESULT hr = S_OK; + HANDLE hToken = NULL; + BOOL fIsLowBox = FALSE; + + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (hProcess == NULL) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + + // Define new TOKEN_INFORMATION_CLASS/ TOKEN_APPCONTAINER_INFORMATION members for Win8 since they are not in the DevDiv copy of WinSDK yet + typedef enum _TOKEN_INFORMATION_CLASS_WIN8 { + TokenIsAppContainer = TokenLogonSid + 1, + TokenCapabilities, + TokenAppContainerSid + } TOKEN_INFORMATION_CLASS_WIN8; + + typedef struct _TOKEN_APPCONTAINER_INFORMATION + { + PSID TokenPackage; + } TOKEN_APPCONTAINER_INFORMATION, *PTOKEN_APPCONTAINER_INFORMATION; + + DWORD size; + if (!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)TokenIsAppContainer, &fIsLowBox, sizeof(fIsLowBox), &size)) + { + DWORD gle = GetLastError(); + if (gle == ERROR_INVALID_PARAMETER || gle == ERROR_INVALID_FUNCTION) + { + hr = S_FALSE; // We are on an OS which doesn't understand LowBox + } + else + { + hr = HRESULT_FROM_WIN32(gle); + } + goto exit; + } + + if (!fIsLowBox) + { + hr = S_FALSE; + goto exit; + } + + UCHAR PackSid[SECURITY_MAX_SID_SIZE + sizeof(TOKEN_APPCONTAINER_INFORMATION)]; + if (!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)TokenAppContainerSid, &PackSid, sizeof(PackSid), &size)) + { + hr = HRESULT_FROM_GetLastError(); + goto exit; + } + + { + PTOKEN_APPCONTAINER_INFORMATION pTokPack = (PTOKEN_APPCONTAINER_INFORMATION)&PackSid; + PSID pLowBoxPackage = pTokPack->TokenPackage; + DWORD dwSidLen = GetLengthSid(pLowBoxPackage); + m_pBuffer = new (nothrow) BYTE[dwSidLen]; + if (m_pBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto exit; + } + else + { + if (!CopySid(dwSidLen, m_pBuffer, pLowBoxPackage)) + { + hr = HRESULT_FROM_GetLastError(); + delete m_pBuffer; + m_pBuffer = NULL; + goto exit; + } + } + } + +exit: + if (hProcess != NULL) + { + CloseHandle(hProcess); + } + if (hToken != NULL) + { + CloseHandle(hToken); + } + + return hr; +} + +// ---------------------------------------------------------------------------- +// SidBuffer::InitFromProcessUserNoThrow +// +// Description: +// Initialize this SidBuffer instance with a Sid from the token of the specified +// process. Use the USER sid from the process token if possible; else use the term +// serv API to find the USER sid from the process token. +// +// Arguments: +// * pid - Process ID from which to grab the SID +// +// Return Value: +// HRESULT indicating success / failure +// + +HRESULT SidBuffer::InitFromProcessUserNoThrow(DWORD pid) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(m_pBuffer == NULL); + HRESULT hr = GetSidFromProcessWorker(pid, kUserSid, (PSID *) &m_pBuffer); +#ifndef FEATURE_CORESYSTEM + if (FAILED(hr)) + { + hr = GetSidFromProcessEXWorker(pid, (PSID *) &m_pBuffer); + } +#endif // !FEATURE_CORESYSTEM + if (FAILED(hr)) + { + return hr; + } + + _ASSERTE(m_pBuffer != NULL); + return S_OK; +} + +// See code:SidBuffer::InitFromProcessUserNoThrow. Throws if there's an error. +void SidBuffer::InitFromProcessUser(DWORD pid) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = InitFromProcessUserNoThrow(pid); + if (FAILED(hr)) + { + ThrowHR(hr); + } +} + +//----------------------------------------------------------------------------- +// Ctor for Dacl class. Wraps a win32 dacl. +//----------------------------------------------------------------------------- +Dacl::Dacl(PACL pAcl) +{ + m_acl = pAcl; +} + +//----------------------------------------------------------------------------- +// Get number of ACE (Access Control Entries) in this DACL. +//----------------------------------------------------------------------------- +SIZE_T Dacl::GetAceCount() +{ + return (SIZE_T) m_acl->AceCount; +} + +//----------------------------------------------------------------------------- +// Get Raw a ACE at the given index. +// Caller assumes index is valid (0 <= dwAceIndex < GetAceCount()) +// Throws on error (which should only be if the index is out of bounds). +//----------------------------------------------------------------------------- +ACE_HEADER * Dacl::GetAce(SIZE_T dwAceIndex) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + } CONTRACTL_END; + + ACE_HEADER * pAce = NULL; + BOOL fOk = ::GetAce(m_acl, (DWORD) dwAceIndex, (LPVOID*) &pAce); + _ASSERTE(fOk == (pAce != NULL)); + if (!fOk) + { + ThrowLastError(); + } + return pAce; +} + + + +//----------------------------------------------------------------------------- +// Ctor for SecurityDescriptor +//----------------------------------------------------------------------------- +Win32SecurityDescriptor::Win32SecurityDescriptor() +{ + m_pDesc = NULL; +} + +//----------------------------------------------------------------------------- +// Dtor for security Descriptor. +//----------------------------------------------------------------------------- +Win32SecurityDescriptor::~Win32SecurityDescriptor() +{ + delete [] ((BYTE*) m_pDesc); +} + + + +//----------------------------------------------------------------------------- +// Get the dacl for this security descriptor. +//----------------------------------------------------------------------------- +Dacl Win32SecurityDescriptor::GetDacl() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + } CONTRACTL_END; + + _ASSERTE(m_pDesc != NULL); + + BOOL bPresent; + BOOL bDaclDefaulted; + PACL acl; + + if (GetSecurityDescriptorDacl(m_pDesc, &bPresent, &acl, &bDaclDefaulted) == 0) + { + ThrowLastError(); + } + if (!bPresent) + { + // No dacl. We consider this an error because all of the objects we expect + // to see should be dacled. If it's not dacled, then it's a malicious user spoofing it. + ThrowHR(E_INVALIDARG); + } + + Dacl d(acl); + return d; +} + +//----------------------------------------------------------------------------- +// Get the owner from the security descriptor. +//----------------------------------------------------------------------------- +HRESULT Win32SecurityDescriptor::GetOwnerNoThrow( PSID* ppSid) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + _ASSERTE(m_pDesc != NULL); + BOOL bOwnerDefaulted; + + if( ppSid == NULL ) + { + return E_INVALIDARG; + } + + if (GetSecurityDescriptorOwner(m_pDesc, ppSid, &bOwnerDefaulted) == 0) + { + DWORD err = GetLastError(); + return HRESULT_FROM_WIN32(err); + } + + return S_OK; +} +Sid Win32SecurityDescriptor::GetOwner() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + } CONTRACTL_END; + + PSID pSid; + HRESULT hr = GetOwnerNoThrow( &pSid ); + if( FAILED(hr) ) + { + ThrowHR( hr ); + } + + Sid s(pSid); + return s; +} + +//----------------------------------------------------------------------------- +// Initialize this instance of a SecurityDescriptor with the SD for the handle. +// The handle must have READ_CONTROL permissions to do this. +// Throws on error. +//----------------------------------------------------------------------------- +HRESULT Win32SecurityDescriptor::InitFromHandleNoThrow(HANDLE h) +{ + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + _ASSERTE(m_pDesc == NULL); // only init once. + + DWORD cbNeeded = 0; + + DWORD flags = OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; + + // Now get the creator's SID. First get the size of the array needed. + BOOL fOk = GetKernelObjectSecurity(h, flags, NULL, 0, &cbNeeded); + DWORD err = GetLastError(); + + // Caller should give us a handle for which this succeeds. First call will + // fail w/ InsufficientBuffer. + CONSISTENCY_CHECK_MSGF(fOk || (err == ERROR_INSUFFICIENT_BUFFER), ("Failed to get KernelSecurity for object handle=%p.Err=%d\n", h, err)); + + PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR) new(nothrow) BYTE[cbNeeded]; + if( pSD == NULL ) + { + return E_OUTOFMEMORY; + } + + if (GetKernelObjectSecurity(h, flags, pSD, cbNeeded, &cbNeeded) == 0) + { + // get last error and fail out. + err = GetLastError(); + delete [] ((BYTE*) pSD); + return HRESULT_FROM_WIN32(err); + } + + m_pDesc = pSD; + return S_OK; +} +void Win32SecurityDescriptor::InitFromHandle(HANDLE h) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + } CONTRACTL_END; + + HRESULT hr = InitFromHandleNoThrow(h); + if (FAILED(hr)) + { + ThrowHR(hr); + } +} diff --git a/src/coreclr/utilcode/sha1.cpp b/src/coreclr/utilcode/sha1.cpp new file mode 100644 index 00000000000..9c2e091b527 --- /dev/null +++ b/src/coreclr/utilcode/sha1.cpp @@ -0,0 +1,368 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// +// +// =========================================================================== +// File: sha1.cpp +// +// =========================================================================== +/*++ + +Abstract: + + SHA-1 implementation + +Revision History: + +--*/ + +/* + File sha1.cpp <STRIP>Version 03 August 2000.</STRIP> + + + This implements the SHA-1 hash function. + For algorithmic background see (for example) + + + Alfred J. Menezes et al + Handbook of Applied Cryptography + The CRC Press Series on Discrete Mathematics + and its Applications + CRC Press LLC, 1997 + ISBN 0-8495-8523-7 + QA76.9A25M643 + + Also see FIPS 180-1 - Secure Hash Standard, + 1993 May 11 and 1995 April 17, by the U.S. + National Institute of Standards and Technology (NIST). + +*/ + +#include "stdafx.h" +#include <utilcode.h> // Utility helpers. +#include "sha1.h" + +typedef const DWORD DWORDC; +#define ROTATE32L(x,n) _rotl(x,n) +#define SHAVE32(x) (DWORD)(x) + +static void SHA1_block(SHA1_CTX *ctx) +/* + Update the SHA-1 hash from a fresh 64 bytes of data. +*/ +{ + static DWORDC sha1_round1 = 0x5A827999u; + static DWORDC sha1_round2 = 0x6ED9EBA1u; + static DWORDC sha1_round3 = 0x8F1BBCDCu; + static DWORDC sha1_round4 = 0xCA62C1D6u; + + DWORD a = ctx->partial_hash[0], b = ctx->partial_hash[1]; + DWORD c = ctx->partial_hash[2], d = ctx->partial_hash[3]; + DWORD e = ctx->partial_hash[4]; + DWORD msg80[80]; + int i; + BOOL OK = TRUE; + + // OACR note: + // Loop conditions are using (i <= limit - increment) instead of (i < limit) to satisfy OACR. When the increment is greater + // than 1, OACR incorrectly thinks that the max value of 'i' is (limit - 1). + + for (i = 0; i < 16; i++) { // Copy to local array, zero original + // Extend length to 80 + DWORDC datval = ctx->awaiting_data[i]; + ctx->awaiting_data[i] = 0; + msg80[i] = datval; + } + + for (i = 16; i <= 80 - 2; i += 2) { + DWORDC temp1 = msg80[i-3] ^ msg80[i-8] + ^ msg80[i-14] ^ msg80[i-16]; + DWORDC temp2 = msg80[i-2] ^ msg80[i-7] + ^ msg80[i-13] ^ msg80[i-15]; + msg80[i ] = ROTATE32L(temp1, 1); + msg80[i+1] = ROTATE32L(temp2, 1); + } + +#define ROUND1(B, C, D) (((D) ^ ((B) & ((C) ^ (D)))) + sha1_round1) + // Equivalent to (B & C) | (~B & D). + // (check cases B = 0 and B = 1) +#define ROUND2(B, C, D) (((B) ^ (C) ^ (D)) + sha1_round2) + +#define ROUND3(B, C, D) ((((C) & ((B) | (D))) | ((B) & (D))) + sha1_round3) + +#define ROUND4(B, C, D) (((B) ^ (C) ^ (D)) + sha1_round4) + +// Round 1 + for (i = 0; i <= 20 - 5; i += 5) { + e += ROTATE32L(a, 5) + ROUND1(b, c, d) + msg80[i]; + b = ROTATE32L(b, 30); + + d += ROTATE32L(e, 5) + ROUND1(a, b, c) + msg80[i+1]; + a = ROTATE32L(a, 30); + + c += ROTATE32L(d, 5) + ROUND1(e, a, b) + msg80[i+2]; + e = ROTATE32L(e, 30); + + b += ROTATE32L(c, 5) + ROUND1(d, e, a) + msg80[i+3]; + d = ROTATE32L(d, 30); + + a += ROTATE32L(b, 5) + ROUND1(c, d, e) + msg80[i+4]; + c = ROTATE32L(c, 30); +#if 0 + printf("i = %ld %08lx %08lx %08lx %08lx %08lx\n", + i, a, b, c, d, e); +#endif + } // for i + +// Round 2 + for (i = 20; i <= 40 - 5; i += 5) { + e += ROTATE32L(a, 5) + ROUND2(b, c, d) + msg80[i]; + b = ROTATE32L(b, 30); + + d += ROTATE32L(e, 5) + ROUND2(a, b, c) + msg80[i+1]; + a = ROTATE32L(a, 30); + + c += ROTATE32L(d, 5) + ROUND2(e, a, b) + msg80[i+2]; + e = ROTATE32L(e, 30); + + b += ROTATE32L(c, 5) + ROUND2(d, e, a) + msg80[i+3]; + d = ROTATE32L(d, 30); + + a += ROTATE32L(b, 5) + ROUND2(c, d, e) + msg80[i+4]; + c = ROTATE32L(c, 30); + } // for i + +// Round 3 + for (i = 40; i <= 60 - 5; i += 5) { + e += ROTATE32L(a, 5) + ROUND3(b, c, d) + msg80[i]; + b = ROTATE32L(b, 30); + + d += ROTATE32L(e, 5) + ROUND3(a, b, c) + msg80[i+1]; + a = ROTATE32L(a, 30); + + c += ROTATE32L(d, 5) + ROUND3(e, a, b) + msg80[i+2]; + e = ROTATE32L(e, 30); + + b += ROTATE32L(c, 5) + ROUND3(d, e, a) + msg80[i+3]; + d = ROTATE32L(d, 30); + + a += ROTATE32L(b, 5) + ROUND3(c, d, e) + msg80[i+4]; + c = ROTATE32L(c, 30); + } // for i + +// Round 4 + for (i = 60; i <= 80 - 5; i += 5) { + e += ROTATE32L(a, 5) + ROUND4(b, c, d) + msg80[i]; + b = ROTATE32L(b, 30); + + d += ROTATE32L(e, 5) + ROUND4(a, b, c) + msg80[i+1]; + a = ROTATE32L(a, 30); + + c += ROTATE32L(d, 5) + ROUND4(e, a, b) + msg80[i+2]; + e = ROTATE32L(e, 30); + + b += ROTATE32L(c, 5) + ROUND4(d, e, a) + msg80[i+3]; + d = ROTATE32L(d, 30); + + a += ROTATE32L(b, 5) + ROUND4(c, d, e) + msg80[i+4]; + c = ROTATE32L(c, 30); + } // for i + +#undef ROUND1 +#undef ROUND2 +#undef ROUND3 +#undef ROUND4 + + ctx->partial_hash[0] += a; + ctx->partial_hash[1] += b; + ctx->partial_hash[2] += c; + ctx->partial_hash[3] += d; + ctx->partial_hash[4] += e; +#if 0 + for (i = 0; i < 16; i++) { + printf("%8lx ", msg16[i]); + if ((i & 7) == 7) printf("\n"); + } + printf("a, b, c, d, e = %08lx %08lx %08lx %08lx %08lx\n", + a, b, c, d, e); + printf("Partial hash = %08lx %08lx %08lx %08lx %08lx\n", + (long)ctx->partial_hash[0], (long)ctx->partial_hash[1], + (long)ctx->partial_hash[2], (long)ctx->partial_hash[3], + (long)ctx->partial_hash[4]); +#endif +} // end SHA1_block + + +void SHA1Hash::SHA1Init(SHA1_CTX *ctx) +{ + ctx->nbit_total[0] = ctx->nbit_total[1] = 0; + + for (DWORD i = 0; i != 16; i++) { + ctx->awaiting_data[i] = 0; + } + + /* + Initialize hash variables. + + */ + + ctx->partial_hash[0] = 0x67452301u; + ctx->partial_hash[1] = 0xefcdab89u; + ctx->partial_hash[2] = ~ctx->partial_hash[0]; + ctx->partial_hash[3] = ~ctx->partial_hash[1]; + ctx->partial_hash[4] = 0xc3d2e1f0u; + +} + +void SHA1Hash::SHA1Update( + SHA1_CTX * ctx, // IN/OUT + const BYTE * msg, // IN + DWORD nbyte) // IN +/* + Append data to a partially hashed SHA-1 message. +*/ +{ + const BYTE *fresh_data = msg; + DWORD nbyte_left = nbyte; + DWORD nbit_occupied = ctx->nbit_total[0] & 511; + DWORD *awaiting_data; + DWORDC nbitnew_low = SHAVE32(8*nbyte); + + + _ASSERTE((nbit_occupied & 7) == 0); // Partial bytes not implemented + + ctx->nbit_total[0] += nbitnew_low; + ctx->nbit_total[1] += (nbyte >> 29) + + (SHAVE32(ctx->nbit_total[0]) < nbitnew_low); + + /* Advance to word boundary in waiting_data */ + + if ((nbit_occupied & 31) != 0) { + awaiting_data = ctx->awaiting_data + nbit_occupied/32; + + while ((nbit_occupied & 31) != 0 && nbyte_left != 0) { + nbit_occupied += 8; + *awaiting_data |= (DWORD)*fresh_data++ + << ((-(int)nbit_occupied) & 31); + nbyte_left--; // Start at most significant byte + } + } // if nbit_occupied + + /* Transfer 4 bytes at a time */ + + do { + DWORDC nword_occupied = nbit_occupied/32; + DWORD nwcopy = min(nbyte_left/4, 16 - nword_occupied); + _ASSERTE (nbit_occupied <= 512); + _ASSERTE ((nbit_occupied & 31) == 0 || nbyte_left == 0); + awaiting_data = ctx->awaiting_data + nword_occupied; + nbyte_left -= 4*nwcopy; + nbit_occupied += 32*nwcopy; + + while (nwcopy != 0) { + DWORDC byte0 = (DWORD)fresh_data[0]; + DWORDC byte1 = (DWORD)fresh_data[1]; + DWORDC byte2 = (DWORD)fresh_data[2]; + DWORDC byte3 = (DWORD)fresh_data[3]; + *awaiting_data++ = byte3 | (byte2 << 8) + | (byte1 << 16) | (byte0 << 24); + /* Big endian */ + fresh_data += 4; + nwcopy--; + } + + if (nbit_occupied == 512) { + SHA1_block(ctx); + nbit_occupied = 0; + awaiting_data -= 16; + _ASSERTE(awaiting_data == ctx->awaiting_data); + } + } while (nbyte_left >= 4); + + _ASSERTE (ctx->awaiting_data + nbit_occupied/32 + == awaiting_data); + + while (nbyte_left != 0) { + DWORDC new_byte = (DWORD)*fresh_data++; + + _ASSERTE((nbit_occupied & 31) <= 16); + nbit_occupied += 8; + *awaiting_data |= new_byte << ((-(int)nbit_occupied) & 31); + nbyte_left--; + } + + _ASSERTE (nbit_occupied == (ctx->nbit_total[0] & 511)); +} // end SHA1Update + + + +void SHA1Hash::SHA1Final( + SHA1_CTX * ctx, // IN/OUT + BYTE * digest) // OUT +/* + Finish a SHA-1 hash. +*/ +{ + DWORDC nbit0 = ctx->nbit_total[0]; + DWORDC nbit1 = ctx->nbit_total[1]; + DWORD nbit_occupied = nbit0 & 511; + DWORD i; + + _ASSERTE((nbit_occupied & 7) == 0); + + ctx->awaiting_data[nbit_occupied/32] + |= (DWORD)0x80 << ((-8-nbit_occupied) & 31); + // Append a 1 bit + nbit_occupied += 8; + + + // Append zero bits until length (in bits) is 448 mod 512. + // Then append the length, in bits. + // Here we assume the buffer was zeroed earlier. + + if (nbit_occupied > 448) { // If fewer than 64 bits left + SHA1_block(ctx); + nbit_occupied = 0; + } + ctx->awaiting_data[14] = nbit1; + ctx->awaiting_data[15] = nbit0; + SHA1_block(ctx); + + /* Copy final digest to user-supplied byte array */ + + for (i = 0; i != 5; i++) { + DWORDC dwi = ctx->partial_hash[i]; + digest[4*i + 0] = (BYTE)((dwi >> 24) & 255); + digest[4*i + 1] = (BYTE)((dwi >> 16) & 255); + digest[4*i + 2] = (BYTE)((dwi >> 8) & 255); + digest[4*i + 3] = (BYTE)(dwi & 255); // Big-endian + } +} // end SHA1Final + +SHA1Hash::SHA1Hash() +{ + m_fFinalized = FALSE; + SHA1Init(&m_Context); +} + +void SHA1Hash::AddData(BYTE *pbData, DWORD cbData) +{ + if (m_fFinalized) + return; + + SHA1Update(&m_Context, pbData, cbData); +} + +// Retrieve a pointer to the final hash. +BYTE *SHA1Hash::GetHash() +{ + if (m_fFinalized) + return m_Value; + + SHA1Final(&m_Context, m_Value); + + m_fFinalized = TRUE; + + return m_Value; +} diff --git a/src/coreclr/utilcode/sigbuilder.cpp b/src/coreclr/utilcode/sigbuilder.cpp new file mode 100644 index 00000000000..368815c55b5 --- /dev/null +++ b/src/coreclr/utilcode/sigbuilder.cpp @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +#include "stdafx.h" +#include "sigbuilder.h" +#include "ex.h" + +void SigBuilder::AppendByte(BYTE b) +{ + STANDARD_VM_CONTRACT; + + Ensure(1); + m_pBuffer[m_dwLength++] = b; +} + +void SigBuilder::AppendData(ULONG data) +{ + STANDARD_VM_CONTRACT; + + // + // Inlined logic from CorSigCompressData + // + + if (data <= 0x7F) + { + Ensure(1); + m_pBuffer[m_dwLength++] = BYTE(data); + return; + } + + if (data <= 0x3FFF) + { + Ensure(2); + + DWORD dwLength = m_dwLength; + BYTE * pBuffer = m_pBuffer; + + pBuffer[dwLength] = BYTE((data >> 8) | 0x80); + pBuffer[dwLength+1] = BYTE(data); + + m_dwLength = dwLength + 2; + return; + } + + if (data <= 0x1FFFFFFF) + { + Ensure(4); + + DWORD dwLength = m_dwLength; + BYTE * pBuffer = m_pBuffer; + + pBuffer[dwLength] = BYTE((data >> 24) | 0xC0); + pBuffer[dwLength+1] = BYTE(data >> 16); + pBuffer[dwLength+2] = BYTE(data >> 8); + pBuffer[dwLength+3] = BYTE(data); + + m_dwLength = dwLength + 4; + return; + } + + // We currently can only represent to 0x1FFFFFFF. + ThrowHR(COR_E_OVERFLOW); +} + +void SigBuilder::AppendToken(mdToken tk) +{ + STANDARD_VM_CONTRACT; + + // + // Inlined logic from CorSigCompressToken + // + + RID rid = RidFromToken(tk); + ULONG32 ulTyp = TypeFromToken(tk); + + _ASSERTE(rid <= 0x3FFFFFF); + rid = (rid << 2); + + // TypeDef is encoded with low bits 00 + // TypeRef is encoded with low bits 01 + // TypeSpec is encoded with low bits 10 + // BaseType is encoded with low bit 11 + // + if (ulTyp == CorSigDecodeTokenType(0)) + { + // make the last two bits 00 + // nothing to do + } + else if (ulTyp == CorSigDecodeTokenType(1)) + { + // make the last two bits 01 + rid |= 0x1; + } + else if (ulTyp == CorSigDecodeTokenType(2)) + { + // make last two bits 0 + rid |= 0x2; + } + else if (ulTyp == CorSigDecodeTokenType(3)) + { + rid |= 0x3; + } + else + { + ThrowHR(COR_E_BADIMAGEFORMAT); + } + + AppendData(rid); +} + +void SigBuilder::AppendBlob(const PVOID pBlob, SIZE_T cbBlob) +{ + STANDARD_VM_CONTRACT; + + Ensure(cbBlob); + memcpy(m_pBuffer + m_dwLength, pBlob, cbBlob); + m_dwLength += (DWORD)cbBlob; +} + +void SigBuilder::Grow(SIZE_T cbMin) +{ + STANDARD_VM_CONTRACT; + + DWORD dwNewAllocation = max(m_dwLength + (DWORD)cbMin, 2 * m_dwAllocation); + + // Overflow checks + if (dwNewAllocation < m_dwLength || (dwNewAllocation - m_dwLength) < cbMin) + ThrowOutOfMemory(); + + BYTE * pNewAllocation = new BYTE[dwNewAllocation]; + memcpy(pNewAllocation, m_pBuffer, m_dwLength); + + BYTE * pOldAllocation = m_pBuffer; + + m_pBuffer = pNewAllocation; + m_dwAllocation = dwNewAllocation; + + if (pOldAllocation != m_prealloc) + delete [] pOldAllocation; +} + +SigBuilder::~SigBuilder() +{ + if (m_pBuffer != m_prealloc) + delete [] m_pBuffer; +} + +SigBuilder::SigBuilder(DWORD cbPreallocationSize) +{ + STANDARD_VM_CONTRACT; + + m_dwLength = 0; + if (cbPreallocationSize <= sizeof(m_prealloc)) + { + m_pBuffer = m_prealloc; + m_dwAllocation = sizeof(m_prealloc); + } + else + { + m_pBuffer = new BYTE[cbPreallocationSize]; + m_dwAllocation = cbPreallocationSize; + } +} diff --git a/src/coreclr/utilcode/sigparser.cpp b/src/coreclr/utilcode/sigparser.cpp new file mode 100644 index 00000000000..890d508c7aa --- /dev/null +++ b/src/coreclr/utilcode/sigparser.cpp @@ -0,0 +1,194 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// +// sigparser.cpp +// + +// +// Signature parsing code +// +#include "stdafx.h" +#include "sigparser.h" +#include "contract.h" + +HRESULT SigParser::SkipExactlyOne() +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + SUPPORTS_DAC; + } + CONTRACTL_END + + CorElementType typ; + HRESULT hr = GetElemType(&typ); + + IfFailRet(hr); + + if (!CorIsPrimitiveType(typ)) + { + switch ((DWORD)typ) + { + default: + // _ASSERT(!"Illegal or unimplement type in COM+ sig."); + return META_E_BAD_SIGNATURE; + break; + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_MVAR: + IfFailRet(GetData(NULL)); // Skip variable number + break; + case ELEMENT_TYPE_VAR_ZAPSIG: + IfFailRet(GetData(NULL)); // Skip RID + break; + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_TYPEDBYREF: + case ELEMENT_TYPE_CANON_ZAPSIG: + break; + + case ELEMENT_TYPE_BYREF: //fallthru + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_PINNED: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_NATIVE_VALUETYPE_ZAPSIG: + IfFailRet(SkipExactlyOne()); // Skip referenced type + break; + + case ELEMENT_TYPE_VALUETYPE: //fallthru + case ELEMENT_TYPE_CLASS: + IfFailRet(GetToken(NULL)); // Skip RID + break; + + case ELEMENT_TYPE_MODULE_ZAPSIG: + IfFailRet(GetData(NULL)); // Skip index + IfFailRet(SkipExactlyOne()); // Skip type + break; + + case ELEMENT_TYPE_FNPTR: + IfFailRet(SkipSignature()); + break; + + case ELEMENT_TYPE_ARRAY: + { + IfFailRet(SkipExactlyOne()); // Skip element type + ULONG rank; + IfFailRet(GetData(&rank)); // Get rank + if (rank) + { + ULONG nsizes; + IfFailRet(GetData(&nsizes)); // Get # of sizes + while (nsizes--) + { + IfFailRet(GetData(NULL)); // Skip size + } + + ULONG nlbounds; + IfFailRet(GetData(&nlbounds)); // Get # of lower bounds + while (nlbounds--) + { + IfFailRet(GetData(NULL)); // Skip lower bounds + } + } + + } + break; + + case ELEMENT_TYPE_SENTINEL: + // Should be unreachable since GetElem strips it + break; + + case ELEMENT_TYPE_INTERNAL: + IfFailRet(GetPointer(NULL)); + break; + + case ELEMENT_TYPE_GENERICINST: + IfFailRet(SkipExactlyOne()); // Skip generic type + ULONG argCnt; + IfFailRet(GetData(&argCnt)); // Get number of parameters + while (argCnt--) + { + IfFailRet(SkipExactlyOne()); // Skip the parameters + } + break; + + } + } + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Skip only a method header signature - not the sigs of the args to the method. +// +HRESULT +SigParser::SkipMethodHeaderSignature( + ULONG * pcArgs) +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + SUPPORTS_DAC; + } + CONTRACTL_END + + HRESULT hr = S_OK; + + // Skip calling convention + ULONG uCallConv; + IfFailRet(GetCallingConvInfo(&uCallConv)); + + if ((uCallConv == IMAGE_CEE_CS_CALLCONV_FIELD) || + (uCallConv == IMAGE_CEE_CS_CALLCONV_LOCAL_SIG)) + { + return META_E_BAD_SIGNATURE; + } + + // Skip type parameter count + if (uCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + IfFailRet(GetData(NULL)); + + // Get arg count; + IfFailRet(GetData(pcArgs)); + + // Skip return type; + IfFailRet(SkipExactlyOne()); + + return hr; +} // SigParser::SkipMethodHeaderSignature + +//--------------------------------------------------------------------------------------- +// +// Skip a sub signature (as immediately follows an ELEMENT_TYPE_FNPTR). +HRESULT SigParser::SkipSignature() +{ + CONTRACTL + { + INSTANCE_CHECK; + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + SUPPORTS_DAC; + } + CONTRACTL_END + + HRESULT hr = S_OK; + + ULONG cArgs; + + IfFailRet(SkipMethodHeaderSignature(&cArgs)); + + // Skip args. + while (cArgs) { + IfFailRet(SkipExactlyOne()); + cArgs--; + } + + return hr; +} diff --git a/src/coreclr/utilcode/splitpath.cpp b/src/coreclr/utilcode/splitpath.cpp new file mode 100644 index 00000000000..b087e6cd3d1 --- /dev/null +++ b/src/coreclr/utilcode/splitpath.cpp @@ -0,0 +1,258 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +/*** +*splitpath.c - break down path name into components +* + +* +*Purpose: +* To provide support for accessing the individual components of an +* arbitrary path name +* +*******************************************************************************/ +#include "stdafx.h" +#include "winwrap.h" +#include "utilcode.h" +#include "sstring.h" + + +/*** +*SplitPath() - split a path name into its individual components +* +*Purpose: +* to split a path name into its individual components +* +*Entry: +* path - pointer to path name to be parsed +* drive - pointer to buffer for drive component, if any +* dir - pointer to buffer for subdirectory component, if any +* fname - pointer to buffer for file base name component, if any +* ext - pointer to buffer for file name extension component, if any +* +*Exit: +* drive - pointer to drive string. Includes ':' if a drive was given. +* dir - pointer to subdirectory string. Includes leading and trailing +* '/' or '\', if any. +* fname - pointer to file base name +* ext - pointer to file extension, if any. Includes leading '.'. +* +*Exceptions: +* +*******************************************************************************/ + +void SplitPath( + const WCHAR *path, + __inout_z __inout_ecount_opt(driveSizeInWords) WCHAR *drive, int driveSizeInWords, + __inout_z __inout_ecount_opt(dirSizeInWords) WCHAR *dir, int dirSizeInWords, + __inout_z __inout_ecount_opt(fnameSizeInWords) WCHAR *fname, size_t fnameSizeInWords, + __inout_z __inout_ecount_opt(extSizeInWords) WCHAR *ext, size_t extSizeInWords) +{ + WRAPPER_NO_CONTRACT; + + LPCWSTR _wszDrive, _wszDir, _wszFileName, _wszExt; + size_t _cchDrive, _cchDir, _cchFileName, _cchExt; + + SplitPathInterior(path, + &_wszDrive, &_cchDrive, + &_wszDir, &_cchDir, + &_wszFileName, &_cchFileName, + &_wszExt, &_cchExt); + + if (drive && _wszDrive) + wcsncpy_s(drive, driveSizeInWords, _wszDrive, min(_cchDrive, _MAX_DRIVE)); + + if (dir && _wszDir) + wcsncpy_s(dir, dirSizeInWords, _wszDir, min(_cchDir, _MAX_DIR)); + + if (fname && _wszFileName) + wcsncpy_s(fname, fnameSizeInWords, _wszFileName, min(_cchFileName, _MAX_FNAME)); + + if (ext && _wszExt) + wcsncpy_s(ext, extSizeInWords, _wszExt, min(_cchExt, _MAX_EXT)); +} + +//******************************************************************************* +// A much more sensible version that just points to each section of the string. +//******************************************************************************* +void SplitPathInterior( + __in LPCWSTR wszPath, + __out_opt LPCWSTR *pwszDrive, __out_opt size_t *pcchDrive, + __out_opt LPCWSTR *pwszDir, __out_opt size_t *pcchDir, + __out_opt LPCWSTR *pwszFileName, __out_opt size_t *pcchFileName, + __out_opt LPCWSTR *pwszExt, __out_opt size_t *pcchExt) +{ + LIMITED_METHOD_CONTRACT; + + // Arguments must come in valid pairs + _ASSERTE(!!pwszDrive == !!pcchDrive); + _ASSERTE(!!pwszDir == !!pcchDir); + _ASSERTE(!!pwszFileName == !!pcchFileName); + _ASSERTE(!!pwszExt == !!pcchExt); + + WCHAR *p; + LPCWSTR last_slash = NULL, dot = NULL; + + /* we assume that the path argument has the following form, where any + * or all of the components may be missing. + * + * <drive><dir><fname><ext> + * + * and each of the components has the following expected form(s) + * + * drive: + * 0 to _MAX_DRIVE-1 characters, the last of which, if any, is a + * ':' + * dir: + * 0 to _MAX_DIR-1 characters in the form of an absolute path + * (leading '/' or '\') or relative path, the last of which, if + * any, must be a '/' or '\'. E.g - + * absolute path: + * \top\next\last\ ; or + * /top/next/last/ + * relative path: + * top\next\last\ ; or + * top/next/last/ + * Mixed use of '/' and '\' within a path is also tolerated + * fname: + * 0 to _MAX_FNAME-1 characters not including the '.' character + * ext: + * 0 to _MAX_EXT-1 characters where, if any, the first must be a + * '.' + * + */ + + /* extract drive letter and :, if any */ + + if ((wcslen(wszPath) > (_MAX_DRIVE - 2)) && (*(wszPath + _MAX_DRIVE - 2) == _T(':'))) { + if (pwszDrive && pcchDrive) { + *pwszDrive = wszPath; + *pcchDrive = _MAX_DRIVE - 1; + } + wszPath += _MAX_DRIVE - 1; + } + else if (pwszDrive && pcchDrive) { + *pwszDrive = NULL; + *pcchDrive = 0; + } + + /* extract path string, if any. Path now points to the first character + * of the path, if any, or the filename or extension, if no path was + * specified. Scan ahead for the last occurrence, if any, of a '/' or + * '\' path separator character. If none is found, there is no path. + * We will also note the last '.' character found, if any, to aid in + * handling the extension. + */ + + for (last_slash = NULL, p = (WCHAR *)wszPath; *p; p++) { + if (*p == _T('/') || *p == _T('\\')) + /* point to one beyond for later copy */ + last_slash = p + 1; + else if (*p == _T('.')) + dot = p; + } + + if (last_slash) { + /* found a path - copy up through last_slash or max. characters + * allowed, whichever is smaller + */ + + if (pwszDir && pcchDir) { + *pwszDir = wszPath; + *pcchDir = last_slash - wszPath; + } + wszPath = last_slash; + } + else if (pwszDir && pcchDir) { + *pwszDir = NULL; + *pcchDir = 0; + } + + /* extract file name and extension, if any. Path now points to the + * first character of the file name, if any, or the extension if no + * file name was given. Dot points to the '.' beginning the extension, + * if any. + */ + + if (dot && (dot >= wszPath)) { + /* found the marker for an extension - copy the file name up to + * the '.'. + */ + if (pwszFileName && pcchFileName) { + *pwszFileName = wszPath; + *pcchFileName = dot - wszPath; + } + /* now we can get the extension - remember that p still points + * to the terminating nul character of path. + */ + if (pwszExt && pcchExt) { + *pwszExt = dot; + *pcchExt = p - dot; + } + } + else { + /* found no extension, give empty extension and copy rest of + * string into fname. + */ + if (pwszFileName && pcchFileName) { + *pwszFileName = wszPath; + *pcchFileName = p - wszPath; + } + if (pwszExt && pcchExt) { + *pwszExt = NULL; + *pcchExt = 0; + } + } +} + +/*** +*SplitPath() - split a path name into its individual components +* +*Purpose: +* to split a path name into its individual components +* +*Entry: +* path - SString representing the path name to be parsed +* drive - Out SString for drive component +* dir - Out SString for subdirectory component +* fname - Out SString for file base name component +* ext - Out SString for file name extension component +* +*Exit: +* drive - Drive string. Includes ':' if a drive was given. +* dir - Subdirectory string. Includes leading and trailing +* '/' or '\', if any. +* fname - File base name +* ext - File extension, if any. Includes leading '.'. +* +*Exceptions: +* +*******************************************************************************/ + +void SplitPath(__in SString const &path, + __inout_opt SString *drive, + __inout_opt SString *dir, + __inout_opt SString *fname, + __inout_opt SString *ext) +{ + LPCWSTR wzDrive, wzDir, wzFname, wzExt; + size_t cchDrive, cchDir, cchFname, cchExt; + + SplitPathInterior(path, + &wzDrive, &cchDrive, + &wzDir, &cchDir, + &wzFname, &cchFname, + &wzExt, &cchExt); + + if (drive != NULL) + drive->Set(wzDrive, (COUNT_T)cchDrive); + + if (dir != NULL) + dir->Set(wzDir, (COUNT_T)cchDir); + + if (fname != NULL) + fname->Set(wzFname, (COUNT_T)cchFname); + + if (ext != NULL) + ext->Set(wzExt, (COUNT_T)cchExt); +} + diff --git a/src/coreclr/utilcode/sstring.cpp b/src/coreclr/utilcode/sstring.cpp new file mode 100644 index 00000000000..a0017a5a003 --- /dev/null +++ b/src/coreclr/utilcode/sstring.cpp @@ -0,0 +1,2790 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// --------------------------------------------------------------------------- +// SString.cpp +// + +// --------------------------------------------------------------------------- + +#include "stdafx.h" +#include "sstring.h" +#include "ex.h" +#include "holder.h" + + +#if defined(_MSC_VER) +#pragma inline_depth (25) +#endif + +//----------------------------------------------------------------------------- +// Static variables +//----------------------------------------------------------------------------- + +// Have one internal, well-known, literal for the empty string. +const BYTE SString::s_EmptyBuffer[2] = { 0 }; + +// @todo: these need to be initialized by calling GetACP() + +UINT SString::s_ACP = 0; + +#ifndef DACCESS_COMPILE +static BYTE s_EmptySpace[sizeof(SString)] = { 0 }; +#endif // DACCESS_COMPILE + +SPTR_IMPL(SString,SString,s_Empty); + +void SString::Startup() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (s_ACP == 0) + { + UINT ACP = GetACP(); + +#ifndef DACCESS_COMPILE + s_Empty = PTR_SString(new (s_EmptySpace) SString()); + s_Empty->SetNormalized(); +#endif // DACCESS_COMPILE + + MemoryBarrier(); + s_ACP = ACP; + } +} + +CHECK SString::CheckStartup() +{ + WRAPPER_NO_CONTRACT; + + CHECK(s_Empty != NULL); + CHECK_OK; +} + +//----------------------------------------------------------------------------- +// Case insensitive helpers. +//----------------------------------------------------------------------------- + +static WCHAR MapChar(WCHAR wc, DWORD dwFlags) +{ + WRAPPER_NO_CONTRACT; + + WCHAR wTmp; + +#ifndef TARGET_UNIX + + int iRet = ::LCMapStringEx(LOCALE_NAME_INVARIANT, dwFlags, &wc, 1, &wTmp, 1, NULL, NULL, 0); + if (!iRet) { + // This can fail in non-exceptional cases becauseof unknown unicode characters. + wTmp = wc; + } + +#else // !TARGET_UNIX + // For PAL, no locale specific processing is done + + if (dwFlags == LCMAP_UPPERCASE) + { + wTmp = +#ifdef SELF_NO_HOST + toupper(wc); +#else + PAL_ToUpperInvariant(wc); +#endif + } + else + { + _ASSERTE(dwFlags == LCMAP_LOWERCASE); + wTmp = +#ifdef SELF_NO_HOST + tolower(wc); +#else + PAL_ToLowerInvariant(wc); +#endif + } +#endif // !TARGET_UNIX + + return wTmp; +} + +#define IS_UPPER_A_TO_Z(x) (((x) >= W('A')) && ((x) <= W('Z'))) +#define IS_LOWER_A_TO_Z(x) (((x) >= W('a')) && ((x) <= W('z'))) +#define CAN_SIMPLE_UPCASE(x) (((x)&~0x7f) == 0) +#define CAN_SIMPLE_DOWNCASE(x) (((x)&~0x7f) == 0) +#define SIMPLE_UPCASE(x) (IS_LOWER_A_TO_Z(x) ? ((x) - W('a') + W('A')) : (x)) +#define SIMPLE_DOWNCASE(x) (IS_UPPER_A_TO_Z(x) ? ((x) - W('A') + W('a')) : (x)) + +/* static */ +int SString::CaseCompareHelper(const WCHAR *buffer1, const WCHAR *buffer2, COUNT_T count, BOOL stopOnNull, BOOL stopOnCount) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(stopOnNull || stopOnCount); + + const WCHAR *buffer1End = buffer1 + count; + int diff = 0; + + while (!stopOnCount || (buffer1 < buffer1End)) + { + WCHAR ch1 = *buffer1++; + WCHAR ch2 = *buffer2++; + diff = ch1 - ch2; + if ((ch1 == 0) || (ch2 == 0)) + { + if (diff != 0 || stopOnNull) + { + break; + } + } + else + { + if (diff != 0) + { + diff = ((CAN_SIMPLE_UPCASE(ch1) ? SIMPLE_UPCASE(ch1) : MapChar(ch1, LCMAP_UPPERCASE)) + - (CAN_SIMPLE_UPCASE(ch2) ? SIMPLE_UPCASE(ch2) : MapChar(ch2, LCMAP_UPPERCASE))); + } + if (diff != 0) + { + break; + } + } + } + + return diff; +} + +#define IS_LOWER_A_TO_Z_ANSI(x) (((x) >= 'a') && ((x) <= 'z')) +#define CAN_SIMPLE_UPCASE_ANSI(x) (((x) >= 0x20) && ((x) <= 0x7f)) +#define SIMPLE_UPCASE_ANSI(x) (IS_LOWER_A_TO_Z(x) ? ((x) - 'a' + 'A') : (x)) + +/* static */ +int SString::CaseCompareHelperA(const CHAR *buffer1, const CHAR *buffer2, COUNT_T count, BOOL stopOnNull, BOOL stopOnCount) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(stopOnNull || stopOnCount); + + const CHAR *buffer1End = buffer1 + count; + int diff = 0; + + while (!stopOnCount || (buffer1 < buffer1End)) + { + CHAR ch1 = *buffer1; + CHAR ch2 = *buffer2; + diff = ch1 - ch2; + if (diff != 0 || stopOnNull) + { + if (ch1 == 0 || ch2 == 0) + { + break; + } + diff = (SIMPLE_UPCASE_ANSI(ch1) - SIMPLE_UPCASE_ANSI(ch2)); + if (diff != 0) + { + break; + } + } + buffer1++; + buffer2++; + } + return diff; +} + + +int CaseHashHelper(const WCHAR *buffer, COUNT_T count) +{ + LIMITED_METHOD_CONTRACT; + + const WCHAR *bufferEnd = buffer + count; + ULONG hash = 5381; + + while (buffer < bufferEnd) + { + WCHAR ch = *buffer++; + ch = CAN_SIMPLE_UPCASE(ch) ? SIMPLE_UPCASE(ch) : MapChar(ch, LCMAP_UPPERCASE); + + hash = (((hash << 5) + hash) ^ ch); + } + + return hash; +} + +static int CaseHashHelperA(const CHAR *buffer, COUNT_T count) +{ + LIMITED_METHOD_CONTRACT; + + const CHAR *bufferEnd = buffer + count; + ULONG hash = 5381; + + while (buffer < bufferEnd) + { + CHAR ch = *buffer++; + ch = SIMPLE_UPCASE_ANSI(ch); + + hash = (((hash << 5) + hash) ^ ch); + } + + return hash; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the unicode string +//----------------------------------------------------------------------------- +void SString::Set(const WCHAR *string) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + if (string == NULL || *string == 0) + Clear(); + else + { + Resize((COUNT_T) wcslen(string), REPRESENTATION_UNICODE); + wcscpy_s(GetRawUnicode(), GetBufferSizeInCharIncludeNullChar(), string); + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the first count characters of the given +// unicode string. +//----------------------------------------------------------------------------- +void SString::Set(const WCHAR *string, COUNT_T count) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + PRECONDITION(CheckCount(count)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (count == 0) + Clear(); + else + { + Resize(count, REPRESENTATION_UNICODE); + wcsncpy_s(GetRawUnicode(), GetBufferSizeInCharIncludeNullChar(), string, count); + GetRawUnicode()[count] = 0; + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a point to the first count characters of the given +// preallocated unicode string (shallow copy). +//----------------------------------------------------------------------------- +void SString::SetPreallocated(const WCHAR *string, COUNT_T count) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + PRECONDITION(CheckCount(count)); + SS_POSTCONDITION(IsEmpty()); + GC_NOTRIGGER; + NOTHROW; + SUPPORTS_DAC_HOST_ONLY; + } + SS_CONTRACT_END; + + SetImmutable(); + SetImmutable((BYTE*) string, count*2); + ClearAllocated(); + SetRepresentation(REPRESENTATION_UNICODE); + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the given ansi string +//----------------------------------------------------------------------------- +void SString::SetASCII(const ASCII *string) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + PRECONDITION(CheckASCIIString(string)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (string == NULL || *string == 0) + Clear(); + else + { + Resize((COUNT_T) strlen(string), REPRESENTATION_ASCII); + strcpy_s(GetRawUTF8(), GetBufferSizeInCharIncludeNullChar(), string); + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the first count characters of the given +// ascii string +//----------------------------------------------------------------------------- +void SString::SetASCII(const ASCII *string, COUNT_T count) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + PRECONDITION(CheckASCIIString(string, count)); + PRECONDITION(CheckCount(count)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (count == 0) + Clear(); + else + { + Resize(count, REPRESENTATION_ASCII); + strncpy_s(GetRawASCII(), GetBufferSizeInCharIncludeNullChar(), string, count); + GetRawASCII()[count] = 0; + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the given UTF8 string +//----------------------------------------------------------------------------- +void SString::SetUTF8(const UTF8 *string) +{ + SS_CONTRACT_VOID + { + // !!! Check for illegal UTF8 encoding? + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + SS_CONTRACT_END; + + if (string == NULL || *string == 0) + Clear(); + else + { + Resize((COUNT_T) strlen(string), REPRESENTATION_UTF8); + strcpy_s(GetRawUTF8(), GetBufferSizeInCharIncludeNullChar(), string); + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the first count characters of the given +// UTF8 string. +//----------------------------------------------------------------------------- +void SString::SetUTF8(const UTF8 *string, COUNT_T count) +{ + SS_CONTRACT_VOID + { + // !!! Check for illegal UTF8 encoding? + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + PRECONDITION(CheckCount(count)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (count == 0) + Clear(); + else + { + Resize(count, REPRESENTATION_UTF8); + strncpy_s(GetRawUTF8(), GetBufferSizeInCharIncludeNullChar(), string, count); + GetRawUTF8()[count] = 0; + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the given ANSI string +//----------------------------------------------------------------------------- +void SString::SetANSI(const ANSI *string) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (string == NULL || *string == 0) + Clear(); + else + { + Resize((COUNT_T) strlen(string), REPRESENTATION_ANSI); + strcpy_s(GetRawANSI(), GetBufferSizeInCharIncludeNullChar(), string); + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to a copy of the first count characters of the given +// ANSI string. +//----------------------------------------------------------------------------- +void SString::SetANSI(const ANSI *string, COUNT_T count) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(string, NULL_OK)); + PRECONDITION(CheckCount(count)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (count == 0) + Clear(); + else + { + Resize(count, REPRESENTATION_ANSI); + strncpy_s(GetRawANSI(), GetBufferSizeInCharIncludeNullChar(), string, count); + GetRawANSI()[count] = 0; + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to the given unicode character +//----------------------------------------------------------------------------- +void SString::Set(WCHAR character) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + SS_CONTRACT_END; + + if (character == 0) + Clear(); + else + { + Resize(1, REPRESENTATION_UNICODE); + GetRawUnicode()[0] = character; + GetRawUnicode()[1] = 0; + } + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to the given UTF8 character +//----------------------------------------------------------------------------- +void SString::SetUTF8(CHAR character) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (character == 0) + Clear(); + else + { + Resize(1, REPRESENTATION_UTF8); + GetRawUTF8()[0] = character; + GetRawUTF8()[1] = 0; + } + + SS_RETURN; +} + + +//----------------------------------------------------------------------------- +// Set this string to the given ansi literal. +// This will share the memory and not make a copy. +//----------------------------------------------------------------------------- +void SString::SetLiteral(const ASCII *literal) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(literal)); + PRECONDITION(CheckASCIIString(literal)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + SString s(Literal, literal); + Set(s); + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Set this string to the given unicode literal. +// This will share the memory and not make a copy. +//----------------------------------------------------------------------------- +void SString::SetLiteral(const WCHAR *literal) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(literal)); + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + SString s(Literal, literal); + Set(s); + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Hash the string contents +//----------------------------------------------------------------------------- +ULONG SString::Hash() const +{ + SS_CONTRACT(ULONG) + { + INSTANCE_CHECK; + THROWS_UNLESS_NORMALIZED; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + ConvertToUnicode(); + + SS_RETURN HashString(GetRawUnicode()); +} + +//----------------------------------------------------------------------------- +// Hash the string contents +//----------------------------------------------------------------------------- +ULONG SString::HashCaseInsensitive() const +{ + SS_CONTRACT(ULONG) + { + INSTANCE_CHECK; + THROWS_UNLESS_NORMALIZED; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + ConvertToIteratable(); + + ULONG result; + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + case REPRESENTATION_EMPTY: + result = CaseHashHelper(GetRawUnicode(), GetRawCount()); + break; + + case REPRESENTATION_ASCII: + result = CaseHashHelperA(GetRawASCII(), GetRawCount()); + break; + + default: + UNREACHABLE(); + } + + SS_RETURN result; +} + +//----------------------------------------------------------------------------- +// Truncate this string to count characters. +//----------------------------------------------------------------------------- +void SString::Truncate(const Iterator &i) +{ + SS_CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + SS_POSTCONDITION(GetRawCount() == i - Begin()); + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + SS_CONTRACT_END; + + CONSISTENCY_CHECK(IsFixedSize()); + + COUNT_T size = i - Begin(); + + Resize(size, GetRepresentation(), PRESERVE); + + i.Resync(this, (BYTE *) (GetRawUnicode() + size)); + + SS_RETURN; +} + +//----------------------------------------------------------------------------- +// Convert the ASCII representation for this String to Unicode. We can do this +// quickly and in-place (if this == &dest), which is why it is optimized. +//----------------------------------------------------------------------------- +void SString::ConvertASCIIToUnicode(SString &dest) const +{ + CONTRACT_VOID + { + PRECONDITION(IsRepresentation(REPRESENTATION_ASCII)); + POSTCONDITION(dest.IsRepresentation(REPRESENTATION_UNICODE)); + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + // Handle the empty case. + if (IsEmpty()) + { + dest.Clear(); + RETURN; + } + + CONSISTENCY_CHECK(CheckPointer(GetRawASCII())); + CONSISTENCY_CHECK(GetRawCount() > 0); + + // If dest is the same as this, then we need to preserve on resize. + dest.Resize(GetRawCount(), REPRESENTATION_UNICODE, + this == &dest ? PRESERVE : DONT_PRESERVE); + + // Make sure the buffer is big enough. + CONSISTENCY_CHECK(dest.GetAllocation() > (GetRawCount() * sizeof(WCHAR))); + + // This is a poor man's widen. Since we know that the representation is ASCII, + // we can just pad the string with a bunch of zero-value bytes. Of course, + // we move from the end of the string to the start so that we can convert in + // place (in the case that &dest == this). + WCHAR *outBuf = dest.GetRawUnicode() + dest.GetRawCount(); + ASCII *inBuf = GetRawASCII() + GetRawCount(); + + while (GetRawASCII() <= inBuf) + { + CONSISTENCY_CHECK(dest.GetRawUnicode() <= outBuf); + // The casting zero-extends the value, thus giving us the zero-valued byte. + *outBuf = (WCHAR) *inBuf; + outBuf--; + inBuf--; + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// Convert the internal representation for this String to Unicode. +//----------------------------------------------------------------------------- +void SString::ConvertToUnicode() const +{ + CONTRACT_VOID + { + POSTCONDITION(IsRepresentation(REPRESENTATION_UNICODE)); + if (IsRepresentation(REPRESENTATION_UNICODE)) NOTHROW; else THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + if (!IsRepresentation(REPRESENTATION_UNICODE)) + { + if (IsRepresentation(REPRESENTATION_ASCII)) + { + ConvertASCIIToUnicode(*(const_cast<SString *>(this))); + } + else + { + StackSString s; + ConvertToUnicode(s); + PREFIX_ASSUME(!s.IsImmutable()); + (const_cast<SString*>(this))->Set(s); + } + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// Convert the internal representation for this String to Unicode, while +// preserving the iterator if the conversion is done. +//----------------------------------------------------------------------------- +void SString::ConvertToUnicode(const CIterator &i) const +{ + CONTRACT_VOID + { + PRECONDITION(i.Check()); + POSTCONDITION(IsRepresentation(REPRESENTATION_UNICODE)); + if (IsRepresentation(REPRESENTATION_UNICODE)) NOTHROW; else THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + if (!IsRepresentation(REPRESENTATION_UNICODE)) + { + CONSISTENCY_CHECK(IsFixedSize()); + + COUNT_T index = 0; + // Get the current index of the iterator + if (i.m_ptr != NULL) + { + CONSISTENCY_CHECK(GetCharacterSizeShift() == 0); + index = (COUNT_T) (i.m_ptr - m_buffer); + } + + if (IsRepresentation(REPRESENTATION_ASCII)) + { + ConvertASCIIToUnicode(*(const_cast<SString *>(this))); + } + else + { + StackSString s; + ConvertToUnicode(s); + (const_cast<SString*>(this))->Set(s); + } + + // Move the iterator to the new location. + if (i.m_ptr != NULL) + { + i.Resync(this, (BYTE *) (GetRawUnicode() + index)); + } + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// Set s to be a copy of this string's contents, but in the unicode format. +//----------------------------------------------------------------------------- +void SString::ConvertToUnicode(SString &s) const +{ + CONTRACT_VOID + { + PRECONDITION(s.Check()); + POSTCONDITION(s.IsRepresentation(REPRESENTATION_UNICODE)); + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + int page = 0; + + switch (GetRepresentation()) + { + case REPRESENTATION_EMPTY: + s.Clear(); + RETURN; + + case REPRESENTATION_UNICODE: + s.Set(*this); + RETURN; + + case REPRESENTATION_UTF8: + page = CP_UTF8; + break; + + case REPRESENTATION_ASCII: + ConvertASCIIToUnicode(s); + RETURN; + + case REPRESENTATION_ANSI: + page = CP_ACP; + break; + + default: + UNREACHABLE(); + } + + COUNT_T length = WszMultiByteToWideChar(page, 0, GetRawANSI(), GetRawCount()+1, 0, 0); + if (length == 0) + ThrowLastError(); + + s.Resize(length-1, REPRESENTATION_UNICODE); + + length = WszMultiByteToWideChar(page, 0, GetRawANSI(), GetRawCount()+1, s.GetRawUnicode(), length); + if (length == 0) + ThrowLastError(); + + RETURN; +} + +//----------------------------------------------------------------------------- +// Set s to be a copy of this string's contents, but in the ANSI format. +//----------------------------------------------------------------------------- +void SString::ConvertToANSI(SString &s) const +{ + CONTRACT_VOID + { + PRECONDITION(s.Check()); + POSTCONDITION(s.IsRepresentation(REPRESENTATION_ANSI)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + switch (GetRepresentation()) + { + case REPRESENTATION_EMPTY: + s.Clear(); + RETURN; + + case REPRESENTATION_ASCII: + case REPRESENTATION_ANSI: + s.Set(*this); + RETURN; + + case REPRESENTATION_UTF8: + // No direct conversion to ANSI + ConvertToUnicode(); + FALLTHROUGH; + + case REPRESENTATION_UNICODE: + break; + + default: + UNREACHABLE(); + } + + // @todo: use WC_NO_BEST_FIT_CHARS + COUNT_T length = WszWideCharToMultiByte(CP_ACP, 0, GetRawUnicode(), GetRawCount()+1, + NULL, 0, NULL, NULL); + + s.Resize(length-1, REPRESENTATION_ANSI); + + // @todo: use WC_NO_BEST_FIT_CHARS + length = WszWideCharToMultiByte(CP_ACP, 0, GetRawUnicode(), GetRawCount()+1, + s.GetRawANSI(), length, NULL, NULL); + if (length == 0) + ThrowLastError(); + + RETURN; +} + +//----------------------------------------------------------------------------- +// Set s to be a copy of this string's contents, but in the utf8 format. +//----------------------------------------------------------------------------- +COUNT_T SString::ConvertToUTF8(SString &s) const +{ + CONTRACT(COUNT_T) + { + PRECONDITION(s.Check()); + POSTCONDITION(s.IsRepresentation(REPRESENTATION_UTF8)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + switch (GetRepresentation()) + { + case REPRESENTATION_EMPTY: + s.Clear(); + RETURN 1; + + case REPRESENTATION_ASCII: + case REPRESENTATION_UTF8: + s.Set(*this); + RETURN s.GetRawCount()+1; + + case REPRESENTATION_ANSI: + // No direct conversion from ANSI to UTF8 + ConvertToUnicode(); + FALLTHROUGH; + + case REPRESENTATION_UNICODE: + break; + + default: + UNREACHABLE(); + } + + // <TODO> @todo: use WC_NO_BEST_FIT_CHARS </TODO> + bool allAscii; + DWORD length; + + HRESULT hr = FString::Unicode_Utf8_Length(GetRawUnicode(), & allAscii, & length); + + if (SUCCEEDED(hr)) + { + s.Resize(length, REPRESENTATION_UTF8); + + //FString::Unicode_Utf8 expects an array all the time + //we optimize the empty string by replacing it with null for SString above in Resize + if (length > 0) + { + hr = FString::Unicode_Utf8(GetRawUnicode(), allAscii, (LPSTR) s.GetRawUTF8(), length); + } + } + + IfFailThrow(hr); + + RETURN length + 1; +} + +//----------------------------------------------------------------------------- +// Replace a single character with another character. +//----------------------------------------------------------------------------- +void SString::Replace(const Iterator &i, WCHAR c) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i, 1)); + POSTCONDITION(Match(i, c)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + if (IsRepresentation(REPRESENTATION_ASCII) && ((c&~0x7f) == 0)) + { + *(BYTE*)i.m_ptr = (BYTE) c; + } + else + { + ConvertToUnicode(i); + + *(USHORT*)i.m_ptr = c; + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// Replace the substring specified by position, length with the given string s. +//----------------------------------------------------------------------------- +void SString::Replace(const Iterator &i, COUNT_T length, const SString &s) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i, length)); + PRECONDITION(s.Check()); + POSTCONDITION(Match(i, s)); + THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + Representation representation = GetRepresentation(); + if (representation == REPRESENTATION_EMPTY) + { + // This special case contains some optimizations (like literal sharing). + Set(s); + ConvertToIteratable(); + i.Resync(this, m_buffer); + } + else + { + StackSString temp; + const SString &source = GetCompatibleString(s, temp, i); + + COUNT_T deleteSize = length<<GetCharacterSizeShift(); + COUNT_T insertSize = source.GetRawCount()<<source.GetCharacterSizeShift(); + + SBuffer::Replace(i, deleteSize, insertSize); + SBuffer::Copy(i, source.m_buffer, insertSize); + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// Find s in this string starting at i. Return TRUE & update iterator if found. +//----------------------------------------------------------------------------- +BOOL SString::Find(CIterator &i, const SString &s) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + PRECONDITION(s.Check()); + POSTCONDITION(RETVAL == Match(i, s)); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACT_END; + + // Get a compatible string from s + StackSString temp; + const SString &source = GetCompatibleString(s, temp, i); + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + { + COUNT_T count = source.GetRawCount(); + const WCHAR *start = i.GetUnicode(); + const WCHAR *end = GetUnicode() + GetRawCount() - count; + while (start <= end) + { + if (wcsncmp(start, source.GetRawUnicode(), count) == 0) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start++; + } + } + break; + + case REPRESENTATION_ANSI: + case REPRESENTATION_ASCII: + { + COUNT_T count = source.GetRawCount(); + const CHAR *start = i.GetASCII(); + const CHAR *end = GetRawASCII() + GetRawCount() - count; + while (start <= end) + { + if (strncmp(start, source.GetRawASCII(), count) == 0) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start++; + } + } + break; + + case REPRESENTATION_EMPTY: + { + if (source.GetRawCount() == 0) + RETURN TRUE; + } + break; + + case REPRESENTATION_UTF8: + default: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Find s in this string starting at i. Return TRUE & update iterator if found. +//----------------------------------------------------------------------------- +BOOL SString::Find(CIterator &i, WCHAR c) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + POSTCONDITION(RETVAL == Match(i, c)); + THROWS_UNLESS_NORMALIZED; + GC_NOTRIGGER; + } + CONTRACT_END; + + // Get a compatible string + if (c & ~0x7f) + ConvertToUnicode(i); + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + { + const WCHAR *start = i.GetUnicode(); + const WCHAR *end = GetUnicode() + GetRawCount() - 1; + while (start <= end) + { + if (*start == c) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start++; + } + } + break; + + case REPRESENTATION_ANSI: + case REPRESENTATION_ASCII: + { + const CHAR *start = i.GetASCII(); + const CHAR *end = GetRawASCII() + GetRawCount() - 1; + while (start <= end) + { + if (*start == c) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start++; + } + } + break; + + case REPRESENTATION_EMPTY: + break; + + case REPRESENTATION_UTF8: + default: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Find s in this string, working backwards staring at i. +// Return TRUE and update iterator if found. +//----------------------------------------------------------------------------- +BOOL SString::FindBack(CIterator &i, const SString &s) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + PRECONDITION(s.Check()); + POSTCONDITION(RETVAL == Match(i, s)); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACT_END; + + // Get a compatible string from s + StackSString temp; + const SString &source = GetCompatibleString(s, temp, i); + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + { + COUNT_T count = source.GetRawCount(); + const WCHAR *start = GetRawUnicode() + GetRawCount() - count; + if (start > i.GetUnicode()) + start = i.GetUnicode(); + const WCHAR *end = GetRawUnicode(); + + while (start >= end) + { + if (wcsncmp(start, source.GetRawUnicode(), count) == 0) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start--; + } + } + break; + + case REPRESENTATION_ANSI: + case REPRESENTATION_ASCII: + { + COUNT_T count = source.GetRawCount(); + const CHAR *start = GetRawASCII() + GetRawCount() - count; + if (start > i.GetASCII()) + start = i.GetASCII(); + const CHAR *end = GetRawASCII(); + + while (start >= end) + { + if (strncmp(start, source.GetRawASCII(), count) == 0) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start--; + } + } + break; + + case REPRESENTATION_EMPTY: + { + if (source.GetRawCount() == 0) + RETURN TRUE; + } + break; + + case REPRESENTATION_UTF8: + default: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Find s in this string, working backwards staring at i. +// Return TRUE and update iterator if found. +//----------------------------------------------------------------------------- +BOOL SString::FindBack(CIterator &i, WCHAR c) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + POSTCONDITION(RETVAL == Match(i, c)); + THROWS_UNLESS_NORMALIZED; + GC_NOTRIGGER; + } + CONTRACT_END; + + // Get a compatible string from s + if (c & ~0x7f) + ConvertToUnicode(i); + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + { + const WCHAR *start = GetRawUnicode() + GetRawCount() - 1; + if (start > i.GetUnicode()) + start = i.GetUnicode(); + const WCHAR *end = GetRawUnicode(); + + while (start >= end) + { + if (*start == c) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start--; + } + } + break; + + case REPRESENTATION_ANSI: + case REPRESENTATION_ASCII: + { + const CHAR *start = GetRawASCII() + GetRawCount() - 1; + if (start > i.GetASCII()) + start = i.GetASCII(); + const CHAR *end = GetRawASCII(); + + while (start >= end) + { + if (*start == c) + { + i.Resync(this, (BYTE*) start); + RETURN TRUE; + } + start--; + } + } + break; + + case REPRESENTATION_EMPTY: + break; + + case REPRESENTATION_UTF8: + default: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Returns TRUE if this string begins with the contents of s +//----------------------------------------------------------------------------- +BOOL SString::BeginsWith(const SString &s) const +{ + WRAPPER_NO_CONTRACT; + + return Match(Begin(), s); +} + +//----------------------------------------------------------------------------- +// Returns TRUE if this string begins with the contents of s +//----------------------------------------------------------------------------- +BOOL SString::BeginsWithCaseInsensitive(const SString &s) const +{ + WRAPPER_NO_CONTRACT; + + return MatchCaseInsensitive(Begin(), s); +} + +//----------------------------------------------------------------------------- +// Returns TRUE if this string ends with the contents of s +//----------------------------------------------------------------------------- +BOOL SString::EndsWith(const SString &s) const +{ + WRAPPER_NO_CONTRACT; + + // Need this check due to iterator arithmetic below. + if (GetCount() < s.GetCount()) + { + return FALSE; + } + + return Match(End() - s.GetCount(), s); +} + +//----------------------------------------------------------------------------- +// Returns TRUE if this string ends with the contents of s +//----------------------------------------------------------------------------- +BOOL SString::EndsWithCaseInsensitive(const SString &s) const +{ + WRAPPER_NO_CONTRACT; + + // Need this check due to iterator arithmetic below. + if (GetCount() < s.GetCount()) + { + return FALSE; + } + + return MatchCaseInsensitive(End() - s.GetCount(), s); +} + +//----------------------------------------------------------------------------- +// Compare this string's contents to s's contents. +// The comparison does not take into account localization issues like case folding. +// Return 0 if equal, <0 if this < s, >0 is this > s. (same as strcmp). +//----------------------------------------------------------------------------- +int SString::Compare(const SString &s) const +{ + CONTRACT(int) + { + INSTANCE_CHECK; + PRECONDITION(s.Check()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACT_END; + + StackSString temp; + const SString &source = GetCompatibleString(s, temp); + + COUNT_T smaller; + int equals = 0; + int result = 0; + + if (GetRawCount() < source.GetRawCount()) + { + smaller = GetRawCount(); + equals = -1; + } + else if (GetRawCount() > source.GetRawCount()) + { + smaller = source.GetRawCount(); + equals = 1; + } + else + { + smaller = GetRawCount(); + equals = 0; + } + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + result = wcsncmp(GetRawUnicode(), source.GetRawUnicode(), smaller); + break; + + case REPRESENTATION_ASCII: + case REPRESENTATION_ANSI: + result = strncmp(GetRawASCII(), source.GetRawASCII(), smaller); + break; + + case REPRESENTATION_EMPTY: + result = 0; + break; + + default: + case REPRESENTATION_UTF8: + UNREACHABLE(); + } + + if (result == 0) + RETURN equals; + else + RETURN result; +} + +//----------------------------------------------------------------------------- +// Compare this string's contents to s's contents. +// Return 0 if equal, <0 if this < s, >0 is this > s. (same as strcmp). +//----------------------------------------------------------------------------- + +int SString::CompareCaseInsensitive(const SString &s) const +{ + CONTRACT(int) + { + INSTANCE_CHECK; + PRECONDITION(s.Check()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACT_END; + + StackSString temp; + const SString &source = GetCompatibleString(s, temp); + + COUNT_T smaller; + int equals = 0; + int result = 0; + + if (GetRawCount() < source.GetRawCount()) + { + smaller = GetRawCount(); + equals = -1; + } + else if (GetRawCount() > source.GetRawCount()) + { + smaller = source.GetRawCount(); + equals = 1; + } + else + { + smaller = GetRawCount(); + equals = 0; + } + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + case REPRESENTATION_ANSI: + result = CaseCompareHelper(GetRawUnicode(), source.GetRawUnicode(), smaller, FALSE, TRUE); + break; + + case REPRESENTATION_ASCII: + result = CaseCompareHelperA(GetRawASCII(), source.GetRawASCII(), smaller, FALSE, TRUE); + break; + + case REPRESENTATION_EMPTY: + result = 0; + break; + + default: + case REPRESENTATION_UTF8: + UNREACHABLE(); + } + + if (result == 0) + RETURN equals; + else + RETURN result; +} + +//----------------------------------------------------------------------------- +// Compare this string's contents to s's contents. +// The comparison does not take into account localization issues like case folding. +// Return 1 if equal, 0 if not. +//----------------------------------------------------------------------------- +BOOL SString::Equals(const SString &s) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(s.Check()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + FAULTS_UNLESS_BOTH_NORMALIZED(s, ThrowOutOfMemory()); + GC_NOTRIGGER; + } + CONTRACT_END; + + StackSString temp; + const SString &source = GetCompatibleString(s, temp); + + COUNT_T count = GetRawCount(); + + if (count != source.GetRawCount()) + RETURN FALSE; + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + RETURN (wcsncmp(GetRawUnicode(), source.GetRawUnicode(), count) == 0); + + case REPRESENTATION_ASCII: + case REPRESENTATION_ANSI: + RETURN (strncmp(GetRawASCII(), source.GetRawASCII(), count) == 0); + + case REPRESENTATION_EMPTY: + RETURN TRUE; + + default: + case REPRESENTATION_UTF8: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Compare this string's contents case insensitively to s's contents. +// Return 1 if equal, 0 if not. +//----------------------------------------------------------------------------- +BOOL SString::EqualsCaseInsensitive(const SString &s) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(s.Check()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + FAULTS_UNLESS_BOTH_NORMALIZED(s, ThrowOutOfMemory()); + GC_NOTRIGGER; + } + CONTRACT_END; + + StackSString temp; + const SString &source = GetCompatibleString(s, temp); + + COUNT_T count = GetRawCount(); + + if (count != source.GetRawCount()) + RETURN FALSE; + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + case REPRESENTATION_ANSI: + RETURN (CaseCompareHelper(GetRawUnicode(), source.GetRawUnicode(), count, FALSE, TRUE) == 0); + + case REPRESENTATION_ASCII: + RETURN (CaseCompareHelperA(GetRawASCII(), source.GetRawASCII(), count, FALSE, TRUE) == 0); + + case REPRESENTATION_EMPTY: + RETURN TRUE; + + default: + case REPRESENTATION_UTF8: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Compare s's contents to the substring starting at position +// The comparison does not take into account localization issues like case folding. +// Return TRUE if equal, FALSE if not +//----------------------------------------------------------------------------- +BOOL SString::Match(const CIterator &i, const SString &s) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + PRECONDITION(s.Check()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACT_END; + + StackSString temp; + const SString &source = GetCompatibleString(s, temp, i); + + COUNT_T remaining = End() - i; + COUNT_T count = source.GetRawCount(); + + if (remaining < count) + RETURN FALSE; + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + RETURN (wcsncmp(i.GetUnicode(), source.GetRawUnicode(), count) == 0); + + case REPRESENTATION_ASCII: + case REPRESENTATION_ANSI: + RETURN (strncmp(i.GetASCII(), source.GetRawASCII(), count) == 0); + + case REPRESENTATION_EMPTY: + RETURN TRUE; + + default: + case REPRESENTATION_UTF8: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Compare s's contents case insensitively to the substring starting at position +// Return TRUE if equal, FALSE if not +//----------------------------------------------------------------------------- +BOOL SString::MatchCaseInsensitive(const CIterator &i, const SString &s) const +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + PRECONDITION(s.Check()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACT_END; + + StackSString temp; + const SString &source = GetCompatibleString(s, temp, i); + + COUNT_T remaining = End() - i; + COUNT_T count = source.GetRawCount(); + + if (remaining < count) + RETURN FALSE; + + switch (GetRepresentation()) + { + case REPRESENTATION_UNICODE: + case REPRESENTATION_ANSI: + RETURN (CaseCompareHelper(i.GetUnicode(), source.GetRawUnicode(), count, FALSE, TRUE) == 0); + + case REPRESENTATION_ASCII: + RETURN (CaseCompareHelperA(i.GetASCII(), source.GetRawASCII(), count, FALSE, TRUE) == 0); + + case REPRESENTATION_EMPTY: + RETURN TRUE; + + default: + case REPRESENTATION_UTF8: + UNREACHABLE(); + } + + RETURN FALSE; +} + +//----------------------------------------------------------------------------- +// Compare c case insensitively to the character at position +// Return TRUE if equal, FALSE if not +//----------------------------------------------------------------------------- +BOOL SString::MatchCaseInsensitive(const CIterator &i, WCHAR c) const +{ + SS_CONTRACT(BOOL) + { + GC_NOTRIGGER; + INSTANCE_CHECK; + PRECONDITION(CheckIteratorRange(i)); + NOTHROW; + } + SS_CONTRACT_END; + + // End() will not throw here + CONTRACT_VIOLATION(ThrowsViolation); + if (i >= End()) + SS_RETURN FALSE; + + WCHAR test = i[0]; + + SS_RETURN (test == c + || ((CAN_SIMPLE_UPCASE(test) ? SIMPLE_UPCASE(test) : MapChar(test, LCMAP_UPPERCASE)) + == (CAN_SIMPLE_UPCASE(c) ? SIMPLE_UPCASE(c) : MapChar(c, LCMAP_UPPERCASE)))); +} + +//----------------------------------------------------------------------------- +// Convert string to unicode lowercase using the invariant culture +// Note: Please don't use it in PATH as multiple character can map to the same +// lower case symbol +//----------------------------------------------------------------------------- +void SString::LowerCase() +{ + SS_CONTRACT_VOID + { + GC_NOTRIGGER; + PRECONDITION(CheckPointer(this)); + SS_POSTCONDITION(CheckPointer(RETVAL)); + if (IsRepresentation(REPRESENTATION_UNICODE)) NOTHROW; else THROWS; + SUPPORTS_DAC; + } + SS_CONTRACT_END; + + ConvertToUnicode(); + + for (WCHAR *pwch = GetRawUnicode(); pwch < GetRawUnicode() + GetRawCount(); ++pwch) + { + *pwch = (CAN_SIMPLE_DOWNCASE(*pwch) ? SIMPLE_DOWNCASE(*pwch) : MapChar(*pwch, LCMAP_LOWERCASE)); + } +} + +//----------------------------------------------------------------------------- +// Convert null-terminated string to lowercase using the invariant culture +//----------------------------------------------------------------------------- +//static +void SString::LowerCase(__inout_z LPWSTR wszString) +{ + SS_CONTRACT_VOID + { + GC_NOTRIGGER; + NOTHROW; + SUPPORTS_DAC; + } + SS_CONTRACT_END; + + if (wszString == NULL) + { + return; + } + + for (WCHAR * pwch = wszString; *pwch != '\0'; ++pwch) + { + *pwch = (CAN_SIMPLE_DOWNCASE(*pwch) ? SIMPLE_DOWNCASE(*pwch) : MapChar(*pwch, LCMAP_LOWERCASE)); + } +} + +//----------------------------------------------------------------------------- +// Convert string to unicode uppercase using the invariant culture +// Note: Please don't use it in PATH as multiple character can map to the same +// upper case symbol +//----------------------------------------------------------------------------- +void SString::UpperCase() +{ + SS_CONTRACT_VOID + { + GC_NOTRIGGER; + PRECONDITION(CheckPointer(this)); + SS_POSTCONDITION(CheckPointer(RETVAL)); + if (IsRepresentation(REPRESENTATION_UNICODE)) NOTHROW; else THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + SS_CONTRACT_END; + + ConvertToUnicode(); + + for (WCHAR *pwch = GetRawUnicode(); pwch < GetRawUnicode() + GetRawCount(); ++pwch) + { + *pwch = (CAN_SIMPLE_UPCASE(*pwch) ? SIMPLE_UPCASE(*pwch) : MapChar(*pwch, LCMAP_UPPERCASE)); + } +} + +//----------------------------------------------------------------------------- +// Get a const pointer to the internal buffer as an ANSI string. +//----------------------------------------------------------------------------- +const CHAR *SString::GetANSI(AbstractScratchBuffer &scratch) const +{ + SS_CONTRACT(const CHAR *) + { + INSTANCE_CHECK_NULL; + THROWS; + GC_NOTRIGGER; + } + SS_CONTRACT_END; + + if (IsRepresentation(REPRESENTATION_ANSI)) + SS_RETURN GetRawANSI(); + + ConvertToANSI((SString&)scratch); + SS_RETURN ((SString&)scratch).GetRawANSI(); +} + +//----------------------------------------------------------------------------- +// Get a const pointer to the internal buffer as a UTF8 string. +//----------------------------------------------------------------------------- +const UTF8 *SString::GetUTF8(AbstractScratchBuffer &scratch) const +{ + CONTRACT(const UTF8 *) + { + INSTANCE_CHECK_NULL; + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + if (IsRepresentation(REPRESENTATION_UTF8)) + RETURN GetRawUTF8(); + + ConvertToUTF8((SString&)scratch); + RETURN ((SString&)scratch).GetRawUTF8(); +} + +const UTF8 *SString::GetUTF8(AbstractScratchBuffer &scratch, COUNT_T *pcbUtf8) const +{ + CONTRACT(const UTF8 *) + { + INSTANCE_CHECK_NULL; + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + if (IsRepresentation(REPRESENTATION_UTF8)) + { + *pcbUtf8 = GetRawCount() + 1; + RETURN GetRawUTF8(); + } + + *pcbUtf8 = ConvertToUTF8((SString&)scratch); + RETURN ((SString&)scratch).GetRawUTF8(); +} + +//----------------------------------------------------------------------------- +// Get a const pointer to the internal buffer which must already be a UTF8 string. +// This avoids the need to create a scratch buffer we know will never be used. +//----------------------------------------------------------------------------- +const UTF8 *SString::GetUTF8NoConvert() const +{ + CONTRACT(const UTF8 *) + { + INSTANCE_CHECK_NULL; + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + if (IsRepresentation(REPRESENTATION_UTF8)) + RETURN GetRawUTF8(); + + ThrowHR(E_INVALIDARG); +} + +//----------------------------------------------------------------------------- +// Safe version of sprintf. +// Prints formatted ansi text w/ var args to this buffer. +//----------------------------------------------------------------------------- +void SString::Printf(const CHAR *format, ...) +{ + WRAPPER_NO_CONTRACT; + + va_list args; + va_start(args, format); + VPrintf(format, args); + va_end(args); +} + +#ifdef _DEBUG +// +// Check the Printf use for potential globalization bugs. %S formatting +// specifier does Unicode->Ansi or Ansi->Unicode conversion using current +// C-locale. This almost always means globalization bug in the CLR codebase. +// +// Ideally, we would elimitate %S from all format strings. Unfortunately, +// %S is too widespread in non-shipping code that such cleanup is not feasible. +// +static void CheckForFormatStringGlobalizationIssues(const SString &format, const SString &result) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + DEBUG_ONLY; + } + CONTRACTL_END; + + BOOL fDangerousFormat = FALSE; + + // Check whether the format string contains the %S formatting specifier + SString::CIterator itrFormat = format.Begin(); + while (*itrFormat) + { + if (*itrFormat++ == '%') + { + // <TODO>Handle the complex format strings like %blahS</TODO> + if (*itrFormat++ == 'S') + { + fDangerousFormat = TRUE; + break; + } + } + } + + if (fDangerousFormat) + { + BOOL fNonAsciiUsed = FALSE; + + // Now check whether there are any non-ASCII characters in the output. + + // Check whether the result contains non-Ascii characters + SString::CIterator itrResult = format.Begin(); + while (*itrResult) + { + if (*itrResult++ > 127) + { + fNonAsciiUsed = TRUE; + break; + } + } + + CONSISTENCY_CHECK_MSGF(!fNonAsciiUsed, + ("Non-ASCII string was produced by %%S format specifier. This is likely globalization bug." + "To fix this, change the format string to %%s and do the correct encoding at the Printf callsite")); + } +} +#endif + +#ifndef EBADF +#define EBADF 9 +#endif + +#ifndef ENOMEM +#define ENOMEM 12 +#endif + +#ifndef ERANGE +#define ERANGE 34 +#endif + +#if defined(_MSC_VER) +#undef va_copy +#define va_copy(dest,src) (dest = src) +#endif + +void SString::VPrintf(const CHAR *format, va_list args) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(format)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + va_list ap; + // sprintf gives us no means to know how many characters are written + // other than guessing and trying + + if (GetRawCount() > 0) + { + // First, try to use the existing buffer + va_copy(ap, args); + int result = _vsnprintf_s(GetRawANSI(), GetRawCount()+1, _TRUNCATE, format, ap); + va_end(ap); + + if (result >=0) + { + // Succeeded in writing. Now resize - + Resize(result, REPRESENTATION_ANSI, PRESERVE); + SString sss(Ansi, format); + INDEBUG(CheckForFormatStringGlobalizationIssues(sss, *this)); + RETURN; + } + } + + // Make a guess how long the result will be (note this will be doubled) + + COUNT_T guess = (COUNT_T) strlen(format)+1; + if (guess < GetRawCount()) + guess = GetRawCount(); + if (guess < MINIMUM_GUESS) + guess = MINIMUM_GUESS; + + while (TRUE) + { + // Double the previous guess - eventually we will get enough space + guess *= 2; + Resize(guess, REPRESENTATION_ANSI); + + // Clear errno to avoid false alarms + errno = 0; + + va_copy(ap, args); + int result = _vsnprintf_s(GetRawANSI(), GetRawCount()+1, _TRUNCATE, format, ap); + va_end(ap); + + if (result >= 0) + { + // Succeed in writing. Shrink the buffer to fit exactly. + Resize(result, REPRESENTATION_ANSI, PRESERVE); + SString sss(Ansi, format); + INDEBUG(CheckForFormatStringGlobalizationIssues(sss, *this)); + RETURN; + } + + if (errno==ENOMEM) + { + ThrowOutOfMemory(); + } + else + if (errno!=0 && errno!=EBADF && errno!=ERANGE) + { + CONSISTENCY_CHECK_MSG(FALSE, "_vsnprintf_s failed. Potential globalization bug."); + ThrowHR(HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION)); + } + } + RETURN; +} + +void SString::Printf(const WCHAR *format, ...) +{ + WRAPPER_NO_CONTRACT; + + va_list args; + va_start(args, format); + VPrintf(format, args); + va_end(args); +} + +void SString::PPrintf(const WCHAR *format, ...) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(format)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + va_list argItr; + va_start(argItr, format); + PVPrintf(format, argItr); + va_end(argItr); + + RETURN; +} + +void SString::VPrintf(const WCHAR *format, va_list args) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(format)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + va_list ap; + // sprintf gives us no means to know how many characters are written + // other than guessing and trying + + if (GetRawCount() > 0) + { + // First, try to use the existing buffer + va_copy(ap, args); + int result = _vsnwprintf_s(GetRawUnicode(), GetRawCount()+1, _TRUNCATE, format, ap); + va_end(ap); + + if (result >= 0) + { + // succeeded + Resize(result, REPRESENTATION_UNICODE, PRESERVE); + SString sss(format); + INDEBUG(CheckForFormatStringGlobalizationIssues(sss, *this)); + RETURN; + } + } + + // Make a guess how long the result will be (note this will be doubled) + + COUNT_T guess = (COUNT_T) wcslen(format)+1; + if (guess < GetRawCount()) + guess = GetRawCount(); + if (guess < MINIMUM_GUESS) + guess = MINIMUM_GUESS; + + while (TRUE) + { + // Double the previous guess - eventually we will get enough space + guess *= 2; + Resize(guess, REPRESENTATION_UNICODE); + + // Clear errno to avoid false alarms + errno = 0; + + va_copy(ap, args); + int result = _vsnwprintf_s(GetRawUnicode(), GetRawCount()+1, _TRUNCATE, format, ap); + va_end(ap); + + if (result >= 0) + { + Resize(result, REPRESENTATION_UNICODE, PRESERVE); + SString sss(format); + INDEBUG(CheckForFormatStringGlobalizationIssues(sss, *this)); + RETURN; + } + + if (errno==ENOMEM) + { + ThrowOutOfMemory(); + } + else + if (errno!=0 && errno!=EBADF && errno!=ERANGE) + { + CONSISTENCY_CHECK_MSG(FALSE, "_vsnwprintf_s failed. Potential globalization bug."); + ThrowHR(HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION)); + } + } + RETURN; +} + +void SString::PVPrintf(const WCHAR *format, va_list args) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + PRECONDITION(CheckPointer(format)); + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + va_list ap; + // sprintf gives us no means to know how many characters are written + // other than guessing and trying + + if (GetRawCount() > 0) + { + // First, try to use the existing buffer + va_copy(ap, args); +#if defined(FEATURE_CORESYSTEM) + int result = _vsnwprintf_s(GetRawUnicode(), GetRawCount()+1, _TRUNCATE, format, ap); +#else + int result = _vswprintf_p(GetRawUnicode(), GetRawCount()+1, format, ap); +#endif + va_end(ap); + if (result >= 0) + { + // succeeded + Resize(result, REPRESENTATION_UNICODE, PRESERVE); + SString sss(format); + INDEBUG(CheckForFormatStringGlobalizationIssues(sss, *this)); + RETURN; + } + } + + // Make a guess how long the result will be (note this will be doubled) + + COUNT_T guess = (COUNT_T) wcslen(format)+1; + if (guess < GetRawCount()) + guess = GetRawCount(); + if (guess < MINIMUM_GUESS) + guess = MINIMUM_GUESS; + + while (TRUE) + { + // Double the previous guess - eventually we will get enough space + guess *= 2; + Resize(guess, REPRESENTATION_UNICODE, DONT_PRESERVE); + + // Clear errno to avoid false alarms + errno = 0; + + va_copy(ap, args); +#if defined(FEATURE_CORESYSTEM) + int result = _vsnwprintf_s(GetRawUnicode(), GetRawCount()+1, _TRUNCATE, format, ap); +#else + int result = _vswprintf_p(GetRawUnicode(), GetRawCount()+1, format, ap); +#endif + va_end(ap); + + if (result >= 0) + { + Resize(result, REPRESENTATION_UNICODE, PRESERVE); + SString sss(format); + INDEBUG(CheckForFormatStringGlobalizationIssues(sss, *this)); + RETURN; + } + + if (errno==ENOMEM) + { + ThrowOutOfMemory(); + } + else + if (errno!=0 && errno!=EBADF && errno!=ERANGE) + { + CONSISTENCY_CHECK_MSG(FALSE, "_vsnwprintf_s failed. Potential globalization bug."); + ThrowHR(HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION)); + } + } + RETURN; +} + +void SString::AppendPrintf(const CHAR *format, ...) +{ + WRAPPER_NO_CONTRACT; + + va_list args; + va_start(args, format); + AppendVPrintf(format, args); + va_end(args); +} + +void SString::AppendVPrintf(const CHAR *format, va_list args) +{ + WRAPPER_NO_CONTRACT; + + StackSString s; + s.VPrintf(format, args); + Append(s); +} + +void SString::AppendPrintf(const WCHAR *format, ...) +{ + WRAPPER_NO_CONTRACT; + + va_list args; + va_start(args, format); + AppendVPrintf(format, args); + va_end(args); +} + +void SString::AppendVPrintf(const WCHAR *format, va_list args) +{ + WRAPPER_NO_CONTRACT; + + StackSString s; + s.VPrintf(format, args); + Append(s); +} + +//---------------------------------------------------------------------------- +// LoadResource - moved to sstring_com.cpp +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +// Format the message and put the contents in this string +//---------------------------------------------------------------------------- + +BOOL SString::FormatMessage(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, + const SString &arg1, const SString &arg2, + const SString &arg3, const SString &arg4, + const SString &arg5, const SString &arg6, + const SString &arg7, const SString &arg8, + const SString &arg9, const SString &arg10) +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + const WCHAR *args[] = {arg1.GetUnicode(), arg2.GetUnicode(), arg3.GetUnicode(), arg4.GetUnicode(), + arg5.GetUnicode(), arg6.GetUnicode(), arg7.GetUnicode(), arg8.GetUnicode(), + arg9.GetUnicode(), arg10.GetUnicode()}; + + if (GetRawCount() > 0) + { + // First, try to use our existing buffer to hold the result. + Resize(GetRawCount(), REPRESENTATION_UNICODE); + + DWORD result = ::WszFormatMessage(dwFlags | FORMAT_MESSAGE_ARGUMENT_ARRAY, + lpSource, dwMessageId, dwLanguageId, + GetRawUnicode(), GetRawCount()+1, (va_list*)args); + + // Although we cannot directly detect truncation, we can tell if we + // used up all the space (in which case we will assume truncation.) + + if (result != 0 && result < GetRawCount()) + { + if (GetRawUnicode()[result-1] == W(' ')) + { + GetRawUnicode()[result-1] = W('\0'); + result -= 1; + } + Resize(result, REPRESENTATION_UNICODE, PRESERVE); + RETURN TRUE; + } + } + + // We don't have enough space in our buffer, do dynamic allocation. + LocalAllocHolder<WCHAR> string; + + DWORD result = ::WszFormatMessage(dwFlags | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY, + lpSource, dwMessageId, dwLanguageId, + (LPWSTR)(LPWSTR*)&string, 0, (va_list*)args); + + if (result == 0) + RETURN FALSE; + else + { + if (string[result-1] == W(' ')) + string[result-1] = W('\0'); + + Set(string); + RETURN TRUE; + } +} + +#if 1 +//---------------------------------------------------------------------------- +// Helper +//---------------------------------------------------------------------------- + +// @todo -this should be removed and placed outside of SString +void SString::MakeFullNamespacePath(const SString &nameSpace, const SString &name) +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + THROWS; + GC_NOTRIGGER; + } + CONTRACT_END; + + if (nameSpace.GetRepresentation() == REPRESENTATION_UTF8 + && name.GetRepresentation() == REPRESENTATION_UTF8) + { + const UTF8 *ns = nameSpace.GetRawUTF8(); + const UTF8 *n = name.GetRawUTF8(); + COUNT_T count = ns::GetFullLength(ns, n)-1; + Resize(count, REPRESENTATION_UTF8); + if (count > 0) + ns::MakePath(GetRawUTF8(), count+1, ns, n); + } + else + { + const WCHAR *ns = nameSpace; + const WCHAR *n = name; + COUNT_T count = ns::GetFullLength(ns, n)-1; + Resize(count, REPRESENTATION_UNICODE); + if (count > 0) + ns::MakePath(GetRawUnicode(), count+1, ns, n); + } + + RETURN; +} +#endif + + + +//---------------------------------------------------------------------------- +// Private helper. +// Check to see if the string fits the suggested representation +//---------------------------------------------------------------------------- +BOOL SString::IsRepresentation(Representation representation) const +{ + CONTRACT(BOOL) + { + PRECONDITION(CheckRepresentation(representation)); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + Representation currentRepresentation = GetRepresentation(); + + // If representations are the same, cool. + if (currentRepresentation == representation) + RETURN TRUE; + + // If we have an empty representation, we match everything + if (currentRepresentation == REPRESENTATION_EMPTY) + RETURN TRUE; + + // If we're a 1 byte charset, there are some more chances to match + if (currentRepresentation != REPRESENTATION_UNICODE + && representation != REPRESENTATION_UNICODE) + { + // If we're ASCII, we can be any 1 byte rep + if (currentRepresentation == REPRESENTATION_ASCII) + RETURN TRUE; + + // We really want to be ASCII - scan to see if we qualify + if (ScanASCII()) + RETURN TRUE; + } + + // Sorry, must convert. + RETURN FALSE; +} + +//---------------------------------------------------------------------------- +// Private helper. +// Get the contents of the given string in a form which is compatible with our +// string (and is in a fixed character set.) Updates the given iterator +// if necessary to keep it in sync. +//---------------------------------------------------------------------------- +const SString &SString::GetCompatibleString(const SString &s, SString &scratch, const CIterator &i) const +{ + CONTRACTL + { + PRECONDITION(s.Check()); + PRECONDITION(scratch.Check()); + PRECONDITION(scratch.CheckEmpty()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + + // Since we have an iterator, we should be fixed size already + CONSISTENCY_CHECK(IsFixedSize()); + + switch (GetRepresentation()) + { + case REPRESENTATION_EMPTY: + return s; + + case REPRESENTATION_ASCII: + if (s.IsRepresentation(REPRESENTATION_ASCII)) + return s; + + // We can't in general convert to ASCII, so try unicode. + ConvertToUnicode(i); + FALLTHROUGH; + + case REPRESENTATION_UNICODE: + if (s.IsRepresentation(REPRESENTATION_UNICODE)) + return s; + + // @todo: we could convert s to unicode - is that a good policy???? + s.ConvertToUnicode(scratch); + return scratch; + + case REPRESENTATION_UTF8: + case REPRESENTATION_ANSI: + // These should all be impossible since we have an CIterator on us. + default: + UNREACHABLE_MSG("Unexpected string representation"); + } + + return s; +} + +//---------------------------------------------------------------------------- +// Private helper. +// Get the contents of the given string in a form which is compatible with our +// string (and is in a fixed character set.) +// May convert our string to unicode. +//---------------------------------------------------------------------------- +const SString &SString::GetCompatibleString(const SString &s, SString &scratch) const +{ + CONTRACTL + { + PRECONDITION(s.Check()); + PRECONDITION(scratch.Check()); + PRECONDITION(scratch.CheckEmpty()); + THROWS_UNLESS_BOTH_NORMALIZED(s); + GC_NOTRIGGER; + } + CONTRACTL_END; + + // First, make sure we have a fixed size. + ConvertToFixed(); + + switch (GetRepresentation()) + { + case REPRESENTATION_EMPTY: + return s; + + case REPRESENTATION_ANSI: + if (s.IsRepresentation(REPRESENTATION_ANSI)) + return s; + + s.ConvertToANSI(scratch); + return scratch; + + case REPRESENTATION_ASCII: + if (s.IsRepresentation(REPRESENTATION_ASCII)) + return s; + + // We can't in general convert to ASCII, so try unicode. + ConvertToUnicode(); + FALLTHROUGH; + + case REPRESENTATION_UNICODE: + if (s.IsRepresentation(REPRESENTATION_UNICODE)) + return s; + + // @todo: we could convert s to unicode in place - is that a good policy???? + s.ConvertToUnicode(scratch); + return scratch; + + case REPRESENTATION_UTF8: + default: + UNREACHABLE(); + } + + return s; +} + +//---------------------------------------------------------------------------- +// Private helper. +// If we have a 1 byte representation, scan the buffer to see if we can gain +// some conversion flexibility by labelling it ASCII +//---------------------------------------------------------------------------- +BOOL SString::ScanASCII() const +{ + CONTRACT(BOOL) + { + POSTCONDITION(IsRepresentation(REPRESENTATION_ASCII) || IsASCIIScanned()); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACT_END; + + if (!IsASCIIScanned()) + { + const CHAR *c = GetRawANSI(); + const CHAR *cEnd = c + GetRawCount(); + while (c < cEnd) + { + if (*c & 0x80) + break; + c++; + } + if (c == cEnd) + { + const_cast<SString *>(this)->SetRepresentation(REPRESENTATION_ASCII); + RETURN TRUE; + } + else + const_cast<SString *>(this)->SetASCIIScanned(); + } + RETURN FALSE; +} + +//---------------------------------------------------------------------------- +// Private helper. +// Resize updates the geometry of the string and ensures that +// the space can be written to. +// count - number of characters (not including null) to hold +// preserve - if we realloc, do we copy data from old to new? +//---------------------------------------------------------------------------- + +void SString::Resize(COUNT_T count, SString::Representation representation, Preserve preserve) +{ + CONTRACT_VOID + { + PRECONDITION(CountToSize(count) >= count); + POSTCONDITION(IsRepresentation(representation)); + POSTCONDITION(GetRawCount() == count); + if (count == 0) NOTHROW; else THROWS; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + // If we are resizing to zero, Clear is more efficient + if (count == 0) + { + Clear(); + } + else + { + SetRepresentation(representation); + + COUNT_T size = CountToSize(count); + + // detect overflow + if (size < count) + ThrowOutOfMemory(); + + ClearNormalized(); + + SBuffer::Resize(size, preserve); + + if (IsImmutable()) + EnsureMutable(); + + NullTerminate(); + } + + RETURN; +} + +//----------------------------------------------------------------------------- +// This is essentially a specialized version of the above for size 0 +//----------------------------------------------------------------------------- +void SString::Clear() +{ + CONTRACT_VOID + { + INSTANCE_CHECK; + POSTCONDITION(IsEmpty()); + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACT_END; + + SetRepresentation(REPRESENTATION_EMPTY); + + if (IsImmutable()) + { + // Use shared empty string rather than allocating a new buffer + SBuffer::SetImmutable(s_EmptyBuffer, sizeof(s_EmptyBuffer)); + } + else + { + // Leave allocated buffer for future growth + SBuffer::TweakSize(sizeof(WCHAR)); + GetRawUnicode()[0] = 0; + } + + RETURN; +} + + +#ifdef DACCESS_COMPILE + +//--------------------------------------------------------------------------------------- +// +// Return a pointer to the raw buffer +// +// Returns: +// A pointer to the raw string buffer. +// +void * SString::DacGetRawContent() const +{ + if (IsEmpty()) + { + return NULL; + } + + switch (GetRepresentation()) + { + case REPRESENTATION_EMPTY: + return NULL; + + case REPRESENTATION_UNICODE: + case REPRESENTATION_UTF8: + case REPRESENTATION_ASCII: + case REPRESENTATION_ANSI: + // Note: no need to call DacInstantiateString because we know the exact length already. + return SBuffer::DacGetRawContent(); + + default: + DacNotImpl(); + return NULL; + } +} + +//--------------------------------------------------------------------------------------- +// +// Return a pointer to the raw buffer as a pointer to a unicode string. Does not +// do conversion, and thus requires that the representation already be in unicode. +// +// Returns: +// A pointer to the raw string buffer as a unicode string. +// +const WCHAR * SString::DacGetRawUnicode() const +{ + if (IsEmpty() || (GetRepresentation() == REPRESENTATION_EMPTY)) + { + return W(""); + } + + if (GetRepresentation() != REPRESENTATION_UNICODE) + { + DacError(E_UNEXPECTED); + } + + HRESULT status = S_OK; + WCHAR* wszBuf = NULL; + EX_TRY + { + wszBuf = static_cast<WCHAR*>(SBuffer::DacGetRawContent()); + } + EX_CATCH_HRESULT(status); + + if (SUCCEEDED(status)) + { + return wszBuf; + } + else + { + return NULL; + } +} + +//--------------------------------------------------------------------------------------- +// +// Copy the string from the target into the provided buffer, converting to unicode if necessary +// +// Arguments: +// cBufChars - size of pBuffer in count of unicode characters. +// pBuffer - a buffer of cBufChars unicode chars. +// pcNeedChars - space to store the number of unicode chars in the SString. +// +// Returns: +// true if successful - and buffer is filled with the unicode representation of +// the string. +// false if unsuccessful. +// +bool SString::DacGetUnicode(COUNT_T cBufChars, + __out_z __inout_ecount(cBufChars) WCHAR * pBuffer, + COUNT_T * pcNeedChars) const +{ + SUPPORTS_DAC; + + PVOID pContent = NULL; + int iPage = CP_ACP; + + if (IsEmpty() || (GetRepresentation() == REPRESENTATION_EMPTY)) + { + if (pcNeedChars) + { + *pcNeedChars = 1; + } + if (pBuffer && cBufChars) + { + pBuffer[0] = 0; + } + return true; + } + + HRESULT status = S_OK; + EX_TRY + { + pContent = SBuffer::DacGetRawContent(); + } + EX_CATCH_HRESULT(status); + + if (SUCCEEDED(status) && pContent != NULL) + { + switch (GetRepresentation()) + { + + case REPRESENTATION_UNICODE: + + if (pcNeedChars) + { + *pcNeedChars = GetCount() + 1; + } + + if (pBuffer && cBufChars) + { + if (cBufChars > GetCount() + 1) + { + cBufChars = GetCount() + 1; + } + memcpy(pBuffer, pContent, cBufChars * sizeof(*pBuffer)); + pBuffer[cBufChars - 1] = 0; + } + + return true; + + case REPRESENTATION_UTF8: + iPage = CP_UTF8; + FALLTHROUGH; + case REPRESENTATION_ASCII: + case REPRESENTATION_ANSI: + // iPage defaults to CP_ACP. + if (pcNeedChars) + { + *pcNeedChars = WszMultiByteToWideChar(iPage, 0, reinterpret_cast<PSTR>(pContent), -1, NULL, 0); + } + if (pBuffer && cBufChars) + { + if (!WszMultiByteToWideChar(iPage, 0, reinterpret_cast<PSTR>(pContent), -1, pBuffer, cBufChars)) + { + return false; + } + } + return true; + + default: + DacNotImpl(); + return false; + } + } + return false; +} + +#endif //DACCESS_COMPILE diff --git a/src/coreclr/utilcode/sstring_com.cpp b/src/coreclr/utilcode/sstring_com.cpp new file mode 100644 index 00000000000..396ec95d735 --- /dev/null +++ b/src/coreclr/utilcode/sstring_com.cpp @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// --------------------------------------------------------------------------- +// SString_COM.cpp + +// --------------------------------------------------------------------------- + +#include "stdafx.h" +#include "sstring.h" +#include "ex.h" +#include "holder.h" + +#define DEFAULT_RESOURCE_STRING_SIZE 255 + +//---------------------------------------------------------------------------- +// Load the string resource into this string. +//---------------------------------------------------------------------------- +BOOL SString::LoadResource(CCompRC::ResourceCategory eCategory, int resourceID) +{ + return SUCCEEDED(LoadResourceAndReturnHR(eCategory, resourceID)); +} + +HRESULT SString::LoadResourceAndReturnHR(CCompRC::ResourceCategory eCategory, int resourceID) +{ + WRAPPER_NO_CONTRACT; + return LoadResourceAndReturnHR(NULL, eCategory,resourceID); +} + +HRESULT SString::LoadResourceAndReturnHR(CCompRC* pResourceDLL, CCompRC::ResourceCategory eCategory, int resourceID) +{ + CONTRACT(BOOL) + { + INSTANCE_CHECK; + NOTHROW; + } + CONTRACT_END; + + HRESULT hr = E_FAIL; + +#ifndef FEATURE_UTILCODE_NO_DEPENDENCIES + if (pResourceDLL == NULL) + { + pResourceDLL = CCompRC::GetDefaultResourceDll(); + } + + if (pResourceDLL != NULL) + { + + int size = 0; + + EX_TRY + { + if (GetRawCount() == 0) + Resize(DEFAULT_RESOURCE_STRING_SIZE, REPRESENTATION_UNICODE); + + while (TRUE) + { + // First try and load the string in the amount of space that we have. + // In fatal error reporting scenarios, we may not have enough memory to + // allocate a larger buffer. + + hr = pResourceDLL->LoadString(eCategory, resourceID, GetRawUnicode(), GetRawCount()+1, &size); + if (hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + if (FAILED(hr)) + { + Clear(); + break; + } + + // Although we cannot generally detect truncation, we can tell if we + // used up all the space (in which case we will assume truncation.) + if (size < (int)GetRawCount()) + { + break; + } + } + + // Double the size and try again. + Resize(size*2, REPRESENTATION_UNICODE); + + } + + if (SUCCEEDED(hr)) + { + Truncate(Begin() + (COUNT_T) wcslen(GetRawUnicode())); + } + + Normalize(); + + } + EX_CATCH + { + hr = E_FAIL; + } + EX_END_CATCH(SwallowAllExceptions); + } +#endif //!FEATURE_UTILCODE_NO_DEPENDENCIES + + RETURN hr; +} // SString::LoadResourceAndReturnHR diff --git a/src/coreclr/utilcode/stacktrace.cpp b/src/coreclr/utilcode/stacktrace.cpp new file mode 100644 index 00000000000..9cab6ee2d1f --- /dev/null +++ b/src/coreclr/utilcode/stacktrace.cpp @@ -0,0 +1,991 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +#include "stdafx.h" + +#include "stacktrace.h" +#include <imagehlp.h> +#include "corhlpr.h" +#include "utilcode.h" +#include "pedecoder.h" // for IMAGE_FILE_MACHINE_NATIVE + +//This is a workaround. We need to work with the debugger team to figure +//out how the module handle of the CLR can be found in a SxS safe way. +HMODULE GetCLRModuleHack() +{ + static HMODULE s_hModCLR = 0; + if (!s_hModCLR) + { + s_hModCLR = GetModuleHandleA(MAIN_CLR_DLL_NAME_A); + } + return s_hModCLR; +} + +HINSTANCE LoadImageHlp() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + SCAN_IGNORE_FAULT; // Faults from Wsz funcs are handled. + + return LoadLibraryExA("imagehlp.dll", NULL, 0); +} + +HINSTANCE LoadDbgHelp() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + SCAN_IGNORE_FAULT; // Faults from Wsz funcs are handled. + + return LoadLibraryExA("dbghelp.dll", NULL, 0); +} + +/**************************************************************************** +* SymCallback * +*---------------------* +* Description: +* Callback for imghelp. +****************************************************************************/ +BOOL __stdcall SymCallback +( +HANDLE hProcess, +ULONG ActionCode, +PVOID CallbackData, +PVOID UserContext +) +{ + WRAPPER_NO_CONTRACT; + + switch (ActionCode) + { + case CBA_DEBUG_INFO: + OutputDebugStringA("IMGHLP: "); + OutputDebugStringA((LPCSTR) CallbackData); + OutputDebugStringA("\n"); + break; + + case CBA_DEFERRED_SYMBOL_LOAD_START: + OutputDebugStringA("IMGHLP: Deferred symbol load start "); + OutputDebugStringA(((IMAGEHLP_DEFERRED_SYMBOL_LOAD*)CallbackData)->FileName); + OutputDebugStringA("\n"); + break; + + case CBA_DEFERRED_SYMBOL_LOAD_COMPLETE: + OutputDebugStringA("IMGHLP: Deferred symbol load complete "); + OutputDebugStringA(((IMAGEHLP_DEFERRED_SYMBOL_LOAD*)CallbackData)->FileName); + OutputDebugStringA("\n"); + break; + + case CBA_DEFERRED_SYMBOL_LOAD_FAILURE: + OutputDebugStringA("IMGHLP: Deferred symbol load failure "); + OutputDebugStringA(((IMAGEHLP_DEFERRED_SYMBOL_LOAD*)CallbackData)->FileName); + OutputDebugStringA("\n"); + break; + + case CBA_DEFERRED_SYMBOL_LOAD_PARTIAL: + OutputDebugStringA("IMGHLP: Deferred symbol load partial "); + OutputDebugStringA(((IMAGEHLP_DEFERRED_SYMBOL_LOAD*)CallbackData)->FileName); + OutputDebugStringA("\n"); + break; + } + + return FALSE; +} + +// @TODO_IA64: all of this stack trace stuff is pretty much broken on 64-bit +// right now because this code doesn't use the new SymXxxx64 functions. + +#define LOCAL_ASSERT(x) +// +//--- Macros ------------------------------------------------------------------ +// + +#define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) + +// +// Types and Constants -------------------------------------------------------- +// + +struct SYM_INFO +{ + DWORD_PTR dwOffset; + char achModule[cchMaxAssertModuleLen]; + char achSymbol[cchMaxAssertSymbolLen]; +}; + +//--- Function Pointers to APIs in IMAGEHLP.DLL. Loaded dynamically. --------- + +typedef LPAPI_VERSION (__stdcall *pfnImgHlp_ImagehlpApiVersionEx)( + LPAPI_VERSION AppVersion + ); + +typedef BOOL (__stdcall *pfnImgHlp_StackWalk)( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME StackFrame, + LPVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE TranslateAddress + ); + +#ifdef HOST_64BIT +typedef DWORD64 (__stdcall *pfnImgHlp_SymGetModuleBase64)( + IN HANDLE hProcess, + IN DWORD64 dwAddr + ); + +typedef IMAGEHLP_SYMBOL64 PLAT_IMAGEHLP_SYMBOL; +typedef IMAGEHLP_MODULE64 PLAT_IMAGEHLP_MODULE; + +#else +typedef IMAGEHLP_SYMBOL PLAT_IMAGEHLP_SYMBOL; +typedef IMAGEHLP_MODULE PLAT_IMAGEHLP_MODULE; +#endif + +#undef IMAGEHLP_SYMBOL +#undef IMAGEHLP_MODULE + + +typedef BOOL (__stdcall *pfnImgHlp_SymGetModuleInfo)( + IN HANDLE hProcess, + IN DWORD_PTR dwAddr, + OUT PLAT_IMAGEHLP_MODULE* ModuleInfo + ); + +typedef LPVOID (__stdcall *pfnImgHlp_SymFunctionTableAccess)( + HANDLE hProcess, + DWORD_PTR AddrBase + ); + +typedef BOOL (__stdcall *pfnImgHlp_SymGetSymFromAddr)( + IN HANDLE hProcess, + IN DWORD_PTR dwAddr, + OUT DWORD_PTR* pdwDisplacement, + OUT PLAT_IMAGEHLP_SYMBOL* Symbol + ); + +typedef BOOL (__stdcall *pfnImgHlp_SymInitialize)( + IN HANDLE hProcess, + IN LPSTR UserSearchPath, + IN BOOL fInvadeProcess + ); + +typedef BOOL (__stdcall *pfnImgHlp_SymUnDName)( + IN PLAT_IMAGEHLP_SYMBOL* sym, // Symbol to undecorate + OUT LPSTR UnDecName, // Buffer to store undecorated name in + IN DWORD UnDecNameLength // Size of the buffer + ); + +typedef BOOL (__stdcall *pfnImgHlp_SymLoadModule)( + IN HANDLE hProcess, + IN HANDLE hFile, + IN PSTR ImageName, + IN PSTR ModuleName, + IN DWORD_PTR BaseOfDll, + IN DWORD SizeOfDll + ); + +typedef BOOL (_stdcall *pfnImgHlp_SymRegisterCallback)( + IN HANDLE hProcess, + IN PSYMBOL_REGISTERED_CALLBACK CallbackFunction, + IN PVOID UserContext + ); + +typedef DWORD (_stdcall *pfnImgHlp_SymSetOptions)( + IN DWORD SymOptions + ); + +typedef DWORD (_stdcall *pfnImgHlp_SymGetOptions)( + ); + + +struct IMGHLPFN_LOAD +{ + LPCSTR pszFnName; + LPVOID * ppvfn; +}; + + +#if defined(HOST_64BIT) +typedef void (*pfn_GetRuntimeStackWalkInfo)( + IN ULONG64 ControlPc, + OUT UINT_PTR* pModuleBase, + OUT UINT_PTR* pFuncEntry + ); +#endif // HOST_64BIT + + +// +// Globals -------------------------------------------------------------------- +// + +static BOOL g_fLoadedImageHlp = FALSE; // set to true on success +static BOOL g_fLoadedImageHlpFailed = FALSE; // set to true on failure +static HINSTANCE g_hinstImageHlp = NULL; +static HINSTANCE g_hinstDbgHelp = NULL; +static HANDLE g_hProcess = NULL; + +pfnImgHlp_ImagehlpApiVersionEx _ImagehlpApiVersionEx; +pfnImgHlp_StackWalk _StackWalk; +pfnImgHlp_SymGetModuleInfo _SymGetModuleInfo; +pfnImgHlp_SymFunctionTableAccess _SymFunctionTableAccess; +pfnImgHlp_SymGetSymFromAddr _SymGetSymFromAddr; +pfnImgHlp_SymInitialize _SymInitialize; +pfnImgHlp_SymUnDName _SymUnDName; +pfnImgHlp_SymLoadModule _SymLoadModule; +pfnImgHlp_SymRegisterCallback _SymRegisterCallback; +pfnImgHlp_SymSetOptions _SymSetOptions; +pfnImgHlp_SymGetOptions _SymGetOptions; +#if defined(HOST_64BIT) +pfn_GetRuntimeStackWalkInfo _GetRuntimeStackWalkInfo; +#endif // HOST_64BIT + +IMGHLPFN_LOAD ailFuncList[] = +{ + { "ImagehlpApiVersionEx", (LPVOID*)&_ImagehlpApiVersionEx }, + { "StackWalk", (LPVOID*)&_StackWalk }, + { "SymGetModuleInfo", (LPVOID*)&_SymGetModuleInfo }, + { "SymFunctionTableAccess", (LPVOID*)&_SymFunctionTableAccess }, + { "SymGetSymFromAddr", (LPVOID*)&_SymGetSymFromAddr }, + { "SymInitialize", (LPVOID*)&_SymInitialize }, + { "SymUnDName", (LPVOID*)&_SymUnDName }, + { "SymLoadModule", (LPVOID*)&_SymLoadModule }, + { "SymRegisterCallback", (LPVOID*)&_SymRegisterCallback }, + { "SymSetOptions", (LPVOID*)&_SymSetOptions }, + { "SymGetOptions", (LPVOID*)&_SymGetOptions }, +}; + + +/**************************************************************************** +* FillSymbolSearchPath * +*----------------------* +* Description: +* Manually pick out all the symbol path information we need for a real +* stack trace to work. This includes the default NT symbol paths and +* places on a VBL build machine where they should live. +****************************************************************************/ +#define MAX_SYM_PATH (1024*8) +#define DEFAULT_SYM_PATH W("symsrv*symsrv.dll*\\\\symbols\\symbols;") +#define STR_ENGINE_NAME MAIN_CLR_DLL_NAME_W +LPSTR FillSymbolSearchPathThrows(CQuickBytes &qb) +{ + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + SCAN_IGNORE_FAULT; // Faults from Wsz funcs are handled. + +#ifndef DACCESS_COMPILE + // not allowed to do allocation if current thread suspends EE. + if (IsSuspendEEThread ()) + return NULL; +#endif + + InlineSString<MAX_SYM_PATH> rcBuff ; // Working buffer + int chTotal = 0; // How full is working buffer. + int ch; + + // If the NT symbol server path vars are there, then use those. + chTotal = WszGetEnvironmentVariable(W("_NT_SYMBOL_PATH"), rcBuff); + if (chTotal + 1 < MAX_SYM_PATH) + rcBuff.Append(W(';')); + + // Copy the defacto NT symbol path as well. + size_t sympathLength = chTotal + NumItems(DEFAULT_SYM_PATH) + 1; + // integer overflow occurred + if (sympathLength < (size_t)chTotal || sympathLength < NumItems(DEFAULT_SYM_PATH)) + { + return NULL; + } + + if (sympathLength < MAX_SYM_PATH) + { + rcBuff.Append(DEFAULT_SYM_PATH); + chTotal = rcBuff.GetCount(); + } + + // Next, if there is a URTTARGET, add that since that is where ndpsetup places + // your symobls on an install. + PathString rcBuffTemp; + ch = WszGetEnvironmentVariable(W("URTTARGET"), rcBuffTemp); + rcBuff.Append(rcBuffTemp); + if (ch != 0 && (chTotal + ch + 1 < MAX_SYM_PATH)) + { + size_t chNewTotal = chTotal + ch; + if (chNewTotal < (size_t)chTotal || chNewTotal < (size_t)ch) + { // integer overflow occurred + return NULL; + } + chTotal += ch; + rcBuff.Append(W(';')); + } + +#ifndef SELF_NO_HOST + // Fetch the path location of the engine dll and add that path as well, just + // in case URTARGET didn't cut it either. + // For no-host builds of utilcode, we don't necessarily have an engine DLL in the + // process, so skip this part. + + ch = WszGetModuleFileName(GetCLRModuleHack(), rcBuffTemp); + + + size_t pathLocationLength = chTotal + ch + 1; + // integer overflow occurred + if (pathLocationLength < (size_t)chTotal || pathLocationLength < (size_t)ch) + { + return NULL; + } + + if (ch != 0 && (pathLocationLength < MAX_SYM_PATH)) + { + chTotal = chTotal + ch - NumItems(STR_ENGINE_NAME); + rcBuff.Append(W(';')); + } +#endif + + // Now we have a working buffer with a bunch of interesting stuff. Time + // to convert it back to ansi for the imagehlp api's. Allocate the buffer + // 2x bigger to handle worst case for MBCS. + ch = ::WszWideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, rcBuff, -1, 0, 0, 0, 0); + LPSTR szRtn = (LPSTR) qb.AllocNoThrow(ch + 1); + if (!szRtn) + return NULL; + WszWideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, rcBuff, -1, szRtn, ch+1, 0, 0); + return (szRtn); +} +LPSTR FillSymbolSearchPath(CQuickBytes &qb) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + SCAN_IGNORE_FAULT; // Faults from Wsz funcs are handled. + LPSTR retval; + HRESULT hr = S_OK; + + EX_TRY + { + retval = FillSymbolSearchPathThrows(qb); + } + EX_CATCH_HRESULT(hr); + + if (hr != S_OK) + { + SetLastError(hr); + retval = NULL; + } + + return retval; +} + +/**************************************************************************** +* MagicInit * +*-----------* +* Description: +* Initializes the symbol loading code. Currently called (if necessary) +* at the beginning of each method that might need ImageHelp to be +* loaded. +****************************************************************************/ +void MagicInit() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + if (g_fLoadedImageHlp || g_fLoadedImageHlpFailed) + { + return; + } + + g_hProcess = GetCurrentProcess(); + + if (g_hinstDbgHelp == NULL) + { + g_hinstDbgHelp = LoadDbgHelp(); + } + if (NULL == g_hinstDbgHelp) + { + // Imagehlp.dll has dependency on dbghelp.dll through delay load. + // If dbghelp.dll is not available, Imagehlp.dll initializes API's like ImageApiVersionEx to + // some dummy function. Then we AV when we use data from _ImagehlpApiVersionEx + g_fLoadedImageHlpFailed = TRUE; + return; + } + + // + // Try to load imagehlp.dll + // + if (g_hinstImageHlp == NULL) { + g_hinstImageHlp = LoadImageHlp(); + } + LOCAL_ASSERT(g_hinstImageHlp); + + if (NULL == g_hinstImageHlp) + { + g_fLoadedImageHlpFailed = TRUE; + return; + } + + // + // Try to get the API entrypoints in imagehlp.dll + // + for (int i = 0; i < COUNT_OF(ailFuncList); i++) + { + *(ailFuncList[i].ppvfn) = GetProcAddress( + g_hinstImageHlp, + ailFuncList[i].pszFnName); + LOCAL_ASSERT(*(ailFuncList[i].ppvfn)); + + if (!*(ailFuncList[i].ppvfn)) + { + g_fLoadedImageHlpFailed = TRUE; + return; + } + } + + API_VERSION AppVersion = { 4, 0, API_VERSION_NUMBER, 0 }; + LPAPI_VERSION papiver = _ImagehlpApiVersionEx(&AppVersion); + + // + // We assume any version 4 or greater is OK. + // + LOCAL_ASSERT(papiver->Revision >= 4); + if (papiver->Revision < 4) + { + g_fLoadedImageHlpFailed = TRUE; + return; + } + + g_fLoadedImageHlp = TRUE; + + // + // Initialize imagehlp.dll. A NULL search path is supposed to resolve + // symbols but never works. So pull in everything and put some additional + // hints that might help out a dev box. + // + + _SymSetOptions(_SymGetOptions() | SYMOPT_DEFERRED_LOADS|SYMOPT_DEBUG); +#ifndef HOST_64BIT + _SymRegisterCallback(g_hProcess, SymCallback, 0); +#endif + + CQuickBytes qbSearchPath; + LPSTR szSearchPath = FillSymbolSearchPath(qbSearchPath); + _SymInitialize(g_hProcess, szSearchPath, TRUE); + + return; +} + + +/**************************************************************************** +* FillSymbolInfo * +*----------------* +* Description: +* Fills in a SYM_INFO structure +****************************************************************************/ +void FillSymbolInfo +( +SYM_INFO *psi, +DWORD_PTR dwAddr +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + if (!g_fLoadedImageHlp) + { + return; + } + + LOCAL_ASSERT(psi); + memset(psi, 0, sizeof(SYM_INFO)); + + PLAT_IMAGEHLP_MODULE mi; + mi.SizeOfStruct = sizeof(mi); + + if (!_SymGetModuleInfo(g_hProcess, dwAddr, &mi)) + { + strcpy_s(psi->achModule, _countof(psi->achModule), "<no module>"); + } + else + { + strcpy_s(psi->achModule, _countof(psi->achModule), mi.ModuleName); + _strupr_s(psi->achModule, _countof(psi->achModule)); + } + + CHAR rgchUndec[256]; + const CHAR * pszSymbol = NULL; + + // Name field of IMAGEHLP_SYMBOL is dynamically sized. + // Pad with space for 255 characters. + union + { + CHAR rgchSymbol[sizeof(PLAT_IMAGEHLP_SYMBOL) + 255]; + PLAT_IMAGEHLP_SYMBOL sym; + }; + + __try + { + sym.SizeOfStruct = sizeof(PLAT_IMAGEHLP_SYMBOL); + sym.Address = dwAddr; + sym.MaxNameLength = 255; + + if (_SymGetSymFromAddr(g_hProcess, dwAddr, &psi->dwOffset, &sym)) + { + pszSymbol = sym.Name; + + if (_SymUnDName(&sym, rgchUndec, COUNT_OF(rgchUndec)-1)) + { + pszSymbol = rgchUndec; + } + } + else + { + pszSymbol = "<no symbol>"; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + pszSymbol = "<EX: no symbol>"; + psi->dwOffset = dwAddr - mi.BaseOfImage; + } + + strcpy_s(psi->achSymbol, _countof(psi->achSymbol), pszSymbol); +} + +/**************************************************************************** +* FunctionTableAccess * +*---------------------* +* Description: +* Helper for imagehlp's StackWalk API. +****************************************************************************/ +LPVOID __stdcall FunctionTableAccess +( +HANDLE hProcess, +DWORD_PTR dwPCAddr +) +{ + WRAPPER_NO_CONTRACT; + + HANDLE hFuncEntry = _SymFunctionTableAccess( hProcess, dwPCAddr ); + +#if defined(HOST_64BIT) + if (hFuncEntry == NULL) + { + if (_GetRuntimeStackWalkInfo == NULL) + { + _GetRuntimeStackWalkInfo = (pfn_GetRuntimeStackWalkInfo) + GetProcAddress(GetCLRModuleHack(), "GetRuntimeStackWalkInfo"); + if (_GetRuntimeStackWalkInfo == NULL) + return NULL; + } + + _GetRuntimeStackWalkInfo((ULONG64)dwPCAddr, NULL, (UINT_PTR*)(&hFuncEntry)); + } +#endif // HOST_64BIT + + return hFuncEntry; +} + +/**************************************************************************** +* GetModuleBase * +*---------------* +* Description: +* Helper for imagehlp's StackWalk API. Retrieves the base address of +* the module containing the giving virtual address. +* +* NOTE: If the module information for the given module hasnot yet been +* loaded, then it is loaded on this call. +* +* Return: +* Base virtual address where the module containing ReturnAddress is +* loaded, or 0 if the address cannot be determined. +****************************************************************************/ +DWORD_PTR __stdcall GetModuleBase +( +HANDLE hProcess, +DWORD_PTR dwAddr +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + PLAT_IMAGEHLP_MODULE ModuleInfo; + ModuleInfo.SizeOfStruct = sizeof(ModuleInfo); + + if (_SymGetModuleInfo(hProcess, dwAddr, &ModuleInfo)) + { + return ModuleInfo.BaseOfImage; + } + else + { + MEMORY_BASIC_INFORMATION mbi; + + if (VirtualQueryEx(hProcess, (LPVOID)dwAddr, &mbi, sizeof(mbi))) + { + if (mbi.Type & MEM_IMAGE) + { + char achFile[MAX_LONGPATH] = {0}; + DWORD cch; + + cch = GetModuleFileNameA( + (HINSTANCE)mbi.AllocationBase, + achFile, + MAX_LONGPATH); + + // Ignore the return code since we can't do anything with it. + _SymLoadModule( + hProcess, + NULL, + ((cch) ? achFile : NULL), + NULL, + (DWORD_PTR)mbi.AllocationBase, + 0); + + return (DWORD_PTR)mbi.AllocationBase; + } + } + } + +#if defined(HOST_64BIT) + if (_GetRuntimeStackWalkInfo == NULL) + { + _GetRuntimeStackWalkInfo = (pfn_GetRuntimeStackWalkInfo) + GetProcAddress(GetCLRModuleHack(), "GetRuntimeStackWalkInfo"); + if (_GetRuntimeStackWalkInfo == NULL) + return NULL; + } + + DWORD_PTR moduleBase; + _GetRuntimeStackWalkInfo((ULONG64)dwAddr, (UINT_PTR*)&moduleBase, NULL); + if (moduleBase != NULL) + return moduleBase; +#endif // HOST_64BIT + + return 0; +} + +#if !defined(DACCESS_COMPILE) +/**************************************************************************** +* GetStackBacktrace * +*-------------------* +* Description: +* Gets a stacktrace of the current stack, including symbols. +* +* Return: +* The number of elements actually retrieved. +****************************************************************************/ + +UINT GetStackBacktrace +( +UINT ifrStart, // How many stack elements to skip before starting. +UINT cfrTotal, // How many elements to trace after starting. +DWORD_PTR* pdwEip, // Array to be filled with stack addresses. +SYM_INFO* psiSymbols, // This array is filled with symbol information. + // It should be big enough to hold cfrTotal elts. + // If NULL, no symbol information is stored. +CONTEXT * pContext // Context to use (or NULL to use current) +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + UINT nElements = 0; + DWORD_PTR* pdw = pdwEip; + SYM_INFO* psi = psiSymbols; + + MagicInit(); + + memset(pdwEip, 0, cfrTotal*sizeof(DWORD_PTR)); + + if (psiSymbols) + { + memset(psiSymbols, 0, cfrTotal * sizeof(SYM_INFO)); + } + + if (!g_fLoadedImageHlp) + { + return 0; + } + + CONTEXT context; + if (pContext == NULL) + { + ClrCaptureContext(&context); + } + else + { + memcpy(&context, pContext, sizeof(CONTEXT)); + } + +#ifdef HOST_64BIT + STACKFRAME64 stkfrm; + memset(&stkfrm, 0, sizeof(STACKFRAME64)); +#else + STACKFRAME stkfrm; + memset(&stkfrm, 0, sizeof(STACKFRAME)); +#endif + + stkfrm.AddrPC.Mode = AddrModeFlat; + stkfrm.AddrStack.Mode = AddrModeFlat; + stkfrm.AddrFrame.Mode = AddrModeFlat; +#if defined(_M_IX86) + stkfrm.AddrPC.Offset = context.Eip; + stkfrm.AddrStack.Offset = context.Esp; + stkfrm.AddrFrame.Offset = context.Ebp; // Frame Pointer +#endif + +#ifndef HOST_X86 + // If we don't have a user-supplied context, then don't skip any frames. + // So ignore this function (GetStackBackTrace) + // ClrCaptureContext on x86 gives us the ESP/EBP/EIP of its caller's caller + // so we don't need to do this. + if (pContext == NULL) + { + ifrStart += 1; + } +#endif // !HOST_X86 + + for (UINT i = 0; i < ifrStart + cfrTotal; i++) + { + if (!_StackWalk(IMAGE_FILE_MACHINE_NATIVE, + g_hProcess, + GetCurrentThread(), + &stkfrm, + &context, + NULL, + (PFUNCTION_TABLE_ACCESS_ROUTINE)FunctionTableAccess, + (PGET_MODULE_BASE_ROUTINE)GetModuleBase, + NULL)) + { + break; + } + + if (i >= ifrStart) + { + *pdw++ = stkfrm.AddrPC.Offset; + nElements++; + + if (psi) + { + FillSymbolInfo(psi++, stkfrm.AddrPC.Offset); + } + } + } + + LOCAL_ASSERT(nElements == (UINT)(pdw - pdwEip)); + return nElements; +} +#endif // !defined(DACCESS_COMPILE) + +/**************************************************************************** +* GetStringFromSymbolInfo * +*-------------------------* +* Description: +* Actually prints the info into the string for the symbol. +****************************************************************************/ + +#ifdef HOST_64BIT + #define FMT_ADDR_BARE "%08x`%08x" + #define FMT_ADDR_OFFSET "%llX" +#else + #define FMT_ADDR_BARE "%08x" + #define FMT_ADDR_OFFSET "%lX" +#endif + +void GetStringFromSymbolInfo +( +DWORD_PTR dwAddr, +SYM_INFO *psi, // @parm Pointer to SYMBOL_INFO. Can be NULL. +__out_ecount (cchMaxAssertStackLevelStringLen) CHAR *pszString // @parm Place to put string. +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + LOCAL_ASSERT(pszString); + + // <module>! <symbol> + 0x<offset> 0x<addr>\n + + if (psi) + { + sprintf_s(pszString, + cchMaxAssertStackLevelStringLen, + "%s! %s + 0x" FMT_ADDR_OFFSET " (0x" FMT_ADDR_BARE ")", + (psi->achModule[0]) ? psi->achModule : "<no module>", + (psi->achSymbol[0]) ? psi->achSymbol : "<no symbol>", + psi->dwOffset, + DBG_ADDR(dwAddr)); + } + else + { + sprintf_s(pszString, cchMaxAssertStackLevelStringLen, "<symbols not available> (0x%p)", (void *)dwAddr); + } + + LOCAL_ASSERT(strlen(pszString) < cchMaxAssertStackLevelStringLen); +} + +#if !defined(DACCESS_COMPILE) + +/**************************************************************************** +* GetStringFromStackLevels * +*--------------------------* +* Description: +* Retrieves a string from the stack frame. If more than one frame, they +* are separated by newlines +****************************************************************************/ +void GetStringFromStackLevels +( +UINT ifrStart, // @parm How many stack elements to skip before starting. +UINT cfrTotal, // @parm How many elements to trace after starting. + // Can't be more than cfrMaxAssertStackLevels. +__out_ecount(cchMaxAssertStackLevelStringLen * cfrTotal) CHAR *pszString, // @parm Place to put string. + // Max size will be cchMaxAssertStackLevelStringLen * cfrTotal. +CONTEXT * pContext // @parm Context to start the stack trace at; null for current context. +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + LOCAL_ASSERT(pszString); + LOCAL_ASSERT(cfrTotal < cfrMaxAssertStackLevels); + + *pszString = '\0'; + + if (cfrTotal == 0) + { + return; + } + + DWORD_PTR rgdwStackAddrs[cfrMaxAssertStackLevels]; + SYM_INFO rgsi[cfrMaxAssertStackLevels]; + + // Ignore this function (GetStringFromStackLevels) if we don't have a user-supplied context. + if (pContext == NULL) + { + ifrStart += 1; + } + + UINT uiRetrieved = + GetStackBacktrace(ifrStart, cfrTotal, rgdwStackAddrs, rgsi, pContext); + + // First level + CHAR aszLevel[cchMaxAssertStackLevelStringLen]; + GetStringFromSymbolInfo(rgdwStackAddrs[0], &rgsi[0], aszLevel); + + size_t bufSize = cchMaxAssertStackLevelStringLen * cfrTotal; + + strcpy_s(pszString, bufSize, aszLevel); + + // Additional levels + for (UINT i = 1; i < uiRetrieved; ++i) + { + strcat_s(pszString, bufSize, "\n"); + GetStringFromSymbolInfo(rgdwStackAddrs[i], + &rgsi[i], aszLevel); + strcat_s(pszString, bufSize, aszLevel); + } + + LOCAL_ASSERT(strlen(pszString) <= cchMaxAssertStackLevelStringLen * cfrTotal); +} +#endif // !defined(DACCESS_COMPILE) + +/**************************************************************************** +* GetStringFromAddr * +*-------------------* +* Description: +* Returns a string from an address. +****************************************************************************/ +void GetStringFromAddr +( +DWORD_PTR dwAddr, +__out_ecount(cchMaxAssertStackLevelStringLen) LPSTR szString // Place to put string. + // Buffer must hold at least cchMaxAssertStackLevelStringLen. +) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + LOCAL_ASSERT(szString); + + SYM_INFO si; + FillSymbolInfo(&si, dwAddr); + + sprintf_s(szString, + cchMaxAssertStackLevelStringLen, + "%s! %s + 0x%p (0x%p)", + (si.achModule[0]) ? si.achModule : "<no module>", + (si.achSymbol[0]) ? si.achSymbol : "<no symbol>", + (void*)si.dwOffset, + (void*)dwAddr); +} + +/**************************************************************************** +* MagicDeinit * +*-------------* +* Description: +* Cleans up for the symbol loading code. Should be called before exit +* to free the dynamically loaded imagehlp.dll. +****************************************************************************/ +void MagicDeinit(void) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (g_hinstImageHlp) + { + FreeLibrary(g_hinstImageHlp); + + g_hinstImageHlp = NULL; + g_fLoadedImageHlp = FALSE; + } +} + +#if defined(HOST_X86) +/**************************************************************************** +* ClrCaptureContext * +*-------------------* +* Description: +* Exactly the contents of RtlCaptureContext for Win7 - Win2K doesn't +* support this, so we need it for CoreCLR 4, if we require Win2K support +****************************************************************************/ +extern "C" __declspec(naked) void __stdcall +ClrCaptureContext(__out PCONTEXT ctx) +{ + __asm { + push ebx; + mov ebx,dword ptr [esp+8] + mov dword ptr [ebx+0B0h],eax + mov dword ptr [ebx+0ACh],ecx + mov dword ptr [ebx+0A8h],edx + mov eax,dword ptr [esp] + mov dword ptr [ebx+0A4h],eax + mov dword ptr [ebx+0A0h],esi + mov dword ptr [ebx+09Ch],edi + mov word ptr [ebx+0BCh],cs + mov word ptr [ebx+098h],ds + mov word ptr [ebx+094h],es + mov word ptr [ebx+090h],fs + mov word ptr [ebx+08Ch],gs + mov word ptr [ebx+0C8h],ss + pushfd + pop dword ptr [ebx+0C0h] + mov eax,dword ptr [ebp+4] + mov dword ptr [ebx+0B8h],eax + mov eax,dword ptr [ebp] + mov dword ptr [ebx+0B4h],eax + lea eax,[ebp+8] + mov dword ptr [ebx+0C4h],eax + mov dword ptr [ebx],10007h + pop ebx + ret 4 + } +} +#endif // HOST_X86 diff --git a/src/coreclr/utilcode/stdafx.h b/src/coreclr/utilcode/stdafx.h new file mode 100644 index 00000000000..f04d0e47f8e --- /dev/null +++ b/src/coreclr/utilcode/stdafx.h @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// stdafx.h +// + +// +// Common include file for utility code. +//***************************************************************************** +#pragma once + +#include <switches.h> +#include <crtwrap.h> + +#define IN_WINFIX_CPP + +#include <winwrap.h> + +#include "volatile.h" +#include "static_assert.h" + diff --git a/src/coreclr/utilcode/stgpool.cpp b/src/coreclr/utilcode/stgpool.cpp new file mode 100644 index 00000000000..7c4c203e754 --- /dev/null +++ b/src/coreclr/utilcode/stgpool.cpp @@ -0,0 +1,2423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// StgPool.cpp +// + +// +// Pools are used to reduce the amount of data actually required in the database. +// This allows for duplicate string and binary values to be folded into one +// copy shared by the rest of the database. Strings are tracked in a hash +// table when insert/changing data to find duplicates quickly. The strings +// are then persisted consecutively in a stream in the database format. +// +//***************************************************************************** +#include "stdafx.h" // Standard include. +#include <stgpool.h> // Our interface definitions. +#include <posterror.h> // Error handling. +#include <safemath.h> // CLRSafeInt integer overflow checking +#include "../md/inc/streamutil.h" + +#include "ex.h" + +#ifdef FEATURE_PREJIT +#include <corcompile.h> +#endif + +using namespace StreamUtil; + +#define MAX_CHAIN_LENGTH 20 // Max chain length before rehashing. + +// +// +// StgPool +// +// + + +//***************************************************************************** +// Free any memory we allocated. +//***************************************************************************** +StgPool::~StgPool() +{ + WRAPPER_NO_CONTRACT; + + Uninit(); +} // StgPool::~StgPool() + + +//***************************************************************************** +// Init the pool for use. This is called for both the create empty case. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::InitNew( + ULONG cbSize, // Estimated size. + ULONG cItems) // Estimated item count. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY); + } + CONTRACTL_END + + // Make sure we aren't stomping anything and are properly initialized. + _ASSERTE(m_pSegData == m_zeros); + _ASSERTE(m_pNextSeg == 0); + _ASSERTE(m_pCurSeg == this); + _ASSERTE(m_cbCurSegOffset == 0); + _ASSERTE(m_cbSegSize == 0); + _ASSERTE(m_cbSegNext == 0); + + m_bReadOnly = false; + m_bFree = false; + + return S_OK; +} // StgPool::InitNew + +//***************************************************************************** +// Init the pool from existing data. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::InitOnMem( + void *pData, // Predefined data. + ULONG iSize, // Size of data. + int bReadOnly) // true if append is forbidden. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + // Make sure we aren't stomping anything and are properly initialized. + _ASSERTE(m_pSegData == m_zeros); + _ASSERTE(m_pNextSeg == 0); + _ASSERTE(m_pCurSeg == this); + _ASSERTE(m_cbCurSegOffset == 0); + + // Create case requires no further action. + if (!pData) + return (E_INVALIDARG); + + // Might we be extending this heap? + m_bReadOnly = bReadOnly; + + + m_pSegData = reinterpret_cast<BYTE*>(pData); + m_cbSegSize = iSize; + m_cbSegNext = iSize; + + m_bFree = false; + + return (S_OK); +} // StgPool::InitOnMem + +//***************************************************************************** +// Called when the pool must stop accessing memory passed to InitOnMem(). +//***************************************************************************** +__checkReturn +HRESULT +StgPool::TakeOwnershipOfInitMem() +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + // If the pool doesn't have a pointer to non-owned memory, done. + if (m_bFree) + return (S_OK); + + // If the pool doesn't have a pointer to memory at all, done. + if (m_pSegData == m_zeros) + { + _ASSERTE(m_cbSegSize == 0); + return (S_OK); + } + + // Get some memory to keep. + BYTE *pData = new (nothrow) BYTE[m_cbSegSize+4]; + if (pData == 0) + return (PostError(OutOfMemory())); + + // Copy the old data to the new memory. + memcpy(pData, m_pSegData, m_cbSegSize); + m_pSegData = pData; + m_bFree = true; + + return (S_OK); +} // StgPool::TakeOwnershipOfInitMem + +//***************************************************************************** +// Clear out this pool. Cannot use until you call InitNew. +//***************************************************************************** +void StgPool::Uninit() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + // Free base segment, if appropriate. + if (m_bFree && (m_pSegData != m_zeros)) + { + delete [] m_pSegData; + m_bFree = false; + } + + // Free chain, if any. + StgPoolSeg *pSeg = m_pNextSeg; + while (pSeg) + { + StgPoolSeg *pNext = pSeg->m_pNextSeg; + delete [] (BYTE*)pSeg; + pSeg = pNext; + } + + // Clear vars. + m_pSegData = (BYTE*)m_zeros; + m_cbSegSize = m_cbSegNext = 0; + m_pNextSeg = 0; + m_pCurSeg = this; + m_cbCurSegOffset = 0; +} // StgPool::Uninit + +//***************************************************************************** +// Called to copy the pool to writable memory, reset the r/o bit. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::ConvertToRW() +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr; // A result. + IfFailRet(TakeOwnershipOfInitMem()); + + IfFailRet(SetHash(true)); + + m_bReadOnly = false; + + return S_OK; +} // StgPool::ConvertToRW + +//***************************************************************************** +// Turn hashing off or on. Real implementation as required in subclass. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::SetHash(int bHash) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + return S_OK; +} // StgPool::SetHash + +//***************************************************************************** +// Trim any empty final segment. +//***************************************************************************** +void StgPool::Trim() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + // If no chained segments, nothing to do. + if (m_pNextSeg == 0) + return; + + // Handle special case for a segment that was completely unused. + if (m_pCurSeg->m_cbSegNext == 0) + { + // Find the segment which points to the empty segment. + StgPoolSeg *pPrev; + for (pPrev = this; pPrev && pPrev->m_pNextSeg != m_pCurSeg; pPrev = pPrev->m_pNextSeg); + _ASSERTE(pPrev && pPrev->m_pNextSeg == m_pCurSeg); + + // Free the empty segment. + delete [] (BYTE*) m_pCurSeg; + + // Fix the pCurSeg pointer. + pPrev->m_pNextSeg = 0; + m_pCurSeg = pPrev; + + // Adjust the base offset, because the PREVIOUS seg is now current. + _ASSERTE(m_pCurSeg->m_cbSegNext <= m_cbCurSegOffset); + m_cbCurSegOffset = m_cbCurSegOffset - m_pCurSeg->m_cbSegNext; + } +} // StgPool::Trim + +//***************************************************************************** +// Allocate memory if we don't have any, or grow what we have. If successful, +// then at least iRequired bytes will be allocated. +//***************************************************************************** +bool StgPool::Grow( // true if successful. + ULONG iRequired) // Min required bytes to allocate. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return FALSE;); + } + CONTRACTL_END + + ULONG iNewSize; // New size we want. + StgPoolSeg *pNew; // Temp pointer for malloc. + + _ASSERTE(!m_bReadOnly); + + // Would this put the pool over 2GB? + if ((m_cbCurSegOffset + iRequired) > INT_MAX) + return (false); + + // Adjust grow size as a ratio to avoid too many reallocs. + if ((m_pCurSeg->m_cbSegNext + m_cbCurSegOffset) / m_ulGrowInc >= 3) + m_ulGrowInc *= 2; + + // NOTE: MD\DataSource\RemoteMDInternalRWSource has taken a dependency that there + // won't be more than 1000 segments. Given the current exponential growth algorithm + // we'll never get anywhere close to that, but if the algorithm changes to allow for + // many segments, please update that source as well. + + // If first time, handle specially. + if (m_pSegData == m_zeros) + { + // Allocate the buffer. + iNewSize = max(m_ulGrowInc, iRequired); + BYTE *pSegData = new (nothrow) BYTE[iNewSize + 4]; + if (pSegData == NULL) + return false; + m_pSegData = pSegData; + + // Will need to delete it. + m_bFree = true; + + // How big is this initial segment? + m_cbSegSize = iNewSize; + + // Do some validation of var fields. + _ASSERTE(m_cbSegNext == 0); + _ASSERTE(m_pCurSeg == this); + _ASSERTE(m_pNextSeg == NULL); + + return true; + } + + // Allocate the new space enough for header + data. + iNewSize = (ULONG)(max(m_ulGrowInc, iRequired) + sizeof(StgPoolSeg)); + pNew = (StgPoolSeg *)new (nothrow) BYTE[iNewSize+4]; + if (pNew == NULL) + return false; + + // Set the fields in the new segment. + pNew->m_pSegData = reinterpret_cast<BYTE*>(pNew) + sizeof(StgPoolSeg); + _ASSERTE(ALIGN4BYTE(reinterpret_cast<ULONG_PTR>(pNew->m_pSegData)) == reinterpret_cast<ULONG_PTR>(pNew->m_pSegData)); + pNew->m_pNextSeg = 0; + pNew->m_cbSegSize = iNewSize - sizeof(StgPoolSeg); + pNew->m_cbSegNext = 0; + + // Calculate the base offset of the new segment. + m_cbCurSegOffset = m_cbCurSegOffset + m_pCurSeg->m_cbSegNext; + + // Handle special case for a segment that was completely unused. + //<TODO>@todo: Trim();</TODO> + if (m_pCurSeg->m_cbSegNext == 0) + { + // Find the segment which points to the empty segment. + StgPoolSeg *pPrev; + for (pPrev = this; pPrev && pPrev->m_pNextSeg != m_pCurSeg; pPrev = pPrev->m_pNextSeg); + _ASSERTE(pPrev && pPrev->m_pNextSeg == m_pCurSeg); + + // Free the empty segment. + delete [] (BYTE *) m_pCurSeg; + + // Link in the new segment. + pPrev->m_pNextSeg = pNew; + m_pCurSeg = pNew; + + return true; + } + + // Fix the size of the old segment. + m_pCurSeg->m_cbSegSize = m_pCurSeg->m_cbSegNext; + + // Link the new segment into the chain. + m_pCurSeg->m_pNextSeg = pNew; + m_pCurSeg = pNew; + + return true; +} // StgPool::Grow + +//***************************************************************************** +// Add a segment to the chain of segments. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::AddSegment( + const void *pData, // The data. + ULONG cbData, // Size of the data. + bool bCopy) // If true, make a copy of the data. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + StgPoolSeg *pNew; // Temp pointer for malloc. + + + // If we need to copy the data, just grow the heap by enough to take the + // the new data, and copy it in. + if (bCopy) + { + void *pDataToAdd = new (nothrow) BYTE[cbData]; + IfNullRet(pDataToAdd); + memcpy(pDataToAdd, pData, cbData); + pData = pDataToAdd; + } + + // If first time, handle specially. + if (m_pSegData == m_zeros) + { // Data was passed in. + m_pSegData = reinterpret_cast<BYTE*>(const_cast<void*>(pData)); + m_cbSegSize = cbData; + m_cbSegNext = cbData; + _ASSERTE(m_pNextSeg == NULL); + + // Will not delete it. + m_bFree = false; + + return S_OK; + } + + // Not first time. Handle a completely empty tail segment. + Trim(); + + // Abandon any space past the end of the current live data. + _ASSERTE(m_pCurSeg->m_cbSegSize >= m_pCurSeg->m_cbSegNext); + m_pCurSeg->m_cbSegSize = m_pCurSeg->m_cbSegNext; + + // Allocate a new segment header. + pNew = (StgPoolSeg *) new (nothrow) BYTE[sizeof(StgPoolSeg)]; + IfNullRet(pNew); + + // Set the fields in the new segment. + pNew->m_pSegData = reinterpret_cast<BYTE*>(const_cast<void*>(pData)); + pNew->m_pNextSeg = NULL; + pNew->m_cbSegSize = cbData; + pNew->m_cbSegNext = cbData; + + // Calculate the base offset of the new segment. + m_cbCurSegOffset = m_cbCurSegOffset + m_pCurSeg->m_cbSegNext; + + // Link the segment into the chain. + _ASSERTE(m_pCurSeg->m_pNextSeg == NULL); + m_pCurSeg->m_pNextSeg = pNew; + m_pCurSeg = pNew; + + return S_OK; +} // StgPool::AddSegment + +#ifndef DACCESS_COMPILE +//***************************************************************************** +// The entire string pool is written to the given stream. The stream is aligned +// to a 4 byte boundary. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::PersistToStream( + IStream *pIStream) // The stream to write to. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY); + } + CONTRACTL_END + + HRESULT hr = S_OK; + ULONG cbTotal; // Total bytes written. + StgPoolSeg *pSeg; // A segment being written. + + _ASSERTE(m_pSegData != m_zeros); + + // Start with the base segment. + pSeg = this; + cbTotal = 0; + + EX_TRY + { + // As long as there is data, write it. + while (pSeg != NULL) + { + // If there is data in the segment . . . + if (pSeg->m_cbSegNext) + { // . . . write and count the data. + if (FAILED(hr = pIStream->Write(pSeg->m_pSegData, pSeg->m_cbSegNext, 0))) + break; + cbTotal += pSeg->m_cbSegNext; + } + + // Get the next segment. + pSeg = pSeg->m_pNextSeg; + } + + if (SUCCEEDED(hr)) + { + // Align to variable (0-4 byte) boundary. + UINT32 cbTotalAligned; + if (FAILED(Align(cbTotal, &cbTotalAligned))) + { + hr = COR_E_BADIMAGEFORMAT; + } + else + { + if (cbTotalAligned > cbTotal) + { + _ASSERTE(sizeof(hr) >= 3); + hr = 0; + hr = pIStream->Write(&hr, cbTotalAligned - cbTotal, 0); + } + } + } + } + EX_CATCH + { + hr = E_FAIL; + } + EX_END_CATCH(SwallowAllExceptions); + + return hr; +} // StgPool::PersistToStream +#endif //!DACCESS_COMPILE + +//***************************************************************************** +// The entire string pool is written to the given stream. The stream is aligned +// to a 4 byte boundary. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::PersistPartialToStream( + IStream *pIStream, // The stream to write to. + ULONG iOffset) // Starting offset. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY); + } + CONTRACTL_END + + HRESULT hr = S_OK; // A result. + ULONG cbTotal; // Total bytes written. + StgPoolSeg *pSeg; // A segment being written. + + _ASSERTE(m_pSegData != m_zeros); + + // Start with the base segment. + pSeg = this; + cbTotal = 0; + + // As long as there is data, write it. + while (pSeg != NULL) + { + // If there is data in the segment . . . + if (pSeg->m_cbSegNext) + { // If this data should be skipped... + if (iOffset >= pSeg->m_cbSegNext) + { // Skip it + iOffset -= pSeg->m_cbSegNext; + } + else + { // At least some data should be written, so write and count the data. + IfFailRet(pIStream->Write(pSeg->m_pSegData+iOffset, pSeg->m_cbSegNext-iOffset, 0)); + cbTotal += pSeg->m_cbSegNext-iOffset; + iOffset = 0; + } + } + + // Get the next segment. + pSeg = pSeg->m_pNextSeg; + } + + // Align to variable (0-4 byte) boundary. + UINT32 cbTotalAligned; + if (FAILED(Align(cbTotal, &cbTotalAligned))) + { + return COR_E_BADIMAGEFORMAT; + } + if (cbTotalAligned > cbTotal) + { + _ASSERTE(sizeof(hr) >= 3); + hr = 0; + hr = pIStream->Write(&hr, cbTotalAligned - cbTotal, 0); + } + + return hr; +} // StgPool::PersistPartialToStream + +// Copies data from pSourcePool starting at index nStartSourceIndex. +__checkReturn +HRESULT +StgPool::CopyPool( + UINT32 nStartSourceIndex, + const StgPool *pSourcePool) +{ + HRESULT hr; + UINT32 cbDataSize; + BYTE *pbData = NULL; + + if (nStartSourceIndex == pSourcePool->GetRawSize()) + { // There's nothing to copy + return S_OK; + } + if (nStartSourceIndex > pSourcePool->GetRawSize()) + { // Invalid input + Debug_ReportInternalError("The caller should not pass invalid start index in the pool."); + IfFailGo(METADATA_E_INDEX_NOTFOUND); + } + + // Allocate new segment + cbDataSize = pSourcePool->GetRawSize() - nStartSourceIndex; + pbData = new (nothrow) BYTE[cbDataSize]; + IfNullGo(pbData); + + // Copy data to the new segment + UINT32 cbCopiedDataSize; + IfFailGo(pSourcePool->CopyData( + nStartSourceIndex, + pbData, + cbDataSize, + &cbCopiedDataSize)); + // Check that we copied everything + if (cbDataSize != cbCopiedDataSize) + { + Debug_ReportInternalError("It is expected to copy everything from the source pool."); + IfFailGo(E_FAIL); + } + + // Add the newly allocated segment to the pool + IfFailGo(AddSegment( + pbData, + cbDataSize, + false)); // fCopyData + +ErrExit: + if (FAILED(hr)) + { + if (pbData != NULL) + { + delete [] pbData; + } + } + return hr; +} // StgPool::CopyPool + +// Copies data from the pool into a buffer. It will correctly walk all segments for the copy. +__checkReturn +HRESULT +StgPool::CopyData( + UINT32 nOffset, + BYTE *pBuffer, + UINT32 cbBuffer, + UINT32 *pcbWritten) const +{ + CONTRACTL + { + NOTHROW; + PRECONDITION(CheckPointer(pBuffer)); + PRECONDITION(CheckPointer(pcbWritten)); + } + CONTRACTL_END + + HRESULT hr = S_OK; + const StgPoolSeg *pSeg; // A segment being written. + + _ASSERTE(m_pSegData != m_zeros); + + // Start with the base segment. + pSeg = this; + *pcbWritten = 0; + + // As long as there is data, write it. + while (pSeg != NULL) + { + // If there is data in the segment . . . + if (pSeg->m_cbSegNext) + { // If this data should be skipped... + if (nOffset >= pSeg->m_cbSegNext) + { // Skip it + nOffset -= pSeg->m_cbSegNext; + } + else + { + ULONG nNumBytesToCopy = pSeg->m_cbSegNext - nOffset; + if (nNumBytesToCopy > (cbBuffer - *pcbWritten)) + { + _ASSERTE(!"Buffer isn't big enough to copy everything!"); + nNumBytesToCopy = cbBuffer - *pcbWritten; + } + + memcpy(pBuffer + *pcbWritten, pSeg->m_pSegData+nOffset, nNumBytesToCopy); + + *pcbWritten += nNumBytesToCopy; + nOffset = 0; + } + } + + // Get the next segment. + pSeg = pSeg->m_pNextSeg; + } + + return hr; +} // StgPool::CopyData + +//***************************************************************************** +// Get a pointer to the data at some offset. May require traversing the +// chain of extensions. It is the caller's responsibility not to attempt +// to access data beyond the end of a segment. +// This is an internal accessor, and should only be called when the data +// is not in the base segment. +//***************************************************************************** +__checkReturn +HRESULT +StgPool::GetData_i( + UINT32 nOffset, + MetaData::DataBlob *pData) +{ + LIMITED_METHOD_CONTRACT; + + // Shouldn't be called on base segment. + _ASSERTE(nOffset >= m_cbSegNext); + StgPoolSeg *pSeg = this; + + while ((nOffset > 0) && (nOffset >= pSeg->m_cbSegNext)) + { + // On to next segment. + nOffset -= pSeg->m_cbSegNext; + pSeg = pSeg->m_pNextSeg; + + // Is there a next? + if (pSeg == NULL) + { + Debug_ReportError("Invalid offset passed - reached end of pool."); + pData->Clear(); + return CLDB_E_INDEX_NOTFOUND; + } + } + + // For the case where we want to read the first item and the pool is empty. + if (nOffset == pSeg->m_cbSegNext) + { // Can only be if both == 0 + Debug_ReportError("Invalid offset passed - it is at the end of pool."); + pData->Clear(); + return CLDB_E_INDEX_NOTFOUND; + } + + pData->Init(pSeg->m_pSegData + nOffset, pSeg->m_cbSegNext - nOffset); + + return S_OK; +} // StgPool::GetData_i + +// +// +// StgStringPool +// +// + + +//***************************************************************************** +// Create a new, empty string pool. +//***************************************************************************** +__checkReturn +HRESULT +StgStringPool::InitNew( + ULONG cbSize, // Estimated size. + ULONG cItems) // Estimated item count. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY); + } + CONTRACTL_END + + HRESULT hr; + UINT32 nEmptyStringOffset; + + // Let base class intialize. + IfFailRet(StgPool::InitNew()); + + // Set initial table sizes, if specified. + if (cbSize > 0) + { + if (!Grow(cbSize)) + { + return E_OUTOFMEMORY; + } + } + if (cItems > 0) + { + m_Hash.SetBuckets(cItems); + } + + // Init with empty string. + IfFailRet(AddString("", &nEmptyStringOffset)); + // Empty string had better be at offset 0. + _ASSERTE(nEmptyStringOffset == 0); + + return hr; +} // StgStringPool::InitNew + +//***************************************************************************** +// Load a string heap from persisted memory. If a copy of the data is made +// (so that it may be updated), then a new hash table is generated which can +// be used to elminate duplicates with new strings. +//***************************************************************************** +__checkReturn +HRESULT +StgStringPool::InitOnMem( + void *pData, // Predefined data. + ULONG iSize, // Size of data. + int bReadOnly) // true if append is forbidden. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY); + } + CONTRACTL_END + + HRESULT hr = S_OK; + + // There may be up to three extra '\0' characters appended for padding. Trim them. + char *pchData = reinterpret_cast<char*>(pData); + while (iSize > 1 && pchData[iSize-1] == 0 && pchData[iSize-2] == 0) + --iSize; + + // Let base class init our memory structure. + IfFailRet(StgPool::InitOnMem(pData, iSize, bReadOnly)); + + //<TODO>@todo: defer this until we hand out a pointer.</TODO> + if (!bReadOnly) + { + IfFailRet(TakeOwnershipOfInitMem()); + IfFailRet(RehashStrings()); + } + + return hr; +} // StgStringPool::InitOnMem + +//***************************************************************************** +// Clears the hash table then calls the base class. +//***************************************************************************** +void StgStringPool::Uninit() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + // Clear the hash table. + m_Hash.Clear(); + + // Let base class clean up. + StgPool::Uninit(); +} // StgStringPool::Uninit + +//***************************************************************************** +// Turn hashing off or on. If you turn hashing on, then any existing data is +// thrown away and all data is rehashed during this call. +//***************************************************************************** +__checkReturn +HRESULT +StgStringPool::SetHash(int bHash) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr = S_OK; + + // If turning on hash again, need to rehash all strings. + if (bHash) + hr = RehashStrings(); + + m_bHash = bHash; + return (hr); +} // StgStringPool::SetHash + +//***************************************************************************** +// The string will be added to the pool. The offset of the string in the pool +// is returned in *piOffset. If the string is already in the pool, then the +// offset will be to the existing copy of the string. +//***************************************************************************** +__checkReturn +HRESULT +StgStringPool::AddString( + LPCSTR szString, // The string to add to pool. + UINT32 *pnOffset) // Return offset of string here. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + STRINGHASH *pHash; // Hash item for add. + ULONG iLen; // To handle non-null strings. + LPSTR pData; // Pointer to location for new string. + HRESULT hr; + + _ASSERTE(!m_bReadOnly); + + // Null pointer is an error. + if (szString == 0) + return (PostError(E_INVALIDARG)); + + // Find the real length we need in buffer. + iLen = (ULONG)(strlen(szString) + 1); + + // Where to put the new string? + if (iLen > GetCbSegAvailable()) + { + if (!Grow(iLen)) + return (PostError(OutOfMemory())); + } + pData = reinterpret_cast<LPSTR>(GetNextLocation()); + + // Copy the data into the buffer. + strcpy_s(pData, iLen, szString); + + // If the hash table is to be kept built (default). + if (m_bHash) + { + // Find or add the entry. + pHash = m_Hash.Find(pData, true); + if (!pHash) + return (PostError(OutOfMemory())); + + // If the entry was new, keep the new string. + if (pHash->iOffset == 0xffffffff) + { + *pnOffset = pHash->iOffset = GetNextOffset(); + SegAllocate(iLen); + + // Check for hash chains that are too long. + if (m_Hash.MaxChainLength() > MAX_CHAIN_LENGTH) + { + IfFailRet(RehashStrings()); + } + } + // Else use the old one. + else + { + *pnOffset = pHash->iOffset; + } + } + // Probably an import which defers the hash table for speed. + else + { + *pnOffset = GetNextOffset(); + SegAllocate(iLen); + } + return S_OK; +} // StgStringPool::AddString + +//***************************************************************************** +// Add a string to the pool with Unicode to UTF8 conversion. +//***************************************************************************** +__checkReturn +HRESULT +StgStringPool::AddStringW( + LPCWSTR szString, // The string to add to pool. + UINT32 *pnOffset) // Return offset of string here. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + STRINGHASH *pHash; // Hash item for add. + ULONG iLen; // Correct length after conversion. + LPSTR pData; // Pointer to location for new string. + + _ASSERTE(!m_bReadOnly); + + // Null pointer is an error. + if (szString == 0) + return (PostError(E_INVALIDARG)); + + // Special case empty string. + if (*szString == '\0') + { + *pnOffset = 0; + return (S_OK); + } + + // How many bytes will be required in the heap? + iLen = ::WszWideCharToMultiByte( + CP_UTF8, + 0, + szString, + -1, // null-terminated string + NULL, + 0, + NULL, + NULL); + // WCTMB includes trailing 0 if (when passing parameter #4 (length) -1. + + // Check for room. + if (iLen > GetCbSegAvailable()) + { + if (!Grow(iLen)) + return (PostError(OutOfMemory())); + } + pData = reinterpret_cast<LPSTR>(GetNextLocation()); + + // Convert the data in place to the correct location. + iLen = ::WszWideCharToMultiByte( + CP_UTF8, + 0, + szString, + -1, + pData, + GetCbSegAvailable(), + NULL, + NULL); + if (iLen == 0) + return (BadError(HRESULT_FROM_NT(GetLastError()))); + + // If the hash table is to be kept built (default). + if (m_bHash) + { + // Find or add the entry. + pHash = m_Hash.Find(pData, true); + if (!pHash) + return (PostError(OutOfMemory())); + + // If the entry was new, keep the new string. + if (pHash->iOffset == 0xffffffff) + { + *pnOffset = pHash->iOffset = GetNextOffset(); + SegAllocate(iLen); + } + // Else use the old one. + else + { + *pnOffset = pHash->iOffset; + } + } + // Probably an import which defers the hash table for speed. + else + { + *pnOffset = GetNextOffset(); + SegAllocate(iLen); + } + return (S_OK); +} // StgStringPool::AddStringW + + +//***************************************************************************** +// Clears out the existing hash table used to eliminate duplicates. Then +// rebuilds the hash table from scratch based on the current data. +//***************************************************************************** +__checkReturn +HRESULT +StgStringPool::RehashStrings() +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + ULONG iOffset; // Loop control. + ULONG iMax; // End of loop. + ULONG iSeg; // Location within segment. + StgPoolSeg *pSeg = this; // To loop over segments. + STRINGHASH *pHash; // Hash item for add. + LPCSTR pString; // A string; + ULONG iLen; // The string's length. + int iBuckets; // Buckets in the hash. + int iCount; // Items in the hash. + int iNewBuckets; // New count of buckets in the hash. + + // Determine the new bucket size. + iBuckets = m_Hash.Buckets(); + iCount = m_Hash.Count(); + iNewBuckets = max(iCount, iBuckets+iBuckets/2+1); + + // Remove any stale data. + m_Hash.Clear(); + m_Hash.SetBuckets(iNewBuckets); + + // How far should the loop go. + iMax = GetNextOffset(); + + // Go through each string, skipping initial empty string. + for (iSeg=iOffset=1; iOffset < iMax; ) + { + // Get the string from the pool. + pString = reinterpret_cast<LPCSTR>(pSeg->m_pSegData + iSeg); + // Add the string to the hash table. + if ((pHash = m_Hash.Add(pString)) == 0) + return (PostError(OutOfMemory())); + pHash->iOffset = iOffset; + + // Move to next string. + iLen = (ULONG)(strlen(pString) + 1); + iOffset += iLen; + iSeg += iLen; + if (iSeg >= pSeg->m_cbSegNext) + { + pSeg = pSeg->m_pNextSeg; + iSeg = 0; + } + } + return (S_OK); +} // StgStringPool::RehashStrings + +// +// +// StgGuidPool +// +// + +__checkReturn +HRESULT +StgGuidPool::InitNew( + ULONG cbSize, // Estimated size. + ULONG cItems) // Estimated item count. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr; // A result. + + if (FAILED(hr = StgPool::InitNew())) + return (hr); + + // Set initial table sizes, if specified. + if (cbSize) + if (!Grow(cbSize)) + return E_OUTOFMEMORY; + if (cItems) + m_Hash.SetBuckets(cItems); + + return (S_OK); +} // StgGuidPool::InitNew + +//***************************************************************************** +// Load a Guid heap from persisted memory. If a copy of the data is made +// (so that it may be updated), then a new hash table is generated which can +// be used to elminate duplicates with new Guids. +//***************************************************************************** +__checkReturn +HRESULT +StgGuidPool::InitOnMem( + void *pData, // Predefined data. + ULONG iSize, // Size of data. + int bReadOnly) // true if append is forbidden. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr; + + // Let base class init our memory structure. + IfFailRet(StgPool::InitOnMem(pData, iSize, bReadOnly)); + + // For init on existing mem case. + if (pData && iSize) + { + // If we cannot update, then we don't need a hash table. + if (bReadOnly) + return S_OK; + + //<TODO>@todo: defer this until we hand out a pointer.</TODO> + IfFailRet(TakeOwnershipOfInitMem()); + + // Build the hash table on the data. + if (FAILED(hr = RehashGuids())) + { + Uninit(); + return hr; + } + } + + return S_OK; +} // StgGuidPool::InitOnMem + +//***************************************************************************** +// Clears the hash table then calls the base class. +//***************************************************************************** +void StgGuidPool::Uninit() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + // Clear the hash table. + m_Hash.Clear(); + + // Let base class clean up. + StgPool::Uninit(); +} // StgGuidPool::Uninit + +//***************************************************************************** +// Add a segment to the chain of segments. +//***************************************************************************** +__checkReturn +HRESULT +StgGuidPool::AddSegment( + const void *pData, // The data. + ULONG cbData, // Size of the data. + bool bCopy) // If true, make a copy of the data. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + // Want an integeral number of GUIDs. + _ASSERTE((cbData % sizeof(GUID)) == 0); + + return StgPool::AddSegment(pData, cbData, bCopy); + +} // StgGuidPool::AddSegment + +//***************************************************************************** +// Turn hashing off or on. If you turn hashing on, then any existing data is +// thrown away and all data is rehashed during this call. +//***************************************************************************** +__checkReturn +HRESULT +StgGuidPool::SetHash(int bHash) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr = S_OK; + + // If turning on hash again, need to rehash all guids. + if (bHash) + hr = RehashGuids(); + + m_bHash = bHash; + return (hr); +} // StgGuidPool::SetHash + +//***************************************************************************** +// The Guid will be added to the pool. The index of the Guid in the pool +// is returned in *piIndex. If the Guid is already in the pool, then the +// index will be to the existing copy of the Guid. +//***************************************************************************** +__checkReturn +HRESULT +StgGuidPool::AddGuid( + const GUID *pGuid, // The Guid to add to pool. + UINT32 *pnIndex) // Return 1-based index of Guid here. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + GUIDHASH *pHash = NULL; // Hash item for add. + + GUID guid = *pGuid; + SwapGuid(&guid); + + // Special case for GUID_NULL + if (guid == GUID_NULL) + { + *pnIndex = 0; + return S_OK; + } + + // If the hash table is to be kept built (default). + if (m_bHash) + { + // Find or add the entry. + pHash = m_Hash.Find(&guid, true); + if (!pHash) + return (PostError(OutOfMemory())); + + // If the guid was found, just use it. + if (pHash->iIndex != 0xffffffff) + { // Return 1-based index. + *pnIndex = pHash->iIndex; + return S_OK; + } + } + + // Space on heap for new guid? + if (sizeof(GUID) > GetCbSegAvailable()) + { + if (!Grow(sizeof(GUID))) + return (PostError(OutOfMemory())); + } + + // Copy the guid to the heap. + *reinterpret_cast<GUID*>(GetNextLocation()) = guid; + + // Give the 1-based index back to caller. + *pnIndex = (GetNextOffset() / sizeof(GUID)) + 1; + + // If hashing, save the 1-based index in the hash. + if (m_bHash) + pHash->iIndex = *pnIndex; + + // Update heap counters. + SegAllocate(sizeof(GUID)); + + return S_OK; +} // StgGuidPool::AddGuid + +//***************************************************************************** +// Recompute the hashes for the pool. +//***************************************************************************** +__checkReturn +HRESULT +StgGuidPool::RehashGuids() +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + ULONG iOffset; // Loop control. + ULONG iMax; // End of loop. + ULONG iSeg; // Location within segment. + StgPoolSeg *pSeg = this; // To loop over segments. + GUIDHASH *pHash; // Hash item for add. + GUID *pGuid; // A guid; + + // Remove any stale data. + m_Hash.Clear(); + + // How far should the loop go. + iMax = GetNextOffset(); + + // Go through each guid. + for (iSeg=iOffset=0; iOffset < iMax; ) + { + // Get a pointer to the guid. + pGuid = reinterpret_cast<GUID*>(pSeg->m_pSegData + iSeg); + // Add the guid to the hash table. + if ((pHash = m_Hash.Add(pGuid)) == 0) + return (PostError(OutOfMemory())); + pHash->iIndex = iOffset / sizeof(GUID); + + // Move to next Guid. + iOffset += sizeof(GUID); + iSeg += sizeof(GUID); + if (iSeg > pSeg->m_cbSegNext) + { + pSeg = pSeg->m_pNextSeg; + iSeg = 0; + } + } + return (S_OK); +} // StgGuidPool::RehashGuids + +// +// +// StgBlobPool +// +// + + + +//***************************************************************************** +// Create a new, empty blob pool. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::InitNew( + ULONG cbSize, // Estimated size. + ULONG cItems, // Estimated item count. + BOOL fAddEmptryItem) // Should we add an empty item at offset 0 +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr; + + // Let base class intialize. + IfFailRet(StgPool::InitNew()); + + // Set initial table sizes, if specified. + if (cbSize > 0) + { + if (!Grow(cbSize)) + return E_OUTOFMEMORY; + } + if (cItems > 0) + m_Hash.SetBuckets(cItems); + + // Init with empty blob. + + // Normally must do this, regardless if we currently have anything in the pool. + // If we don't do this, the first blob that gets added to the pool will + // have an offset of 0. This will cause this blob to have a token of + // 0x70000000, which is considered a nil string token. + // + // By inserting a zero length blob into the pool the being with, we're + // assured that the first blob added to the pool will have an offset + // of 1 and a token of 0x70000001, which is a valid token. + // + // The only time we wouldn't want to do this is if we're reading in a delta metadata. + // Then, we don't care if the first string is at offset 0... when the delta gets applied, + // the string will get moved to the appropriate offset. + if (fAddEmptryItem) + { + MetaData::DataBlob emptyBlob(NULL, 0); + UINT32 nIndex_Ignore; + IfFailRet(AddBlob(&emptyBlob, &nIndex_Ignore)); + // Empty blob better be at offset 0. + _ASSERTE(nIndex_Ignore == 0); + } + return hr; +} // StgBlobPool::InitNew + +//***************************************************************************** +// Init the blob pool for use. This is called for both create and read case. +// If there is existing data and bCopyData is true, then the data is rehashed +// to eliminate dupes in future adds. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::InitOnMem( + void *pBuf, // Predefined data. + ULONG iBufSize, // Size of data. + int bReadOnly) // true if append is forbidden. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr; + + // Let base class init our memory structure. + IfFailRet(StgPool::InitOnMem(pBuf, iBufSize, bReadOnly)); + + // Init hash table from existing data. + // If we cannot update, we don't need a hash table. + if (bReadOnly) + { + return S_OK; + } + + //<TODO>@todo: defer this until we hand out a pointer.</TODO> + IfFailRet(TakeOwnershipOfInitMem()); + + UINT32 nMaxOffset = GetNextOffset(); + for (UINT32 nOffset = 0; nOffset < nMaxOffset; ) + { + MetaData::DataBlob blob; + BLOBHASH *pHash; + + IfFailRet(GetBlobWithSizePrefix(nOffset, &blob)); + + // Add the blob to the hash table. + if ((pHash = m_Hash.Add(blob.GetDataPointer())) == NULL) + { + Uninit(); + return E_OUTOFMEMORY; + } + pHash->iOffset = nOffset; + + nOffset += blob.GetSize(); + } + return S_OK; +} // StgBlobPool::InitOnMem + +//***************************************************************************** +// Clears the hash table then calls the base class. +//***************************************************************************** +void StgBlobPool::Uninit() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + // Clear the hash table. + m_Hash.Clear(); + + // Let base class clean up. + StgPool::Uninit(); +} // StgBlobPool::Uninit + + +//***************************************************************************** +// The blob will be added to the pool. The offset of the blob in the pool +// is returned in *piOffset. If the blob is already in the pool, then the +// offset will be to the existing copy of the blob. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::AddBlob( + const MetaData::DataBlob *pData, + UINT32 *pnOffset) // Return offset of blob here. +{ + BLOBHASH *pHash; // Hash item for add. + void *pBytes; // Working pointer. + BYTE *pStartLoc; // Location to write real blob + ULONG iRequired; // How much buffer for this blob? + ULONG iFillerLen; // space to fill to make byte-aligned + HRESULT hr; + + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + // Can we handle this blob? + if (pData->GetSize() > CPackedLen::MAX_LEN) + return (PostError(CLDB_E_TOO_BIG)); + + // worst case is we need three more bytes to ensure byte-aligned, hence the 3 + iRequired = pData->GetSize() + CPackedLen::Size(pData->GetSize()) + 3; + if (iRequired > GetCbSegAvailable()) + { + if (!Grow(iRequired)) + return (PostError(OutOfMemory())); + } + + // unless changed due to alignment, the location of the blob is just + // the value returned by GetNextLocation(), which is also a iFillerLen of + // 0 + + pStartLoc = (BYTE *)GetNextLocation(); + iFillerLen = 0; + + // technichally, only the data portion must be DWORD-aligned. So, if the + // data length is zero, we don't need to worry about alignment. + + // Pack in the length at pStartLoc (the start location) + pBytes = CPackedLen::PutLength(pStartLoc, pData->GetSize()); + + // Put the bytes themselves. + memcpy(pBytes, pData->GetDataPointer(), pData->GetSize()); + + // Find or add the entry. + if ((pHash = m_Hash.Find(GetNextLocation() + iFillerLen, true)) == NULL) + return (PostError(OutOfMemory())); + + // If the entry was new, keep the new blob. + if (pHash->iOffset == 0xffffffff) + { + // this blob's offset is increased by iFillerLen bytes + pHash->iOffset = *pnOffset = GetNextOffset() + iFillerLen; + // only SegAllocate what we actually used, rather than what we requested + SegAllocate(pData->GetSize() + CPackedLen::Size(pData->GetSize()) + iFillerLen); + + // Check for hash chains that are too long. + if (m_Hash.MaxChainLength() > MAX_CHAIN_LENGTH) + { + IfFailRet(RehashBlobs()); + } + } + // Else use the old one. + else + { + *pnOffset = pHash->iOffset; + } + + return S_OK; +} // StgBlobPool::AddBlob + +//***************************************************************************** +// Return a pointer to a blob, and the size of the blob. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::GetBlob( + UINT32 nOffset, // Offset of blob in pool. + MetaData::DataBlob *pData) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + HRESULT hr; + + if (nOffset == 0) + { + // TODO: It would be nice to remove it, but people read behind the end of buffer, + // e.g. VBC reads 2 zeros even though the size is 0 when it's storing string in the blob. + // Nice to have: Move this to the public API only as a compat layer. + pData->Init((BYTE *)m_zeros, 0); + return S_OK; + } + + IfFailGo(StgPool::GetData(nOffset, pData)); + + UINT32 cbBlobContentSize; + if (!pData->GetCompressedU(&cbBlobContentSize)) + { + IfFailGo(COR_E_BADIMAGEFORMAT); + } + if (!pData->TruncateToExactSize(cbBlobContentSize)) + { + IfFailGo(COR_E_BADIMAGEFORMAT); + } + + return S_OK; +ErrExit: + pData->Clear(); + return hr; +} // StgBlobPool::GetBlob + +//***************************************************************************** +// Return a pointer to a blob, and the size of the blob. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::GetBlobWithSizePrefix( + UINT32 nOffset, // Offset of blob in pool. + MetaData::DataBlob *pData) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + HRESULT hr; + + if (nOffset == 0) + { + // TODO: Should be a static empty blob once we get rid of m_zeros + pData->Init((BYTE *)m_zeros, 1); + return S_OK; + } + + IfFailGo(StgPool::GetData(nOffset, pData)); + + UINT32 cbBlobContentSize; + UINT32 cbBlobSizePrefixSize; + if (!pData->PeekCompressedU(&cbBlobContentSize, &cbBlobSizePrefixSize)) + { + IfFailGo(COR_E_BADIMAGEFORMAT); + } + //_ASSERTE(cbBlobSizePrefixSize <= 4); + //_ASSERTE(cbBlobContentSize <= CompressedInteger::const_Max); + + // Cannot overflow, because previous asserts hold (in comments) + UINT32 cbBlobSize; + cbBlobSize = cbBlobContentSize + cbBlobSizePrefixSize; + if (!pData->TruncateToExactSize(cbBlobSize)) + { + IfFailGo(COR_E_BADIMAGEFORMAT); + } + + return S_OK; +ErrExit: + pData->Clear(); + return hr; +} // StgBlobPool::GetBlob + +//***************************************************************************** +// Turn hashing off or on. If you turn hashing on, then any existing data is +// thrown away and all data is rehashed during this call. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::SetHash(int bHash) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + HRESULT hr = S_OK; + + // If turning on hash again, need to rehash all Blobs. + if (bHash) + hr = RehashBlobs(); + + //<TODO>@todo: m_bHash = bHash;</TODO> + return (hr); +} // StgBlobPool::SetHash + +//***************************************************************************** +// Clears out the existing hash table used to eliminate duplicates. Then +// rebuilds the hash table from scratch based on the current data. +//***************************************************************************** +__checkReturn +HRESULT +StgBlobPool::RehashBlobs() +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + void const *pBlob; // Pointer to a given blob. + ULONG cbBlob; // Length of a blob. + int iSizeLen = 0; // Size of an encoded length. + ULONG iOffset; // Location within iteration. + ULONG iMax; // End of loop. + ULONG iSeg; // Location within segment. + StgPoolSeg *pSeg = this; // To loop over segments. + BLOBHASH *pHash; // Hash item for add. + int iBuckets; // Buckets in the hash. + int iCount; // Items in the hash. + int iNewBuckets; // New count of buckets in the hash. + + // Determine the new bucket size. + iBuckets = m_Hash.Buckets(); + iCount = m_Hash.Count(); + iNewBuckets = max(iCount, iBuckets+iBuckets/2+1); + + // Remove any stale data. + m_Hash.Clear(); + m_Hash.SetBuckets(iNewBuckets); + + // How far should the loop go. + iMax = GetNextOffset(); + + // Go through each string, skipping initial empty string. + for (iSeg=iOffset=0; iOffset < iMax; ) + { + // Get the string from the pool. + pBlob = pSeg->m_pSegData + iSeg; + + cbBlob = CPackedLen::GetLength(pBlob, &iSizeLen); + if (cbBlob == (ULONG)-1) + { // Invalid blob size encoding + + //#GarbageInBlobHeap + // Note that this is allowed in ECMA spec (see chapter "#US and #Blob heaps"): + // Both these heaps can contain garbage, as long as any part that is reachable from any of + // the tables contains a valid 'blob'. + + // The hash is incomplete, which means that we might emit duplicate blob entries ... that is fine + return S_OK; + } + //_ASSERTE((iSizeLen >= 1) && (iSizeLen <= 4) && (cbBlob <= 0x1fffffff)); + + // Make it blob size incl. its size encoding (cannot integer overflow) + cbBlob += iSizeLen; + // Check for integer overflow and that the entire blob entry is in this segment + if ((iSeg > (iSeg + cbBlob)) || ((iSeg + cbBlob) > pSeg->m_cbSegNext)) + { // Invalid blob size + + // See code:#GarbageInBlobHeap + // The hash is incomplete, which means that we might emit duplicate blob entries ... that is fine + return S_OK; + } + + // Add the blob to the hash table. + if ((pHash = m_Hash.Add(pBlob)) == 0) + { + Uninit(); + return (E_OUTOFMEMORY); + } + pHash->iOffset = iOffset; + + // Move to next blob. + iOffset += cbBlob; + iSeg += cbBlob; + if (iSeg >= pSeg->m_cbSegNext) + { + pSeg = pSeg->m_pNextSeg; + iSeg = 0; + } + } + return (S_OK); +} // StgBlobPool::RehashBlobs + + +// +// CInMemoryStream +// + + +ULONG +STDMETHODCALLTYPE CInMemoryStream::Release() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + SUPPORTS_DAC_HOST_ONLY; + } + CONTRACTL_END + + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + { + if (m_dataCopy != NULL) + delete [] m_dataCopy; + + delete this; + } + return (cRef); +} // CInMemoryStream::Release + +HRESULT +STDMETHODCALLTYPE +CInMemoryStream::QueryInterface(REFIID riid, PVOID *ppOut) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + if (!ppOut) + { + return E_POINTER; + } + + *ppOut = NULL; + if (riid == IID_IStream || riid == IID_ISequentialStream || riid == IID_IUnknown) + { + *ppOut = this; + AddRef(); + return (S_OK); + } + + return E_NOINTERFACE; + +} // CInMemoryStream::QueryInterface + +HRESULT +STDMETHODCALLTYPE +CInMemoryStream::Read( + void *pv, + ULONG cb, + ULONG *pcbRead) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY; + + ULONG cbRead = min(cb, m_cbSize - m_cbCurrent); + + if (cbRead == 0) + return (S_FALSE); + memcpy(pv, (void *) ((ULONG_PTR) m_pMem + m_cbCurrent), cbRead); + if (pcbRead) + *pcbRead = cbRead; + m_cbCurrent += cbRead; + return (S_OK); +} // CInMemoryStream::Read + +HRESULT +STDMETHODCALLTYPE +CInMemoryStream::Write( + const void *pv, + ULONG cb, + ULONG *pcbWritten) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY; + + if (ovadd_gt(m_cbCurrent, cb, m_cbSize)) + return (OutOfMemory()); + + memcpy((BYTE *) m_pMem + m_cbCurrent, pv, cb); + m_cbCurrent += cb; + if (pcbWritten) *pcbWritten = cb; + return (S_OK); +} // CInMemoryStream::Write + +HRESULT +STDMETHODCALLTYPE +CInMemoryStream::Seek( + LARGE_INTEGER dlibMove, + DWORD dwOrigin, + ULARGE_INTEGER *plibNewPosition) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY; + + _ASSERTE(dwOrigin == STREAM_SEEK_SET || dwOrigin == STREAM_SEEK_CUR); + _ASSERTE(dlibMove.QuadPart <= static_cast<LONGLONG>(UINT32_MAX)); + + if (dwOrigin == STREAM_SEEK_SET) + { + m_cbCurrent = (ULONG) dlibMove.QuadPart; + } + else + if (dwOrigin == STREAM_SEEK_CUR) + { + m_cbCurrent+= (ULONG)dlibMove.QuadPart; + } + + if (plibNewPosition) + { + plibNewPosition->QuadPart = m_cbCurrent; + } + + return (m_cbCurrent < m_cbSize) ? (S_OK) : E_FAIL; +} // CInMemoryStream::Seek + +HRESULT +STDMETHODCALLTYPE +CInMemoryStream::CopyTo( + IStream *pstm, + ULARGE_INTEGER cb, + ULARGE_INTEGER *pcbRead, + ULARGE_INTEGER *pcbWritten) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY; + + HRESULT hr; + // We don't handle pcbRead or pcbWritten. + _ASSERTE(pcbRead == 0); + _ASSERTE(pcbWritten == 0); + + _ASSERTE(cb.QuadPart <= UINT32_MAX); + ULONG cbTotal = min(static_cast<ULONG>(cb.QuadPart), m_cbSize - m_cbCurrent); + ULONG cbRead=min(1024, cbTotal); + CQuickBytes rBuf; + void *pBuf = rBuf.AllocNoThrow(cbRead); + if (pBuf == 0) + return (PostError(OutOfMemory())); + + while (cbTotal) + { + if (cbRead > cbTotal) + cbRead = cbTotal; + if (FAILED(hr=Read(pBuf, cbRead, 0))) + return (hr); + if (FAILED(hr=pstm->Write(pBuf, cbRead, 0))) + return (hr); + cbTotal -= cbRead; + } + + // Adjust seek pointer to the end. + m_cbCurrent = m_cbSize; + + return (S_OK); +} // CInMemoryStream::CopyTo + +HRESULT +CInMemoryStream::CreateStreamOnMemory( + void *pMem, // Memory to create stream on. + ULONG cbSize, // Size of data. + IStream **ppIStream, // Return stream object here. + BOOL fDeleteMemoryOnRelease) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + CInMemoryStream *pIStream; // New stream object. + if ((pIStream = new (nothrow) CInMemoryStream) == 0) + return (PostError(OutOfMemory())); + pIStream->InitNew(pMem, cbSize); + if (fDeleteMemoryOnRelease) + { + // make sure this memory is allocated using new + pIStream->m_dataCopy = (BYTE *)pMem; + } + *ppIStream = pIStream; + return (S_OK); +} // CInMemoryStream::CreateStreamOnMemory + +HRESULT +CInMemoryStream::CreateStreamOnMemoryCopy( + void *pMem, + ULONG cbSize, + IStream **ppIStream) +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY;); + } + CONTRACTL_END + + CInMemoryStream *pIStream; // New stream object. + if ((pIStream = new (nothrow) CInMemoryStream) == 0) + return (PostError(OutOfMemory())); + + // Init the stream. + pIStream->m_cbCurrent = 0; + pIStream->m_cbSize = cbSize; + + // Copy the data. + pIStream->m_dataCopy = new (nothrow) BYTE[cbSize]; + + if (pIStream->m_dataCopy == NULL) + { + delete pIStream; + return (PostError(OutOfMemory())); + } + + pIStream->m_pMem = pIStream->m_dataCopy; + memcpy(pIStream->m_dataCopy, pMem, cbSize); + + *ppIStream = pIStream; + return (S_OK); +} // CInMemoryStream::CreateStreamOnMemoryCopy + +//--------------------------------------------------------------------------- +// CGrowableStream is a simple IStream implementation that grows as +// its written to. All the memory is contigious, so read access is +// fast. A grow does a realloc, so be aware of that if you're going to +// use this. +//--------------------------------------------------------------------------- + +//Constructs a new GrowableStream +// multiplicativeGrowthRate - when the stream grows it will be at least this +// multiple of its old size. Values greater than 1 ensure O(N) amortized +// performance growing the stream to size N, 1 ensures O(N^2) amortized perf +// but gives the tightest memory usage. Valid range is [1.0, 2.0]. +// additiveGrowthRate - when the stream grows it will increase in size by at least +// this number of bytes. Larger numbers cause fewer re-allocations at the cost of +// increased memory usage. +CGrowableStream::CGrowableStream(float multiplicativeGrowthRate, DWORD additiveGrowthRate) +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + m_swBuffer = NULL; + m_dwBufferSize = 0; + m_dwBufferIndex = 0; + m_dwStreamLength = 0; + m_cRef = 1; + + // Lets make sure these values stay somewhat sane... if you adjust the limits + // make sure you also write correct overflow checking code in EnsureCapcity + _ASSERTE(multiplicativeGrowthRate >= 1.0F && multiplicativeGrowthRate <= 2.0F); + m_multiplicativeGrowthRate = min(max(1.0F, multiplicativeGrowthRate), 2.0F); + + _ASSERTE(additiveGrowthRate >= 1); + m_additiveGrowthRate = max(1, additiveGrowthRate); +} // CGrowableStream::CGrowableStream + +#ifndef DACCESS_COMPILE + +CGrowableStream::~CGrowableStream() +{ + CONTRACTL + { + NOTHROW; + FORBID_FAULT; + } + CONTRACTL_END + + // Destroy the buffer. + if (m_swBuffer != NULL) + delete [] m_swBuffer; + + m_swBuffer = NULL; + m_dwBufferSize = 0; +} // CGrowableStream::~CGrowableStream + +// Grows the stream and optionally the internal buffer to ensure it is at least +// newLogicalSize +HRESULT CGrowableStream::EnsureCapacity(DWORD newLogicalSize) +{ + _ASSERTE(m_dwBufferSize >= m_dwStreamLength); + + // If there is no enough space left in the buffer, grow it + if (newLogicalSize > m_dwBufferSize) + { + // Grow to max of newLogicalSize, m_dwBufferSize*multiplicativeGrowthRate, and + // m_dwBufferSize+m_additiveGrowthRate + S_UINT32 addSize = S_UINT32(m_dwBufferSize) + S_UINT32(m_additiveGrowthRate); + if (addSize.IsOverflow()) + { + addSize = S_UINT32(UINT_MAX); + } + + // this should have been enforced in the constructor too + _ASSERTE(m_multiplicativeGrowthRate <= 2.0 && m_multiplicativeGrowthRate >= 1.0); + + // 2*UINT_MAX doesn't overflow a float so this certain to be safe + float multSizeF = (float)m_dwBufferSize * m_multiplicativeGrowthRate; + DWORD multSize; + if(multSizeF > (float)UINT_MAX) + { + multSize = UINT_MAX; + } + else + { + multSize = (DWORD)multSizeF; + } + + DWORD newBufferSize = max(max(newLogicalSize, multSize), addSize.Value()); + + char *tmp = new (nothrow) char[newBufferSize]; + if(tmp == NULL) + { + return E_OUTOFMEMORY; + } + + if (m_swBuffer) { + memcpy (tmp, m_swBuffer, m_dwBufferSize); + delete [] m_swBuffer; + } + m_swBuffer = (BYTE *)tmp; + m_dwBufferSize = newBufferSize; + } + + _ASSERTE(m_dwBufferSize >= newLogicalSize); + // the internal buffer is big enough, might have to increase logical size + // though + if(newLogicalSize > m_dwStreamLength) + { + m_dwStreamLength = newLogicalSize; + } + + _ASSERTE(m_dwBufferSize >= m_dwStreamLength); + return S_OK; +} + +ULONG +STDMETHODCALLTYPE +CGrowableStream::Release() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + ULONG cRef = InterlockedDecrement(&m_cRef); + + if (cRef == 0) + delete this; + + return cRef; +} // CGrowableStream::Release + +HRESULT +STDMETHODCALLTYPE +CGrowableStream::QueryInterface( + REFIID riid, + PVOID *ppOut) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + if (riid != IID_IUnknown && riid!=IID_ISequentialStream && riid!=IID_IStream) + return E_NOINTERFACE; + + *ppOut = this; + AddRef(); + return (S_OK); +} // CGrowableStream::QueryInterface + +HRESULT +CGrowableStream::Read( + void *pv, + ULONG cb, + ULONG *pcbRead) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + HRESULT hr = S_OK; + DWORD dwCanReadBytes = 0; + + if (NULL == pv) + return E_POINTER; + + // short-circuit a zero-length read or see if we are at the end + if (cb == 0 || m_dwBufferIndex >= m_dwStreamLength) + { + if (pcbRead != NULL) + *pcbRead = 0; + + return S_OK; + } + + // Figure out if we have enough room in the stream (excluding any + // unused space at the end of the buffer) + dwCanReadBytes = cb; + + S_UINT32 dwNewIndex = S_UINT32(dwCanReadBytes) + S_UINT32(m_dwBufferIndex); + if (dwNewIndex.IsOverflow() || (dwNewIndex.Value() > m_dwStreamLength)) + { + // Only read whatever is left in the buffer (if any) + dwCanReadBytes = (m_dwStreamLength - m_dwBufferIndex); + } + + // copy from our buffer to caller's buffer + memcpy(pv, &m_swBuffer[m_dwBufferIndex], dwCanReadBytes); + + // adjust our current position + m_dwBufferIndex += dwCanReadBytes; + + // if they want the info, tell them how many byte we read for them + if (pcbRead != NULL) + *pcbRead = dwCanReadBytes; + + return hr; +} // CGrowableStream::Read + +HRESULT +CGrowableStream::Write( + const void *pv, + ULONG cb, + ULONG *pcbWritten) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + HRESULT hr = S_OK; + DWORD dwActualWrite = 0; + + // avoid NULL write + if (cb == 0) + { + hr = S_OK; + goto Error; + } + + // Check if our buffer is large enough + _ASSERTE(m_dwBufferIndex <= m_dwStreamLength); + _ASSERTE(m_dwStreamLength <= m_dwBufferSize); + + // If there is no enough space left in the buffer, grow it + if (cb > (m_dwStreamLength - m_dwBufferIndex)) + { + // Determine the new size needed + S_UINT32 size = S_UINT32(m_dwBufferSize) + S_UINT32(cb); + if (size.IsOverflow()) + { + hr = HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + goto Error; + } + + hr = EnsureCapacity(size.Value()); + if(FAILED(hr)) + { + goto Error; + } + } + + if ((pv != NULL) && (cb > 0)) + { + // write to current position in the buffer + memcpy(&m_swBuffer[m_dwBufferIndex], pv, cb); + + // now update our current index + m_dwBufferIndex += cb; + + // in case they want to know the number of bytes written + dwActualWrite = cb; + } + +Error: + if (pcbWritten) + *pcbWritten = dwActualWrite; + + return hr; +} // CGrowableStream::Write + +STDMETHODIMP +CGrowableStream::Seek( + LARGE_INTEGER dlibMove, + DWORD dwOrigin, + ULARGE_INTEGER *plibNewPosition) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + // a Seek() call on STREAM_SEEK_CUR and a dlibMove == 0 is a + // request to get the current seek position. + if ((dwOrigin == STREAM_SEEK_CUR && dlibMove.u.LowPart == 0) && + (dlibMove.u.HighPart == 0) && + (NULL != plibNewPosition)) + { + goto Error; + } + + // we only support STREAM_SEEK_SET (beginning of buffer) + if (dwOrigin != STREAM_SEEK_SET) + return E_NOTIMPL; + + // did they ask to seek past end of stream? If so we're supposed to + // extend with zeros. But we've never supported that. + if (dlibMove.u.LowPart > m_dwStreamLength) + return E_UNEXPECTED; + + // we ignore the high part of the large integer + SIMPLIFYING_ASSUMPTION(dlibMove.u.HighPart == 0); + m_dwBufferIndex = dlibMove.u.LowPart; + +Error: + if (NULL != plibNewPosition) + { + plibNewPosition->u.HighPart = 0; + plibNewPosition->u.LowPart = m_dwBufferIndex; + } + + return S_OK; +} // CGrowableStream::Seek + +STDMETHODIMP +CGrowableStream::SetSize( + ULARGE_INTEGER libNewSize) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + DWORD dwNewSize = libNewSize.u.LowPart; + + _ASSERTE(libNewSize.u.HighPart == 0); + + // we don't support large allocations + if (libNewSize.u.HighPart > 0) + return E_OUTOFMEMORY; + + HRESULT hr = EnsureCapacity(dwNewSize); + if(FAILED(hr)) + { + return hr; + } + + // EnsureCapacity doesn't shrink the logicalSize if dwNewSize is smaller + // and SetSize is allowed to shrink the stream too. Note that we won't + // release physical memory here, we just appear to get smaller + m_dwStreamLength = dwNewSize; + + return S_OK; +} // CGrowableStream::SetSize + +STDMETHODIMP +CGrowableStream::Stat( + STATSTG *pstatstg, + DWORD grfStatFlag) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + if (NULL == pstatstg) + return E_POINTER; + + // this is the only useful information we hand out - the length of the stream + pstatstg->cbSize.u.HighPart = 0; + pstatstg->cbSize.u.LowPart = m_dwStreamLength; + pstatstg->type = STGTY_STREAM; + + // we ignore the grfStatFlag - we always assume STATFLAG_NONAME + pstatstg->pwcsName = NULL; + + pstatstg->grfMode = 0; + pstatstg->grfLocksSupported = 0; + pstatstg->clsid = CLSID_NULL; + pstatstg->grfStateBits = 0; + + return S_OK; +} // CGrowableStream::Stat + +// +// Clone - Make a deep copy of the stream into a new cGrowableStream instance +// +// Arguments: +// ppStream - required output parameter for the new stream instance +// +// Returns: +// S_OK on succeess, or an error code on failure. +// +HRESULT +CGrowableStream::Clone( + IStream **ppStream) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; //E_OUTOFMEMORY + + if (NULL == ppStream) + return E_POINTER; + + // Copy our entire buffer into the new stream + CGrowableStream * newStream = new (nothrow) CGrowableStream(); + if (newStream == NULL) + { + return E_OUTOFMEMORY; + } + + HRESULT hr = newStream->Write(m_swBuffer, m_dwStreamLength, NULL); + if (FAILED(hr)) + { + delete newStream; + return hr; + } + + *ppStream = newStream; + return S_OK; +} // CGrowableStream::Clone + +#endif // !DACCESS_COMPILE diff --git a/src/coreclr/utilcode/stgpooli.cpp b/src/coreclr/utilcode/stgpooli.cpp new file mode 100644 index 00000000000..5e56835bc44 --- /dev/null +++ b/src/coreclr/utilcode/stgpooli.cpp @@ -0,0 +1,347 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// StgPool.cpp +// + +// +// Pools are used to reduce the amount of data actually required in the database. +// This allows for duplicate string and binary values to be folded into one +// copy shared by the rest of the database. Strings are tracked in a hash +// table when insert/changing data to find duplicates quickly. The strings +// are then persisted consecutively in a stream in the database format. +// +//***************************************************************************** +#include "stdafx.h" // Standard include. +#include <stgpool.h> // Our interface definitions. + +int CStringPoolHash::Cmp( + const void *pData, // A string. + void *pItem) // A hash item which refers to a string. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + LPCSTR p1 = reinterpret_cast<LPCSTR>(pData); + LPCSTR p2; + if (FAILED(m_Pool->GetString(reinterpret_cast<STRINGHASH*>(pItem)->iOffset, &p2))) + { + return -1; + } + return (strcmp(p1, p2)); +} // int CStringPoolHash::Cmp() + + +int CBlobPoolHash::Cmp( + const void *pData, // A blob. + void *pItem) // A hash item which refers to a blob. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + ULONG ul1; + ULONG ul2; + MetaData::DataBlob data2; + + // Get size of first item. + ul1 = CPackedLen::GetLength(pData); + // Adjust size to include the length of size field. + ul1 += CPackedLen::Size(ul1); + + // Get the second item. + if (FAILED(m_Pool->GetData(reinterpret_cast<BLOBHASH*>(pItem)->iOffset, &data2))) + { + return -1; + } + + // Get and adjust size of second item. + ul2 = CPackedLen::GetLength(data2.GetDataPointer()); + ul2 += CPackedLen::Size(ul2); + + if (ul1 < ul2) + return (-1); + else if (ul1 > ul2) + return (1); + return (memcmp(pData, data2.GetDataPointer(), ul1)); +} // int CBlobPoolHash::Cmp() + +int CGuidPoolHash::Cmp(const void *pData, void *pItem) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + GUID *p2; + if (FAILED(m_Pool->GetGuid(reinterpret_cast<GUIDHASH*>(pItem)->iIndex, &p2))) + { + return -1; + } + return (memcmp(pData, p2, sizeof(GUID))); +} // int CGuidPoolHash::Cmp() + +// +// +// CPackedLen +// +// + + +//***************************************************************************** +// Parse a length, return the data, store length. +//***************************************************************************** +void const *CPackedLen::GetData( // Pointer to data, or 0 on error. + void const *pData, // First byte of length. + ULONG *pLength) // Put length here, or -1 on error. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + BYTE const *pBytes = reinterpret_cast<BYTE const*>(pData); + + if ((*pBytes & 0x80) == 0x00) // 0??? ???? + { + *pLength = (*pBytes & 0x7f); + return pBytes + 1; + } + + if ((*pBytes & 0xC0) == 0x80) // 10?? ???? + { + *pLength = ((*pBytes & 0x3f) << 8 | *(pBytes+1)); + return pBytes + 2; + } + + if ((*pBytes & 0xE0) == 0xC0) // 110? ???? + { + *pLength = ((*pBytes & 0x1f) << 24 | *(pBytes+1) << 16 | *(pBytes+2) << 8 | *(pBytes+3)); + return pBytes + 4; + } + + *pLength = (ULONG) -1; + return 0; +} // void const *CPackedLen::GetData() + +#ifndef MAX_PTR +#define MAX_PTR ((BYTE*)(~(SSIZE_T)0)) +#endif + +//***************************************************************************** +// Parse a length, return the data, store length. +//***************************************************************************** +HRESULT CPackedLen::SafeGetLength( // S_OK, or error + void const *pDataSource, // First byte of length. + void const *pDataSourceEnd, // End of valid source data memory + ULONG *pLength, // Length of data, if return S_OK + void const **ppDataNext) // Pointer immediately following encoded length +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + if (pDataSource == NULL || + pDataSourceEnd == NULL || + pDataSourceEnd < pDataSource || + ppDataNext == NULL || + pLength == NULL || + pDataSource > (MAX_PTR - 4)) + { + return E_INVALIDARG; + } + + BYTE const *pBytes = reinterpret_cast<BYTE const*>(pDataSource); + BYTE const *pBytesEnd = reinterpret_cast<BYTE const*>(pDataSourceEnd); + + size_t cbAvail = pBytesEnd - pBytes; + + if (cbAvail < 1) + { // Fail if no source data available + return COR_E_OVERFLOW; + } + + if ((*pBytes & 0x80) == 0x00) // 0??? ???? + { + *pLength = (*pBytes & 0x7f); + *ppDataNext = pBytes + 1; + return S_OK; + } + + if (cbAvail < 2) + { // Fail if not enough source data available + return COR_E_OVERFLOW; + } + + if ((*pBytes & 0xC0) == 0x80) // 10?? ???? + { + *pLength = ((*pBytes & 0x3f) << 8 | *(pBytes+1)); + *ppDataNext = pBytes + 2; + return S_OK; + } + + if (cbAvail < 4) + { // Fail if not enough source data available + return COR_E_OVERFLOW; + } + + if ((*pBytes & 0xE0) == 0xC0) // 110? ???? + { + *pLength = ((*pBytes & 0x1f) << 24 | *(pBytes+1) << 16 | *(pBytes+2) << 8 | *(pBytes+3)); + *ppDataNext = pBytes + 4; + return S_OK;; + } + + return COR_E_OVERFLOW; +} // CPackedLen::GetLength + +//***************************************************************************** +// Parse a length, return the data, store length. +//***************************************************************************** +HRESULT CPackedLen::SafeGetData( // S_OK, or error + void const *pDataSource, // First byte of length. + void const *pDataSourceEnd, // End of valid source data memory + ULONG *pcbData, // Length of data + void const **ppData) // Start of data +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + HRESULT hr = S_OK; + + IfFailRet(SafeGetLength(pDataSource, pDataSourceEnd, pcbData, ppData)); + + if (*pcbData == 0) + { // Zero length value means zero data, so no range checking required. + return S_OK; + } + + BYTE const *pbData = reinterpret_cast<BYTE const*>(*ppData); + + if (pbData + *pcbData < pbData) + { // First check for integer overflow + return COR_E_OVERFLOW; + } + + if (pDataSourceEnd < pbData + *pcbData) + { // Now check for data buffer overflow + return COR_E_OVERFLOW; + } + + return S_OK; +} // CPackedLen::GetLength + +//***************************************************************************** +// Parse a length, return the data, store length. +//***************************************************************************** +HRESULT CPackedLen::SafeGetData( // S_OK, or error + void const *pDataSource, // First byte of data + ULONG cbDataSource, // Count of valid bytes in data source + ULONG *pcbData, // Length of data + void const **ppData) // Start of data +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + return SafeGetData(pDataSource, (void const *)((BYTE const *)pDataSource + cbDataSource), pcbData, ppData); +} // CPackedLen::GetLength + +//***************************************************************************** +// Parse a length, return the length, pointer to actual bytes. +//***************************************************************************** +ULONG CPackedLen::GetLength( // Length or -1 on error. + void const *pData, // First byte of length. + void const **ppCode) // Put pointer to bytes here, if not 0. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + BYTE const *pBytes = reinterpret_cast<BYTE const*>(pData); + + if ((*pBytes & 0x80) == 0x00) // 0??? ???? + { + if (ppCode) *ppCode = pBytes + 1; + return (*pBytes & 0x7f); + } + + if ((*pBytes & 0xC0) == 0x80) // 10?? ???? + { + if (ppCode) *ppCode = pBytes + 2; + return ((*pBytes & 0x3f) << 8 | *(pBytes+1)); + } + + if ((*pBytes & 0xE0) == 0xC0) // 110? ???? + { + if (ppCode) *ppCode = pBytes + 4; + return ((*pBytes & 0x1f) << 24 | *(pBytes+1) << 16 | *(pBytes+2) << 8 | *(pBytes+3)); + } + + return (ULONG) -1; +} // ULONG CPackedLen::GetLength() + +//***************************************************************************** +// Parse a length, return the length, size of the length. +//***************************************************************************** +ULONG CPackedLen::GetLength( // Length or -1 on error. + void const *pData, // First byte of length. + int *pSizeLen) // Put size of length here, if not 0. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + BYTE const *pBytes = reinterpret_cast<BYTE const*>(pData); + + if ((*pBytes & 0x80) == 0x00) // 0??? ???? + { + if (pSizeLen) *pSizeLen = 1; + return (*pBytes & 0x7f); + } + + if ((*pBytes & 0xC0) == 0x80) // 10?? ???? + { + if (pSizeLen) *pSizeLen = 2; + return ((*pBytes & 0x3f) << 8 | *(pBytes+1)); + } + + if ((*pBytes & 0xE0) == 0xC0) // 110? ???? + { + if (pSizeLen) *pSizeLen = 4; + return ((*pBytes & 0x1f) << 24 | *(pBytes+1) << 16 | *(pBytes+2) << 8 | *(pBytes+3)); + } + + return (ULONG) -1; +} // ULONG CPackedLen::GetLength() + +//***************************************************************************** +// Encode a length. +//***************************************************************************** +#ifdef _MSC_VER +#pragma warning(disable:4244) // conversion from unsigned long to unsigned char +#endif +void* CPackedLen::PutLength( // First byte past length. + void *pData, // Pack the length here. + ULONG iLen) // The length. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + BYTE *pBytes = reinterpret_cast<BYTE*>(pData); + + if (iLen <= 0x7F) + { + *pBytes = iLen; + return pBytes + 1; + } + + if (iLen <= 0x3FFF) + { + *pBytes = (iLen >> 8) | 0x80; + *(pBytes+1) = iLen & 0xFF; + return pBytes + 2; + } + + _ASSERTE(iLen <= 0x1FFFFFFF); + *pBytes = (iLen >> 24) | 0xC0; + *(pBytes+1) = (iLen >> 16) & 0xFF; + *(pBytes+2) = (iLen >> 8) & 0xFF; + *(pBytes+3) = iLen & 0xFF; + return pBytes + 4; +} // void* CPackedLen::PutLength() +#ifdef _MSC_VER +#pragma warning(default:4244) // conversion from unsigned long to unsigned char +#endif + diff --git a/src/coreclr/utilcode/stgpoolreadonly.cpp b/src/coreclr/utilcode/stgpoolreadonly.cpp new file mode 100644 index 00000000000..439e9ca42d2 --- /dev/null +++ b/src/coreclr/utilcode/stgpoolreadonly.cpp @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// StgPoolReadOnly.cpp +// + +// +// Read only pools are used to reduce the amount of data actually required in the database. +// +//***************************************************************************** +#include "stdafx.h" // Standard include. +#include <stgpool.h> // Our interface definitions. +#include "metadatatracker.h" +// +// +// StgPoolReadOnly +// +// + +#if METADATATRACKER_ENABLED +MetaDataTracker *MetaDataTracker::m_MDTrackers = NULL; +BOOL MetaDataTracker::s_bEnabled = FALSE; + +void (*MetaDataTracker::s_IBCLogMetaDataAccess)(const void *addr) = NULL; +void (*MetaDataTracker::s_IBCLogMetaDataSearch)(const void *result) = NULL; + +#endif // METADATATRACKER_ENABLED + +const BYTE StgPoolSeg::m_zeros[64] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + +//***************************************************************************** +// Free any memory we allocated. +//***************************************************************************** +StgPoolReadOnly::~StgPoolReadOnly() +{ + LIMITED_METHOD_CONTRACT; +} + + +//***************************************************************************** +// Init the pool from existing data. +//***************************************************************************** +HRESULT StgPoolReadOnly::InitOnMemReadOnly(// Return code. + void *pData, // Predefined data. + ULONG iSize) // Size of data. +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return E_OUTOFMEMORY); + } + CONTRACTL_END + + // Make sure we aren't stomping anything and are properly initialized. + _ASSERTE(m_pSegData == m_zeros); + + // Create case requires no further action. + if (pData == NULL) + return E_INVALIDARG; + + // Keep m_zeros data pointer if there's no content of the pool + if (iSize != 0) + { + m_pSegData = reinterpret_cast<BYTE*>(pData); + } + m_cbSegSize = iSize; + m_cbSegNext = iSize; + return S_OK; +} + +//***************************************************************************** +// Prepare to shut down or reinitialize. +//***************************************************************************** +void StgPoolReadOnly::Uninit() +{ + LIMITED_METHOD_CONTRACT; + + m_pSegData = (BYTE*)m_zeros; + m_pNextSeg = 0; +} + + +//***************************************************************************** +// Convert a string to UNICODE into the caller's buffer. +//***************************************************************************** +HRESULT StgPoolReadOnly::GetStringW( // Return code. + ULONG iOffset, // Offset of string in pool. + __out_ecount(cchBuffer) LPWSTR szOut, // Output buffer for string. + int cchBuffer) // Size of output buffer. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; + + HRESULT hr; + LPCSTR pString; // The string in UTF8. + int iChars; + + IfFailRet(GetString(iOffset, &pString)); + iChars = ::WszMultiByteToWideChar(CP_UTF8, 0, pString, -1, szOut, cchBuffer); + if (iChars == 0) + return (BadError(HRESULT_FROM_NT(GetLastError()))); + return S_OK; +} + +//***************************************************************************** +// Return a pointer to a null terminated blob given an offset previously +// handed out by Addblob or Findblob. +//***************************************************************************** +HRESULT +StgPoolReadOnly::GetBlob( + UINT32 nOffset, // Offset of blob in pool. + MetaData::DataBlob *pData) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + HRESULT hr; + UINT32 cbBlobContentSize; + + // This should not be a necessary special case. The zero byte at the + // start of the pool will code for a length of zero. We will return + // a pointer to the next length byte, but the caller should notice that + // the size is zero, and should not look at any bytes. + // [SL] Yes, but we don't need all further computations and checks if iOffset==0 + + if (nOffset == 0) + { + pData->Clear(); + return S_OK; + } + + // Is the offset within this heap? + if (!IsValidOffset(nOffset)) + { + Debug_ReportError("Invalid blob offset."); + IfFailGo(CLDB_E_INDEX_NOTFOUND); + } + + IfFailGo(GetDataReadOnly(nOffset, pData)); + if (!pData->GetCompressedU(&cbBlobContentSize)) + { + Debug_ReportError("Invalid blob - size compression."); + IfFailGo(COR_E_BADIMAGEFORMAT); + } + if (!pData->TruncateToExactSize(cbBlobContentSize)) + { + Debug_ReportError("Invalid blob - reaches behind the end of data block."); + IfFailGo(COR_E_BADIMAGEFORMAT); + } + + return S_OK; +ErrExit: + pData->Clear(); + return hr; +} // StgPoolReadOnly::GetBlob + +//***************************************************************************** +// code:StgPoolReadOnly::GetBlob specialization with inlined check for valid offsets to avoid redundant code:StgPoolReadOnly::GetDataReadOnly calls. +// code:StgPoolReadOnly::GetDataReadOnly is not cheap because of it performs binary lookup in hot metadata. +//***************************************************************************** +HRESULT +StgBlobPoolReadOnly::GetBlob( + UINT32 nOffset, // Offset of blob in pool. + MetaData::DataBlob *pData) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + HRESULT hr; + UINT32 cbBlobContentSize; + + // This should not be a necessary special case. The zero byte at the + // start of the pool will code for a length of zero. We will return + // a pointer to the next length byte, but the caller should notice that + // the size is zero, and should not look at any bytes. + // [SL] Yes, but we don't need all further computations and checks if iOffset==0 + + if (nOffset == 0) + { + pData->Clear(); + return S_OK; + } + + if (m_pSegData == m_zeros) + { + Debug_ReportError("Invalid blob offset."); + IfFailGo(CLDB_E_INDEX_NOTFOUND); + } + + IfFailGo(GetDataReadOnly(nOffset, pData)); + if (!pData->GetCompressedU(&cbBlobContentSize)) + { + Debug_ReportError("Invalid blob - size compression."); + IfFailGo(CLDB_E_INDEX_NOTFOUND); + } + if (!pData->TruncateToExactSize(cbBlobContentSize)) + { + Debug_ReportError("Invalid blob - reaches behind the end of data block."); + IfFailGo(CLDB_E_INDEX_NOTFOUND); + } + + return S_OK; +ErrExit: + pData->Clear(); + return hr; +} // StgBlobPoolReadOnly::GetBlob diff --git a/src/coreclr/utilcode/stresslog.cpp b/src/coreclr/utilcode/stresslog.cpp new file mode 100644 index 00000000000..108080455d7 --- /dev/null +++ b/src/coreclr/utilcode/stresslog.cpp @@ -0,0 +1,684 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/*************************************************************************************/ +/* StressLog.cpp */ +/*************************************************************************************/ + +/*************************************************************************************/ + +#include "stdafx.h" // precompiled headers + +#include "switches.h" +#include "stresslog.h" +#include "clrhost.h" +#define DONOT_DEFINE_ETW_CALLBACK +#include "eventtracebase.h" +#include "ex.h" + + #if !defined(STRESS_LOG_READONLY) +#ifdef HOST_WINDOWS +HANDLE StressLogChunk::s_LogChunkHeap = NULL; +#endif +thread_local ThreadStressLog* StressLog::t_pCurrentThreadLog; +#endif // !STRESS_LOG_READONLY + +/*********************************************************************************/ +#if defined(HOST_X86) + +/* This is like QueryPerformanceCounter but a lot faster. On machines with + variable-speed CPUs (for power management), this is not accurate, but may + be good enough. +*/ +__forceinline __declspec(naked) unsigned __int64 getTimeStamp() { + STATIC_CONTRACT_LEAF; + + __asm { + RDTSC // read time stamp counter + ret + }; +} + +#else // HOST_X86 +unsigned __int64 getTimeStamp() { + STATIC_CONTRACT_LEAF; + + LARGE_INTEGER ret; + ZeroMemory(&ret, sizeof(LARGE_INTEGER)); + + QueryPerformanceCounter(&ret); + + return ret.QuadPart; +} + +#endif // HOST_X86 + +#if defined(HOST_X86) && !defined(HOST_UNIX) + +/*********************************************************************************/ +/* Get the the frequency cooresponding to 'getTimeStamp'. For x86, this is the + frequency of the RDTSC instruction, which is just the clock rate of the CPU. + This can vary due to power management, so this is at best a rough approximation. +*/ +unsigned __int64 getTickFrequency() +{ + // + // At startup, the OS calculates the CPU clock frequency and makes it available + // at HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0 + // + + unsigned __int64 hz = 0; + + HKEY hKey; + if (ERROR_SUCCESS == RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + W("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), + 0, + KEY_QUERY_VALUE, + &hKey)) + { + DWORD mhz; + DWORD mhzType; + DWORD cbMhz = (DWORD)sizeof(mhz); + if (ERROR_SUCCESS == RegQueryValueExW( + hKey, + W("~MHz"), + NULL, + &mhzType, + (LPBYTE)&mhz, + &cbMhz)) + { + _ASSERTE(REG_DWORD == mhzType); + _ASSERTE((DWORD)sizeof(mhz) == cbMhz); + + hz = (unsigned __int64)mhz * 1000000; + } + + RegCloseKey(hKey); + } + + return hz; +} + +#else // HOST_X86 + + +/*********************************************************************************/ +/* Get the the frequency cooresponding to 'getTimeStamp'. For non-x86 + architectures, this is just the performance counter frequency. +*/ +unsigned __int64 getTickFrequency() +{ + LARGE_INTEGER ret; + ZeroMemory(&ret, sizeof(LARGE_INTEGER)); + QueryPerformanceFrequency(&ret); + return ret.QuadPart; +} + +#endif // HOST_X86 + +#ifdef STRESS_LOG + +StressLog StressLog::theLog = { 0, 0, 0, 0, 0, 0, TLS_OUT_OF_INDEXES, 0, 0, 0 }; +const static unsigned __int64 RECYCLE_AGE = 0x40000000L; // after a billion cycles, we can discard old threads + +/*********************************************************************************/ +void StressLog::Enter(CRITSEC_COOKIE) { + STATIC_CONTRACT_LEAF; + + IncCantAllocCount(); + ClrEnterCriticalSection(theLog.lock); + DecCantAllocCount(); +} + +void StressLog::Leave(CRITSEC_COOKIE) { + STATIC_CONTRACT_LEAF; + + IncCantAllocCount(); + ClrLeaveCriticalSection(theLog.lock); + DecCantAllocCount(); +} + +/*********************************************************************************/ +void StressLog::Initialize(unsigned facilities, unsigned level, unsigned maxBytesPerThread, + unsigned maxBytesTotal, void* moduleBase) +{ + STATIC_CONTRACT_LEAF; + + if (theLog.MaxSizePerThread != 0) + { + // guard ourself against multiple initialization. First init wins. + return; + } + + theLog.lock = ClrCreateCriticalSection(CrstStressLog,(CrstFlags)(CRST_UNSAFE_ANYMODE|CRST_DEBUGGER_THREAD|CRST_TAKEN_DURING_SHUTDOWN)); + // StressLog::Terminate is going to free memory. + if (maxBytesPerThread < STRESSLOG_CHUNK_SIZE) + { + maxBytesPerThread = STRESSLOG_CHUNK_SIZE; + } + theLog.MaxSizePerThread = maxBytesPerThread; + + if (maxBytesTotal < STRESSLOG_CHUNK_SIZE * 256) + { + maxBytesTotal = STRESSLOG_CHUNK_SIZE * 256; + } + theLog.MaxSizeTotal = maxBytesTotal; + theLog.totalChunk = 0; + theLog.facilitiesToLog = facilities | LF_ALWAYS; + theLog.levelToLog = level; + theLog.deadCount = 0; + + theLog.tickFrequency = getTickFrequency(); + + GetSystemTimeAsFileTime (&theLog.startTime); + theLog.startTimeStamp = getTimeStamp(); + theLog.moduleOffset = (SIZE_T)moduleBase; + +#ifndef HOST_UNIX +#ifdef _DEBUG + HMODULE hModNtdll = GetModuleHandleA("ntdll.dll"); + theLog.RtlCaptureStackBackTrace = reinterpret_cast<PFNRtlCaptureStackBackTrace>( + GetProcAddress(hModNtdll, "RtlCaptureStackBackTrace")); +#endif // _DEBUG +#endif // !HOST_UNIX + +#if !defined (STRESS_LOG_READONLY) && defined(HOST_WINDOWS) + StressLogChunk::s_LogChunkHeap = HeapCreate (0, STRESSLOG_CHUNK_SIZE * 128, 0); + if (StressLogChunk::s_LogChunkHeap == NULL) + { + StressLogChunk::s_LogChunkHeap = GetProcessHeap (); + } + _ASSERTE (StressLogChunk::s_LogChunkHeap); +#endif //!STRESS_LOG_READONLY +} + +/*********************************************************************************/ +void StressLog::Terminate(BOOL fProcessDetach) { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + theLog.facilitiesToLog = 0; + + StressLogLockHolder lockh(theLog.lock, FALSE); + if (!fProcessDetach) { + lockh.Acquire(); lockh.Release(); // The Enter() Leave() forces a memory barrier on weak memory model systems + // we want all the other threads to notice that facilitiesToLog is now zero + + // This is not strictly threadsafe, since there is no way of insuring when all the + // threads are out of logMsg. In practice, since they can no longer enter logMsg + // and there are no blocking operations in logMsg, simply sleeping will insure + // that everyone gets out. + ClrSleepEx(2, FALSE); + lockh.Acquire(); + } + + // Free the log memory + ThreadStressLog* ptr = theLog.logs; + theLog.logs = 0; + while(ptr != 0) { + ThreadStressLog* tmp = ptr; + ptr = ptr->next; + delete tmp; + } + + if (!fProcessDetach) { + lockh.Release(); + } + +#if !defined (STRESS_LOG_READONLY) && defined(HOST_WINDOWS) + if (StressLogChunk::s_LogChunkHeap != NULL && StressLogChunk::s_LogChunkHeap != GetProcessHeap ()) + { + HeapDestroy (StressLogChunk::s_LogChunkHeap); + } +#endif //!STRESS_LOG_READONLY +} + +/*********************************************************************************/ +/* create a new thread stress log buffer associated with Thread local slot, for the Stress log */ + +ThreadStressLog* StressLog::CreateThreadStressLog() { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + } + CONTRACTL_END; + + static PVOID callerID = NULL; + + ThreadStressLog* msgs = t_pCurrentThreadLog; + if (msgs != NULL) + { + return msgs; + } + + if (callerID == ClrTeb::GetFiberPtrId()) + { + return NULL; + } + +#ifdef HOST_WINDOWS + if (!StressLogChunk::s_LogChunkHeap) + { + return NULL; + } +#endif + + //if we are not allowed to allocate stress log, we should not even try to take the lock + if (IsInCantAllocStressLogRegion ()) + { + return NULL; + } + + // if it looks like we won't be allowed to allocate a new chunk, exit early + if (theLog.deadCount == 0 && !AllowNewChunk (0)) + { + return NULL; + } + + StressLogLockHolder lockh(theLog.lock, FALSE); + + class NestedCaller + { + public: + NestedCaller() + { + } + ~NestedCaller() + { + callerID = NULL; + } + void Mark() + { + callerID = ClrTeb::GetFiberPtrId(); + } + }; + + NestedCaller nested; + + BOOL noFLSNow = FALSE; + + PAL_CPP_TRY + { + // Acquiring the lack can throw an OOM exception the first time its called on a thread. We go + // ahead and try to provoke that now, before we've altered the list of available stress logs, and bail if + // we fail. + lockh.Acquire(); + nested.Mark(); + + // ClrFlsSetValue can throw an OOM exception the first time its called on a thread for a given slot. We go + // ahead and try to provoke that now, before we've altered the list of available stress logs, and bail if + // we fail. + t_pCurrentThreadLog = NULL; + } +#pragma warning(suppress: 4101) + PAL_CPP_CATCH_DERIVED(OutOfMemoryException, obj) + { + // Just leave on any exception. Note: can't goto or return from within EX_CATCH... + noFLSNow = TRUE; + } + PAL_CPP_ENDTRY; + + if (noFLSNow == FALSE && theLog.facilitiesToLog != 0) + msgs = CreateThreadStressLogHelper(); + + return msgs; +} + +ThreadStressLog* StressLog::CreateThreadStressLogHelper() { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + FORBID_FAULT; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + BOOL skipInsert = FALSE; + ThreadStressLog* msgs = NULL; + + // See if we can recycle a dead thread + if (theLog.deadCount > 0) + { + unsigned __int64 recycleStamp = getTimeStamp() - RECYCLE_AGE; + msgs = theLog.logs; + //find out oldest dead ThreadStressLog in case we can't find one within + //recycle age but can't create a new chunk + ThreadStressLog * oldestDeadMsg = NULL; + + while(msgs != 0) + { + if (msgs->isDead) + { + BOOL hasTimeStamp = msgs->curPtr != (StressMsg *)msgs->chunkListTail->EndPtr(); + if (hasTimeStamp && msgs->curPtr->timeStamp < recycleStamp) + { + skipInsert = TRUE; + InterlockedDecrement(&theLog.deadCount); + break; + } + + if (!oldestDeadMsg) + { + oldestDeadMsg = msgs; + } + else if (hasTimeStamp && oldestDeadMsg->curPtr->timeStamp > msgs->curPtr->timeStamp) + { + oldestDeadMsg = msgs; + } + } + + msgs = msgs->next; + } + + //if the total stress log size limit is already passed and we can't add new chunk, + //always reuse the oldest dead msg + if (!AllowNewChunk (0) && !msgs) + { + msgs = oldestDeadMsg; + skipInsert = TRUE; + InterlockedDecrement(&theLog.deadCount); + } + } + + if (msgs == 0) { + FAULT_NOT_FATAL(); // We don't mind if we can't allocate here, we'll try again later. + if (IsInCantAllocStressLogRegion ()) + { + goto LEAVE; + } + + msgs = new (nothrow) ThreadStressLog; + + if (msgs == 0 ||!msgs->IsValid ()) + { + delete msgs; + msgs = 0; + goto LEAVE; + } + } + + msgs->Activate (); + + t_pCurrentThreadLog = msgs; + + if (!skipInsert) { +#ifdef _DEBUG + ThreadStressLog* walk = theLog.logs; + while (walk) + { + _ASSERTE (walk != msgs); + walk = walk->next; + } +#endif + // Put it into the stress log + msgs->next = theLog.logs; + theLog.logs = msgs; + } + +LEAVE: + ; + return msgs; +} + +/*********************************************************************************/ +/* static */ +void StressLog::ThreadDetach() { + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + ThreadStressLog* msgs = t_pCurrentThreadLog; + +#ifndef DACCESS_COMPILE + if (msgs == 0) + { + return; + } + + t_pCurrentThreadLog = NULL; + + // We are deleting a fiber. The thread is running a different fiber now. + // We should write this message to the StressLog for deleted fiber. + msgs->LogMsg (LF_STARTUP, 0, "******* DllMain THREAD_DETACH called Thread dying *******\n"); +#endif + + msgs->isDead = TRUE; + InterlockedIncrement(&theLog.deadCount); +} + +BOOL StressLog::AllowNewChunk (LONG numChunksInCurThread) +{ + _ASSERTE (numChunksInCurThread <= theLog.totalChunk); + DWORD perThreadLimit = theLog.MaxSizePerThread; + +#ifndef DACCESS_COMPILE + if (numChunksInCurThread == 0 && IsSuspendEEThread()) + return TRUE; + + if (IsGCSpecialThread()) + { + perThreadLimit *= GC_STRESSLOG_MULTIPLY; + } +#endif + + if ((DWORD)numChunksInCurThread * STRESSLOG_CHUNK_SIZE >= perThreadLimit) + { + return FALSE; + } + + return (DWORD)theLog.totalChunk * STRESSLOG_CHUNK_SIZE < theLog.MaxSizeTotal; +} + +BOOL StressLog::ReserveStressLogChunks (unsigned chunksToReserve) +{ + ThreadStressLog* msgs = t_pCurrentThreadLog; + + if (msgs == 0) + { + msgs = CreateThreadStressLog(); + + if (msgs == 0) + return FALSE; + } + + if (chunksToReserve == 0) + { + chunksToReserve = (theLog.MaxSizePerThread + STRESSLOG_CHUNK_SIZE - 1) / STRESSLOG_CHUNK_SIZE; + } + + LONG numTries = (LONG)chunksToReserve - msgs->chunkListLength; + for (LONG i = 0; i < numTries; i++) + { + msgs->GrowChunkList (); + } + + return msgs->chunkListLength >= (LONG)chunksToReserve; +} + +void (*FSwitchToSOTolerant)(); +void (*FSwitchToSOIntolerant)(); +void TrackSO(BOOL tolerance) +{ + if (tolerance) + { + if (FSwitchToSOTolerant) + { + FSwitchToSOTolerant(); + } + } + else + { + if (FSwitchToSOIntolerant) + { + FSwitchToSOIntolerant(); + } + } +} + +/*********************************************************************************/ +/* fetch a buffer that can be used to write a stress message, it is thread safe */ +void ThreadStressLog::LogMsg(unsigned facility, int cArgs, const char* format, va_list Args) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + // Asserts in this function cause infinite loops in the asserting mechanism. + // Just use debug breaks instead. + +#ifndef DACCESS_COMPILE +#ifdef _DEBUG + // _ASSERTE ( cArgs >= 0 && cArgs <= 7 ); + if (cArgs < 0 || cArgs > 7) DebugBreak(); +#endif // + + size_t offs = ((size_t)format - StressLog::theLog.moduleOffset); + + // _ASSERTE ( offs < StressMsg::maxOffset ); + if (offs >= StressMsg::maxOffset) + { +#ifdef _DEBUG + DebugBreak(); // in lieu of the above _ASSERTE +#endif // _DEBUG + + // Set it to this string instead. + offs = +#ifdef _DEBUG + (size_t)"<BUG: StressLog format string beyond maxOffset>"; +#else // _DEBUG + 0; // a 0 offset is ignored by StressLog::Dump +#endif // _DEBUG else + } + + // Get next available slot + StressMsg* msg = AdvanceWrite(cArgs); + + msg->timeStamp = getTimeStamp(); + msg->facility = facility; + msg->formatOffset = offs; + msg->numberOfArgs = cArgs; + + for ( int i = 0; i < cArgs; ++i ) + { + void* data = va_arg(Args, void*); + msg->args[i] = data; + } + +#ifdef _DEBUG + if (!IsValid () || threadId != GetCurrentThreadId ()) + DebugBreak(); +#endif // _DEBUG +#endif //DACCESS_COMPILE +} + +FORCEINLINE BOOL StressLog::InlinedStressLogOn(unsigned facility, unsigned level) +{ + STATIC_CONTRACT_LEAF; + STATIC_CONTRACT_SUPPORTS_DAC; + +#if defined(DACCESS_COMPILE) + return FALSE; +#else + return ((theLog.facilitiesToLog & facility) && (level <= theLog.levelToLog)); +#endif +} + +BOOL StressLog::StressLogOn(unsigned facility, unsigned level) +{ + STATIC_CONTRACT_LEAF; + STATIC_CONTRACT_SUPPORTS_DAC; + + return InlinedStressLogOn(facility, level); +} + +FORCEINLINE BOOL StressLog::InlinedETWLogOn(unsigned facility, unsigned level) +{ + STATIC_CONTRACT_LEAF; + STATIC_CONTRACT_SUPPORTS_DAC; + + return FALSE; +} + +BOOL StressLog::ETWLogOn(unsigned facility, unsigned level) +{ + STATIC_CONTRACT_LEAF; + STATIC_CONTRACT_SUPPORTS_DAC; + + return InlinedETWLogOn(facility, level); +} + +#if !defined(DACCESS_COMPILE) +BOOL StressLog::LogOn(unsigned facility, unsigned level) +{ + STATIC_CONTRACT_LEAF; + STATIC_CONTRACT_SUPPORTS_DAC; + + return InlinedStressLogOn(facility, level) || InlinedETWLogOn(facility, level); +} +#endif + +/* static */ +void StressLog::LogMsg (unsigned level, unsigned facility, int cArgs, const char* format, ... ) +{ + STATIC_CONTRACT_SUPPORTS_DAC; +#ifndef DACCESS_COMPILE + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SUPPORTS_DAC; + + // Any stresslog LogMsg could theoretically create a new stress log and thus + // enter a critical section. But we don't want these to cause violations in + // CANNOT_TAKE_LOCK callers, since the callers would otherwise be fine in runs that don't + // set the stress log config parameter. + CONTRACT_VIOLATION(TakesLockViolation); + + _ASSERTE ( cArgs >= 0 && cArgs <= 7 ); + + va_list Args; + + if(InlinedStressLogOn(facility, level)) + { + ThreadStressLog* msgs = t_pCurrentThreadLog; + + if (msgs == 0) { + msgs = CreateThreadStressLog(); + + if (msgs == 0) + return; + } + va_start(Args, format); + msgs->LogMsg (facility, cArgs, format, Args); + va_end(Args); + } + +// Stress Log ETW feature available only on the desktop versions of the runtime +#endif //!DACCESS_COMPILE +} + +#ifdef _DEBUG +/* static */ +void StressLog::LogCallStack(const char *const callTag){ + if (theLog.RtlCaptureStackBackTrace) + { + size_t CallStackTrace[MAX_CALL_STACK_TRACE]; + ULONG hash; + USHORT stackTraceCount = theLog.RtlCaptureStackBackTrace (2, MAX_CALL_STACK_TRACE, (PVOID *)CallStackTrace, &hash); + if (stackTraceCount > MAX_CALL_STACK_TRACE) + stackTraceCount = MAX_CALL_STACK_TRACE; + LogMsgOL("Start of %s stack \n", callTag); + USHORT i = 0; + for (;i < stackTraceCount; i++) + { + LogMsgOL("(%s stack)%pK\n", callTag, CallStackTrace[i]); + } + LogMsgOL("End of %s stack\n", callTag); + } +} +#endif //_DEBUG + +#endif // STRESS_LOG + diff --git a/src/coreclr/utilcode/util.cpp b/src/coreclr/utilcode/util.cpp new file mode 100644 index 00000000000..c034ae19318 --- /dev/null +++ b/src/coreclr/utilcode/util.cpp @@ -0,0 +1,3292 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// util.cpp +// + +// +// This contains a bunch of C++ utility classes. +// +//***************************************************************************** +#include "stdafx.h" // Precompiled header key. +#include "utilcode.h" +#include "metadata.h" +#include "ex.h" +#include "pedecoder.h" +#include "loaderheap.h" +#include "sigparser.h" +#include "cor.h" +#include "corinfo.h" +#include "volatile.h" +#include "mdfileformat.h" + +#ifndef DACCESS_COMPILE +UINT32 g_nClrInstanceId = 0; +#endif //!DACCESS_COMPILE + +//********** Code. ************************************************************ + +#if defined(FEATURE_COMINTEROP) && !defined(FEATURE_CORESYSTEM) +extern WinRTStatusEnum gWinRTStatus = WINRT_STATUS_UNINITED; +#endif // FEATURE_COMINTEROP && !FEATURE_CORESYSTEM + +#if defined(FEATURE_COMINTEROP) && !defined(FEATURE_CORESYSTEM) +//------------------------------------------------------------------------------ +// +// Attempt to detect the presense of Windows Runtime support on the current OS. +// Our algorithm to do this is to ensure that: +// 1. combase.dll exists +// 2. combase.dll contains a RoInitialize export +// + +void InitWinRTStatus() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + WinRTStatusEnum winRTStatus = WINRT_STATUS_UNSUPPORTED; + + const WCHAR wszComBaseDll[] = W("\\combase.dll"); + const SIZE_T cchComBaseDll = _countof(wszComBaseDll); + + WCHAR wszComBasePath[MAX_LONGPATH + 1]; + const SIZE_T cchComBasePath = _countof(wszComBasePath); + + ZeroMemory(wszComBasePath, cchComBasePath * sizeof(wszComBasePath[0])); + + UINT cchSystemDirectory = WszGetSystemDirectory(wszComBasePath, MAX_LONGPATH); + + // Make sure that we're only probing in the system directory. If we can't find the system directory, or + // we find it but combase.dll doesn't fit into it, we'll fall back to a safe default of saying that WinRT + // is simply not present. + if (cchSystemDirectory > 0 && cchComBasePath - cchSystemDirectory >= cchComBaseDll) + { + if (wcscat_s(wszComBasePath, wszComBaseDll) == 0) + { + HModuleHolder hComBase(WszLoadLibrary(wszComBasePath)); + if (hComBase != NULL) + { + FARPROC activateInstace = GetProcAddress(hComBase, "RoInitialize"); + if (activateInstace != NULL) + { + winRTStatus = WINRT_STATUS_SUPPORTED; + } + } + } + } + + gWinRTStatus = winRTStatus; +} +#endif // FEATURE_COMINTEROP && !FEATURE_CORESYSTEM +//***************************************************************************** +// Convert a string of hex digits into a hex value of the specified # of bytes. +//***************************************************************************** +HRESULT GetHex( // Return status. + LPCSTR szStr, // String to convert. + int size, // # of bytes in pResult. + void *pResult) // Buffer for result. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int count = size * 2; // # of bytes to take from string. + unsigned int Result = 0; // Result value. + char ch; + + _ASSERTE(size == 1 || size == 2 || size == 4); + + while (count-- && (ch = *szStr++) != '\0') + { + switch (ch) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + Result = 16 * Result + (ch - '0'); + break; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + Result = 16 * Result + 10 + (ch - 'A'); + break; + + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + Result = 16 * Result + 10 + (ch - 'a'); + break; + + default: + return (E_FAIL); + } + } + + // Set the output. + switch (size) + { + case 1: + *((BYTE *) pResult) = (BYTE) Result; + break; + + case 2: + *((WORD *) pResult) = (WORD) Result; + break; + + case 4: + *((DWORD *) pResult) = Result; + break; + + default: + _ASSERTE(0); + break; + } + return (S_OK); +} + +//***************************************************************************** +// Convert a pointer to a string into a GUID. +//***************************************************************************** +HRESULT LPCSTRToGuid( // Return status. + LPCSTR szGuid, // String to convert. + GUID *psGuid) // Buffer for converted GUID. +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int i; + + // Verify the surrounding syntax. + if (strlen(szGuid) != 38 || szGuid[0] != '{' || szGuid[9] != '-' || + szGuid[14] != '-' || szGuid[19] != '-' || szGuid[24] != '-' || szGuid[37] != '}') + { + return (E_FAIL); + } + + // Parse the first 3 fields. + if (FAILED(GetHex(szGuid + 1, 4, &psGuid->Data1))) + return E_FAIL; + if (FAILED(GetHex(szGuid + 10, 2, &psGuid->Data2))) + return E_FAIL; + if (FAILED(GetHex(szGuid + 15, 2, &psGuid->Data3))) + return E_FAIL; + + // Get the last two fields (which are byte arrays). + for (i = 0; i < 2; ++i) + { + if (FAILED(GetHex(szGuid + 20 + (i * 2), 1, &psGuid->Data4[i]))) + { + return E_FAIL; + } + } + for (i=0; i < 6; ++i) + { + if (FAILED(GetHex(szGuid + 25 + (i * 2), 1, &psGuid->Data4[i+2]))) + { + return E_FAIL; + } + } + return S_OK; +} + +// +// +// Global utility functions. +// +// + + + +typedef HRESULT __stdcall DLLGETCLASSOBJECT(REFCLSID rclsid, + REFIID riid, + void **ppv); + +EXTERN_C const IID _IID_IClassFactory = + {0x00000001, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; + +namespace +{ + HRESULT FakeCoCallDllGetClassObject( + REFCLSID rclsid, + LPCWSTR wszDllPath, + REFIID riid, + void **ppv, + HMODULE *phmodDll) + { + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + _ASSERTE(ppv != nullptr); + + HRESULT hr = S_OK; + + // Initialize [out] HMODULE (if it was requested) + if (phmodDll != nullptr) + *phmodDll = nullptr; + + bool fIsDllPathPrefix = (wszDllPath != nullptr) && (wcslen(wszDllPath) > 0) && (wszDllPath[wcslen(wszDllPath) - 1] == W('\\')); + + // - An empty string will be treated as NULL. + // - A string ending will a backslash will be treated as a prefix for where to look for the DLL + // if the InProcServer32 value is just a DLL name and not a full path. + StackSString ssDllName; + if ((wszDllPath == nullptr) || (wszDllPath[0] == W('\0')) || fIsDllPathPrefix) + { +#ifndef TARGET_UNIX + IfFailRet(Clr::Util::Com::FindInprocServer32UsingCLSID(rclsid, ssDllName)); + + EX_TRY + { + if (fIsDllPathPrefix) + { + SString::Iterator i = ssDllName.Begin(); + if (!ssDllName.Find(i, W('\\'))) + { // If the InprocServer32 is just a DLL name (not a fully qualified path), then + // prefix wszFilePath with wszDllPath. + ssDllName.Insert(i, wszDllPath); + } + } + } + EX_CATCH_HRESULT(hr); + IfFailRet(hr); + + wszDllPath = ssDllName.GetUnicode(); +#else // !TARGET_UNIX + return E_FAIL; +#endif // !TARGET_UNIX + } + _ASSERTE(wszDllPath != nullptr); + + // We've got the name of the DLL to load, so load it. + HModuleHolder hDll = WszLoadLibraryEx(wszDllPath, nullptr, GetLoadWithAlteredSearchPathFlag()); + if (hDll == nullptr) + return HRESULT_FROM_GetLastError(); + + // We've loaded the DLL, so find the DllGetClassObject function. + DLLGETCLASSOBJECT *dllGetClassObject = (DLLGETCLASSOBJECT*)GetProcAddress(hDll, "DllGetClassObject"); + if (dllGetClassObject == nullptr) + return HRESULT_FROM_GetLastError(); + + // Call the function to get a class object for the rclsid and riid passed in. + IfFailRet(dllGetClassObject(rclsid, riid, ppv)); + + hDll.SuppressRelease(); + + if (phmodDll != nullptr) + *phmodDll = hDll.GetValue(); + + return hr; + } +} + +// ---------------------------------------------------------------------------- +// FakeCoCreateInstanceEx +// +// Description: +// A private function to do the equivalent of a CoCreateInstance in cases where we +// can't make the real call. Use this when, for instance, you need to create a symbol +// reader in the Runtime but we're not CoInitialized. Obviously, this is only good +// for COM objects for which CoCreateInstance is just a glorified find-and-load-me +// operation. +// +// Arguments: +// * rclsid - [in] CLSID of object to instantiate +// * wszDllPath [in] - Path to profiler DLL. If wszDllPath is NULL, FakeCoCreateInstanceEx +// will look up the registry to find the path of the COM dll associated with rclsid. +// If the path ends in a backslash, FakeCoCreateInstanceEx will treat this as a prefix +// if the InprocServer32 found in the registry is a simple filename (not a full path). +// This allows the caller to specify the directory in which the InprocServer32 should +// be found. +// * riid - [in] IID of interface on object to return in ppv +// * ppv - [out] Pointer to implementation of requested interface +// * phmodDll - [out] HMODULE of DLL that was loaded to instantiate the COM object. +// The caller may eventually call FreeLibrary() on this if it can be determined +// that we no longer reference the generated COM object or dependencies. Else, the +// caller may ignore this and the DLL will stay loaded forever. If caller +// specifies phmodDll==NULL, then this parameter is ignored and the HMODULE is not +// returned. +// +// Return Value: +// HRESULT indicating success or failure. +// +// Notes: +// * (*phmodDll) on [out] may always be trusted, even if this function returns an +// error. Therefore, even if creation of the COM object failed, if (*phmodDll != +// NULL), then the DLL was actually loaded. The caller may wish to call +// FreeLibrary on (*phmodDll) in such a case. +HRESULT FakeCoCreateInstanceEx(REFCLSID rclsid, + LPCWSTR wszDllPath, + REFIID riid, + void ** ppv, + HMODULE * phmodDll) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + // Call the function to get a class factory for the rclsid passed in. + HModuleHolder hDll; + ReleaseHolder<IClassFactory> classFactory; + IfFailRet(FakeCoCallDllGetClassObject(rclsid, wszDllPath, _IID_IClassFactory, (void**)&classFactory, &hDll)); + + // Ask the class factory to create an instance of the + // necessary object. + IfFailRet(classFactory->CreateInstance(NULL, riid, ppv)); + + hDll.SuppressRelease(); + + if (phmodDll != NULL) + { + *phmodDll = hDll.GetValue(); + } + + return hr; +} + +#if USE_UPPER_ADDRESS +static BYTE * s_CodeMinAddr; // Preferred region to allocate the code in. +static BYTE * s_CodeMaxAddr; +static BYTE * s_CodeAllocStart; +static BYTE * s_CodeAllocHint; // Next address to try to allocate for code in the preferred region. +#endif + +// +// Use this function to initialize the s_CodeAllocHint +// during startup. base is runtime .dll base address, +// size is runtime .dll virtual size. +// +void InitCodeAllocHint(SIZE_T base, SIZE_T size, int randomPageOffset) +{ +#if USE_UPPER_ADDRESS + +#ifdef _DEBUG + // If GetForceRelocs is enabled we don't constrain the pMinAddr + if (PEDecoder::GetForceRelocs()) + return; +#endif + +// + // If we are using the UPPER_ADDRESS space (on Win64) + // then for any code heap that doesn't specify an address + // range using [pMinAddr..pMaxAddr] we place it in the + // upper address space + // This enables us to avoid having to use long JumpStubs + // to reach the code for our ngen-ed images. + // Which are also placed in the UPPER_ADDRESS space. + // + SIZE_T reach = 0x7FFF0000u; + + // We will choose the preferred code region based on the address of clr.dll. The JIT helpers + // in clr.dll are the most heavily called functions. + s_CodeMinAddr = (base + size > reach) ? (BYTE *)(base + size - reach) : (BYTE *)0; + s_CodeMaxAddr = (base + reach > base) ? (BYTE *)(base + reach) : (BYTE *)-1; + + BYTE * pStart; + + if (s_CodeMinAddr <= (BYTE *)CODEHEAP_START_ADDRESS && + (BYTE *)CODEHEAP_START_ADDRESS < s_CodeMaxAddr) + { + // clr.dll got loaded at its preferred base address? (OS without ASLR - pre-Vista) + // Use the code head start address that does not cause collisions with NGen images. + // This logic is coupled with scripts that we use to assign base addresses. + pStart = (BYTE *)CODEHEAP_START_ADDRESS; + } + else + if (base > UINT32_MAX) + { + // clr.dll got address assigned by ASLR? + // Try to occupy the space as far as possible to minimize collisions with other ASLR assigned + // addresses. Do not start at s_CodeMinAddr exactly so that we can also reach common native images + // that can be placed at higher addresses than clr.dll. + pStart = s_CodeMinAddr + (s_CodeMaxAddr - s_CodeMinAddr) / 8; + } + else + { + // clr.dll missed the base address? + // Try to occupy the space right after it. + pStart = (BYTE *)(base + size); + } + + // Randomize the address space + pStart += GetOsPageSize() * randomPageOffset; + + s_CodeAllocStart = pStart; + s_CodeAllocHint = pStart; +#endif +} + +// +// Use this function to reset the s_CodeAllocHint +// after unloading an AppDomain +// +void ResetCodeAllocHint() +{ + LIMITED_METHOD_CONTRACT; +#if USE_UPPER_ADDRESS + s_CodeAllocHint = s_CodeAllocStart; +#endif +} + +// +// Returns TRUE if p is located in near clr.dll that allows us +// to use rel32 IP-relative addressing modes. +// +BOOL IsPreferredExecutableRange(void * p) +{ + LIMITED_METHOD_CONTRACT; +#if USE_UPPER_ADDRESS + if (s_CodeMinAddr <= (BYTE *)p && (BYTE *)p < s_CodeMaxAddr) + return TRUE; +#endif + return FALSE; +} + +// +// Allocate free memory that will be used for executable code +// Handles the special requirements that we have on 64-bit platforms +// where we want the executable memory to be located near clr.dll +// +BYTE * ClrVirtualAllocExecutable(SIZE_T dwSize, + DWORD flAllocationType, + DWORD flProtect) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + +#if USE_UPPER_ADDRESS + // + // If we are using the UPPER_ADDRESS space (on Win64) + // then for any heap that will contain executable code + // we will place it in the upper address space + // + // This enables us to avoid having to use JumpStubs + // to reach the code for our ngen-ed images on x64, + // since they are also placed in the UPPER_ADDRESS space. + // + BYTE * pHint = s_CodeAllocHint; + + if (dwSize <= (SIZE_T)(s_CodeMaxAddr - s_CodeMinAddr) && pHint != NULL) + { + // Try to allocate in the preferred region after the hint + BYTE * pResult = ClrVirtualAllocWithinRange(pHint, s_CodeMaxAddr, dwSize, flAllocationType, flProtect); + + if (pResult != NULL) + { + s_CodeAllocHint = pResult + dwSize; + return pResult; + } + + // Try to allocate in the preferred region before the hint + pResult = ClrVirtualAllocWithinRange(s_CodeMinAddr, pHint + dwSize, dwSize, flAllocationType, flProtect); + + if (pResult != NULL) + { + s_CodeAllocHint = pResult + dwSize; + return pResult; + } + + s_CodeAllocHint = NULL; + } + + // Fall through to +#endif // USE_UPPER_ADDRESS + +#ifdef HOST_UNIX + // Tell PAL to use the executable memory allocator to satisfy this request for virtual memory. + // This will allow us to place JIT'ed code close to the coreclr library + // and thus improve performance by avoiding jump stubs in managed code. + flAllocationType |= MEM_RESERVE_EXECUTABLE; +#endif // HOST_UNIX + + return (BYTE *) ClrVirtualAlloc (NULL, dwSize, flAllocationType, flProtect); + +} + +// +// Allocate free memory with specific alignment. +// +LPVOID ClrVirtualAllocAligned(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect, SIZE_T alignment) +{ + // Verify that the alignment is a power of 2 + _ASSERTE(alignment != 0); + _ASSERTE((alignment & (alignment - 1)) == 0); + +#ifdef HOST_WINDOWS + + // The VirtualAlloc on Windows ensures 64kB alignment + _ASSERTE(alignment <= 0x10000); + return ClrVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); + +#else // HOST_WINDOWS + + if(alignment < GetOsPageSize()) alignment = GetOsPageSize(); + + // UNIXTODO: Add a specialized function to PAL so that we don't have to waste memory + dwSize += alignment; + SIZE_T addr = (SIZE_T)ClrVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); + return (LPVOID)((addr + (alignment - 1)) & ~(alignment - 1)); + +#endif // HOST_WINDOWS +} + +#ifdef _DEBUG +static DWORD ShouldInjectFaultInRange() +{ + static DWORD fInjectFaultInRange = 99; + + if (fInjectFaultInRange == 99) + fInjectFaultInRange = (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_InjectFault) & 0x40); + return fInjectFaultInRange; +} +#endif + +// Reserves free memory within the range [pMinAddr..pMaxAddr] using +// ClrVirtualQuery to find free memory and ClrVirtualAlloc to reserve it. +// +// This method only supports the flAllocationType of MEM_RESERVE, and expects that the memory +// is being reserved for the purpose of eventually storing executable code. +// +// Callers also should set dwSize to a multiple of sysInfo.dwAllocationGranularity (64k). +// That way they can reserve a large region and commit smaller sized pages +// from that region until it fills up. +// +// This functions returns the reserved memory block upon success +// +// It returns NULL when it fails to find any memory that satisfies +// the range. +// + +BYTE * ClrVirtualAllocWithinRange(const BYTE *pMinAddr, + const BYTE *pMaxAddr, + SIZE_T dwSize, + DWORD flAllocationType, + DWORD flProtect) +{ + CONTRACTL + { + NOTHROW; + PRECONDITION(dwSize != 0); + PRECONDITION(flAllocationType == MEM_RESERVE); + } + CONTRACTL_END; + + BYTE *pResult = nullptr; // our return value; + + static unsigned countOfCalls = 0; // We log the number of tims we call this method + countOfCalls++; // increment the call counter + + if (dwSize == 0) + { + return nullptr; + } + + // + // First lets normalize the pMinAddr and pMaxAddr values + // + // If pMinAddr is NULL then set it to BOT_MEMORY + if ((pMinAddr == 0) || (pMinAddr < (BYTE *) BOT_MEMORY)) + { + pMinAddr = (BYTE *) BOT_MEMORY; + } + + // If pMaxAddr is NULL then set it to TOP_MEMORY + if ((pMaxAddr == 0) || (pMaxAddr > (BYTE *) TOP_MEMORY)) + { + pMaxAddr = (BYTE *) TOP_MEMORY; + } + + // If pMaxAddr is not greater than pMinAddr we can not make an allocation + if (pMaxAddr <= pMinAddr) + { + return nullptr; + } + + // If pMinAddr is BOT_MEMORY and pMaxAddr is TOP_MEMORY + // then we can call ClrVirtualAlloc instead + if ((pMinAddr == (BYTE *) BOT_MEMORY) && (pMaxAddr == (BYTE *) TOP_MEMORY)) + { + return (BYTE*) ClrVirtualAlloc(nullptr, dwSize, flAllocationType, flProtect); + } + +#ifdef HOST_UNIX + pResult = (BYTE *)PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(pMinAddr, pMaxAddr, dwSize); + if (pResult != nullptr) + { + return pResult; + } +#endif // HOST_UNIX + + // We will do one scan from [pMinAddr .. pMaxAddr] + // First align the tryAddr up to next 64k base address. + // See docs for VirtualAllocEx and lpAddress and 64k alignment for reasons. + // + BYTE * tryAddr = (BYTE *)ALIGN_UP((BYTE *)pMinAddr, VIRTUAL_ALLOC_RESERVE_GRANULARITY); + bool virtualQueryFailed = false; + bool faultInjected = false; + unsigned virtualQueryCount = 0; + + // Now scan memory and try to find a free block of the size requested. + while ((tryAddr + dwSize) <= (BYTE *) pMaxAddr) + { + MEMORY_BASIC_INFORMATION mbInfo; + + // Use VirtualQuery to find out if this address is MEM_FREE + // + virtualQueryCount++; + if (!ClrVirtualQuery((LPCVOID)tryAddr, &mbInfo, sizeof(mbInfo))) + { + // Exit and return nullptr if the VirtualQuery call fails. + virtualQueryFailed = true; + break; + } + + // Is there enough memory free from this start location? + // Note that for most versions of UNIX the mbInfo.RegionSize returned will always be 0 + if ((mbInfo.State == MEM_FREE) && + (mbInfo.RegionSize >= (SIZE_T) dwSize || mbInfo.RegionSize == 0)) + { + // Try reserving the memory using VirtualAlloc now + pResult = (BYTE*)ClrVirtualAlloc(tryAddr, dwSize, MEM_RESERVE, flProtect); + + // Normally this will be successful + // + if (pResult != nullptr) + { + // return pResult + break; + } + +#ifdef _DEBUG + if (ShouldInjectFaultInRange()) + { + // return nullptr (failure) + faultInjected = true; + break; + } +#endif // _DEBUG + + // On UNIX we can also fail if our request size 'dwSize' is larger than 64K and + // and our tryAddr is pointing at a small MEM_FREE region (smaller than 'dwSize') + // However we can't distinguish between this and the race case. + + // We might fail in a race. So just move on to next region and continue trying + tryAddr = tryAddr + VIRTUAL_ALLOC_RESERVE_GRANULARITY; + } + else + { + // Try another section of memory + tryAddr = max(tryAddr + VIRTUAL_ALLOC_RESERVE_GRANULARITY, + (BYTE*) mbInfo.BaseAddress + mbInfo.RegionSize); + } + } + + STRESS_LOG7(LF_JIT, LL_INFO100, + "ClrVirtualAllocWithinRange request #%u for %08x bytes in [ %p .. %p ], query count was %u - returned %s: %p\n", + countOfCalls, (DWORD)dwSize, pMinAddr, pMaxAddr, + virtualQueryCount, (pResult != nullptr) ? "success" : "failure", pResult); + + // If we failed this call the process will typically be terminated + // so we log any additional reason for failing this call. + // + if (pResult == nullptr) + { + if ((tryAddr + dwSize) > (BYTE *)pMaxAddr) + { + // Our tryAddr reached pMaxAddr + STRESS_LOG0(LF_JIT, LL_INFO100, "Additional reason: Address space exhausted.\n"); + } + + if (virtualQueryFailed) + { + STRESS_LOG0(LF_JIT, LL_INFO100, "Additional reason: VirtualQuery operation failed.\n"); + } + + if (faultInjected) + { + STRESS_LOG0(LF_JIT, LL_INFO100, "Additional reason: fault injected.\n"); + } + } + + return pResult; +} + +//****************************************************************************** +// NumaNodeInfo +//****************************************************************************** +#if !defined(FEATURE_REDHAWK) + +/*static*/ LPVOID NumaNodeInfo::VirtualAllocExNuma(HANDLE hProc, LPVOID lpAddr, SIZE_T dwSize, + DWORD allocType, DWORD prot, DWORD node) +{ + return ::VirtualAllocExNuma(hProc, lpAddr, dwSize, allocType, prot, node); +} + +#ifdef HOST_WINDOWS +/*static*/ BOOL NumaNodeInfo::GetNumaProcessorNodeEx(PPROCESSOR_NUMBER proc_no, PUSHORT node_no) +{ + return ::GetNumaProcessorNodeEx(proc_no, node_no); +} +/*static*/ bool NumaNodeInfo::GetNumaInfo(PUSHORT total_nodes, DWORD* max_procs_per_node) +{ + if (m_enableGCNumaAware) + { + DWORD currentProcsOnNode = 0; + for (int i = 0; i < m_nNodes; i++) + { + GROUP_AFFINITY processorMask; + if (GetNumaNodeProcessorMaskEx(i, &processorMask)) + { + DWORD procsOnNode = 0; + uintptr_t mask = (uintptr_t)processorMask.Mask; + while (mask) + { + procsOnNode++; + mask &= mask - 1; + } + + currentProcsOnNode = max(currentProcsOnNode, procsOnNode); + } + } + + *max_procs_per_node = currentProcsOnNode; + *total_nodes = m_nNodes; + return true; + } + + return false; +} +#else // HOST_WINDOWS +/*static*/ BOOL NumaNodeInfo::GetNumaProcessorNodeEx(USHORT proc_no, PUSHORT node_no) +{ + return PAL_GetNumaProcessorNode(proc_no, node_no); +} +#endif // HOST_WINDOWS +#endif + +/*static*/ BOOL NumaNodeInfo::m_enableGCNumaAware = FALSE; +/*static*/ uint16_t NumaNodeInfo::m_nNodes = 0; +/*static*/ BOOL NumaNodeInfo::InitNumaNodeInfoAPI() +{ +#if !defined(FEATURE_REDHAWK) + //check for numa support if multiple heaps are used + ULONG highest = 0; + + if (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_GCNumaAware) == 0) + return FALSE; + + // fail to get the highest numa node number + if (!::GetNumaHighestNodeNumber(&highest) || (highest == 0)) + return FALSE; + + m_nNodes = (USHORT)(highest + 1); + + return TRUE; +#else + return FALSE; +#endif +} + +/*static*/ BOOL NumaNodeInfo::CanEnableGCNumaAware() +{ + return m_enableGCNumaAware; +} + +/*static*/ void NumaNodeInfo::InitNumaNodeInfo() +{ + m_enableGCNumaAware = InitNumaNodeInfoAPI(); +} + +#ifdef HOST_WINDOWS + +//****************************************************************************** +// CPUGroupInfo +//****************************************************************************** +#if !defined(FEATURE_REDHAWK) +/*static*/ //CPUGroupInfo::PNTQSIEx CPUGroupInfo::m_pNtQuerySystemInformationEx = NULL; + +/*static*/ BOOL CPUGroupInfo::GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP relationship, + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *slpiex, PDWORD count) +{ + LIMITED_METHOD_CONTRACT; + return ::GetLogicalProcessorInformationEx(relationship, slpiex, count); +} + +/*static*/ BOOL CPUGroupInfo::SetThreadGroupAffinity(HANDLE h, + const GROUP_AFFINITY *groupAffinity, GROUP_AFFINITY *previousGroupAffinity) +{ + LIMITED_METHOD_CONTRACT; + return ::SetThreadGroupAffinity(h, groupAffinity, previousGroupAffinity); +} + +/*static*/ BOOL CPUGroupInfo::GetThreadGroupAffinity(HANDLE h, GROUP_AFFINITY *groupAffinity) +{ + LIMITED_METHOD_CONTRACT; + return ::GetThreadGroupAffinity(h, groupAffinity); +} + +/*static*/ BOOL CPUGroupInfo::GetSystemTimes(FILETIME *idleTime, FILETIME *kernelTime, FILETIME *userTime) +{ + LIMITED_METHOD_CONTRACT; + +#ifdef HOST_WINDOWS + return ::GetSystemTimes(idleTime, kernelTime, userTime); +#else + return FALSE; +#endif +} +#endif + +/*static*/ BOOL CPUGroupInfo::m_enableGCCPUGroups = FALSE; +/*static*/ BOOL CPUGroupInfo::m_threadUseAllCpuGroups = FALSE; +/*static*/ BOOL CPUGroupInfo::m_threadAssignCpuGroups = FALSE; +/*static*/ WORD CPUGroupInfo::m_nGroups = 0; +/*static*/ WORD CPUGroupInfo::m_nProcessors = 0; +/*static*/ WORD CPUGroupInfo::m_initialGroup = 0; +/*static*/ CPU_Group_Info *CPUGroupInfo::m_CPUGroupInfoArray = NULL; +/*static*/ LONG CPUGroupInfo::m_initialization = 0; +/*static*/ bool CPUGroupInfo::s_hadSingleProcessorAtStartup = false; + +#if !defined(FEATURE_REDHAWK) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) +// Calculate greatest common divisor +DWORD GCD(DWORD u, DWORD v) +{ + while (v != 0) + { + DWORD dwTemp = v; + v = u % v; + u = dwTemp; + } + + return u; +} + +// Calculate least common multiple +DWORD LCM(DWORD u, DWORD v) +{ + return u / GCD(u, v) * v; +} +#endif + +/*static*/ BOOL CPUGroupInfo::InitCPUGroupInfoArray() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#if !defined(FEATURE_REDHAWK) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + BYTE *bBuffer = NULL; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *pSLPIEx = NULL; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *pRecord = NULL; + DWORD cbSLPIEx = 0; + DWORD byteOffset = 0; + DWORD dwNumElements = 0; + DWORD dwWeight = 1; + + if (CPUGroupInfo::GetLogicalProcessorInformationEx(RelationGroup, pSLPIEx, &cbSLPIEx) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return FALSE; + + _ASSERTE(cbSLPIEx); + + // Fail to allocate buffer + bBuffer = new (nothrow) BYTE[ cbSLPIEx ]; + if (bBuffer == NULL) + return FALSE; + + pSLPIEx = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)bBuffer; + if (!::GetLogicalProcessorInformationEx(RelationGroup, pSLPIEx, &cbSLPIEx)) + { + delete[] bBuffer; + return FALSE; + } + + pRecord = pSLPIEx; + while (byteOffset < cbSLPIEx) + { + if (pRecord->Relationship == RelationGroup) + { + m_nGroups = pRecord->Group.ActiveGroupCount; + break; + } + byteOffset += pRecord->Size; + pRecord = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *)(bBuffer + byteOffset); + } + + m_CPUGroupInfoArray = new (nothrow) CPU_Group_Info[m_nGroups]; + if (m_CPUGroupInfoArray == NULL) + { + delete[] bBuffer; + return FALSE; + } + + for (DWORD i = 0; i < m_nGroups; i++) + { + m_CPUGroupInfoArray[i].nr_active = (WORD)pRecord->Group.GroupInfo[i].ActiveProcessorCount; + m_CPUGroupInfoArray[i].active_mask = pRecord->Group.GroupInfo[i].ActiveProcessorMask; + m_nProcessors += m_CPUGroupInfoArray[i].nr_active; + dwWeight = LCM(dwWeight, (DWORD)m_CPUGroupInfoArray[i].nr_active); + } + + // The number of threads per group that can be supported will depend on the number of CPU groups + // and the number of LPs within each processor group. For example, when the number of LPs in + // CPU groups is the same and is 64, the number of threads per group before weight overflow + // would be 2^32/2^6 = 2^26 (64M threads) + for (DWORD i = 0; i < m_nGroups; i++) + { + m_CPUGroupInfoArray[i].groupWeight = dwWeight / (DWORD)m_CPUGroupInfoArray[i].nr_active; + m_CPUGroupInfoArray[i].activeThreadWeight = 0; + } + + delete[] bBuffer; // done with it; free it + return TRUE; +#else + return FALSE; +#endif +} + +/*static*/ BOOL CPUGroupInfo::InitCPUGroupInfoRange() +{ + LIMITED_METHOD_CONTRACT; + +#if !defined(FEATURE_REDHAWK) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + WORD begin = 0; + WORD nr_proc = 0; + + for (WORD i = 0; i < m_nGroups; i++) + { + nr_proc += m_CPUGroupInfoArray[i].nr_active; + m_CPUGroupInfoArray[i].begin = begin; + m_CPUGroupInfoArray[i].end = nr_proc - 1; + begin = nr_proc; + } + return TRUE; +#else + return FALSE; +#endif +} + +/*static*/ void CPUGroupInfo::InitCPUGroupInfo() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#if !defined(FEATURE_REDHAWK) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + BOOL enableGCCPUGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCCpuGroup) != 0; + BOOL threadUseAllCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_UseAllCpuGroups) != 0; + BOOL threadAssignCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_AssignCpuGroups) != 0; + + if (!enableGCCPUGroups) + return; + + if (!InitCPUGroupInfoArray()) + return; + + if (!InitCPUGroupInfoRange()) + return; + + // initalGroup is whatever the CPU group that the main thread is running on + GROUP_AFFINITY groupAffinity; + CPUGroupInfo::GetThreadGroupAffinity(GetCurrentThread(), &groupAffinity); + m_initialGroup = groupAffinity.Group; + + // only enable CPU groups if more than one group exists + BOOL hasMultipleGroups = m_nGroups > 1; + m_enableGCCPUGroups = enableGCCPUGroups && hasMultipleGroups; + m_threadUseAllCpuGroups = threadUseAllCpuGroups && hasMultipleGroups; + m_threadAssignCpuGroups = threadAssignCpuGroups && hasMultipleGroups; +#endif // TARGET_AMD64 || TARGET_ARM64 + + // Determine if the process is affinitized to a single processor (or if the system has a single processor) + DWORD_PTR processAffinityMask, systemAffinityMask; + if (GetProcessAffinityMask(GetCurrentProcess(), &processAffinityMask, &systemAffinityMask)) + { + processAffinityMask &= systemAffinityMask; + if (processAffinityMask != 0 && // only one CPU group is involved + (processAffinityMask & (processAffinityMask - 1)) == 0) // only one bit is set + { + s_hadSingleProcessorAtStartup = true; + } + } +} + +/*static*/ BOOL CPUGroupInfo::IsInitialized() +{ + LIMITED_METHOD_CONTRACT; + return (m_initialization == -1); +} + +/*static*/ void CPUGroupInfo::EnsureInitialized() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // CPUGroupInfo needs to be initialized only once. This could happen in three cases + // 1. CLR initialization at beginning of EEStartup, or + // 2. Sometimes, when hosted by ASP.NET, the hosting process may initialize ThreadPool + // before initializing CLR, thus require CPUGroupInfo to be initialized to determine + // if CPU group support should/could be enabled. + // 3. Call into Threadpool functions before Threadpool _and_ CLR is initialized. + // Vast majority of time, CPUGroupInfo is initialized in case 1. or 2. + // The chance of contention will be extremely small, so the following code should be fine + // +retry: + if (IsInitialized()) + return; + + if (InterlockedCompareExchange(&m_initialization, 1, 0) == 0) + { + InitCPUGroupInfo(); + m_initialization = -1; + } + else //some other thread started initialization, just wait until complete; + { + while (m_initialization != -1) + { + SwitchToThread(); + } + goto retry; + } +} + +/*static*/ WORD CPUGroupInfo::GetNumActiveProcessors() +{ + LIMITED_METHOD_CONTRACT; + return (WORD)m_nProcessors; +} + +/*static*/ void CPUGroupInfo::GetGroupForProcessor(WORD processor_number, + WORD* group_number, WORD* group_processor_number) +{ + LIMITED_METHOD_CONTRACT; + +#if !defined(FEATURE_REDHAWK) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + WORD bTemp = 0; + WORD bDiff = processor_number - bTemp; + + for (WORD i=0; i < m_nGroups; i++) + { + bTemp += m_CPUGroupInfoArray[i].nr_active; + if (bTemp > processor_number) + { + *group_number = i; + *group_processor_number = bDiff; + + break; + } + bDiff = processor_number - bTemp; + } +#else + *group_number = 0; + *group_processor_number = 0; +#endif +} + +/*static*/ DWORD CPUGroupInfo::CalculateCurrentProcessorNumber() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#if !defined(FEATURE_REDHAWK) && (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + // m_enableGCCPUGroups and m_threadUseAllCpuGroups must be TRUE + _ASSERTE(m_enableGCCPUGroups && m_threadUseAllCpuGroups); + + PROCESSOR_NUMBER proc_no; + proc_no.Group=0; + proc_no.Number=0; + proc_no.Reserved=0; + ::GetCurrentProcessorNumberEx(&proc_no); + + DWORD fullNumber = 0; + for (WORD i = 0; i < proc_no.Group; i++) + fullNumber += (DWORD)m_CPUGroupInfoArray[i].nr_active; + fullNumber += (DWORD)(proc_no.Number); + + return fullNumber; +#else + return 0; +#endif +} + +// There can be different numbers of procs in groups. We take the max. +/*static*/ bool CPUGroupInfo::GetCPUGroupInfo(PUSHORT total_groups, DWORD* max_procs_per_group) +{ + if (m_enableGCCPUGroups) + { + *total_groups = m_nGroups; + DWORD currentProcsInGroup = 0; + for (WORD i = 0; i < m_nGroups; i++) + { + currentProcsInGroup = max(currentProcsInGroup, m_CPUGroupInfoArray[i].nr_active); + } + *max_procs_per_group = currentProcsInGroup; + return true; + } + + return false; +} + +#if !defined(FEATURE_REDHAWK) +//Lock ThreadStore before calling this function, so that updates of weights/counts are consistent +/*static*/ void CPUGroupInfo::ChooseCPUGroupAffinity(GROUP_AFFINITY *gf) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + WORD i, minGroup = 0; + DWORD minWeight = 0; + + // m_enableGCCPUGroups, m_threadUseAllCpuGroups, and m_threadAssignCpuGroups must be TRUE + _ASSERTE(m_enableGCCPUGroups && m_threadUseAllCpuGroups && m_threadAssignCpuGroups); + + for (i = 0; i < m_nGroups; i++) + { + minGroup = (m_initialGroup + i) % m_nGroups; + + // the group is not filled up, use it + if (m_CPUGroupInfoArray[minGroup].activeThreadWeight / m_CPUGroupInfoArray[minGroup].groupWeight + < (DWORD)m_CPUGroupInfoArray[minGroup].nr_active) + goto found; + } + + // all groups filled up, distribute proportionally + minGroup = m_initialGroup; + minWeight = m_CPUGroupInfoArray[m_initialGroup].activeThreadWeight; + for (i = 0; i < m_nGroups; i++) + { + if (m_CPUGroupInfoArray[i].activeThreadWeight < minWeight) + { + minGroup = i; + minWeight = m_CPUGroupInfoArray[i].activeThreadWeight; + } + } + +found: + gf->Group = minGroup; + gf->Mask = m_CPUGroupInfoArray[minGroup].active_mask; + gf->Reserved[0] = 0; + gf->Reserved[1] = 0; + gf->Reserved[2] = 0; + m_CPUGroupInfoArray[minGroup].activeThreadWeight += m_CPUGroupInfoArray[minGroup].groupWeight; +#endif +} + +//Lock ThreadStore before calling this function, so that updates of weights/counts are consistent +/*static*/ void CPUGroupInfo::ClearCPUGroupAffinity(GROUP_AFFINITY *gf) +{ + LIMITED_METHOD_CONTRACT; +#if (defined(TARGET_AMD64) || defined(TARGET_ARM64)) + // m_enableGCCPUGroups, m_threadUseAllCpuGroups, and m_threadAssignCpuGroups must be TRUE + _ASSERTE(m_enableGCCPUGroups && m_threadUseAllCpuGroups && m_threadAssignCpuGroups); + + WORD group = gf->Group; + m_CPUGroupInfoArray[group].activeThreadWeight -= m_CPUGroupInfoArray[group].groupWeight; +#endif +} + +BOOL CPUGroupInfo::GetCPUGroupRange(WORD group_number, WORD* group_begin, WORD* group_size) +{ + if (group_number >= m_nGroups) + { + return FALSE; + } + + *group_begin = m_CPUGroupInfoArray[group_number].begin; + *group_size = m_CPUGroupInfoArray[group_number].nr_active; + + return TRUE; +} + +#endif + +/*static*/ BOOL CPUGroupInfo::CanEnableGCCPUGroups() +{ + LIMITED_METHOD_CONTRACT; + return m_enableGCCPUGroups; +} + +/*static*/ BOOL CPUGroupInfo::CanEnableThreadUseAllCpuGroups() +{ + LIMITED_METHOD_CONTRACT; + return m_threadUseAllCpuGroups; +} + +/*static*/ BOOL CPUGroupInfo::CanAssignCpuGroupsToThreads() +{ + LIMITED_METHOD_CONTRACT; + return m_threadAssignCpuGroups; +} +#endif // HOST_WINDOWS + +//****************************************************************************** +// Returns the number of processors that a process has been configured to run on +//****************************************************************************** +int GetCurrentProcessCpuCount() +{ + CONTRACTL + { + NOTHROW; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + static int cCPUs = 0; + + if (cCPUs != 0) + return cCPUs; + + unsigned int count = 0; + +#ifdef HOST_WINDOWS + DWORD_PTR pmask, smask; + + if (!GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask)) + { + count = 1; + } + else + { + pmask &= smask; + + while (pmask) + { + pmask &= (pmask - 1); + count++; + } + + // GetProcessAffinityMask can return pmask=0 and smask=0 on systems with more + // than 64 processors, which would leave us with a count of 0. Since the GC + // expects there to be at least one processor to run on (and thus at least one + // heap), we'll return 64 here if count is 0, since there are likely a ton of + // processors available in that case. The GC also cannot (currently) handle + // the case where there are more than 64 processors, so we will return a + // maximum of 64 here. + if (count == 0 || count > 64) + count = 64; + } + +#else // HOST_WINDOWS + count = PAL_GetLogicalCpuCountFromOS(); + + uint32_t cpuLimit; + if (PAL_GetCpuLimit(&cpuLimit) && cpuLimit < count) + count = cpuLimit; +#endif // HOST_WINDOWS + + cCPUs = count; + + return count; +} + +#ifdef HOST_WINDOWS +DWORD_PTR GetCurrentProcessCpuMask() +{ + CONTRACTL + { + NOTHROW; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + +#ifdef HOST_WINDOWS + DWORD_PTR pmask, smask; + + if (!GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask)) + return 1; + + pmask &= smask; + return pmask; +#else + return 0; +#endif +} +#endif // HOST_WINDOWS + +uint32_t GetOsPageSizeUncached() +{ + SYSTEM_INFO sysInfo; + ::GetSystemInfo(&sysInfo); + return sysInfo.dwAllocationGranularity ? sysInfo.dwAllocationGranularity : 0x1000; +} + +namespace +{ + Volatile<uint32_t> g_pageSize = 0; +} + +uint32_t GetOsPageSize() +{ +#ifdef HOST_UNIX + size_t result = g_pageSize.LoadWithoutBarrier(); + + if(!result) + { + result = GetOsPageSizeUncached(); + + g_pageSize.StoreWithoutBarrier(result); + } + + return result; +#else + return 0x1000; +#endif +} + +/**************************************************************************/ + +/**************************************************************************/ +void ConfigMethodSet::init(const CLRConfig::ConfigStringInfo & info) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + // make sure that the memory was zero initialized + _ASSERTE(m_inited == 0 || m_inited == 1); + + LPWSTR str = CLRConfig::GetConfigValue(info); + if (str) + { + m_list.Insert(str); + delete[] str; + } + m_inited = 1; +} + +/**************************************************************************/ +bool ConfigMethodSet::contains(LPCUTF8 methodName, LPCUTF8 className, PCCOR_SIGNATURE sig) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE(m_inited == 1); + + if (m_list.IsEmpty()) + return false; + return(m_list.IsInList(methodName, className, sig)); +} + +/**************************************************************************/ +bool ConfigMethodSet::contains(LPCUTF8 methodName, LPCUTF8 className, CORINFO_SIG_INFO* pSigInfo) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE(m_inited == 1); + + if (m_list.IsEmpty()) + return false; + return(m_list.IsInList(methodName, className, pSigInfo)); +} + +/**************************************************************************/ +void ConfigDWORD::init_DontUse_(__in_z LPCWSTR keyName, DWORD defaultVal) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // make sure that the memory was zero initialized + _ASSERTE(m_inited == 0 || m_inited == 1); + + m_value = REGUTIL::GetConfigDWORD_DontUse_(keyName, defaultVal); + m_inited = 1; +} + +/**************************************************************************/ +void ConfigString::init(const CLRConfig::ConfigStringInfo & info) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // make sure that the memory was zero initialized + _ASSERTE(m_inited == 0 || m_inited == 1); + + // Note: m_value will be leaking + m_value = CLRConfig::GetConfigValue(info); + m_inited = 1; +} + +//============================================================================= +// AssemblyNamesList +//============================================================================= +// The string should be of the form +// MyAssembly +// MyAssembly;mscorlib;System +// MyAssembly;mscorlib System + +AssemblyNamesList::AssemblyNamesList(__in LPWSTR list) +{ + CONTRACTL { + THROWS; + } CONTRACTL_END; + + WCHAR prevChar = '?'; // dummy + LPWSTR nameStart = NULL; // start of the name currently being processed. NULL if no current name + AssemblyName ** ppPrevLink = &m_pNames; + + for (LPWSTR listWalk = list; prevChar != '\0'; prevChar = *listWalk, listWalk++) + { + WCHAR curChar = *listWalk; + + if (iswspace(curChar) || curChar == ';' || curChar == '\0' ) + { + // + // Found white-space + // + + if (nameStart) + { + // Found the end of the current name + + AssemblyName * newName = new AssemblyName(); + size_t nameLen = listWalk - nameStart; + + MAKE_UTF8PTR_FROMWIDE(temp, nameStart); + newName->m_assemblyName = new char[nameLen + 1]; + memcpy(newName->m_assemblyName, temp, nameLen * sizeof(newName->m_assemblyName[0])); + newName->m_assemblyName[nameLen] = '\0'; + + *ppPrevLink = newName; + ppPrevLink = &newName->m_next; + + nameStart = NULL; + } + } + else if (!nameStart) + { + // + // Found the start of a new name + // + + nameStart = listWalk; + } + } + + _ASSERTE(!nameStart); // cannot be in the middle of a name + *ppPrevLink = NULL; +} + +AssemblyNamesList::~AssemblyNamesList() +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + for (AssemblyName * pName = m_pNames; pName; /**/) + { + AssemblyName * cur = pName; + pName = pName->m_next; + + delete [] cur->m_assemblyName; + delete cur; + } +} + +bool AssemblyNamesList::IsInList(LPCUTF8 assemblyName) +{ + if (IsEmpty()) + return false; + + for (AssemblyName * pName = m_pNames; pName; pName = pName->m_next) + { + if (_stricmp(pName->m_assemblyName, assemblyName) == 0) + return true; + } + + return false; +} + +//============================================================================= +// MethodNamesList +//============================================================================= +// str should be of the form : +// "foo1 MyNamespace.MyClass:foo3 *:foo4 foo5(x,y,z)" +// "MyClass:foo2 MyClass:*" will match under _DEBUG +// + +void MethodNamesListBase::Insert(__in_z LPWSTR str) +{ + CONTRACTL { + THROWS; + } CONTRACTL_END; + + enum State { NO_NAME, CLS_NAME, FUNC_NAME, ARG_LIST }; // parsing state machine + + const char SEP_CHAR = ' '; // current character use to separate each entry +// const char SEP_CHAR = ';'; // better character use to separate each entry + + WCHAR lastChar = '?'; // dummy + LPWSTR nameStart = NULL; // while walking over the classname or methodname, this points to start + MethodName nameBuf; // Buffer used while parsing the current entry + MethodName** lastName = &pNames; // last entry inserted into the list + bool bQuote = false; + + nameBuf.methodName = NULL; + nameBuf.className = NULL; + nameBuf.numArgs = -1; + nameBuf.next = NULL; + + for(State state = NO_NAME; lastChar != '\0'; str++) + { + lastChar = *str; + + switch(state) + { + case NO_NAME: + if (*str != SEP_CHAR) + { + nameStart = str; + state = CLS_NAME; // we have found the start of the next entry + } + break; + + case CLS_NAME: + if (*nameStart == '"') + { + while (*str && *str!='"') + { + str++; + } + nameStart++; + bQuote=true; + } + + if (*str == ':') + { + if (*nameStart == '*' && !bQuote) + { + // Is the classname string a wildcard. Then set it to NULL + nameBuf.className = NULL; + } + else + { + int len = (int)(str - nameStart); + + // Take off the quote + if (bQuote) { len--; bQuote=false; } + + nameBuf.className = new char[len + 1]; + MAKE_UTF8PTR_FROMWIDE(temp, nameStart); + memcpy(nameBuf.className, temp, len*sizeof(nameBuf.className[0])); + nameBuf.className[len] = '\0'; + } + if (str[1] == ':') // Accept class::name syntax too + str++; + nameStart = str + 1; + state = FUNC_NAME; + } + else if (*str == '\0' || *str == SEP_CHAR || *str == '(') + { + /* This was actually a method name without any class */ + nameBuf.className = NULL; + goto DONE_FUNC_NAME; + } + break; + + case FUNC_NAME: + if (*nameStart == '"') + { + while ( (nameStart==str) || // workaround to handle when className!=NULL + (*str && *str!='"')) + { + str++; + } + + nameStart++; + bQuote=true; + } + + if (*str == '\0' || *str == SEP_CHAR || *str == '(') + { + DONE_FUNC_NAME: + _ASSERTE(*str == '\0' || *str == SEP_CHAR || *str == '('); + + if (*nameStart == '*' && !bQuote) + { + // Is the name string a wildcard. Then set it to NULL + nameBuf.methodName = NULL; + } + else + { + int len = (int)(str - nameStart); + + // Take off the quote + if (bQuote) { len--; bQuote=false; } + + nameBuf.methodName = new char[len + 1]; + MAKE_UTF8PTR_FROMWIDE(temp, nameStart); + memcpy(nameBuf.methodName, temp, len*sizeof(nameBuf.methodName[0])); + nameBuf.methodName[len] = '\0'; + } + + if (*str == '\0' || *str == SEP_CHAR) + { + nameBuf.numArgs = -1; + goto DONE_ARG_LIST; + } + else + { + _ASSERTE(*str == '('); + nameBuf.numArgs = -1; + state = ARG_LIST; + } + } + break; + + case ARG_LIST: + if (*str == '\0' || *str == ')') + { + if (nameBuf.numArgs == -1) + nameBuf.numArgs = 0; + + DONE_ARG_LIST: + _ASSERTE(*str == '\0' || *str == SEP_CHAR || *str == ')'); + + // We have parsed an entire method name. + // Create a new entry in the list for it + + MethodName * newName = new MethodName(); + *newName = nameBuf; + newName->next = NULL; + *lastName = newName; + lastName = &newName->next; + state = NO_NAME; + + // Skip anything after the argument list until we find the next + // separator character, otherwise if we see "func(a,b):foo" we + // create entries for "func(a,b)" as well as ":foo". + if (*str == ')') + { + while (*str && *str != SEP_CHAR) + { + str++; + } + lastChar = *str; + } + } + else + { + if (*str != SEP_CHAR && nameBuf.numArgs == -1) + nameBuf.numArgs = 1; + if (*str == ',') + nameBuf.numArgs++; + } + break; + + default: _ASSERTE(!"Bad state"); break; + } + } +} + +/**************************************************************/ + +void MethodNamesListBase::Destroy() +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + for(MethodName * pName = pNames; pName; /**/) + { + if (pName->className) + delete [] pName->className; + if (pName->methodName) + delete [] pName->methodName; + + MethodName * curName = pName; + pName = pName->next; + delete curName; + } +} + +/**************************************************************/ +bool MethodNamesListBase::IsInList(LPCUTF8 methName, LPCUTF8 clsName, PCCOR_SIGNATURE sig) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int numArgs = -1; + if (sig != NULL) + { + sig++; // Skip calling convention + numArgs = CorSigUncompressData(sig); + } + + return IsInList(methName, clsName, numArgs); +} + +/**************************************************************/ +bool MethodNamesListBase::IsInList(LPCUTF8 methName, LPCUTF8 clsName, CORINFO_SIG_INFO* pSigInfo) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int numArgs = -1; + if (pSigInfo != NULL) + { + numArgs = pSigInfo->numArgs; + } + + return IsInList(methName, clsName, numArgs); +} + +/**************************************************************/ +bool MethodNamesListBase::IsInList(LPCUTF8 methName, LPCUTF8 clsName, int numArgs) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // Try to match all the entries in the list + + for(MethodName * pName = pNames; pName; pName = pName->next) + { + // If numArgs is valid, check for mismatch + if (pName->numArgs != -1 && pName->numArgs != numArgs) + continue; + + // If methodName is valid, check for mismatch + if (pName->methodName) { + if (strcmp(pName->methodName, methName) != 0) { + + // C++ embeds the class name into the method name, + // deal with that here (workaround) + const char* ptr = strchr(methName, ':'); + if (ptr != 0 && ptr[1] == ':' && strcmp(&ptr[2], pName->methodName) == 0) { + unsigned clsLen = (unsigned)(ptr - methName); + if (pName->className == 0 || strncmp(pName->className, methName, clsLen) == 0) + return true; + } + continue; + } + } + + // check for class Name exact match + if (clsName == 0 || pName->className == 0 || strcmp(pName->className, clsName) == 0) + return true; + + // check for suffix wildcard like System.* + unsigned len = (unsigned)strlen(pName->className); + if (len > 0 && pName->className[len-1] == '*' && strncmp(pName->className, clsName, len-1) == 0) + return true; + +#ifdef _DEBUG + // Maybe className doesnt include namespace. Try to match that + LPCUTF8 onlyClass = ns::FindSep(clsName); + if (onlyClass && strcmp(pName->className, onlyClass+1) == 0) + return true; +#endif + } + return(false); +} + +//============================================================================= +// Signature Validation Functions (scaled down version from MDValidator +//============================================================================= + +//***************************************************************************** +// This function validates one argument given an offset into the signature +// where the argument begins. This function assumes that the signature is well +// formed as far as the compression scheme is concerned. +// <TODO>@todo: Validate tokens embedded.</TODO> +//***************************************************************************** +HRESULT validateOneArg( + mdToken tk, // [IN] Token whose signature needs to be validated. + SigParser *pSig, + ULONG *pulNSentinels, // [IN/OUT] Number of sentinels + IMDInternalImport* pImport, // [IN] Internal MD Import interface ptr + BOOL bNoVoidAllowed) // [IN] Flag indicating whether "void" is disallowed for this arg + +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + BYTE elementType; // Current element type being processed. + mdToken token; // Embedded token. + ULONG ulArgCnt; // Argument count for function pointer. + ULONG ulIndex; // Index for type parameters + ULONG ulRank; // Rank of the array. + ULONG ulSizes; // Count of sized dimensions of the array. + ULONG ulLbnds; // Count of lower bounds of the array. + ULONG ulCallConv; + + HRESULT hr = S_OK; // Value returned. + BOOL bRepeat = TRUE; // MODOPT and MODREQ belong to the arg after them + + while(bRepeat) + { + bRepeat = FALSE; + // Validate that the argument is not missing. + + // Get the element type. + if (FAILED(pSig->GetByte(&elementType))) + { + IfFailGo(VLDTR_E_SIG_MISSARG); + } + + // Walk past all the modifier types. + while (elementType & ELEMENT_TYPE_MODIFIER) + { + if (elementType == ELEMENT_TYPE_SENTINEL) + { + if(pulNSentinels) *pulNSentinels+=1; + if(TypeFromToken(tk) != mdtMemberRef) IfFailGo(VLDTR_E_SIG_SENTINMETHODDEF); + } + if (FAILED(pSig->GetByte(&elementType))) + { + IfFailGo(VLDTR_E_SIG_MISSELTYPE); + } + } + + switch (elementType) + { + case ELEMENT_TYPE_VOID: + if(bNoVoidAllowed) IfFailGo(VLDTR_E_SIG_BADVOID); + FALLTHROUGH; + + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_TYPEDBYREF: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_I: + break; + case ELEMENT_TYPE_PTR: + // Validate the referenced type. + if(FAILED(hr = validateOneArg(tk, pSig, pulNSentinels, pImport, FALSE))) IfFailGo(hr); + break; + case ELEMENT_TYPE_BYREF: //fallthru + if(TypeFromToken(tk)==mdtFieldDef) IfFailGo(VLDTR_E_SIG_BYREFINFIELD); + FALLTHROUGH; + case ELEMENT_TYPE_PINNED: + case ELEMENT_TYPE_SZARRAY: + // Validate the referenced type. + if(FAILED(hr = validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE))) IfFailGo(hr); + break; + case ELEMENT_TYPE_CMOD_OPT: + case ELEMENT_TYPE_CMOD_REQD: + bRepeat = TRUE; // go on validating, we're not done with this arg + FALLTHROUGH; + case ELEMENT_TYPE_VALUETYPE: //fallthru + case ELEMENT_TYPE_CLASS: + // See if the token is missing. + if (FAILED(pSig->GetToken(&token))) + { + IfFailGo(VLDTR_E_SIG_MISSTKN); + } + // Token validation . + if(pImport) + { + ULONG rid = RidFromToken(token); + ULONG typ = TypeFromToken(token); + ULONG maxrid = pImport->GetCountWithTokenKind(typ); + if(typ == mdtTypeDef) maxrid++; + if((rid==0)||(rid > maxrid)) IfFailGo(VLDTR_E_SIG_TKNBAD); + } + break; + + case ELEMENT_TYPE_FNPTR: + // <TODO>@todo: More function pointer validation?</TODO> + // Validate that calling convention is present. + if (FAILED(pSig->GetCallingConvInfo(&ulCallConv))) + { + IfFailGo(VLDTR_E_SIG_MISSFPTR); + } + if(((ulCallConv & IMAGE_CEE_CS_CALLCONV_MASK) >= IMAGE_CEE_CS_CALLCONV_MAX) + ||((ulCallConv & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS) + &&(!(ulCallConv & IMAGE_CEE_CS_CALLCONV_HASTHIS)))) IfFailGo(VLDTR_E_MD_BADCALLINGCONV); + + // Validate that argument count is present. + if (FAILED(pSig->GetData(&ulArgCnt))) + { + IfFailGo(VLDTR_E_SIG_MISSFPTRARGCNT); + } + + // FNPTR signature must follow the rules of MethodDef + // Validate and consume return type. + IfFailGo(validateOneArg(mdtMethodDef, pSig, NULL, pImport, FALSE)); + + // Validate and consume the arguments. + while(ulArgCnt--) + { + IfFailGo(validateOneArg(mdtMethodDef, pSig, NULL, pImport, TRUE)); + } + break; + + case ELEMENT_TYPE_ARRAY: + // Validate and consume the base type. + IfFailGo(validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE)); + + // Validate that the rank is present. + if (FAILED(pSig->GetData(&ulRank))) + { + IfFailGo(VLDTR_E_SIG_MISSRANK); + } + + // Process the sizes. + if (ulRank) + { + // Validate that the count of sized-dimensions is specified. + if (FAILED(pSig->GetData(&ulSizes))) + { + IfFailGo(VLDTR_E_SIG_MISSNSIZE); + } + + // Loop over the sizes. + while(ulSizes--) + { + // Validate the current size. + if (FAILED(pSig->GetData(NULL))) + { + IfFailGo(VLDTR_E_SIG_MISSSIZE); + } + } + + // Validate that the count of lower bounds is specified. + if (FAILED(pSig->GetData(&ulLbnds))) + { + IfFailGo(VLDTR_E_SIG_MISSNLBND); + } + + // Loop over the lower bounds. + while(ulLbnds--) + { + // Validate the current lower bound. + if (FAILED(pSig->GetData(NULL))) + { + IfFailGo(VLDTR_E_SIG_MISSLBND); + } + } + } + break; + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_MVAR: + // Validate that index is present. + if (FAILED(pSig->GetData(&ulIndex))) + { + IfFailGo(VLDTR_E_SIG_MISSFPTRARGCNT); + } + + //@todo GENERICS: check that index is in range + break; + + case ELEMENT_TYPE_GENERICINST: + // Validate the generic type. + IfFailGo(validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE)); + + // Validate that parameter count is present. + if (FAILED(pSig->GetData(&ulArgCnt))) + { + IfFailGo(VLDTR_E_SIG_MISSFPTRARGCNT); + } + + //@todo GENERICS: check that number of parameters matches definition? + + // Validate and consume the parameters. + while(ulArgCnt--) + { + IfFailGo(validateOneArg(tk, pSig, NULL, pImport, TRUE)); + } + break; + + case ELEMENT_TYPE_SENTINEL: // this case never works because all modifiers are skipped before switch + if(TypeFromToken(tk) == mdtMethodDef) IfFailGo(VLDTR_E_SIG_SENTINMETHODDEF); + break; + + default: + IfFailGo(VLDTR_E_SIG_BADELTYPE); + break; + } // switch (ulElementType) + } // end while(bRepeat) +ErrExit: + return hr; +} // validateOneArg() + +//***************************************************************************** +// This function validates the given Method/Field/Standalone signature. +//@todo GENERICS: MethodInstantiation? +//***************************************************************************** +HRESULT validateTokenSig( + mdToken tk, // [IN] Token whose signature needs to be validated. + PCCOR_SIGNATURE pbSig, // [IN] Signature. + ULONG cbSig, // [IN] Size in bytes of the signature. + DWORD dwFlags, // [IN] Method flags. + IMDInternalImport* pImport) // [IN] Internal MD Import interface ptr +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + ULONG ulCallConv; // Calling convention. + ULONG ulArgCount = 1; // Count of arguments (1 because of the return type) + ULONG ulTyArgCount = 0; // Count of type arguments + ULONG ulArgIx = 0; // Starting index of argument (standalone sig: 1) + ULONG i; // Looping index. + HRESULT hr = S_OK; // Value returned. + ULONG ulNSentinels = 0; + SigParser sig(pbSig, cbSig); + + _ASSERTE(TypeFromToken(tk) == mdtMethodDef || + TypeFromToken(tk) == mdtMemberRef || + TypeFromToken(tk) == mdtSignature || + TypeFromToken(tk) == mdtFieldDef); + + // Check for NULL signature. + if (!pbSig || !cbSig) return VLDTR_E_SIGNULL; + + // Validate the calling convention. + + // Moves behind calling convention + IfFailRet(sig.GetCallingConvInfo(&ulCallConv)); + i = ulCallConv & IMAGE_CEE_CS_CALLCONV_MASK; + switch(TypeFromToken(tk)) + { + case mdtMethodDef: // MemberRefs have no flags available + // If HASTHIS is set on the calling convention, the method should not be static. + if ((ulCallConv & IMAGE_CEE_CS_CALLCONV_HASTHIS) && + IsMdStatic(dwFlags)) return VLDTR_E_MD_THISSTATIC; + + // If HASTHIS is not set on the calling convention, the method should be static. + if (!(ulCallConv & IMAGE_CEE_CS_CALLCONV_HASTHIS) && + !IsMdStatic(dwFlags)) return VLDTR_E_MD_NOTTHISNOTSTATIC; + // fall thru to callconv check; + FALLTHROUGH; + + case mdtMemberRef: + if(i == IMAGE_CEE_CS_CALLCONV_FIELD) return validateOneArg(tk, &sig, NULL, pImport, TRUE); + + // EXPLICITTHIS and native call convs are for stand-alone sigs only (for calli) + if(((i != IMAGE_CEE_CS_CALLCONV_DEFAULT)&&( i != IMAGE_CEE_CS_CALLCONV_VARARG)) + || (ulCallConv & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS)) return VLDTR_E_MD_BADCALLINGCONV; + break; + + case mdtSignature: + if(i != IMAGE_CEE_CS_CALLCONV_LOCAL_SIG) // then it is function sig for calli + { + if((i >= IMAGE_CEE_CS_CALLCONV_MAX) + ||((ulCallConv & IMAGE_CEE_CS_CALLCONV_EXPLICITTHIS) + &&(!(ulCallConv & IMAGE_CEE_CS_CALLCONV_HASTHIS)))) return VLDTR_E_MD_BADCALLINGCONV; + } + else + ulArgIx = 1; // Local variable signatures don't have a return type + break; + + case mdtFieldDef: + if(i != IMAGE_CEE_CS_CALLCONV_FIELD) return VLDTR_E_MD_BADCALLINGCONV; + return validateOneArg(tk, &sig, NULL, pImport, TRUE); + } + // Is there any sig left for arguments? + + // Get the type argument count + if (ulCallConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + if (FAILED(sig.GetData(&ulTyArgCount))) + { + return VLDTR_E_MD_NOARGCNT; + } + } + + // Get the argument count. + if (FAILED(sig.GetData(&ulArgCount))) + { + return VLDTR_E_MD_NOARGCNT; + } + + // Validate the return type and the arguments. + // (at this moment ulArgCount = num.args+1, ulArgIx = (standalone sig. ? 1 :0); ) + for(; ulArgIx < ulArgCount; ulArgIx++) + { + if(FAILED(hr = validateOneArg(tk, &sig, &ulNSentinels, pImport, (ulArgIx!=0)))) return hr; + } + + // <TODO>@todo: we allow junk to be at the end of the signature (we may not consume it all) + // do we care?</TODO> + + if((ulNSentinels != 0) && ((ulCallConv & IMAGE_CEE_CS_CALLCONV_MASK) != IMAGE_CEE_CS_CALLCONV_VARARG )) + return VLDTR_E_SIG_SENTMUSTVARARG; + if(ulNSentinels > 1) return VLDTR_E_SIG_MULTSENTINELS; + return S_OK; +} // validateTokenSig() + +HRESULT GetImageRuntimeVersionString(PVOID pMetaData, LPCSTR* pString) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE(pString); + STORAGESIGNATURE* pSig = (STORAGESIGNATURE*) pMetaData; + + // Verify the signature. + + // If signature didn't match, you shouldn't be here. + if (pSig->GetSignature() != STORAGE_MAGIC_SIG) + return CLDB_E_FILE_CORRUPT; + + // The version started in version 1.1 + if (pSig->GetMajorVer() < 1) + return CLDB_E_FILE_OLDVER; + + if (pSig->GetMajorVer() == 1 && pSig->GetMinorVer() < 1) + return CLDB_E_FILE_OLDVER; + + // Header data starts after signature. + *pString = (LPCSTR) pSig->pVersion; + return S_OK; +} + +//***************************************************************************** +// Convert a UTF8 string to Unicode, into a CQuickArray<WCHAR>. +//***************************************************************************** +HRESULT Utf2Quick( + LPCUTF8 pStr, // The string to convert. + CQuickArray<WCHAR> &rStr, // The QuickArray<WCHAR> to convert it into. + int iCurLen) // Inital characters in the array to leave (default 0). +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HRESULT hr = S_OK; // A result. + int iReqLen; // Required additional length. + int iActLen; + int bAlloc = 0; // If non-zero, allocation was required. + + if (iCurLen < 0 ) + { + _ASSERTE_MSG(false, "Invalid current length"); + return E_INVALIDARG; + } + + // Calculate the space available + S_SIZE_T cchAvail = S_SIZE_T(rStr.MaxSize()) - S_SIZE_T(iCurLen); + if (cchAvail.IsOverflow() || cchAvail.Value() > INT_MAX) + { + _ASSERTE_MSG(false, "Integer overflow/underflow"); + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + + // Attempt the conversion. + LPWSTR rNewStr = rStr.Ptr()+iCurLen; + if(rNewStr < rStr.Ptr()) + { + _ASSERTE_MSG(false, "Integer overflow/underflow"); + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + iReqLen = WszMultiByteToWideChar(CP_UTF8, 0, pStr, -1, rNewStr, (int)(cchAvail.Value())); + + // If the buffer was too small, determine what is required. + if (iReqLen == 0) + bAlloc = iReqLen = WszMultiByteToWideChar(CP_UTF8, 0, pStr, -1, 0, 0); + // Resize the buffer. If the buffer was large enough, this just sets the internal + // counter, but if it was too small, this will attempt a reallocation. Note that + // the length includes the terminating W('/0'). + IfFailGo(rStr.ReSizeNoThrow(iCurLen+iReqLen)); + // If we had to realloc, then do the conversion again, now that the buffer is + // large enough. + if (bAlloc) { + //recalculating cchAvail since MaxSize could have been changed. + cchAvail = S_SIZE_T(rStr.MaxSize()) - S_SIZE_T(iCurLen); + if (cchAvail.IsOverflow() || cchAvail.Value() > INT_MAX) + { + _ASSERTE_MSG(false, "Integer overflow/underflow"); + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + //reculculating rNewStr + rNewStr = rStr.Ptr()+iCurLen; + + if(rNewStr < rStr.Ptr()) + { + _ASSERTE_MSG(false, "Integer overflow/underflow"); + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + iActLen = WszMultiByteToWideChar(CP_UTF8, 0, pStr, -1, rNewStr, (int)(cchAvail.Value())); + _ASSERTE(iReqLen == iActLen); + } +ErrExit: + return hr; +} // HRESULT Utf2Quick() + + +//***************************************************************************** +// Extract the movl 64-bit unsigned immediate from an IA64 bundle +// (Format X2) +//***************************************************************************** +UINT64 GetIA64Imm64(UINT64 * pBundle) +{ + WRAPPER_NO_CONTRACT; + + UINT64 temp0 = PTR_UINT64(pBundle)[0]; + UINT64 temp1 = PTR_UINT64(pBundle)[1]; + + return GetIA64Imm64(temp0, temp1); +} + +UINT64 GetIA64Imm64(UINT64 qword0, UINT64 qword1) +{ + LIMITED_METHOD_CONTRACT; + + UINT64 imm64 = 0; + +#ifdef _DEBUG_IMPL + // + // make certain we're decoding a movl opcode, with template 4 or 5 + // + UINT64 templa = (qword0 >> 0) & 0x1f; + UINT64 opcode = (qword1 >> 60) & 0xf; + + _ASSERTE((opcode == 0x6) && ((templa == 0x4) || (templa == 0x5))); +#endif + + imm64 = (qword1 >> 59) << 63; // 1 i + imm64 |= (qword1 << 41) >> 1; // 23 high bits of imm41 + imm64 |= (qword0 >> 46) << 22; // 18 low bits of imm41 + imm64 |= (qword1 >> 23) & 0x200000; // 1 ic + imm64 |= (qword1 >> 29) & 0x1F0000; // 5 imm5c + imm64 |= (qword1 >> 43) & 0xFF80; // 9 imm9d + imm64 |= (qword1 >> 36) & 0x7F; // 7 imm7b + + return imm64; +} + +//***************************************************************************** +// Deposit the movl 64-bit unsigned immediate into an IA64 bundle +// (Format X2) +//***************************************************************************** +void PutIA64Imm64(UINT64 * pBundle, UINT64 imm64) +{ + LIMITED_METHOD_CONTRACT; + +#ifdef _DEBUG_IMPL + // + // make certain we're decoding a movl opcode, with template 4 or 5 + // + UINT64 templa = (pBundle[0] >> 0) & 0x1f; + UINT64 opcode = (pBundle[1] >> 60) & 0xf ; + + _ASSERTE((opcode == 0x6) && ((templa == 0x4) || (templa == 0x5))); +#endif + + const UINT64 mask0 = UI64(0x00003FFFFFFFFFFF); + const UINT64 mask1 = UI64(0xF000080FFF800000); + + /* Clear all bits used as part of the imm64 */ + pBundle[0] &= mask0; + pBundle[1] &= mask1; + + UINT64 temp0; + UINT64 temp1; + + temp1 = (imm64 >> 63) << 59; // 1 i + temp1 |= (imm64 & 0xFF80) << 43; // 9 imm9d + temp1 |= (imm64 & 0x1F0000) << 29; // 5 imm5c + temp1 |= (imm64 & 0x200000) << 23; // 1 ic + temp1 |= (imm64 & 0x7F) << 36; // 7 imm7b + temp1 |= (imm64 << 1) >> 41; // 23 high bits of imm41 + temp0 = (imm64 >> 22) << 46; // 18 low bits of imm41 + + /* Or in the new bits used in the imm64 */ + pBundle[0] |= temp0; + pBundle[1] |= temp1; + FlushInstructionCache(GetCurrentProcess(),pBundle,16); +} + +//***************************************************************************** +// Extract the IP-Relative signed 25-bit immediate from an IA64 bundle +// (Formats B1, B2 or B3) +// Note that due to branch target alignment requirements +// the lowest four bits in the result will always be zero. +//***************************************************************************** +INT32 GetIA64Rel25(UINT64 * pBundle, UINT32 slot) +{ + WRAPPER_NO_CONTRACT; + + UINT64 temp0 = PTR_UINT64(pBundle)[0]; + UINT64 temp1 = PTR_UINT64(pBundle)[1]; + + return GetIA64Rel25(temp0, temp1, slot); +} + +INT32 GetIA64Rel25(UINT64 qword0, UINT64 qword1, UINT32 slot) +{ + LIMITED_METHOD_CONTRACT; + + INT32 imm25 = 0; + + if (slot == 2) + { + if ((qword1 >> 59) & 1) + imm25 = 0xFF000000; + imm25 |= (qword1 >> 32) & 0x00FFFFF0; // 20 imm20b + } + else if (slot == 1) + { + if ((qword1 >> 18) & 1) + imm25 = 0xFF000000; + imm25 |= (qword1 << 9) & 0x00FFFE00; // high 15 of imm20b + imm25 |= (qword0 >> 55) & 0x000001F0; // low 5 of imm20b + } + else if (slot == 0) + { + if ((qword0 >> 41) & 1) + imm25 = 0xFF000000; + imm25 |= (qword0 >> 14) & 0x00FFFFF0; // 20 imm20b + } + + return imm25; +} + +//***************************************************************************** +// Deposit the IP-Relative signed 25-bit immediate into an IA64 bundle +// (Formats B1, B2 or B3) +// Note that due to branch target alignment requirements +// the lowest four bits are required to be zero. +//***************************************************************************** +void PutIA64Rel25(UINT64 * pBundle, UINT32 slot, INT32 imm25) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE((imm25 & 0xF) == 0); + + if (slot == 2) + { + const UINT64 mask1 = UI64(0xF700000FFFFFFFFF); + /* Clear all bits used as part of the imm25 */ + pBundle[1] &= mask1; + + UINT64 temp1; + + temp1 = (UINT64) (imm25 & 0x1000000) << 35; // 1 s + temp1 |= (UINT64) (imm25 & 0x0FFFFF0) << 32; // 20 imm20b + + /* Or in the new bits used in the imm64 */ + pBundle[1] |= temp1; + } + else if (slot == 1) + { + const UINT64 mask0 = UI64(0x0EFFFFFFFFFFFFFF); + const UINT64 mask1 = UI64(0xFFFFFFFFFFFB8000); + /* Clear all bits used as part of the imm25 */ + pBundle[0] &= mask0; + pBundle[1] &= mask1; + + UINT64 temp0; + UINT64 temp1; + + temp1 = (UINT64) (imm25 & 0x1000000) >> 7; // 1 s + temp1 |= (UINT64) (imm25 & 0x0FFFE00) >> 9; // high 15 of imm20b + temp0 = (UINT64) (imm25 & 0x00001F0) << 55; // low 5 of imm20b + + /* Or in the new bits used in the imm64 */ + pBundle[0] |= temp0; + pBundle[1] |= temp1; + } + else if (slot == 0) + { + const UINT64 mask0 = UI64(0xFFFFFDC00003FFFF); + /* Clear all bits used as part of the imm25 */ + pBundle[0] &= mask0; + + UINT64 temp0; + + temp0 = (UINT64) (imm25 & 0x1000000) << 16; // 1 s + temp0 |= (UINT64) (imm25 & 0x0FFFFF0) << 14; // 20 imm20b + + /* Or in the new bits used in the imm64 */ + pBundle[0] |= temp0; + + } + FlushInstructionCache(GetCurrentProcess(),pBundle,16); +} + +//***************************************************************************** +// Extract the IP-Relative signed 64-bit immediate from an IA64 bundle +// (Formats X3 or X4) +//***************************************************************************** +INT64 GetIA64Rel64(UINT64 * pBundle) +{ + WRAPPER_NO_CONTRACT; + + UINT64 temp0 = PTR_UINT64(pBundle)[0]; + UINT64 temp1 = PTR_UINT64(pBundle)[1]; + + return GetIA64Rel64(temp0, temp1); +} + +INT64 GetIA64Rel64(UINT64 qword0, UINT64 qword1) +{ + LIMITED_METHOD_CONTRACT; + + INT64 imm64 = 0; + +#ifdef _DEBUG_IMPL + // + // make certain we're decoding a brl opcode, with template 4 or 5 + // + UINT64 templa = (qword0 >> 0) & 0x1f; + UINT64 opcode = (qword1 >> 60) & 0xf; + + _ASSERTE(((opcode == 0xC) || (opcode == 0xD)) && + ((templa == 0x4) || (templa == 0x5))); +#endif + + imm64 = (qword1 >> 59) << 63; // 1 i + imm64 |= (qword1 << 41) >> 1; // 23 high bits of imm39 + imm64 |= (qword0 >> 48) << 24; // 16 low bits of imm39 + imm64 |= (qword1 >> 32) & 0xFFFFF0; // 20 imm20b + // 4 bits of zeros + return imm64; +} + +//***************************************************************************** +// Deposit the IP-Relative signed 64-bit immediate into an IA64 bundle +// (Formats X3 or X4) +//***************************************************************************** +void PutIA64Rel64(UINT64 * pBundle, INT64 imm64) +{ + LIMITED_METHOD_CONTRACT; + +#ifdef _DEBUG_IMPL + // + // make certain we're decoding a brl opcode, with template 4 or 5 + // + UINT64 templa = (pBundle[0] >> 0) & 0x1f; + UINT64 opcode = (pBundle[1] >> 60) & 0xf; + + _ASSERTE(((opcode == 0xC) || (opcode == 0xD)) && + ((templa == 0x4) || (templa == 0x5))); + _ASSERTE((imm64 & 0xF) == 0); +#endif + + const UINT64 mask0 = UI64(0x00003FFFFFFFFFFF); + const UINT64 mask1 = UI64(0xF700000FFF800000); + + /* Clear all bits used as part of the imm64 */ + pBundle[0] &= mask0; + pBundle[1] &= mask1; + + UINT64 temp0 = (imm64 & UI64(0x000000FFFF000000)) << 24; // 16 low bits of imm39 + UINT64 temp1 = (imm64 & UI64(0x8000000000000000)) >> 4 // 1 i + | (imm64 & UI64(0x7FFFFF0000000000)) >> 40 // 23 high bits of imm39 + | (imm64 & UI64(0x0000000000FFFFF0)) << 32; // 20 imm20b + + /* Or in the new bits used in the imm64 */ + pBundle[0] |= temp0; + pBundle[1] |= temp1; + FlushInstructionCache(GetCurrentProcess(),pBundle,16); +} + +//***************************************************************************** +// Extract the 16-bit immediate from ARM Thumb2 Instruction (format T2_N) +//***************************************************************************** +static FORCEINLINE UINT16 GetThumb2Imm16(UINT16 * p) +{ + LIMITED_METHOD_CONTRACT; + + return ((p[0] << 12) & 0xf000) | + ((p[0] << 1) & 0x0800) | + ((p[1] >> 4) & 0x0700) | + ((p[1] >> 0) & 0x00ff); +} + +//***************************************************************************** +// Extract the 32-bit immediate from movw/movt sequence +//***************************************************************************** +UINT32 GetThumb2Mov32(UINT16 * p) +{ + LIMITED_METHOD_CONTRACT; + + // Make sure we are decoding movw/movt sequence + _ASSERTE_IMPL((*(p+0) & 0xFBF0) == 0xF240); + _ASSERTE_IMPL((*(p+2) & 0xFBF0) == 0xF2C0); + + return (UINT32)GetThumb2Imm16(p) + ((UINT32)GetThumb2Imm16(p + 2) << 16); +} + +//***************************************************************************** +// Deposit the 16-bit immediate into ARM Thumb2 Instruction (format T2_N) +//***************************************************************************** +static FORCEINLINE void PutThumb2Imm16(UINT16 * p, UINT16 imm16) +{ + LIMITED_METHOD_CONTRACT; + + USHORT Opcode0 = p[0]; + USHORT Opcode1 = p[1]; + Opcode0 &= ~((0xf000 >> 12) | (0x0800 >> 1)); + Opcode1 &= ~((0x0700 << 4) | (0x00ff << 0)); + Opcode0 |= (imm16 & 0xf000) >> 12; + Opcode0 |= (imm16 & 0x0800) >> 1; + Opcode1 |= (imm16 & 0x0700) << 4; + Opcode1 |= (imm16 & 0x00ff) << 0; + p[0] = Opcode0; + p[1] = Opcode1; +} + +//***************************************************************************** +// Deposit the 32-bit immediate into movw/movt Thumb2 sequence +//***************************************************************************** +void PutThumb2Mov32(UINT16 * p, UINT32 imm32) +{ + LIMITED_METHOD_CONTRACT; + + // Make sure we are decoding movw/movt sequence + _ASSERTE_IMPL((*(p+0) & 0xFBF0) == 0xF240); + _ASSERTE_IMPL((*(p+2) & 0xFBF0) == 0xF2C0); + + PutThumb2Imm16(p, (UINT16)imm32); + PutThumb2Imm16(p + 2, (UINT16)(imm32 >> 16)); +} + +//***************************************************************************** +// Extract the 24-bit rel offset from bl instruction +//***************************************************************************** +INT32 GetThumb2BlRel24(UINT16 * p) +{ + LIMITED_METHOD_CONTRACT; + + USHORT Opcode0 = p[0]; + USHORT Opcode1 = p[1]; + + UINT32 S = Opcode0 >> 10; + UINT32 J2 = Opcode1 >> 11; + UINT32 J1 = Opcode1 >> 13; + + INT32 ret = + ((S << 24) & 0x1000000) | + (((J1 ^ S ^ 1) << 23) & 0x0800000) | + (((J2 ^ S ^ 1) << 22) & 0x0400000) | + ((Opcode0 << 12) & 0x03FF000) | + ((Opcode1 << 1) & 0x0000FFE); + + // Sign-extend and return + return (ret << 7) >> 7; +} + +//***************************************************************************** +// Extract the 24-bit rel offset from bl instruction +//***************************************************************************** +void PutThumb2BlRel24(UINT16 * p, INT32 imm24) +{ + LIMITED_METHOD_CONTRACT; + + // Verify that we got a valid offset + _ASSERTE(FitsInThumb2BlRel24(imm24)); + +#if defined(TARGET_ARM) + // Ensure that the ThumbBit is not set on the offset + // as it cannot be encoded. + _ASSERTE(!(imm24 & THUMB_CODE)); +#endif // TARGET_ARM + + USHORT Opcode0 = p[0]; + USHORT Opcode1 = p[1]; + Opcode0 &= 0xF800; + Opcode1 &= 0xD000; + + UINT32 S = (imm24 & 0x1000000) >> 24; + UINT32 J1 = ((imm24 & 0x0800000) >> 23) ^ S ^ 1; + UINT32 J2 = ((imm24 & 0x0400000) >> 22) ^ S ^ 1; + + Opcode0 |= ((imm24 & 0x03FF000) >> 12) | (S << 10); + Opcode1 |= ((imm24 & 0x0000FFE) >> 1) | (J1 << 13) | (J2 << 11); + + p[0] = Opcode0; + p[1] = Opcode1; + + _ASSERTE(GetThumb2BlRel24(p) == imm24); +} + +//***************************************************************************** +// Extract the PC-Relative offset from a b or bl instruction +//***************************************************************************** +INT32 GetArm64Rel28(UINT32 * pCode) +{ + LIMITED_METHOD_CONTRACT; + + UINT32 branchInstr = *pCode; + + // first shift 6 bits left to set the sign bit, + // then arithmetic shift right by 4 bits + INT32 imm28 = (((INT32)(branchInstr & 0x03FFFFFF)) << 6) >> 4; + + return imm28; +} + +//***************************************************************************** +// Extract the PC-Relative offset from an adrp instruction +//***************************************************************************** +INT32 GetArm64Rel21(UINT32 * pCode) +{ + LIMITED_METHOD_CONTRACT; + + UINT32 addInstr = *pCode; + + // 23-5 bits for the high part. Shift it by 5. + INT32 immhi = (((INT32)(addInstr & 0xFFFFE0))) >> 5; + // 30,29 bits for the lower part. Shift it by 29. + INT32 immlo = ((INT32)(addInstr & 0x60000000)) >> 29; + + // Merge them + INT32 imm21 = (immhi << 2) | immlo; + + return imm21; +} + +//***************************************************************************** +// Extract the PC-Relative offset from an add instruction +//***************************************************************************** +INT32 GetArm64Rel12(UINT32 * pCode) +{ + LIMITED_METHOD_CONTRACT; + + UINT32 addInstr = *pCode; + + // 21-10 contains value. Mask 12 bits and shift by 10 bits. + INT32 imm12 = (INT32)(addInstr & 0x003FFC00) >> 10; + + return imm12; +} + +//***************************************************************************** +// Deposit the PC-Relative offset 'imm28' into a b or bl instruction +//***************************************************************************** +void PutArm64Rel28(UINT32 * pCode, INT32 imm28) +{ + LIMITED_METHOD_CONTRACT; + + // Verify that we got a valid offset + _ASSERTE(FitsInRel28(imm28)); + _ASSERTE((imm28 & 0x3) == 0); // the low two bits must be zero + + UINT32 branchInstr = *pCode; + + branchInstr &= 0xFC000000; // keep bits 31-26 + + // Assemble the pc-relative delta 'imm28' into the branch instruction + branchInstr |= ((imm28 >> 2) & 0x03FFFFFF); + + *pCode = branchInstr; // write the assembled instruction + + _ASSERTE(GetArm64Rel28(pCode) == imm28); +} + +//***************************************************************************** +// Deposit the PC-Relative offset 'imm21' into an adrp instruction +//***************************************************************************** +void PutArm64Rel21(UINT32 * pCode, INT32 imm21) +{ + LIMITED_METHOD_CONTRACT; + + // Verify that we got a valid offset + _ASSERTE(FitsInRel21(imm21)); + + UINT32 adrpInstr = *pCode; + // Check adrp opcode 1ii1 0000 ... + _ASSERTE((adrpInstr & 0x9F000000) == 0x90000000); + + adrpInstr &= 0x9F00001F; // keep bits 31, 28-24, 4-0. + INT32 immlo = imm21 & 0x03; // Extract low 2 bits which will occupy 30-29 bits. + INT32 immhi = (imm21 & 0x1FFFFC) >> 2; // Extract high 19 bits which will occupy 23-5 bits. + adrpInstr |= ((immlo << 29) | (immhi << 5)); + + *pCode = adrpInstr; // write the assembled instruction + + _ASSERTE(GetArm64Rel21(pCode) == imm21); +} + +//***************************************************************************** +// Deposit the PC-Relative offset 'imm12' into an add instruction +//***************************************************************************** +void PutArm64Rel12(UINT32 * pCode, INT32 imm12) +{ + LIMITED_METHOD_CONTRACT; + + // Verify that we got a valid offset + _ASSERTE(FitsInRel12(imm12)); + + UINT32 addInstr = *pCode; + // Check add opcode 1001 0001 00... + _ASSERTE((addInstr & 0xFFC00000) == 0x91000000); + + addInstr &= 0xFFC003FF; // keep bits 31-22, 9-0 + addInstr |= (imm12 << 10); // Occupy 21-10. + + *pCode = addInstr; // write the assembled instruction + + _ASSERTE(GetArm64Rel12(pCode) == imm12); +} + +//--------------------------------------------------------------------- +// Splits a command line into argc/argv lists, using the VC7 parsing rules. +// +// This functions interface mimics the CommandLineToArgvW api. +// +// If function fails, returns NULL. +// +// If function suceeds, call delete [] on return pointer when done. +// +//--------------------------------------------------------------------- +// NOTE: Implementation-wise, once every few years it would be a good idea to +// compare this code with the C runtime library's parse_cmdline method, +// which is in vctools\crt\crtw32\startup\stdargv.c. (Note we don't +// support wild cards, and we use Unicode characters exclusively.) +// We are up to date as of ~6/2005. +//--------------------------------------------------------------------- +LPWSTR *SegmentCommandLine(LPCWSTR lpCmdLine, DWORD *pNumArgs) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_FAULT; + + + *pNumArgs = 0; + + int nch = (int)wcslen(lpCmdLine); + + // Calculate the worstcase storage requirement. (One pointer for + // each argument, plus storage for the arguments themselves.) + int cbAlloc = (nch+1)*sizeof(LPWSTR) + sizeof(WCHAR)*(nch + 1); + LPWSTR pAlloc = new (nothrow) WCHAR[cbAlloc / sizeof(WCHAR)]; + if (!pAlloc) + return NULL; + + LPWSTR *argv = (LPWSTR*) pAlloc; // We store the argv pointers in the first halt + LPWSTR pdst = (LPWSTR)( ((BYTE*)pAlloc) + sizeof(LPWSTR)*(nch+1) ); // A running pointer to second half to store arguments + LPCWSTR psrc = lpCmdLine; + WCHAR c; + BOOL inquote; + BOOL copychar; + int numslash; + + // First, parse the program name (argv[0]). Argv[0] is parsed under + // special rules. Anything up to the first whitespace outside a quoted + // subtring is accepted. Backslashes are treated as normal characters. + argv[ (*pNumArgs)++ ] = pdst; + inquote = FALSE; + do { + if (*psrc == W('"') ) + { + inquote = !inquote; + c = *psrc++; + continue; + } + *pdst++ = *psrc; + + c = *psrc++; + + } while ( (c != W('\0') && (inquote || (c != W(' ') && c != W('\t')))) ); + + if ( c == W('\0') ) { + psrc--; + } else { + *(pdst-1) = W('\0'); + } + + inquote = FALSE; + + + + /* loop on each argument */ + for(;;) + { + if ( *psrc ) + { + while (*psrc == W(' ') || *psrc == W('\t')) + { + ++psrc; + } + } + + if (*psrc == W('\0')) + break; /* end of args */ + + /* scan an argument */ + argv[ (*pNumArgs)++ ] = pdst; + + /* loop through scanning one argument */ + for (;;) + { + copychar = 1; + /* Rules: 2N backslashes + " ==> N backslashes and begin/end quote + 2N+1 backslashes + " ==> N backslashes + literal " + N backslashes ==> N backslashes */ + numslash = 0; + while (*psrc == W('\\')) + { + /* count number of backslashes for use below */ + ++psrc; + ++numslash; + } + if (*psrc == W('"')) + { + /* if 2N backslashes before, start/end quote, otherwise + copy literally */ + if (numslash % 2 == 0) + { + if (inquote && psrc[1] == W('"')) + { + psrc++; /* Double quote inside quoted string */ + } + else + { + /* skip first quote char and copy second */ + copychar = 0; /* don't copy quote */ + inquote = !inquote; + } + } + numslash /= 2; /* divide numslash by two */ + } + + /* copy slashes */ + while (numslash--) + { + *pdst++ = W('\\'); + } + + /* if at end of arg, break loop */ + if (*psrc == W('\0') || (!inquote && (*psrc == W(' ') || *psrc == W('\t')))) + break; + + /* copy character into argument */ + if (copychar) + { + *pdst++ = *psrc; + } + ++psrc; + } + + /* null-terminate the argument */ + + *pdst++ = W('\0'); /* terminate string */ + } + + /* We put one last argument in -- a null ptr */ + argv[ (*pNumArgs) ] = NULL; + + // If we hit this assert, we overwrote our destination buffer. + // Since we're supposed to allocate for the worst + // case, either the parsing rules have changed or our worse case + // formula is wrong. + _ASSERTE((BYTE*)pdst <= (BYTE*)pAlloc + cbAlloc); + return argv; +} + +//====================================================================== +// This function returns true, if it can determine that the instruction pointer +// refers to a code address that belongs in the range of the given image. +BOOL IsIPInModule(PTR_VOID pModuleBaseAddress, PCODE ip) +{ + STATIC_CONTRACT_LEAF; + SUPPORTS_DAC; + + struct Param + { + PTR_VOID pModuleBaseAddress; + PCODE ip; + BOOL fRet; + } param; + param.pModuleBaseAddress = pModuleBaseAddress; + param.ip = ip; + param.fRet = FALSE; + +// UNIXTODO: implement a proper version for PAL +#ifdef HOST_WINDOWS + PAL_TRY(Param *, pParam, ¶m) + { + PTR_BYTE pBase = dac_cast<PTR_BYTE>(pParam->pModuleBaseAddress); + + PTR_IMAGE_DOS_HEADER pDOS = NULL; + PTR_IMAGE_NT_HEADERS pNT = NULL; + USHORT cbOptHdr; + PCODE baseAddr; + + // + // First, must validate the format of the PE headers to make sure that + // the fields we're interested in using exist in the image. + // + + // Validate the DOS header. + pDOS = PTR_IMAGE_DOS_HEADER(pBase); + if (pDOS->e_magic != VAL16(IMAGE_DOS_SIGNATURE) || + pDOS->e_lfanew == 0) + { + goto lDone; + } + + // Validate the NT header + pNT = PTR_IMAGE_NT_HEADERS(pBase + VAL32(pDOS->e_lfanew)); + + if (pNT->Signature != VAL32(IMAGE_NT_SIGNATURE)) + { + goto lDone; + } + + // Validate that the optional header is large enough to contain the fields + // we're interested, namely IMAGE_OPTIONAL_HEADER::SizeOfImage. The reason + // we don't just check that SizeOfOptionalHeader == IMAGE_SIZEOF_NT_OPTIONAL_HEADER + // is due to VSW443590, which states that the extensibility of this structure + // is such that it is possible to include only a portion of the optional header. + cbOptHdr = pNT->FileHeader.SizeOfOptionalHeader; + + // Check that the magic field is contained by the optional header and set to the correct value. + if (cbOptHdr < (offsetof(IMAGE_OPTIONAL_HEADER, Magic) + sizeofmember(IMAGE_OPTIONAL_HEADER, Magic)) || + pNT->OptionalHeader.Magic != VAL16(IMAGE_NT_OPTIONAL_HDR_MAGIC)) + { + goto lDone; + } + + // Check that the SizeOfImage is contained by the optional header. + if (cbOptHdr < (offsetof(IMAGE_OPTIONAL_HEADER, SizeOfImage) + sizeofmember(IMAGE_OPTIONAL_HEADER, SizeOfImage))) + { + goto lDone; + } + + // + // The real check + // + + baseAddr = dac_cast<PCODE>(pBase); + if ((pParam->ip < baseAddr) || (pParam->ip >= (baseAddr + VAL32(pNT->OptionalHeader.SizeOfImage)))) + { + goto lDone; + } + + pParam->fRet = TRUE; + +lDone: ; + } + PAL_EXCEPT (EXCEPTION_EXECUTE_HANDLER) + { + } + PAL_ENDTRY +#endif // HOST_WINDOWS + + return param.fRet; +} + +namespace Clr +{ +namespace Util +{ +#ifdef HOST_WINDOWS + // Struct used to scope suspension of client impersonation for the current thread. + // https://docs.microsoft.com/en-us/windows/desktop/secauthz/client-impersonation + class SuspendImpersonation + { + public: + SuspendImpersonation() + : _token(nullptr) + { + // The approach used here matches what is used elsewhere in CLR (RevertIfImpersonated). + // In general, OpenThreadToken fails with ERROR_NO_TOKEN if impersonation is not active, + // fails with ERROR_CANT_OPEN_ANONYMOUS if anonymous impersonation is active, and otherwise + // succeeds and returns the active impersonation token. + BOOL res = ::OpenThreadToken(::GetCurrentThread(), TOKEN_IMPERSONATE, /* OpenAsSelf */ TRUE, &_token); + if (res != FALSE) + { + ::RevertToSelf(); + } + else + { + _token = nullptr; + } + } + + ~SuspendImpersonation() + { + if (_token != nullptr) + ::SetThreadToken(nullptr, _token); + } + + private: + HandleHolder _token; + }; + + struct ProcessIntegrityResult + { + BOOL Success; + DWORD Integrity; + HRESULT LastError; + + HRESULT RecordAndReturnError(HRESULT hr) + { + LastError = hr; + return hr; + } + }; + + // The system calls in this code can fail if run with reduced privileges. + // It is the caller's responsibility to choose an appropriate default in the event + // that this function fails to retrieve the current process integrity. + HRESULT GetCurrentProcessIntegrity(DWORD *integrity) + { + static ProcessIntegrityResult s_Result = { FALSE, 0, S_FALSE }; + + if (FALSE != InterlockedCompareExchangeT(&s_Result.Success, FALSE, FALSE)) + { + *integrity = s_Result.Integrity; + return S_OK; + } + + // Temporarily suspend impersonation (if possible) while computing the integrity level. + // If impersonation is active, the OpenProcessToken call below will check the impersonation + // token against the process token ACL, and will generally fail with ERROR_ACCESS_DENIED if + // the impersonation token is less privileged than this process's primary token. + Clr::Util::SuspendImpersonation si; + + HandleHolder hToken; + if(!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) + return s_Result.RecordAndReturnError(HRESULT_FROM_GetLastError()); + + DWORD dwSize = 0; + DWORD err = ERROR_SUCCESS; + if(!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)TokenIntegrityLevel, nullptr, 0, &dwSize)) + err = GetLastError(); + + // We need to make sure that GetTokenInformation failed in a predictable manner so we know that + // dwSize has the correct buffer size in it. + if (err != ERROR_INSUFFICIENT_BUFFER || dwSize == 0) + return s_Result.RecordAndReturnError((err == ERROR_SUCCESS) ? E_FAIL : HRESULT_FROM_WIN32(err)); + + NewArrayHolder<BYTE> pLabel = new (nothrow) BYTE[dwSize]; + if (pLabel == NULL) + return s_Result.RecordAndReturnError(E_OUTOFMEMORY); + + if(!GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)TokenIntegrityLevel, pLabel, dwSize, &dwSize)) + return s_Result.RecordAndReturnError(HRESULT_FROM_GetLastError()); + + TOKEN_MANDATORY_LABEL *ptml = (TOKEN_MANDATORY_LABEL *)(void*)pLabel; + PSID psidIntegrityLevelLabel = ptml->Label.Sid; + + s_Result.Integrity = *GetSidSubAuthority(psidIntegrityLevelLabel, (*GetSidSubAuthorityCount(psidIntegrityLevelLabel) - 1)); + *integrity = s_Result.Integrity; + InterlockedExchangeT(&s_Result.Success, TRUE); + return S_OK; + } + +namespace Reg +{ + HRESULT ReadStringValue(HKEY hKey, LPCWSTR wszSubKeyName, LPCWSTR wszValueName, SString & ssValue) + { + STANDARD_VM_CONTRACT; + + if (hKey == NULL) + { + return E_INVALIDARG; + } + + RegKeyHolder hTargetKey; + if (wszSubKeyName == NULL || *wszSubKeyName == W('\0')) + { // No subkey was requested, use hKey as the resolved key. + hTargetKey = hKey; + hTargetKey.SuppressRelease(); + } + else + { // Try to open the specified subkey. + if (WszRegOpenKeyEx(hKey, wszSubKeyName, 0, KEY_READ, &hTargetKey) != ERROR_SUCCESS) + return REGDB_E_CLASSNOTREG; + } + + DWORD type; + DWORD size; + if ((WszRegQueryValueEx(hTargetKey, wszValueName, 0, &type, 0, &size) == ERROR_SUCCESS) && + type == REG_SZ && size > 0) + { + LPWSTR wszValueBuf = ssValue.OpenUnicodeBuffer(static_cast<COUNT_T>((size / sizeof(WCHAR)) - 1)); + LONG lResult = WszRegQueryValueEx( + hTargetKey, + wszValueName, + 0, + 0, + reinterpret_cast<LPBYTE>(wszValueBuf), + &size); + + _ASSERTE(lResult == ERROR_SUCCESS); + if (lResult == ERROR_SUCCESS) + { + // Can't count on the returned size being accurate - I've seen at least + // one string with an extra NULL at the end that will cause the resulting + // SString to count the extra NULL as part of the string. An extra + // terminating NULL is not a legitimate scenario for REG_SZ - this must + // be done using REG_MULTI_SZ - however this was tolerated in the + // past and so it would be a breaking change to stop doing so. + _ASSERTE(wcslen(wszValueBuf) <= (size / sizeof(WCHAR)) - 1); + ssValue.CloseBuffer((COUNT_T)wcsnlen(wszValueBuf, (size_t)size)); + } + else + { + ssValue.CloseBuffer(0); + return HRESULT_FROM_WIN32(lResult); + } + + return S_OK; + } + else + { + return REGDB_E_KEYMISSING; + } + } + + HRESULT ReadStringValue(HKEY hKey, LPCWSTR wszSubKey, LPCWSTR wszName, __deref_out __deref_out_z LPWSTR* pwszValue) + { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + } CONTRACTL_END; + + HRESULT hr = S_OK; + EX_TRY + { + StackSString ssValue; + if (SUCCEEDED(hr = ReadStringValue(hKey, wszSubKey, wszName, ssValue))) + { + *pwszValue = new WCHAR[ssValue.GetCount() + 1]; + wcscpy_s(*pwszValue, ssValue.GetCount() + 1, ssValue.GetUnicode()); + } + } + EX_CATCH_HRESULT(hr); + return hr; + } +} // namespace Reg + +namespace Com +{ + namespace __imp + { + __success(return == S_OK) + static + HRESULT FindSubKeyDefaultValueForCLSID(REFCLSID rclsid, LPCWSTR wszSubKeyName, SString & ssValue) + { + STANDARD_VM_CONTRACT; + + WCHAR wszClsid[39]; + if (GuidToLPWSTR(rclsid, wszClsid, NumItems(wszClsid)) == 0) + return E_UNEXPECTED; + + StackSString ssKeyName; + ssKeyName.Append(SL(W("CLSID\\"))); + ssKeyName.Append(wszClsid); + ssKeyName.Append(SL(W("\\"))); + ssKeyName.Append(wszSubKeyName); + + // Query HKCR first to retain backwards compat with previous implementation where HKCR was only queried. + // This is being done due to registry caching. This value will be used if the process integrity is medium or less. + HRESULT hkcrResult = Clr::Util::Reg::ReadStringValue(HKEY_CLASSES_ROOT, ssKeyName.GetUnicode(), nullptr, ssValue); + + // HKCR is a virtualized registry hive that weaves together HKCU\Software\Classes and HKLM\Software\Classes + // Processes with high integrity or greater should only read from HKLM to avoid being hijacked by medium + // integrity processes writing to HKCU. + DWORD integrity = SECURITY_MANDATORY_PROTECTED_PROCESS_RID; + HRESULT hr = Clr::Util::GetCurrentProcessIntegrity(&integrity); + if (hr != S_OK) + { + // In the event that we are unable to get the current process integrity, + // we assume that this process is running in an elevated state. + // GetCurrentProcessIntegrity may fail if the process has insufficient rights to get the integrity level + integrity = SECURITY_MANDATORY_PROTECTED_PROCESS_RID; + } + + if (integrity > SECURITY_MANDATORY_MEDIUM_RID) + { + Clr::Util::SuspendImpersonation si; + + // Clear the previous HKCR queried value + ssValue.Clear(); + + // Force to use HKLM + StackSString ssHklmKeyName(SL(W("SOFTWARE\\Classes\\"))); + ssHklmKeyName.Append(ssKeyName); + return Clr::Util::Reg::ReadStringValue(HKEY_LOCAL_MACHINE, ssHklmKeyName.GetUnicode(), nullptr, ssValue); + } + + return hkcrResult; + } + } + + HRESULT FindInprocServer32UsingCLSID(REFCLSID rclsid, SString & ssInprocServer32Name) + { + WRAPPER_NO_CONTRACT; + return __imp::FindSubKeyDefaultValueForCLSID(rclsid, W("InprocServer32"), ssInprocServer32Name); + } +} // namespace Com +#endif // HOST_WINDOWS + +} // namespace Util +} // namespace Clr diff --git a/src/coreclr/utilcode/util_nodependencies.cpp b/src/coreclr/utilcode/util_nodependencies.cpp new file mode 100644 index 00000000000..fb812b21ba6 --- /dev/null +++ b/src/coreclr/utilcode/util_nodependencies.cpp @@ -0,0 +1,814 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// Util_NoDependencies.cpp +// + +// +// This contains a bunch of C++ utility classes needed also for UtilCode without dependencies +// (standalone version without CLR/clr.dll/mscoree.dll dependencies). +// +//***************************************************************************** + +#include "stdafx.h" +#include "utilcode.h" +#include "ex.h" + +#if !defined(FEATURE_UTILCODE_NO_DEPENDENCIES) || defined(_DEBUG) + +RunningOnStatusEnum gRunningOnStatus = RUNNING_ON_STATUS_UNINITED; + +#define NON_SUPPORTED_PLATFORM_MSGBOX_TITLE W("Platform not supported") +#define NON_SUPPORTED_PLATFORM_MSGBOX_TEXT W("The minimum supported platform is Windows 7") +#define NON_SUPPORTED_PLATFORM_TERMINATE_ERROR_CODE 0xBAD1BAD1 + +//***************************************************************************** +// One time initialization of the OS version +//***************************************************************************** +void InitRunningOnVersionStatus () +{ +#ifndef TARGET_UNIX + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + BOOL fSupportedPlatform = FALSE; + OSVERSIONINFOEX sVer; + DWORDLONG dwlConditionMask; + + ZeroMemory(&sVer, sizeof(OSVERSIONINFOEX)); + sVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + sVer.dwMajorVersion = 6; + sVer.dwMinorVersion = 2; + sVer.dwPlatformId = VER_PLATFORM_WIN32_NT; + + + dwlConditionMask = 0; + dwlConditionMask = VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL); + dwlConditionMask = VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); + dwlConditionMask = VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); + + if(VerifyVersionInfo(&sVer, VER_MAJORVERSION | VER_PLATFORMID | VER_MINORVERSION, dwlConditionMask)) + { + gRunningOnStatus = RUNNING_ON_WIN8; + fSupportedPlatform = TRUE; + goto CHECK_SUPPORTED; + } + + + ZeroMemory(&sVer, sizeof(OSVERSIONINFOEX)); + sVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + sVer.dwMajorVersion = 6; + sVer.dwMinorVersion = 1; + sVer.dwPlatformId = VER_PLATFORM_WIN32_NT; + + + dwlConditionMask = 0; + dwlConditionMask = VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL); + dwlConditionMask = VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); + dwlConditionMask = VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); + + if(VerifyVersionInfo(&sVer, VER_MAJORVERSION | VER_PLATFORMID | VER_MINORVERSION, dwlConditionMask)) + { + gRunningOnStatus = RUNNING_ON_WIN7; + fSupportedPlatform = TRUE; + goto CHECK_SUPPORTED; + } + +CHECK_SUPPORTED: + + if (!fSupportedPlatform) + { + // The current platform isn't supported. Display a message box to this effect and exit. + // Note that this should never happen since the .NET Fx setup should not install on + // non supported platforms (which is why the message box text isn't localized). + UtilMessageBoxCatastrophicNonLocalized(NON_SUPPORTED_PLATFORM_MSGBOX_TEXT, NON_SUPPORTED_PLATFORM_MSGBOX_TITLE, MB_OK | MB_ICONERROR, TRUE); + TerminateProcess(GetCurrentProcess(), NON_SUPPORTED_PLATFORM_TERMINATE_ERROR_CODE); + } +#endif // TARGET_UNIX +} // InitRunningOnVersionStatus + +#ifndef HOST_64BIT +//------------------------------------------------------------------------------ +// Returns TRUE if we are running on a 64-bit OS in WoW, FALSE otherwise. +BOOL RunningInWow64() +{ + #ifdef TARGET_UNIX + return FALSE; + #else + static int s_Wow64Process; + + if (s_Wow64Process == 0) + { + BOOL fWow64Process = FALSE; + + if (!IsWow64Process(GetCurrentProcess(), &fWow64Process)) + fWow64Process = FALSE; + + s_Wow64Process = fWow64Process ? 1 : -1; + } + + return (s_Wow64Process == 1) ? TRUE : FALSE; + #endif +} +#endif + +#ifndef TARGET_UNIX +//------------------------------------------------------------------------------ +// +// GetRegistryLongValue - Reads a configuration LONG value from the registry. +// +// Parameters +// hKeyParent -- Parent key +// szKey -- key to open +// szName -- name of the value +// pValue -- put value here, if found +// fReadNonVirtualizedKey -- whether to read 64-bit hive on WOW64 +// +// Returns +// TRUE -- If the value was found and read +// FALSE -- The value was not found, could not be read, or was not DWORD +// +// Exceptions +// None +//------------------------------------------------------------------------------ +BOOL GetRegistryLongValue(HKEY hKeyParent, + LPCWSTR szKey, + LPCWSTR szName, + long *pValue, + BOOL fReadNonVirtualizedKey) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DWORD ret; // Return value from registry operation. + HKEYHolder hkey; // Registry key. + long iValue; // The value to read. + DWORD iType; // Type of value to get. + DWORD iSize; // Size of buffer. + REGSAM samDesired = KEY_READ; // Desired access rights to the key + + if (fReadNonVirtualizedKey) + { + if (RunningInWow64()) + { + samDesired |= KEY_WOW64_64KEY; + } + } + + ret = WszRegOpenKeyEx(hKeyParent, szKey, 0, samDesired, &hkey); + + // If we opened the key, see if there is a value. + if (ret == ERROR_SUCCESS) + { + iType = REG_DWORD; + iSize = sizeof(long); + ret = WszRegQueryValueEx(hkey, szName, NULL, &iType, reinterpret_cast<BYTE*>(&iValue), &iSize); + + if (ret == ERROR_SUCCESS && iType == REG_DWORD && iSize == sizeof(long)) + { // We successfully read a DWORD value. + *pValue = iValue; + return TRUE; + } + } + + return FALSE; +} // GetRegistryLongValue + +//---------------------------------------------------------------------------- +// +// GetCurrentModuleFileName - Retrieve the current module's filename +// +// Arguments: +// pBuffer - output string buffer +// +// Return Value: +// S_OK on success, else detailed error code. +// +// Note: +// +//---------------------------------------------------------------------------- +HRESULT GetCurrentModuleFileName(SString& pBuffer) +{ + LIMITED_METHOD_CONTRACT; + + + DWORD ret = WszGetModuleFileName(NULL, pBuffer); + + if (ret == 0) + { + return E_UNEXPECTED; + } + + + return S_OK; +} + +//---------------------------------------------------------------------------- +// +// IsCurrentModuleFileNameInAutoExclusionList - decide if the current module's filename +// is in the AutoExclusionList list +// +// Arguments: +// None +// +// Return Value: +// TRUE or FALSE +// +// Note: +// This function cannot be used in out of process scenarios like DAC because it +// looks at current module's filename. In OOP we want to use target process's +// module's filename. +// +//---------------------------------------------------------------------------- +BOOL IsCurrentModuleFileNameInAutoExclusionList() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HKEYHolder hKeyHolder; + + // Look for "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\AutoExclusionList" + DWORD ret = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, kUnmanagedDebuggerAutoExclusionListKey, 0, KEY_READ, &hKeyHolder); + + if (ret != ERROR_SUCCESS) + { + // there's not even an AutoExclusionList hive + return FALSE; + } + + PathString wszAppName; + + // Get the appname to look up in the exclusion or inclusion list. + if (GetCurrentModuleFileName(wszAppName) != S_OK) + { + // Assume it is not on the exclusion list if we cannot find the module's filename. + return FALSE; + } + + // Look in AutoExclusionList key for appName get the size of any value stored there. + DWORD value, valueType, valueSize = sizeof(value); + ret = WszRegQueryValueEx(hKeyHolder, wszAppName, 0, &valueType, reinterpret_cast<BYTE*>(&value), &valueSize); + if ((ret == ERROR_SUCCESS) && (valueType == REG_DWORD) && (value == 1)) + { + return TRUE; + } + + return FALSE; +} // IsCurrentModuleFileNameInAutoExclusionList + +//***************************************************************************** +// Retrieve information regarding what registered default debugger +//***************************************************************************** +void GetDebuggerSettingInfo(SString &ssDebuggerString, BOOL *pfAuto) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + EX_TRY + { + DWORD cchDebuggerString = MAX_LONGPATH; + INDEBUG(DWORD cchOldDebuggerString = cchDebuggerString); + + WCHAR * buf = ssDebuggerString.OpenUnicodeBuffer(cchDebuggerString); + HRESULT hr = GetDebuggerSettingInfoWorker(buf, &cchDebuggerString, pfAuto); + ssDebuggerString.CloseBuffer(cchDebuggerString); + + while (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + _ASSERTE(cchDebuggerString > cchOldDebuggerString); + INDEBUG(cchOldDebuggerString = cchDebuggerString); + + buf = ssDebuggerString.OpenUnicodeBuffer(cchDebuggerString); + hr = GetDebuggerSettingInfoWorker(buf, &cchDebuggerString, pfAuto); + ssDebuggerString.CloseBuffer(cchDebuggerString); + } + + if (*ssDebuggerString.GetUnicode() == W('\0')) + { + ssDebuggerString.Clear(); + } + + if (FAILED(hr)) + { + ssDebuggerString.Clear(); + if (pfAuto) + { + *pfAuto = FALSE; + } + } + } + EX_CATCH + { + ssDebuggerString.Clear(); + if (pfAuto) + { + *pfAuto = FALSE; + } + } + EX_END_CATCH(SwallowAllExceptions); +} // GetDebuggerSettingInfo + +//--------------------------------------------------------------------------------------- +// +// GetDebuggerSettingInfoWorker - retrieve information regarding what registered default debugger +// +// Arguments: +// * wszDebuggerString - [out] the string buffer to store the registered debugger launch +// string +// * pcchDebuggerString - [in, out] the size of string buffer in characters +// * pfAuto - [in] the flag to indicate whether the debugger neeeds to be launched +// automatically +// +// Return Value: +// HRESULT indicating success or failure. +// +// Notes: +// * wszDebuggerString can be NULL. When wszDebuggerString is NULL, pcchDebuggerString should +// * point to a DWORD of zero. pcchDebuggerString cannot be NULL, and the DWORD pointed by +// * pcchDebuggerString will store the used or required string buffer size in characters. +HRESULT GetDebuggerSettingInfoWorker(__out_ecount_part_opt(*pcchDebuggerString, *pcchDebuggerString) LPWSTR wszDebuggerString, DWORD * pcchDebuggerString, BOOL * pfAuto) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(pcchDebuggerString != NULL); + } + CONTRACTL_END; + + if ((pcchDebuggerString == NULL) || ((wszDebuggerString == NULL) && (*pcchDebuggerString != 0))) + { + return E_INVALIDARG; + } + + // Initialize the output values before we start. + if ((wszDebuggerString != NULL) && (*pcchDebuggerString > 0)) + { + *wszDebuggerString = W('\0'); + } + + if (pfAuto != NULL) + { + *pfAuto = FALSE; + } + + HKEYHolder hKeyHolder; + + // Look for "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug" + DWORD ret = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, kUnmanagedDebuggerKey, 0, KEY_READ, &hKeyHolder); + + if (ret != ERROR_SUCCESS) + { // Wow, there's not even an AeDebug hive, so no native debugger, no auto. + return S_OK; + } + + // Look in AeDebug key for "Debugger"; get the size of any value stored there. + DWORD valueType, valueSize = 0; + ret = WszRegQueryValueEx(hKeyHolder, kUnmanagedDebuggerValue, 0, &valueType, 0, &valueSize); + + _ASSERTE(pcchDebuggerString != NULL); + if ((wszDebuggerString == NULL) || (*pcchDebuggerString < valueSize / sizeof(WCHAR))) + { + *pcchDebuggerString = valueSize / sizeof(WCHAR) + 1; + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + *pcchDebuggerString = valueSize / sizeof(WCHAR); + + // The size of an empty string with the null terminator is 2. + BOOL fIsDebuggerStringEmptry = valueSize <= 2 ? TRUE : FALSE; + + if ((ret != ERROR_SUCCESS) || (valueType != REG_SZ) || fIsDebuggerStringEmptry) + { + return S_OK; + } + + _ASSERTE(wszDebuggerString != NULL); + ret = WszRegQueryValueEx(hKeyHolder, kUnmanagedDebuggerValue, NULL, NULL, reinterpret_cast< LPBYTE >(wszDebuggerString), &valueSize); + if (ret != ERROR_SUCCESS) + { + *wszDebuggerString = W('\0'); + return S_OK; + } + + // The callers are in nothrow scope, so we must swallow exceptions and reset the output parameters to the + // default values if exceptions like OOM ever happen. + EX_TRY + { + if (pfAuto != NULL) + { + BOOL fAuto = FALSE; + + // Get the appname to look up in DebugApplications key. + PathString wzAppName; + long iValue; + + // Check DebugApplications setting + if ((SUCCEEDED(GetCurrentModuleFileName(wzAppName))) && + ( + GetRegistryLongValue(HKEY_LOCAL_MACHINE, kDebugApplicationsPoliciesKey, wzAppName, &iValue, TRUE) || + GetRegistryLongValue(HKEY_LOCAL_MACHINE, kDebugApplicationsKey, wzAppName, &iValue, TRUE) || + GetRegistryLongValue(HKEY_CURRENT_USER, kDebugApplicationsPoliciesKey, wzAppName, &iValue, TRUE) || + GetRegistryLongValue(HKEY_CURRENT_USER, kDebugApplicationsKey, wzAppName, &iValue, TRUE) + ) && + (iValue == 1)) + { + fAuto = TRUE; + } + else + { + // Look in AeDebug key for "Auto"; get the size of any value stored there. + ret = WszRegQueryValueEx(hKeyHolder, kUnmanagedDebuggerAutoValue, 0, &valueType, 0, &valueSize); + if ((ret == ERROR_SUCCESS) && (valueType == REG_SZ) && (valueSize / sizeof(WCHAR) < MAX_LONGPATH)) + { + WCHAR wzAutoKey[MAX_LONGPATH]; + valueSize = NumItems(wzAutoKey) * sizeof(WCHAR); + WszRegQueryValueEx(hKeyHolder, kUnmanagedDebuggerAutoValue, NULL, NULL, reinterpret_cast< LPBYTE >(wzAutoKey), &valueSize); + + // The OS's behavior is to consider Auto to be FALSE unless the first character is set + // to 1. They don't take into consideration the following characters. Also if the value + // isn't present they assume an Auto value of FALSE. + if ((wzAutoKey[0] == W('1')) && !IsCurrentModuleFileNameInAutoExclusionList()) + { + fAuto = TRUE; + } + } + } + + *pfAuto = fAuto; + } + } + EX_CATCH + { + if ((wszDebuggerString != NULL) && (*pcchDebuggerString > 0)) + { + *wszDebuggerString = W('\0'); + } + + if (pfAuto != NULL) + { + *pfAuto = FALSE; + } + } + EX_END_CATCH(SwallowAllExceptions); + + return S_OK; +} // GetDebuggerSettingInfoWorker +#endif // TARGET_UNIX + +#endif //!defined(FEATURE_UTILCODE_NO_DEPENDENCIES) || defined(_DEBUG) + +//***************************************************************************** +// Convert hex value into a wide string of hex digits +//***************************************************************************** +HRESULT GetStr( + DWORD hHexNum, + __out_ecount((cbHexNum * 2)) LPWSTR szHexNum, + DWORD cbHexNum) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE (szHexNum); + cbHexNum *= 2; // each nibble is a char + while (cbHexNum != 0) + { + DWORD thisHexDigit = hHexNum % 16; + hHexNum /= 16; + cbHexNum--; + if (thisHexDigit < 10) + { + *(szHexNum+cbHexNum) = (BYTE)(thisHexDigit + W('0')); + } + else + { + *(szHexNum+cbHexNum) = (BYTE)(thisHexDigit - 10 + W('A')); + } + } + return S_OK; +} + +//***************************************************************************** +// Convert a GUID into a pointer to a Wide char string +//***************************************************************************** +int +GuidToLPWSTR( + GUID Guid, // The GUID to convert. + __out_ecount(cchGuid) LPWSTR szGuid, // String into which the GUID is stored + DWORD cchGuid) // Count in wchars +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int i; + + // successive fields break the GUID into the form DWORD-WORD-WORD-WORD-WORD.DWORD + // covering the 128-bit GUID. The string includes enclosing braces, which are an OLE convention. + + if (cchGuid < 39) // 38 chars + 1 null terminating. + return 0; + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^ + szGuid[0] = W('{'); + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^^^^^ + if (FAILED (GetStr(Guid.Data1, szGuid+1 , 4))) return 0; + + szGuid[9] = W('-'); + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^ + if (FAILED (GetStr(Guid.Data2, szGuid+10, 2))) return 0; + + szGuid[14] = W('-'); + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^ + if (FAILED (GetStr(Guid.Data3, szGuid+15, 2))) return 0; + + szGuid[19] = W('-'); + + // Get the last two fields (which are byte arrays). + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^ + for (i=0; i < 2; ++i) + if (FAILED(GetStr(Guid.Data4[i], szGuid + 20 + (i * 2), 1))) + return (0); + + szGuid[24] = W('-'); + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^^^^^^^^^ + for (i=0; i < 6; ++i) + if (FAILED(GetStr(Guid.Data4[i+2], szGuid + 25 + (i * 2), 1))) + return (0); + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^ + szGuid[37] = W('}'); + szGuid[38] = W('\0'); + + return 39; +} // GuidToLPWSTR + +//***************************************************************************** +// Convert wide string of (at most eight) hex digits into a hex value +//***************************************************************************** +HRESULT GetHex( + DWORD * phHexNum, + __in_ecount((cbHexNum * 2)) LPCWSTR szHexNum, + DWORD cbHexNum) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + _ASSERTE (szHexNum && phHexNum); + _ASSERTE(cbHexNum == 1 || cbHexNum == 2 || cbHexNum == 4); + + cbHexNum *= 2; // each nibble is a char + DWORD val = 0; + for (DWORD i = 0; i < cbHexNum; ++i) + { + DWORD nibble = 0; + if (szHexNum[i] >= W('0') && szHexNum[i] <= W('9')) + { + nibble = szHexNum[i] - '0'; + } + else if (szHexNum[i] >= W('A') && szHexNum[i] <= W('F')) + { + nibble = 10 + szHexNum[i] - 'A'; + } + else if (szHexNum[i] >= W('a') && szHexNum[i] <= W('f')) + { + nibble = 10 + szHexNum[i] - 'a'; + } + else + { + return E_FAIL; + } + val = (val << 4) + nibble; + } + *phHexNum = val; + return S_OK; +} + +//***************************************************************************** +// Parse a Wide char string into a GUID +//***************************************************************************** +BOOL +LPWSTRToGuid( + GUID * Guid, // [OUT] The GUID to fill in + __in_ecount(cchGuid) LPCWSTR szGuid, // [IN] String to parse + DWORD cchGuid) // [IN] Count in wchars in string +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + int i; + DWORD dw; + + // successive fields break the GUID into the form DWORD-WORD-WORD-WORD-WORD.DWORD + // covering the 128-bit GUID. The string includes enclosing braces, which are an OLE convention. + + if (cchGuid < 38) // 38 chars + 1 null terminating. + return FALSE; + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^ + if (szGuid[0] != W('{')) return FALSE; + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^^^^^ + if (FAILED (GetHex(&dw, szGuid+1 , 4))) return FALSE; + Guid->Data1 = dw; + + if (szGuid[9] != W('-')) return FALSE; + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^ + if (FAILED (GetHex(&dw, szGuid+10, 2))) return FALSE; + Guid->Data2 = (WORD)dw; + + if (szGuid[14] != W('-')) return FALSE; + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^ + if (FAILED (GetHex(&dw, szGuid+15, 2))) return FALSE; + Guid->Data3 = (WORD)dw; + + if (szGuid[19] != W('-')) return FALSE; + + // Get the last two fields (which are byte arrays). + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^ + for (i=0; i < 2; ++i) + { + if (FAILED(GetHex(&dw, szGuid + 20 + (i * 2), 1))) return FALSE; + Guid->Data4[i] = (BYTE)dw; + } + + if (szGuid[24] != W('-')) return FALSE; + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^^^^^^^^^^^^ + for (i=0; i < 6; ++i) + { + if (FAILED(GetHex(&dw, szGuid + 25 + (i * 2), 1))) return FALSE; + Guid->Data4[i+2] = (BYTE)dw; + } + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + // ^ + if (szGuid[37] != W('}')) return FALSE; + + return TRUE; +} // GuidToLPWSTR + + +#ifdef _DEBUG +// Always write regardless of registry. +void _cdecl DbgWriteEx(LPCTSTR szFmt, ...) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + WCHAR rcBuff[1024]; + va_list marker; + + va_start(marker, szFmt); + _vsnwprintf_s(rcBuff, _countof(rcBuff), _TRUNCATE, szFmt, marker); + va_end(marker); + WszOutputDebugString(rcBuff); +} +#endif //_DEBUG + +/**************************************************************************/ +void ConfigDWORD::init(const CLRConfig::ConfigDWORDInfo & info) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // make sure that the memory was zero initialized + _ASSERTE(m_inited == 0 || m_inited == 1); + + m_value = CLRConfig::GetConfigValue(info); + m_inited = 1; +} + +//--------------------------------------------------------------------------------------- +// +// Takes a const input string, and returns the start & size of the substring that has all +// leading and trailing whitespace removed. The original string is not modified. +// +// Arguments: +// * pwsz - [in] points to const string we want to trim; [out] points to beginning +// of trimmed substring of input string +// * pcch - [in] Points to length in chars of input string (not counting null +// terminator); [out] Points to length in chars of trimmed substring (not +// counting null terminator) +// +void TrimWhiteSpace(__deref_inout_ecount(*pcch) LPCWSTR *pwsz, __inout LPDWORD pcch) +{ + LIMITED_METHOD_DAC_CONTRACT; + + _ASSERTE (pwsz != NULL); + _ASSERTE (*pwsz != NULL); + _ASSERTE (pcch != NULL); + + DWORD cch = *pcch; + LPCWSTR wszBeginning = *pwsz; + LPCWSTR wszEnd = wszBeginning + (cch - 1); + + while ((cch != 0) && iswspace(*wszBeginning)) + { + wszBeginning++; + cch--; + } + + while ((cch != 0) && iswspace(*wszEnd)) + { + wszEnd--; + cch--; + } + + *pwsz = wszBeginning; + *pcch = cch; +} // TrimWhiteSpace + +BOOL ThreadWillCreateGuardPage(SIZE_T sizeReservedStack, SIZE_T sizeCommitedStack) +{ + // We need to make sure there will be a reserved but never committed page at the end + // of the stack. We do here the check NT does when it creates the user stack to decide + // if there is going to be a guard page. However, that is not enough, as if we only + // have a guard page, we have nothing to protect us from going pass it. Well, in + // fact, there is something that we will protect you, there are certain places + // (RTLUnwind) in NT that will check that the current frame is within stack limits. + // If we are not it will bomb out. We will also bomb out if we touch the hard guard + // page. + // + // For situation B, teb->StackLimit is at the beggining of the user stack (ie + // before updating StackLimit it checks if it was able to create a new guard page, + // in this case, it can't), which makes the check fail in RtlUnwind. + // + // Situation A [ Hard guard page | Guard page | user stack] + // + // Situation B [ Guard page | User stack ] + // + // Situation C [ User stack ( no room for guard page) ] + // + // Situation D (W9x) : Guard page or not, w9x has a 64k reserved region below + // the stack, we don't need any checks at all + // + // We really want to be in situation A all the time, so we add one more page + // to our requirements (we require guard page + hard guard) + + SYSTEM_INFO sysInfo; + ::GetSystemInfo(&sysInfo); + + // OS rounds up sizes the following way to decide if it marks a guard page + sizeReservedStack = ALIGN(sizeReservedStack, ((size_t)sysInfo.dwAllocationGranularity)); // Allocation granularity + sizeCommitedStack = ALIGN(sizeCommitedStack, ((size_t)sysInfo.dwPageSize)); // Page Size + + // OS wont create guard page, we can't execute managed code safely. + // We also have to make sure we have a 'hard' guard, thus we add another + // page to the memory we would need comitted. + // That is, the following code will check if sizeReservedStack is at least 2 pages + // more than sizeCommitedStack. + return (sizeReservedStack > sizeCommitedStack + ((size_t)sysInfo.dwPageSize)); +} // ThreadWillCreateGuardPage + diff --git a/src/coreclr/utilcode/utilmessagebox.cpp b/src/coreclr/utilcode/utilmessagebox.cpp new file mode 100644 index 00000000000..5a8b1f5e2d6 --- /dev/null +++ b/src/coreclr/utilcode/utilmessagebox.cpp @@ -0,0 +1,412 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// UtilMessageBox.cpp +// + +// +// This module contains the message box utility code for the CLR. It is used +// by code in the CLR itself as well as other tools that build in the CLR tree. +// For message boxes inside the ExecutionEngine, EEMessageBox must be used +// instead of the these APIs. +// +//***************************************************************************** +#include "stdafx.h" // Standard header. +#include <utilcode.h> // Utility helpers. +#include <corerror.h> +#include "clrversion.h" +#include "../dlls/mscorrc/resource.h" +#include "ex.h" +#if !defined(FEATURE_CORESYSTEM) +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WIN7 +#include "commctrl.h" +#endif + + +BOOL ShouldDisplayMsgBoxOnCriticalFailure() +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + +#ifdef _DEBUG + // To help find issues, we will always display dialogs for critical failures + // under debug builds. This includes asserts and other critical issues. + return TRUE; +#else + // Retrieve error mode + UINT last = SetErrorMode(0); + SetErrorMode(last); //set back to previous value + + // SEM_FAILCRITICALERRORS indicates that the system does not display the critical-error-handler + // message box. Instead, the system sends the error to the calling process. + return !(last & SEM_FAILCRITICALERRORS); +#endif // _DEBUG +} + + + + +// We'd like to use TaskDialogIndirect for asserts coming from managed code in particular +// to display the detailedText in a scrollable way. Also, we'd like to reuse the CLR's +// plumbing code for the rest of parts of the assert dialog. Note that the simple +// Win32 MessageBox does not support the detailedText value. +// If we later refactor MessageBoxImpl into its own DLL, move the lines referencing +// "Microsoft.Windows.Common-Controls" version 6 in stdafx.h as well. +int MessageBoxImpl( + HWND hWnd, // Handle to Owner Window + LPCWSTR message, // Message + LPCWSTR title, // Dialog box title + LPCWSTR detailedText, // Details like a stack trace, etc. + UINT uType) +{ + CONTRACTL + { + // May pump messages. Callers should be GC_TRIGGERS and MODE_PREEMPTIVE, + // but we can't include EE contracts here. + THROWS; + INJECT_FAULT(return IDCANCEL;); + + // Assert if none of MB_ICON is set + PRECONDITION((uType & MB_ICONMASK) != 0); + } + CONTRACTL_END; + + return WszMessageBox(hWnd, message, title, uType); +} + +int UtilMessageBoxVA( + HWND hWnd, // Handle to Owner Window + UINT uText, // Resource Identifier for Text message + UINT uTitle, // Resource Identifier for Title + UINT uType, // Style of MessageBox + BOOL displayForNonInteractive, // Display even if the process is running non interactive + BOOL showFileNameInTitle, // Flag to show FileName in Caption + va_list args) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return IDCANCEL;); + } + CONTRACTL_END; + + SString text; + SString title; + int result = IDCANCEL; + + EX_TRY + { + text.LoadResource(CCompRC::Error, uText); + title.LoadResource(CCompRC::Error, uTitle); + + result = UtilMessageBoxNonLocalizedVA(hWnd, (LPWSTR)text.GetUnicode(), + (LPWSTR)title.GetUnicode(), uType, displayForNonInteractive, showFileNameInTitle, NULL, args); + } + EX_CATCH + { + result = IDCANCEL; + } + EX_END_CATCH(SwallowAllExceptions); + + return result; +} + +int UtilMessageBoxNonLocalizedVA( + HWND hWnd, // Handle to Owner Window + LPCWSTR lpText, // Text message + LPCWSTR lpTitle, // Title + UINT uType, // Style of MessageBox + BOOL displayForNonInteractive, // Display even if the process is running non interactive + BOOL showFileNameInTitle, // Flag to show FileName in Caption + BOOL * pInputFromUser, // To distinguish between user pressing abort vs. assuming abort. + va_list args) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return IDCANCEL;); + + // Assert if none of MB_ICON is set + PRECONDITION((uType & MB_ICONMASK) != 0); + } + CONTRACTL_END; + + return UtilMessageBoxNonLocalizedVA(hWnd, lpText, lpTitle, NULL, uType, displayForNonInteractive, showFileNameInTitle, pInputFromUser, args); +} + +int UtilMessageBoxNonLocalizedVA( + HWND hWnd, // Handle to Owner Window + LPCWSTR lpText, // Text message + LPCWSTR lpTitle, // Title + LPCWSTR lpDetails,// Details like a stack trace, etc. + UINT uType, // Style of MessageBox + BOOL displayForNonInteractive, // Display even if the process is running non interactive + BOOL showFileNameInTitle, // Flag to show FileName in Caption + BOOL * pInputFromUser, // To distinguish between user pressing abort vs. assuming abort. + va_list args) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + INJECT_FAULT(return IDCANCEL;); + + // Assert if none of MB_ICON is set + PRECONDITION((uType & MB_ICONMASK) != 0); + } + CONTRACTL_END; + + int result = IDCANCEL; + if (pInputFromUser != NULL) + { + *pInputFromUser = FALSE; + } + + EX_TRY + { + StackSString formattedMessage; + StackSString formattedTitle; + SString details(lpDetails); + PathString fileName; + BOOL fDisplayMsgBox = TRUE; + + // Format message string using optional parameters + formattedMessage.VPrintf(lpText, args); + + // Try to get filename of Module and add it to title + if (showFileNameInTitle && WszGetModuleFileName(NULL, fileName)) + { + LPCWSTR wszName = NULL; + size_t cchName = 0; + + + + SplitPathInterior(fileName, NULL, NULL, NULL, NULL, &wszName, &cchName, NULL, NULL); + formattedTitle.Printf(W("%s - %s"), wszName, lpTitle); + } + else + { + formattedTitle.Set(lpTitle); + } + +#if !defined(FEATURE_UTILCODE_NO_DEPENDENCIES) + // If the current process isn't interactive (a service for example), then we report the message + // in the event log and via OutputDebugString. + // + // We may still however attempt to display the message box if the MB_SERVICE_NOTIFICATION + // message box style was specified. + if (!RunningInteractive()) + { + StackSString message; + + message.Printf(W(".NET Runtime version : %s - "), CLR_PRODUCT_VERSION_L); + if (lpTitle) + message.Append(lpTitle); + if (!formattedMessage.IsEmpty()) + message.Append(formattedMessage); + + ClrReportEvent(W(".NET Runtime"), + EVENTLOG_ERROR_TYPE, // event type + 0, // category zero + 1024, // event identifier + NULL, // no user security identifier + message.GetUnicode()); + + if(lpTitle != NULL) + WszOutputDebugString(lpTitle); + if(!formattedMessage.IsEmpty()) + WszOutputDebugString(formattedMessage); + + // If we are running as a service and displayForNonInteractive is FALSE then IDABORT is + // the best value to return as it will most likely cause callers of this API to abort the process. + // This is the right thing to do since attaching a debugger doesn't make much sense when the process isn't + // running in interactive mode. + if(!displayForNonInteractive) + { + fDisplayMsgBox = FALSE; + result = IDABORT; + } + else + { + // Include in the MB_DEFAULT_DESKTOP_ONLY style. + uType |= MB_DEFAULT_DESKTOP_ONLY; + } + } +#endif //!defined(FEATURE_UTILCODE_NO_DEPENDENCIES) + + if (fDisplayMsgBox) + { + // We normally want to set the reading direction (right-to-left etc.) based on the resources + // in use. However, outside the CLR (SELF_NO_HOST) we can't assume we have resources and + // in CORECLR we can't even necessarily expect that our CLR callbacks have been initialized. + // This code path is used for ASSERT dialogs. + + result = MessageBoxImpl(hWnd, formattedMessage, formattedTitle, details, uType); + + if (pInputFromUser != NULL) + { + *pInputFromUser = TRUE; + } + } + } + EX_CATCH + { + result = IDCANCEL; + } + EX_END_CATCH(SwallowAllExceptions); + + return result; +} + +int UtilMessageBox( + HWND hWnd, // Handle to Owner Window + UINT uText, // Resource Identifier for Text message + UINT uTitle, // Resource Identifier for Title + UINT uType, // Style of MessageBox + BOOL displayForNonInteractive, // Display even if the process is running non interactive + BOOL showFileNameInTitle, // Flag to show FileName in Caption + ...) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + va_list marker; + va_start(marker, showFileNameInTitle); + + int result = UtilMessageBoxVA(hWnd, uText, uTitle, uType, displayForNonInteractive, showFileNameInTitle, marker); + va_end( marker ); + + return result; +} + +int UtilMessageBoxNonLocalized( + HWND hWnd, // Handle to Owner Window + LPCWSTR lpText, // Text message + LPCWSTR lpTitle, // Title message + UINT uType, // Style of MessageBox + BOOL displayForNonInteractive, // Display even if the process is running non interactive + BOOL showFileNameInTitle, // Flag to show FileName in Caption + ... ) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + va_list marker; + va_start(marker, showFileNameInTitle); + + int result = UtilMessageBoxNonLocalizedVA( + hWnd, lpText, lpTitle, uType, displayForNonInteractive, showFileNameInTitle, NULL, marker); + va_end( marker ); + + return result; +} + +int UtilMessageBoxCatastrophic( + UINT uText, // Text for MessageBox + UINT uTitle, // Title for MessageBox + UINT uType, // Style of MessageBox + BOOL showFileNameInTitle, // Flag to show FileName in Caption + ...) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + va_list marker; + va_start(marker, showFileNameInTitle); + + int result = UtilMessageBoxCatastrophicVA(uText, uTitle, uType, showFileNameInTitle, marker); + va_end( marker ); + + return result; +} + +int UtilMessageBoxCatastrophicNonLocalized( + LPCWSTR lpText, // Text for MessageBox + LPCWSTR lpTitle, // Title for MessageBox + UINT uType, // Style of MessageBox + BOOL showFileNameInTitle, // Flag to show FileName in Caption + ...) +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + va_list marker; + va_start(marker, showFileNameInTitle); + + int result = UtilMessageBoxCatastrophicNonLocalizedVA(lpText, lpTitle, uType, showFileNameInTitle, marker); + va_end( marker ); + + return result; +} + +int UtilMessageBoxCatastrophicVA( + UINT uText, // Text for MessageBox + UINT uTitle, // Title for MessageBox + UINT uType, // Style of MessageBox + BOOL showFileNameInTitle, // Flag to show FileName in Caption + va_list args) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HWND hwnd = NULL; + + // We are already in a catastrophic situation so we can tolerate faults as well as GC mode violations to keep going. + CONTRACT_VIOLATION(FaultNotFatal | GCViolation | ModeViolation); + + if (!ShouldDisplayMsgBoxOnCriticalFailure()) + return IDABORT; + + // Add the MB_TASKMODAL style to indicate that the dialog should be displayed on top of the windows + // owned by the current thread and should prevent interaction with them until dismissed. + uType |= MB_TASKMODAL; + + return UtilMessageBoxVA(hwnd, uText, uTitle, uType, TRUE, showFileNameInTitle, args); +} + +int UtilMessageBoxCatastrophicNonLocalizedVA( + LPCWSTR lpText, // Text for MessageBox + LPCWSTR lpTitle, // Title for MessageBox + UINT uType, // Style of MessageBox + BOOL showFileNameInTitle, // Flag to show FileName in Caption + va_list args) // Additional Arguments +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + HWND hwnd = NULL; + + // We are already in a catastrophic situation so we can tolerate faults as well as GC mode violations to keep going. + CONTRACT_VIOLATION(FaultNotFatal | GCViolation | ModeViolation); + + if (!ShouldDisplayMsgBoxOnCriticalFailure()) + return IDABORT; + + // Add the MB_TASKMODAL style to indicate that the dialog should be displayed on top of the windows + // owned by the current thread and should prevent interaction with them until dismissed. + uType |= MB_TASKMODAL; + + return UtilMessageBoxNonLocalizedVA(hwnd, lpText, lpTitle, uType, TRUE, showFileNameInTitle, NULL, args); +} + diff --git a/src/coreclr/utilcode/utsem.cpp b/src/coreclr/utilcode/utsem.cpp new file mode 100644 index 00000000000..66544c5db73 --- /dev/null +++ b/src/coreclr/utilcode/utsem.cpp @@ -0,0 +1,505 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +/****************************************************************************** + FILE : UTSEM.CPP + + + + Purpose: Part of the utilities library for the VIPER project + + Abstract : Implements the UTSemReadWrite class. +------------------------------------------------------------------------------- +Revision History: + + +*******************************************************************************/ +#include "stdafx.h" +#include "clrhost.h" +#include "ex.h" + +#include <utsem.h> +#include "contract.h" + +// Consider replacing this with a #ifdef INTEROP_DEBUGGING +#if !defined(SELF_NO_HOST) && defined(TARGET_X86) && !defined(TARGET_UNIX) +// For Interop debugging, the UTSemReadWrite class must inform the debugger +// that this thread can't be suspended currently. See vm\util.hpp for the +// implementation of these methods. +void IncCantStopCount(); +void DecCantStopCount(); +#else +#define IncCantStopCount() +#define DecCantStopCount() +#endif // !SELF_NO_HOST && TARGET_X86 + +/****************************************************************************** +Definitions of the bit fields in UTSemReadWrite::m_dwFlag: + +Warning: The code assume that READER_MASK is in the low-order bits of the DWORD. +******************************************************************************/ + +const ULONG READERS_MASK = 0x000003FF; // field that counts number of readers +const ULONG READERS_INCR = 0x00000001; // amount to add to increment number of readers + +// The following field is 2 bits long to make it easier to catch errors. +// (If the number of writers ever exceeds 1, we've got problems.) +const ULONG WRITERS_MASK = 0x00000C00; // field that counts number of writers +const ULONG WRITERS_INCR = 0x00000400; // amount to add to increment number of writers + +const ULONG READWAITERS_MASK = 0x003FF000; // field that counts number of threads waiting to read +const ULONG READWAITERS_INCR = 0x00001000; // amount to add to increment number of read waiters + +const ULONG WRITEWAITERS_MASK = 0xFFC00000; // field that counts number of threads waiting to write +const ULONG WRITEWAITERS_INCR = 0x00400000; // amount to add to increment number of write waiters + +// ====================================================================================== +// Spinning support + +// Copy of definition from file:..\VM\spinlock.h +#define CALLER_LIMITS_SPINNING 0 + +#if (defined(SELF_NO_HOST) && !defined(CROSSGEN_COMPILE)) || (defined(TARGET_UNIX) && defined(DACCESS_COMPILE)) + +// When we do not have host, we just call OS - see file:..\VM\hosting.cpp#__SwitchToThread +BOOL __SwitchToThread(DWORD dwSleepMSec, DWORD dwSwitchCount) +{ + // This is just simple implementation that does not support full dwSwitchCount arg + _ASSERTE(dwSwitchCount == CALLER_LIMITS_SPINNING); + return SwitchToThread(); +} + +Volatile<BOOL> g_fInitializedGlobalSystemInfo = FALSE; + +// Global System Information +SYSTEM_INFO g_SystemInfo; + +// Configurable constants used across our spin locks +SpinConstants g_SpinConstants = { + 50, // dwInitialDuration + 40000, // dwMaximumDuration - ideally (20000 * max(2, numProc)) ... updated in code:InitializeSpinConstants_NoHost + 3, // dwBackoffFactor + 10, // dwRepetitions + 0 // dwMonitorSpinCount +}; + +inline void InitializeSpinConstants_NoHost() +{ + g_SpinConstants.dwMaximumDuration = max(2, g_SystemInfo.dwNumberOfProcessors) * 20000; +} + +#else //!SELF_NO_HOST || CROSSGEN_COMPILE + +// Use VM/CrossGen functions and variables +BOOL __SwitchToThread (DWORD dwSleepMSec, DWORD dwSwitchCount); +extern SYSTEM_INFO g_SystemInfo; +extern SpinConstants g_SpinConstants; + +#endif //!SELF_NO_HOST || CROSSGEN_COMPILE + +/****************************************************************************** +Function : UTSemReadWrite::UTSemReadWrite + +Abstract: Constructor. +******************************************************************************/ +UTSemReadWrite::UTSemReadWrite() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#if defined(SELF_NO_HOST) && !defined(CROSSGEN_COMPILE) + if (!g_fInitializedGlobalSystemInfo) + { + GetSystemInfo(&g_SystemInfo); + InitializeSpinConstants_NoHost(); + + g_fInitializedGlobalSystemInfo = TRUE; + } +#endif //SELF_NO_HOST && !CROSSGEN_COMPILE + + m_dwFlag = 0; + m_hReadWaiterSemaphore = NULL; + m_hWriteWaiterEvent = NULL; +} + + +/****************************************************************************** +Function : UTSemReadWrite::~UTSemReadWrite + +Abstract: Destructor +******************************************************************************/ +UTSemReadWrite::~UTSemReadWrite() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE_MSG((m_dwFlag == (ULONG)0), "Destroying a UTSemReadWrite while in use"); + + if (m_hReadWaiterSemaphore != NULL) + CloseHandle(m_hReadWaiterSemaphore); + + if (m_hWriteWaiterEvent != NULL) + CloseHandle(m_hWriteWaiterEvent); +} + +//======================================================================================= +// +// Initialize the lock (its semaphore and event) +// +HRESULT +UTSemReadWrite::Init() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + + _ASSERTE(m_hReadWaiterSemaphore == NULL); + _ASSERTE(m_hWriteWaiterEvent == NULL); + + m_hReadWaiterSemaphore = WszCreateSemaphore(NULL, 0, MAXLONG, NULL); + IfNullRet(m_hReadWaiterSemaphore); + + m_hWriteWaiterEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL); + IfNullRet(m_hWriteWaiterEvent); + + return S_OK; +} // UTSemReadWrite::Init + +/****************************************************************************** +Function : UTSemReadWrite::LockRead + +Abstract: Obtain a shared lock +******************************************************************************/ +HRESULT UTSemReadWrite::LockRead() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + // Inform CLR that the debugger shouldn't suspend this thread while + // holding this lock. + IncCantStopCount(); + + // First do some spinning - copied from file:..\VM\crst.cpp#CrstBase::SpinEnter + for (DWORD iter = 0; iter < g_SpinConstants.dwRepetitions; iter++) + { + DWORD i = g_SpinConstants.dwInitialDuration; + + do + { + DWORD dwFlag = m_dwFlag; + + if (dwFlag < READERS_MASK) + { // There are just readers in the play, try to add one more + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + READERS_INCR, dwFlag)) + { + goto ReadLockAcquired; + } + } + + if (g_SystemInfo.dwNumberOfProcessors <= 1) + { // We do not need to spin on a single processor + break; + } + + // Delay by approximately 2*i clock cycles (Pentium III). + YieldProcessorNormalizedForPreSkylakeCount(i); + + // exponential backoff: wait a factor longer in the next iteration + i *= g_SpinConstants.dwBackoffFactor; + } while (i < g_SpinConstants.dwMaximumDuration); + + __SwitchToThread(0, CALLER_LIMITS_SPINNING); + } + // Stop spinning + + // Start waiting + for (;;) + { + DWORD dwFlag = m_dwFlag; + + if (dwFlag < READERS_MASK) + { // There are just readers in the play, try to add one more + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + READERS_INCR, dwFlag)) + { + break; + } + } + else if ((dwFlag & READERS_MASK) == READERS_MASK) + { // The number of readers has reached the maximum (0x3ff), wait 1s + ClrSleepEx(1000, FALSE); + } + else if ((dwFlag & READWAITERS_MASK) == READWAITERS_MASK) + { // The number of readers waiting on semaphore has reached the maximum (0x3ff), wait 1s + ClrSleepEx(1000, FALSE); + } + else + { // Try to add waiting reader and then wait for signal + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + READWAITERS_INCR, dwFlag)) + { + WaitForSingleObjectEx(m_hReadWaiterSemaphore, INFINITE, FALSE); + break; + } + } + } + +ReadLockAcquired: + _ASSERTE ((m_dwFlag & READERS_MASK) != 0 && "reader count is zero after acquiring read lock"); + _ASSERTE ((m_dwFlag & WRITERS_MASK) == 0 && "writer count is nonzero after acquiring write lock"); + EE_LOCK_TAKEN(this); + + return S_OK; +} // UTSemReadWrite::LockRead + + + +/****************************************************************************** +Function : UTSemReadWrite::LockWrite + +Abstract: Obtain an exclusive lock +******************************************************************************/ +HRESULT UTSemReadWrite::LockWrite() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + // Inform CLR that the debugger shouldn't suspend this thread while + // holding this lock. + IncCantStopCount(); + + // First do some spinning - copied from file:..\VM\crst.cpp#CrstBase::SpinEnter + for (DWORD iter = 0; iter < g_SpinConstants.dwRepetitions; iter++) + { + DWORD i = g_SpinConstants.dwInitialDuration; + + do + { + DWORD dwFlag = m_dwFlag; + + if (dwFlag == 0) + { // No readers/writers in play, try to add a writer + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, WRITERS_INCR, dwFlag)) + { + goto WriteLockAcquired; + } + } + + if (g_SystemInfo.dwNumberOfProcessors <= 1) + { // We do not need to spin on a single processor + break; + } + + // Delay by approximately 2*i clock cycles (Pentium III). + YieldProcessorNormalizedForPreSkylakeCount(i); + + // exponential backoff: wait a factor longer in the next iteration + i *= g_SpinConstants.dwBackoffFactor; + } while (i < g_SpinConstants.dwMaximumDuration); + + __SwitchToThread(0, CALLER_LIMITS_SPINNING); + } + // Stop spinning + + // Start waiting + for (;;) + { + DWORD dwFlag = m_dwFlag; + + if (dwFlag == 0) + { // No readers/writers in play, try to add a writer + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, WRITERS_INCR, dwFlag)) + { + break; + } + } + else if ((dwFlag & WRITEWAITERS_MASK) == WRITEWAITERS_MASK) + { // The number of writers waiting on semaphore has reached the maximum (0x3ff), wait 1s + ClrSleepEx(1000, FALSE); + } + else + { // Try to add waiting writer and then wait for signal + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag + WRITEWAITERS_INCR, dwFlag)) + { + WaitForSingleObjectEx(m_hWriteWaiterEvent, INFINITE, FALSE); + break; + } + } + + } + +WriteLockAcquired: + _ASSERTE ((m_dwFlag & READERS_MASK) == 0 && "reader count is nonzero after acquiring write lock"); + _ASSERTE ((m_dwFlag & WRITERS_MASK) == WRITERS_INCR && "writer count is not 1 after acquiring write lock"); + EE_LOCK_TAKEN(this); + + return S_OK; +} // UTSemReadWrite::LockWrite + + + +/****************************************************************************** +Function : UTSemReadWrite::UnlockRead + +Abstract: Release a shared lock +******************************************************************************/ +void UTSemReadWrite::UnlockRead() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + ULONG dwFlag; + + + _ASSERTE ((m_dwFlag & READERS_MASK) != 0 && "reader count is zero before releasing read lock"); + _ASSERTE ((m_dwFlag & WRITERS_MASK) == 0 && "writer count is nonzero before releasing read lock"); + + for (;;) + { + dwFlag = m_dwFlag; + + if (dwFlag == READERS_INCR) + { // we're the last reader, and nobody is waiting + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, (ULONG)0, dwFlag)) + { + break; + } + } + + else if ((dwFlag & READERS_MASK) > READERS_INCR) + { // we're not the last reader + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag - READERS_INCR, dwFlag)) + { + break; + } + } + + else + { + // here, there should be exactly 1 reader (us), and at least one waiting writer. + _ASSERTE ((dwFlag & READERS_MASK) == READERS_INCR && "UnlockRead consistency error 1"); + _ASSERTE ((dwFlag & WRITEWAITERS_MASK) != 0 && "UnlockRead consistency error 2"); + + // one or more writers is waiting, do one of them next + // (remove a reader (us), remove a write waiter, add a writer + if (dwFlag == + InterlockedCompareExchangeT( + &m_dwFlag, + dwFlag - READERS_INCR - WRITEWAITERS_INCR + WRITERS_INCR, + dwFlag)) + { + SetEvent(m_hWriteWaiterEvent); + break; + } + } + } + + DecCantStopCount(); + EE_LOCK_RELEASED(this); +} // UTSemReadWrite::UnlockRead + + +/****************************************************************************** +Function : UTSemReadWrite::UnlockWrite + +Abstract: Release an exclusive lock +******************************************************************************/ +void UTSemReadWrite::UnlockWrite() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + ULONG dwFlag; + ULONG count; + + _ASSERTE ((m_dwFlag & READERS_MASK) == 0 && "reader count is nonzero before releasing write lock"); + _ASSERTE ((m_dwFlag & WRITERS_MASK) == WRITERS_INCR && "writer count is not 1 before releasing write lock"); + + for (;;) + { + dwFlag = m_dwFlag; + + if (dwFlag == WRITERS_INCR) + { // nobody is waiting + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, (ULONG)0, dwFlag)) + { + break; + } + } + + else if ((dwFlag & READWAITERS_MASK) != 0) + { // one or more readers are waiting, do them all next + count = (dwFlag & READWAITERS_MASK) / READWAITERS_INCR; + // remove a writer (us), remove all read waiters, turn them into readers + if (dwFlag == + InterlockedCompareExchangeT( + &m_dwFlag, + dwFlag - WRITERS_INCR - count * READWAITERS_INCR + count * READERS_INCR, + dwFlag)) + { + ReleaseSemaphore(m_hReadWaiterSemaphore, count, NULL); + break; + } + } + + else + { // one or more writers is waiting, do one of them next + _ASSERTE ((dwFlag & WRITEWAITERS_MASK) != 0 && "UnlockWrite consistency error"); + // (remove a writer (us), remove a write waiter, add a writer + if (dwFlag == InterlockedCompareExchangeT (&m_dwFlag, dwFlag - WRITEWAITERS_INCR, dwFlag)) + { + SetEvent(m_hWriteWaiterEvent); + break; + } + } + } + + DecCantStopCount(); + EE_LOCK_RELEASED(this); +} // UTSemReadWrite::UnlockWrite + +#ifdef _DEBUG + +//======================================================================================= +BOOL +UTSemReadWrite::Debug_IsLockedForRead() +{ + return ((m_dwFlag & READERS_MASK) != 0); +} + +//======================================================================================= +BOOL +UTSemReadWrite::Debug_IsLockedForWrite() +{ + return ((m_dwFlag & WRITERS_MASK) != 0); +} + +#endif //_DEBUG + diff --git a/src/coreclr/utilcode/winfix.cpp b/src/coreclr/utilcode/winfix.cpp new file mode 100644 index 00000000000..acc91c6d5ba --- /dev/null +++ b/src/coreclr/utilcode/winfix.cpp @@ -0,0 +1,300 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +//***************************************************************************** +// WinWrap.cpp +// + +// +// This file contains wrapper functions for Win32 API's that take strings. +// +// COM+ internally uses UNICODE as the internal state and string format. This +// file will undef the mapping macros so that one cannot mistakingly call a +// method that isn't going to work. Instead, you have to call the correct +// wrapper API. +// +//***************************************************************************** + +#include "stdafx.h" // Precompiled header key. +#include "winwrap.h" // Header for macros and functions. +#include "utilcode.h" +#include "holder.h" +#include "pedecoder.h" + + +// ====== READ BEFORE ADDING CONTRACTS ================================================== +// The functions in this file propagate SetLastError codes to their callers. +// Contracts are not guaranteed to preserve these codes (and no, we're not taking +// the overhead hit to make them do so. Don't bother asking.) +// +// Most of the wrappers have a contract of the form: +// +// NOTHROW; +// INJECT_FAULT(xxx); +// +// For such functions, use the special purpose construct: +// +// WINWRAPPER_NO_CONTRACT(xxx); +// +// For everything else, use STATIC_CONTRACT. +// +#undef CONTRACT +#define CONTRACT $$$$$$$$READ_COMMENT_IN_WINFIX_CPP$$$$$$$$$$ + +#undef CONTRACTL +#define CONTRACTL $$$$$$$$READ_COMMENT_IN_WINFIX_CPP$$$$$$$$$$ + +#ifdef ENABLE_CONTRACTS_IMPL +static BOOL gWinWrapperContractRecursionBreak = FALSE; + + +class WinWrapperContract +{ + public: + WinWrapperContract(const char *szFunction, const char *szFile, int lineNum) + { + CANNOT_HAVE_CONTRACT; + + m_pClrDebugState = NULL; + + if (gWinWrapperContractRecursionBreak) + { + return; + } + + m_pClrDebugState = GetClrDebugState(); + + // Save old debug state + m_IncomingClrDebugState = *m_pClrDebugState; + + + m_pClrDebugState->ViolationMaskReset( ThrowsViolation ); + + if (m_pClrDebugState->IsFaultForbid() && !(m_pClrDebugState->ViolationMask() & (FaultViolation|FaultNotFatal|BadDebugState))) + { + gWinWrapperContractRecursionBreak = TRUE; + + CONTRACT_ASSERT("INJECT_FAULT called in a FAULTFORBID region.", + Contract::FAULT_Forbid, + Contract::FAULT_Mask, + szFunction, + szFile, + lineNum + ); + } + + + }; + + ~WinWrapperContract() + { + CANNOT_HAVE_CONTRACT; + + //!!!!!! THIS DESTRUCTOR MUST NOT CHANGE THE GETLASTERROR VALUE !!!!!! + + // Backout all changes to debug state. + if (m_pClrDebugState != NULL) + { + *m_pClrDebugState = m_IncomingClrDebugState; + } + } + private: + ClrDebugState *m_pClrDebugState; + ClrDebugState m_IncomingClrDebugState; + +}; + + + +#endif + +#ifdef ENABLE_CONTRACTS_IMPL +#define WINWRAPPER_NO_CONTRACT(stmt) \ + STATIC_CONTRACT_NOTHROW; \ + STATIC_CONTRACT_FAULT; \ + STATIC_CONTRACT_CANNOT_TAKE_LOCK; \ + WinWrapperContract __wcontract(__FUNCTION__, __FILE__, __LINE__); \ + if (0) {stmt} \ + +#define STATIC_WINWRAPPER_NO_CONTRACT(stmt) \ + STATIC_CONTRACT_NOTHROW; \ + STATIC_CONTRACT_CANNOT_TAKE_LOCK; \ + STATIC_CONTRACT_FAULT; \ + if (0) {stmt} \ + + +#else +#define WINWRAPPER_NO_CONTRACT(stmt) +#define STATIC_WINWRAPPER_NO_CONTRACT(stmt) +#endif + +ULONG g_dwMaxDBCSCharByteSize = 0; + +// The only purpose of this function is to make a local copy of lpCommandLine. +// Because windows implementation of CreateProcessW can actually change lpCommandLine, +// but we'd like to keep it const. +BOOL +WszCreateProcess( + LPCWSTR lpApplicationName, + LPCWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation + ) +{ + WINWRAPPER_NO_CONTRACT(SetLastError(ERROR_OUTOFMEMORY); return 0;); + + BOOL fResult; + DWORD err; + { + size_t commandLineLength = wcslen(lpCommandLine) + 1; + NewArrayHolder<WCHAR> nonConstCommandLine(new (nothrow) WCHAR[commandLineLength]); + if (nonConstCommandLine == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + return 0; + } + + memcpy(nonConstCommandLine, lpCommandLine, commandLineLength * sizeof(WCHAR)); + + fResult = CreateProcessW(lpApplicationName, + nonConstCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + (LPWSTR)lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation); + + // At the end of the current scope, the last error code will be overwritten by the destructor of + // NewArrayHolder. So we save the error code here, and restore it after the end of the current scope. + err = GetLastError(); + } + + SetLastError(err); + return fResult; +} + +#ifndef TARGET_UNIX + + +#include "psapi.h" +#include "tlhelp32.h" +#include "winnls.h" + +//********** Globals. ********************************************************* +bool g_fEnsureCharSetInfoInitialized = FALSE; // true if we've detected the platform's character set characteristics + +// Detect Unicode support of the operating system, and initialize globals +void EnsureCharSetInfoInitialized() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_CANNOT_TAKE_LOCK; + + if (!g_fEnsureCharSetInfoInitialized) + { + // NOTE: Do not use any of the Wsz* wrapper functions right now. They will have + // problems. + + // Per Shupak, you're supposed to get the maximum size of a DBCS char + // dynamically to work properly on all locales (bug 2757). + CPINFO cpInfo; + if (GetCPInfo(CP_ACP, &cpInfo)) + g_dwMaxDBCSCharByteSize = cpInfo.MaxCharSize; + else + g_dwMaxDBCSCharByteSize = 2; + + VolatileStore(&g_fEnsureCharSetInfoInitialized, true); + } + + return; +} + + +// Running with an interactive workstation. +BOOL RunningInteractive() +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + static int fInteractive = -1; + if (fInteractive != -1) + return fInteractive != 0; + +#if !defined(FEATURE_CORESYSTEM) + HWINSTA hwinsta = NULL; + + if ((hwinsta = GetProcessWindowStation() ) != NULL) + { + DWORD lengthNeeded; + USEROBJECTFLAGS flags; + + if (GetUserObjectInformationW (hwinsta, UOI_FLAGS, &flags, sizeof(flags), &lengthNeeded)) + { + if ((flags.dwFlags & WSF_VISIBLE) == 0) + fInteractive = 0; + } + } +#endif // !FEATURE_CORESYSTEM + + if (fInteractive != 0) + fInteractive = 1; + + return fInteractive != 0; +} + +typedef HRESULT(WINAPI *pfnSetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription); +extern pfnSetThreadDescription g_pfnSetThreadDescription; + +// Dummy method if windows version does not support it +HRESULT SetThreadDescriptionDummy(HANDLE hThread, PCWSTR lpThreadDescription) +{ + return NOERROR; +} + +HRESULT WINAPI InitializeSetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription) +{ + HMODULE hKernel32 = WszLoadLibrary(W("kernel32.dll")); + + pfnSetThreadDescription pLocal = NULL; + if (hKernel32 != NULL) + { + // store to thread local variable to prevent data race + pLocal = (pfnSetThreadDescription)GetProcAddress(hKernel32, "SetThreadDescription"); + } + + if (pLocal == NULL) // method is only available with Windows 10 Creators Update or later + { + g_pfnSetThreadDescription = SetThreadDescriptionDummy; + } + else + { + g_pfnSetThreadDescription = pLocal; + } + + return g_pfnSetThreadDescription(hThread, lpThreadDescription); +} + +pfnSetThreadDescription g_pfnSetThreadDescription = &InitializeSetThreadDescription; + +// Set unmanaged thread name which will show up in ETW and Debuggers which know how to read this data. +HRESULT SetThreadName(HANDLE hThread, PCWSTR lpThreadDescription) +{ + return g_pfnSetThreadDescription(hThread, lpThreadDescription); +} + +#else //!TARGET_UNIX + +HRESULT SetThreadName(HANDLE hThread, PCWSTR lpThreadDescription) +{ + return SetThreadDescription(hThread, lpThreadDescription); +} + +#endif //!TARGET_UNIX diff --git a/src/coreclr/utilcode/yieldprocessornormalized.cpp b/src/coreclr/utilcode/yieldprocessornormalized.cpp new file mode 100644 index 00000000000..4242f82792b --- /dev/null +++ b/src/coreclr/utilcode/yieldprocessornormalized.cpp @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "stdafx.h" + +// Defaults are for when InitializeYieldProcessorNormalized has not yet been called or when no measurement is done, and are +// tuned for Skylake processors +unsigned int g_yieldsPerNormalizedYield = 1; // current value is for Skylake processors, this is expected to be ~8 for pre-Skylake +unsigned int g_optimalMaxNormalizedYieldsPerSpinIteration = 7; |