diff options
Diffstat (limited to 'src/ToolBox/superpmi/superpmi')
29 files changed, 7329 insertions, 0 deletions
diff --git a/src/ToolBox/superpmi/superpmi/.gitmirror b/src/ToolBox/superpmi/superpmi/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/ToolBox/superpmi/superpmi/CMakeLists.txt b/src/ToolBox/superpmi/superpmi/CMakeLists.txt new file mode 100644 index 0000000000..8756fa0e32 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/CMakeLists.txt @@ -0,0 +1,76 @@ +project(superpmi) + +remove_definitions(-DUNICODE) +remove_definitions(-D_UNICODE) + +add_definitions(-DFEATURE_NO_HOST) +add_definitions(-DSELF_NO_HOST) + +if(WIN32) + #use static crt + add_definitions(-MT) +endif(WIN32) + +include_directories(.) +include_directories(../superpmi-shared) + +# When it is ready (the build works on all platforms, referencing the coredistools +# package), define this: +# add_definitions(-DUSE_COREDISTOOLS) + +set(SUPERPMI_SOURCES + commandline.cpp + coreclrcallbacks.cpp + cycletimer.cpp + icorjitinfo.cpp + ieememorymanager.cpp + iexecutionengine.cpp + jitdebugger.cpp + jitinstance.cpp + methodstatsemitter.cpp + neardiffer.cpp + parallelsuperpmi.cpp + superpmi.cpp + jithost.cpp + ../superpmi-shared/callutils.cpp + ../superpmi-shared/compileresult.cpp + ../superpmi-shared/errorhandling.cpp + ../superpmi-shared/logging.cpp + ../superpmi-shared/mclist.cpp + ../superpmi-shared/methodcontext.cpp + ../superpmi-shared/methodcontextreader.cpp + ../superpmi-shared/simpletimer.cpp + ../superpmi-shared/spmiutil.cpp + ../superpmi-shared/tocfile.cpp + ../superpmi-shared/typeutils.cpp +) + +add_precompiled_header( + standardpch.h + ../superpmi-shared/standardpch.cpp + SUPERPMI_SOURCES +) + +add_executable(superpmi + ${SUPERPMI_SOURCES} +) + +if(CLR_CMAKE_PLATFORM_UNIX) + target_link_libraries(superpmi + utilcodestaticnohost + mscorrc_debug + coreclrpal + palrt + ) +else() + target_link_libraries(superpmi + version.lib + advapi32.lib + ${STATIC_MT_CRT_LIB} + ${STATIC_MT_CPP_LIB} + ) + + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/superpmi.pdb DESTINATION PDB) +endif(CLR_CMAKE_PLATFORM_UNIX) + +install (TARGETS superpmi DESTINATION .) diff --git a/src/ToolBox/superpmi/superpmi/commandline.cpp b/src/ToolBox/superpmi/superpmi/commandline.cpp new file mode 100644 index 0000000000..56a2f32d1d --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/commandline.cpp @@ -0,0 +1,543 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//---------------------------------------------------------- +// CommandLine.cpp - tiny very specific command line parser +//---------------------------------------------------------- + +#include "standardpch.h" +#include "commandline.h" +#include "superpmi.h" +#include "mclist.h" +#include "methodcontext.h" +#include "logging.h" + +// NOTE: this is parsed by parallelsuperpmi.cpp::ProcessChildStdOut() to determine if an incorrect +// argument usage error has occurred. +const char* const g_SuperPMIUsageFirstLine = "SuperPMI is a JIT compiler testing tool."; + +void CommandLine::DumpHelp(const char* program) +{ + printf("%s\n", g_SuperPMIUsageFirstLine); + printf("\n"); + printf("Usage: %s [options] jitname [jitname2] filename.mc\n", program); + printf(" jitname" PLATFORM_SHARED_LIB_SUFFIX_A " - path of jit to be tested\n"); + printf(" jitname2" PLATFORM_SHARED_LIB_SUFFIX_A " - optional path of second jit to be tested\n"); + printf(" filename.mc - load method contexts from filename.mc\n"); + printf(" -j[it] Name - optionally -jit can be used to specify jits\n"); + printf(" -l[oad] filename - optionally -load can be used to specify method contexts\n"); + printf("\n"); + printf("Options:\n"); + printf("\n"); + printf(" -boe\n"); + printf(" Break on error return from compileMethod\n"); + printf("\n"); + printf(" -boa\n"); + printf(" Break on assert from the JIT\n"); + printf("\n"); + printf(" -v[erbosity] messagetypes\n"); + printf(" Controls which types of messages SuperPMI logs. Specify a string of\n"); + printf(" characters representing message categories to enable, where:\n"); + printf(" e - errors (internal fatal errors that are non-recoverable)\n"); + printf(" w - warnings (internal conditions that are unusual, but not serious)\n"); + printf(" m - missing (failures due to missing JIT-EE interface details)\n"); + printf(" i - issues (issues found with the JIT, e.g. asm diffs, asserts)\n"); + printf(" n - information (notifications/summaries, e.g. 'Loaded 5 Jitted 4 FailedCompile 1')\n"); + printf(" v - verbose (status messages, e.g. 'Jit startup took '151.12ms')\n"); + printf(" d - debug (lots of detailed output)\n"); + printf(" a - all (enable all message types; overrides other enable message types)\n"); + printf(" q - quiet (disable all output; overrides all others)\n"); + printf(" e.g. '-v ew' only writes error and warning messages to the console.\n"); + printf(" 'q' takes precedence over any other message type specified.\n"); + printf(" Default set of messages enabled is 'ewminv'.\n"); + printf("\n"); + printf(" -w[riteLogFile] logfile\n"); + printf(" Write log messages to the specified file.\n"); + printf("\n"); + printf(" -c[ompile] <indices>\n"); + printf(" Compile only those method contexts whose indices are specified.\n"); + printf(" Indices can be either a single index, comma separated values,\n"); + printf(" a range, or the name of a .MCL file with newline delimited indices.\n"); + printf(" e.g. -compile 20\n"); + printf(" e.g. -compile 20,25,30,32\n"); + printf(" e.g. -compile 10-99\n"); + printf(" e.g. -compile 5,10-99,101,201-300\n"); + printf(" e.g. -compile failed.mcl\n"); + printf("\n"); + printf(" -m[atchHash] <MD5 Hash>\n"); + printf(" Compile only method context with specific MD5 hash\n"); + printf("\n"); + printf(" -e[mitMethodStats] <stats-types>\n"); + printf(" Emit method statistics in CSV format to filename.mc.stats.\n"); + printf(" Specify a string of characters representing statistics to emit, where:\n"); + printf(" i - method IL code size\n"); + printf(" a - method compiled ASM code size\n"); + printf(" h - method hash to uniquely identify a method across MCH files\n"); + printf(" n - method number inside the source MCH\n"); + printf(" t - method throughput time\n"); + printf(" * - all available method stats\n"); + printf("\n"); + printf(" -a[pplyDiff]\n"); + printf(" Compare the compile result generated from the provided JIT with the\n"); + printf(" compile result stored with the MC. If two JITs are provided, this\n"); + printf(" compares the compile results generated by the two JITs.\n"); + printf("\n"); + printf(" -r[eproName] prefix\n"); + printf(" Write out failing methods to prefix-n.mc\n"); + printf("\n"); + printf(" -f[ailingMCList] mclfilename\n"); + printf(" Write out failing methods to mclfilename.\n"); + printf(" If using -applyDiff and no -diffMCList is specified,\n"); + printf(" comparison failures also get written to mclfilename.\n"); + printf("\n"); + printf(" -diffMCList diffMCLfilename\n"); + printf(" Write out methods that differ between compilations to diffMCLfilename.\n"); + printf(" This only works with -applyDiff.\n"); + printf("\n"); + printf(" -p[arallel] [workerCount]\n"); + printf(" Run in parallel mode by spawning 'workerCount' processes to do processing.\n"); + printf(" If 'workerCount' is not specified, the number of workers used is\n"); + printf(" the number of processors on the machine.\n"); + printf("\n"); + printf(" -skipCleanup\n"); + printf(" Skip deletion of temporary files created by child SuperPMI processes with -parallel.\n"); + printf("\n"); + printf(" -target <target>\n"); + printf(" Used by the assembly differences calculator. This specifies the target\n"); + printf(" architecture for cross-compilation. Currently allowed <target> value: arm64\n"); + printf("\n"); +#ifdef USE_COREDISTOOLS + printf(" -coredistools\n"); + printf(" Use disassembly tools from the CoreDisTools library\n"); + printf("\n"); +#endif // USE_COREDISTOOLS + printf("Inputs are case sensitive.\n"); + printf("\n"); + printf("SuperPMI method contexts are stored in files with extension .MC, implying\n"); + printf("a single method context, or .MCH, implying a set of method contexts. Either\n"); + printf("extension works equivalently.\n"); + printf("\n"); + printf("Exit codes:\n"); + printf("0 : success\n"); + printf("-1 : general fatal error (e.g., failed to initialize, failed to read files)\n"); + printf("-2 : JIT failed to initialize\n"); + printf("1 : there were compilation failures\n"); + printf("2 : there were assembly diffs\n"); + printf("\n"); + printf("Examples:\n"); + printf(" %s " MAKEDLLNAME_A("clrjit") " test.mch\n", program); + printf(" ; compile all functions in test.mch using " MAKEDLLNAME_A("clrjit") "\n"); + printf(" %s -p " MAKEDLLNAME_A("clrjit") " test.mch\n", program); + printf(" ; same as above, but use all available processors to compile in parallel\n"); + printf(" %s -f fail.mcl " MAKEDLLNAME_A("clrjit") " test.mch\n", program); + printf(" ; if there are any failures, record their MC numbers in the file fail.mcl\n"); +} + +//Assumption: All inputs are initialized to default or real value. we'll just set the stuff we see on the command line. +//Assumption: Single byte names are passed in.. mb stuff doesnt cause an obvious problem... but it might have issues... +//Assumption: Values larger than 2^31 aren't expressible from the commandline.... (atoi) Unless you pass in negatives.. :-| +bool CommandLine::Parse(int argc, char* argv[], /* OUT */ Options* o) +{ + size_t argLen = 0; + size_t tempLen = 0; + bool foundJit = false; + bool foundFile = false; + + if (argc == 1) //Print help when no args are passed + { + DumpHelp(argv[0]); + return false; + } + + for (int i = 1; i<argc; i++) + { + bool isASwitch = (argv[i][0] == '-'); +#ifndef FEATURE_PAL + if (argv[i][0] == '/') // Also accept "/" on Windows + { + isASwitch = true; + } +#endif // !FEATURE_PAL + + //Process a switch + if (isASwitch) + { + argLen = strlen(argv[i]); + + if (argLen >1) + argLen--; //adjust for leading switch + else + { + DumpHelp(argv[0]); + return false; + } + + if ((_strnicmp(&argv[i][1], "help", argLen) == 0) || + (_strnicmp(&argv[i][1], "HELP", argLen) == 0) || + (_strnicmp(&argv[i][1], "?", argLen) == 0)) + { + DumpHelp(argv[0]); + return false; + } + else if ((_strnicmp(&argv[i][1], "load", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + processMethodContext: + + tempLen = strlen(argv[i]); + if (tempLen == 0) + { + LogError("Arg '%s' is invalid, name of file missing.", argv[i]); + DumpHelp(argv[0]); + return false; + } + o->nameOfInputMethodContextFile = new char[tempLen + 1]; + strcpy_s(o->nameOfInputMethodContextFile, tempLen + 1, argv[i]); + foundFile = true; + } + else if ((_strnicmp(&argv[i][1], "jit", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + processJit: + + tempLen = strlen(argv[i]); + if (tempLen == 0) + { + LogError("Arg '%s' is invalid, name of jit missing.", argv[i]); + DumpHelp(argv[0]); + return false; + } + char *tempStr = new char[tempLen + 1]; + strcpy_s(tempStr, tempLen + 1, argv[i]); + if (!foundJit) + { + o->nameOfJit = tempStr; + foundJit = true; + } + else + { + o->nameOfJit2 = tempStr; + } + } + else if ((_strnicmp(&argv[i][1], "reproName", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + tempLen = strlen(argv[i]); + if (tempLen == 0) + { + LogError("Arg '%s' is invalid, name of prefix missing.", argv[i]); + DumpHelp(argv[0]); + return false; + } + char *tempStr = new char[tempLen + 1]; + strcpy_s(tempStr, tempLen + 1, argv[i]); + o->reproName = tempStr; + } + else if ((_strnicmp(&argv[i][1], "failingMCList", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->mclFilename = argv[i]; + } + else if ((_strnicmp(&argv[i][1], "diffMCList", 10) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->diffMCLFilename = argv[i]; + } + else if ((_strnicmp(&argv[i][1], "target", 6) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->targetArchitecture = argv[i]; + } + else if ((_strnicmp(&argv[i][1], "boe", 3) == 0)) + { + o->breakOnError = true; + } + else if ((_strnicmp(&argv[i][1], "boa", 3) == 0)) + { + o->breakOnAssert = true; + } + else if ((_strnicmp(&argv[i][1], "verbosity", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + Logger::SetLogLevel(Logger::ParseLogLevelString(argv[i])); + } + else if ((_strnicmp(&argv[i][1], "writeLogFile", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->writeLogFile = argv[i]; + Logger::OpenLogFile(argv[i]); + } + else if ((_strnicmp(&argv[i][1], "emitMethodStats", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->methodStatsTypes = argv[i]; + } + else if ((_strnicmp(&argv[i][1], "applyDiff", argLen) == 0)) + { + o->applyDiff = true; + } + else if ((_strnicmp(&argv[i][1], "compile", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + bool isValidList = MCList::processArgAsMCL(argv[i], &o->indexCount, &o->indexes); + if (!isValidList) + { + LogError("Arg '%s' is invalid, needed method context list.", argv[i]); + DumpHelp(argv[0]); + return false; + } + if (o->hash != nullptr) + { + LogError("Cannot use both method context list and method context hash."); + DumpHelp(argv[0]); + return false; + } + if (o->offset > 0 && o->increment > 0) + { + LogError("Cannot use method context list in parallel mode."); + DumpHelp(argv[0]); + return false; + } + + o->compileList = argv[i]; // Save this in case we need it for -parallel. + } +#ifdef USE_COREDISTOOLS + else if ((_strnicmp(&argv[i][1], "coredistools", argLen) == 0)) { + o->useCoreDisTools = true; + } +#endif // USE_COREDISTOOLS + else if ((_strnicmp(&argv[i][1], "matchHash", argLen) == 0)) + { + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + if (strlen(argv[i]) != (MD5_HASH_BUFFER_SIZE - 1)) + { + LogError("Arg '%s' is invalid, needed a valid method context hash.", argv[i]); + DumpHelp(argv[0]); + return false; + } + if (o->indexCount > 0) + { + LogError("Cannot use both method context list and method context hash."); + DumpHelp(argv[0]); + return false; + } + if (o->offset > 0 && o->increment > 0) + { + LogError("Cannot use method context hash in parallel mode."); + DumpHelp(argv[0]); + return false; + } + o->hash = argv[i]; + } + else if ((_strnicmp(&argv[i][1], "parallel", argLen) == 0)) + { + o->parallel = true; + + // Is there another argument? + if (i + 1 < argc) + { + // If so, does it look like a worker count? + bool isWorkerCount = true; + size_t nextlen = strlen(argv[i + 1]); + for (size_t j = 0; j < nextlen; j++) + { + if (!isdigit(argv[i + 1][j])) + { + isWorkerCount = false; // Doesn't look like a worker count; bail out and let someone else handle it. + break; + } + } + if (isWorkerCount) + { + ++i; + o->workerCount = atoi(argv[i]); + + if (o->workerCount < 1) + { + LogError("Invalid workers count specified, workers count must be at least 1."); + DumpHelp(argv[0]); + return false; + } + if (o->workerCount > MAXIMUM_WAIT_OBJECTS) + { + LogError("Invalid workers count specified, workers count cannot be more than %d.", MAXIMUM_WAIT_OBJECTS); + DumpHelp(argv[0]); + return false; + } + } + } + } + else if ((_stricmp(&argv[i][1], "skipCleanup") == 0)) + { + o->skipCleanup = true; + } + else if ((_strnicmp(&argv[i][1], "stride", argLen) == 0)) + { + // "-stride" is an internal switch used by -parallel. Usage is: + // + // -stride offset increment + // + // It compiles methods in this series until end-of-file: + // offset, offset+increment, offset+2*increment, offset+3*increment, ... + + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->offset = atoi(argv[i]); + + if (++i >= argc) + { + DumpHelp(argv[0]); + return false; + } + + o->increment = atoi(argv[i]); + + if (o->offset < 1 || o->increment < 1) + { + LogError("Incorrect offset/increment specified for -stride. Offset and increment both must be > 0."); + DumpHelp(argv[0]); + return false; + } + if (o->indexCount > 0) + { + LogError("Cannot use method context list in parallel mode."); + DumpHelp(argv[0]); + return false; + } + if (o->hash != nullptr) + { + LogError("Cannot use method context hash in parallel mode."); + DumpHelp(argv[0]); + return false; + } + } + else + { + LogError("Unknown switch '%s' passed as argument.", argv[i]); + DumpHelp(argv[0]); + return false; + } + } + //Process an input filename + //String comparisons on file extensions must be case-insensitive since we run on Windows + else + { + char *lastdot = strrchr(argv[i], '.'); + if (lastdot == nullptr) + { + DumpHelp(argv[0]); + return false; + } + + if (_stricmp(lastdot, PLATFORM_SHARED_LIB_SUFFIX_A) == 0) + goto processJit; + else if (_stricmp(lastdot, ".mc") == 0) + goto processMethodContext; + else if (_stricmp(lastdot, ".mch") == 0) + goto processMethodContext; + else if (_stricmp(lastdot, ".mct") == 0) + goto processMethodContext; + else + { + LogError("Unknown file type passed as argument, '%s'.", argv[i]); + DumpHelp(argv[0]); + return false; + } + } + } + + // Do some argument validation. + + if (o->nameOfJit == nullptr) + { + LogError("Missing name of a Jit."); + DumpHelp(argv[0]); + return false; + } + if (o->nameOfInputMethodContextFile == nullptr) + { + LogError("Missing name of an input file."); + DumpHelp(argv[0]); + return false; + } + if (o->diffMCLFilename != nullptr && !o->applyDiff) + { + LogError("-diffMCList specified without -applyDiff."); + DumpHelp(argv[0]); + return false; + } + if (o->targetArchitecture != nullptr && (0 != _stricmp(o->targetArchitecture, "arm64"))) + { + LogError("Illegal target architecture specified with -target (only arm64 is supported)."); + DumpHelp(argv[0]); + return false; + } + if (o->skipCleanup && !o->parallel) + { + LogError("-skipCleanup requires -parallel."); + DumpHelp(argv[0]); + return false; + } + return true; +} diff --git a/src/ToolBox/superpmi/superpmi/commandline.h b/src/ToolBox/superpmi/superpmi/commandline.h new file mode 100644 index 0000000000..051eb6def2 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/commandline.h @@ -0,0 +1,74 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//---------------------------------------------------------- +// CommandLine.h - tiny very specific command line parser +//---------------------------------------------------------- +#ifndef _CommandLine +#define _CommandLine + +class CommandLine +{ +public: + + class Options + { + public: + Options() : + nameOfJit(nullptr), + nameOfJit2(nullptr), + nameOfInputMethodContextFile(nullptr), + writeLogFile(nullptr), + reproName(nullptr), + breakOnError(false), + breakOnAssert(false), + applyDiff(false), + parallel(false), + useCoreDisTools(false), + skipCleanup(false), + workerCount(-1), + indexCount(-1), + indexes(nullptr), + hash(nullptr), + methodStatsTypes(nullptr), + mclFilename(nullptr), + diffMCLFilename(nullptr), + targetArchitecture(nullptr), + compileList(nullptr), + offset(-1), + increment(-1) + { + } + + char* nameOfJit; + char* nameOfJit2; + char* nameOfInputMethodContextFile; + char* writeLogFile; + char* reproName; + bool breakOnError; + bool breakOnAssert; + bool applyDiff; + bool parallel; // User specified to use /parallel mode. + bool useCoreDisTools; // Use CoreDisTools library instead of Msvcdis + bool skipCleanup; // In /parallel mode, do we skip cleanup of temporary files? Used for debugging /parallel. + int workerCount; // Number of workers to use for /parallel mode. -1 (or 1) means don't use parallel mode. + int indexCount; // If indexCount is -1 and hash points to nullptr it means compile all. + int* indexes; + char* hash; + char* methodStatsTypes; + char* mclFilename; + char* diffMCLFilename; + char* targetArchitecture; + char* compileList; + int offset; + int increment; + }; + + static bool Parse(int argc, char* argv[], /* OUT */ Options* o); + +private: + static void DumpHelp(const char* program); +}; +#endif diff --git a/src/ToolBox/superpmi/superpmi/coreclrcallbacks.cpp b/src/ToolBox/superpmi/superpmi/coreclrcallbacks.cpp new file mode 100644 index 0000000000..7a66d20940 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/coreclrcallbacks.cpp @@ -0,0 +1,69 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "spmiutil.h" +#include "coreclrcallbacks.h" +#include "iexecutionengine.h" + +IExecutionEngine* STDMETHODCALLTYPE IEE_t() +{ + MyIEE *iee = InitIExecutionEngine(); + return iee; +} + +/*#pragma warning( suppress :4996 ) //deprecated +HRESULT STDMETHODCALLTYPE GetCORSystemDirectory(LPWSTR pbuffer, DWORD cchBuffer, DWORD* pdwlength) +{ + DebugBreakorAV(131); + return 0; +} +*/ + +HANDLE ourHeap = nullptr; + +LPVOID STDMETHODCALLTYPE EEHeapAllocInProcessHeap (DWORD dwFlags, SIZE_T dwBytes) +{ + if(ourHeap==nullptr) + ourHeap = HeapCreate(0, 4096 ,0); + if(ourHeap==nullptr) + { + LogError("HeapCreate Failed"); + __debugbreak(); + return nullptr; + } + LPVOID result = HeapAlloc(ourHeap, dwFlags, dwBytes); +// LogDebug("EEHeapAllocInProcessHeap %p %u %u", result, dwFlags, dwBytes); + return result; +} + +BOOL STDMETHODCALLTYPE EEHeapFreeInProcessHeap (DWORD dwFlags, LPVOID lpMem) +{ +// return true; + return HeapFree(ourHeap, dwFlags, lpMem); +} + +void* STDMETHODCALLTYPE GetCLRFunction(LPCSTR functionName) +{ + if(strcmp(functionName, "EEHeapAllocInProcessHeap")==0) + return (void*)EEHeapAllocInProcessHeap; + if(strcmp(functionName, "EEHeapFreeInProcessHeap")==0) + return (void*)EEHeapFreeInProcessHeap; + DebugBreakorAV(132); + return nullptr; +} + +CoreClrCallbacks *InitCoreClrCallbacks() +{ + CoreClrCallbacks *temp = new CoreClrCallbacks(); + ::ZeroMemory(temp, sizeof(CoreClrCallbacks)); + + temp->m_hmodCoreCLR = (HINSTANCE)(size_t)0xbadbad01; // any non-null value seems okay... + temp->m_pfnIEE = IEE_t; + temp->m_pfnGetCORSystemDirectory = nullptr;//GetCORSystemDirectory; + temp->m_pfnGetCLRFunction = GetCLRFunction; + + return temp; +} diff --git a/src/ToolBox/superpmi/superpmi/coreclrcallbacks.h b/src/ToolBox/superpmi/superpmi/coreclrcallbacks.h new file mode 100644 index 0000000000..cb3815525e --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/coreclrcallbacks.h @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _CoreClrCallbacks +#define _CoreClrCallbacks + +#include "runtimedetails.h" + +IExecutionEngine* STDMETHODCALLTYPE IEE_t(); +HRESULT STDMETHODCALLTYPE GetCORSystemDirectory(LPWSTR pbuffer, DWORD cchBuffer, DWORD* pdwlength); +LPVOID STDMETHODCALLTYPE EEHeapAllocInProcessHeap (DWORD dwFlags, SIZE_T dwBytes); +BOOL STDMETHODCALLTYPE EEHeapFreeInProcessHeap (DWORD dwFlags, LPVOID lpMem); +void* STDMETHODCALLTYPE GetCLRFunction(LPCSTR functionName); +CoreClrCallbacks *InitCoreClrCallbacks(); + +#endif
\ No newline at end of file diff --git a/src/ToolBox/superpmi/superpmi/cycletimer.cpp b/src/ToolBox/superpmi/superpmi/cycletimer.cpp new file mode 100644 index 0000000000..de0e19b935 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/cycletimer.cpp @@ -0,0 +1,60 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "cycletimer.h" + +CycleTimer::CycleTimer() +{ + start = 0; + stop = 0; + overhead = QueryOverhead(); +} + +CycleTimer::~CycleTimer() +{ +} + +void CycleTimer::Start() +{ + BOOL retVal = QueryThreadCycleTime(GetCurrentThread(), &start); + + if(retVal == FALSE) + { + LogError("CycleTimer::Start unable to QPC. error was 0x%08x", ::GetLastError()); + ::__debugbreak(); + } +} + +void CycleTimer::Stop() +{ + BOOL retVal = QueryThreadCycleTime(GetCurrentThread(), &stop); + + if(retVal == FALSE) + { + LogError("CycleTimer::Stop unable to QPC. error was 0x%08x", ::GetLastError()); + ::__debugbreak(); + } +} + +unsigned __int64 CycleTimer::GetCycles() +{ + return stop - start - overhead; +} + +unsigned __int64 CycleTimer::QueryOverhead() +{ + unsigned __int64 tot = 0; + unsigned __int64 startCycles; + unsigned __int64 endCycles; + const int N = 1000; + for (int i = 0; i < N; i++) + { + QueryThreadCycleTime(GetCurrentThread(), &startCycles); + QueryThreadCycleTime(GetCurrentThread(), &endCycles); + tot += (endCycles-startCycles); + } + return tot/N; +}
\ No newline at end of file diff --git a/src/ToolBox/superpmi/superpmi/cycletimer.h b/src/ToolBox/superpmi/superpmi/cycletimer.h new file mode 100644 index 0000000000..9ad855501a --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/cycletimer.h @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _CycleTimer +#define _CycleTimer + +#include "errorhandling.h" + +class CycleTimer +{ +public: + CycleTimer(); + ~CycleTimer(); + + void Start(); + void Stop(); + unsigned __int64 GetCycles(); + unsigned __int64 QueryOverhead(); + +private: + // Cycles + unsigned __int64 start; + unsigned __int64 stop; + unsigned __int64 overhead; +}; +#endif diff --git a/src/ToolBox/superpmi/superpmi/filecache.cpp b/src/ToolBox/superpmi/superpmi/filecache.cpp new file mode 100644 index 0000000000..dcd783a09b --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/filecache.cpp @@ -0,0 +1,150 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// TODO-Cleanup: this class is unused + +#include "standardpch.h" +#include "filecache.h" + +#define FileCacheSize 0xFFFFFF //needs to be bigger than the biggest read request. + +HANDLE FileCache::cachedHandle; +bool FileCache::openAsCache; +BYTE *FileCache::rawBuff; +unsigned int FileCache::offset; +unsigned int FileCache::length; +__int64 FileCache::fileoffset; + +HANDLE +FileCache::CreateFileA( + _In_ LPCSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile + ) +{ + openAsCache = false; + rawBuff = nullptr; + offset = 0; + length = 0; + + if((dwShareMode&CACHE_THIS_FILE)==CACHE_THIS_FILE) + { + dwShareMode ^= CACHE_THIS_FILE; + openAsCache = true; + } + HANDLE temp = ::CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + if(openAsCache) + cachedHandle = temp; //yes yes.. this is unsafe.. but one accessor now is okay. bswhack + return temp; +} + +HANDLE +FileCache::CreateFileW( + _In_ LPCWSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile + ) +{ + openAsCache = false; + rawBuff = nullptr; + offset = 0; + length = 0; + return ::CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); +} + + +//Somewhat sloppy quick copy... we don't treat lpNumberOfBytesRead etc correctly +BOOL +FileCache::ReadFile( + _In_ HANDLE hFile, + _Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer, + _In_ DWORD nNumberOfBytesToRead, + _Out_opt_ LPDWORD lpNumberOfBytesRead, + _Inout_opt_ LPOVERLAPPED lpOverlapped + ) +{ + if(!openAsCache) + return ::ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped); + else + { + if(rawBuff == nullptr) + { + rawBuff = new BYTE[FileCacheSize]; + length = FileCacheSize; + offset = FileCacheSize; + } + if(nNumberOfBytesToRead > FileCacheSize) + { + printf("ERROR: nNumberOfBytesToRead exceeds FileCacheSize %u > %u\n", nNumberOfBytesToRead, FileCacheSize); + __debugbreak(); + } + if((offset+nNumberOfBytesToRead) > length) + { + memmove(rawBuff, &rawBuff[offset], length-offset); //Use memmove since we have overlapping regions more than half the time + ::ReadFile(hFile, &rawBuff[length-offset], offset, lpNumberOfBytesRead, lpOverlapped); + if(*lpNumberOfBytesRead ==0) + __debugbreak(); + length -= offset-(*lpNumberOfBytesRead); + LARGE_INTEGER DataTemp; + LARGE_INTEGER zero; + zero.QuadPart = 0; + ::SetFilePointerEx(hFile, zero, &DataTemp, FILE_CURRENT); + fileoffset = DataTemp.QuadPart; + + offset = 0; + } + memcpy(lpBuffer, &rawBuff[offset], nNumberOfBytesToRead); + offset+=nNumberOfBytesToRead; + if(offset > FileCacheSize) + __debugbreak(); + return true; + } +} + +BOOL +FileCache::WriteFile( + _In_ HANDLE hFile, + _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer, + _In_ DWORD nNumberOfBytesToWrite, + _Out_opt_ LPDWORD lpNumberOfBytesWritten, + _Inout_opt_ LPOVERLAPPED lpOverlapped + ) +{ + if(!openAsCache) + return ::WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); + else + { + printf("ERROR: We only support one file open via the cache.\n"); + __debugbreak(); + return false; + } +} + +BOOL +FileCache::CloseHandle( + _In_ HANDLE hObject + ) +{ + if(!openAsCache) + return ::CloseHandle(hObject); + else + { + if(rawBuff!=nullptr) + delete []rawBuff; + return ::CloseHandle(hObject); + } +} +__int64 FileCache::GetFilePos(HANDLE hFile) +{ + return (fileoffset - (__int64)length) + (__int64)offset; +} diff --git a/src/ToolBox/superpmi/superpmi/filecache.h b/src/ToolBox/superpmi/superpmi/filecache.h new file mode 100644 index 0000000000..51df5edde9 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/filecache.h @@ -0,0 +1,78 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// TODO-Cleanup: this class is unused + +//---------------------------------------------------------- +// FileCache.h - very simple read ahead/ReadFile abstraction +//---------------------------------------------------------- +#ifndef _FileCache +#define _FileCache + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#define CACHE_THIS_FILE 0xFF + +class FileCache +{ +public: + +static HANDLE +CreateFileA( + _In_ LPCSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile + ); + +static HANDLE +CreateFileW( + _In_ LPCWSTR lpFileName, + _In_ DWORD dwDesiredAccess, + _In_ DWORD dwShareMode, + _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + _In_ DWORD dwCreationDisposition, + _In_ DWORD dwFlagsAndAttributes, + _In_opt_ HANDLE hTemplateFile + ); + +static BOOL +ReadFile( + _In_ HANDLE hFile, + _Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer, + _In_ DWORD nNumberOfBytesToRead, + _Out_opt_ LPDWORD lpNumberOfBytesRead, + _Inout_opt_ LPOVERLAPPED lpOverlapped + ); + +static BOOL +WriteFile( + _In_ HANDLE hFile, + _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer, + _In_ DWORD nNumberOfBytesToWrite, + _Out_opt_ LPDWORD lpNumberOfBytesWritten, + _Inout_opt_ LPOVERLAPPED lpOverlapped + ); + +static BOOL +CloseHandle( + _In_ HANDLE hObject + ); + +static __int64 GetFilePos(HANDLE hFile); + +private: + static HANDLE cachedHandle; + static bool openAsCache; + static BYTE *rawBuff; + static unsigned int offset; + static unsigned int length; + static __int64 fileoffset; +}; +#endif diff --git a/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp new file mode 100644 index 0000000000..41b0195a6d --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/icorjitinfo.cpp @@ -0,0 +1,2044 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "icorjitinfo.h" +#include "jitdebugger.h" +#include "spmiutil.h" + +ICorJitInfo *pICJI = nullptr; + +ICorJitInfo* InitICorJitInfo(JitInstance *jitInstance) +{ + MyICJI *icji = new MyICJI(); + icji->jitInstance = jitInstance; + pICJI = icji; + return icji; +} + +//Stuff on ICorStaticInfo +/**********************************************************************************/ +// +// ICorMethodInfo +// +/**********************************************************************************/ + +// return flags (defined above, CORINFO_FLG_PUBLIC ...) +DWORD MyICJI::getMethodAttribs (CORINFO_METHOD_HANDLE ftn /* IN */) +{ + jitInstance->mc->cr->AddCall("getMethodAttribs"); + return jitInstance->mc->repGetMethodAttribs(ftn); +} + +// sets private JIT flags, which can be, retrieved using getAttrib. +void MyICJI::setMethodAttribs (CORINFO_METHOD_HANDLE ftn,/* IN */ + CorInfoMethodRuntimeFlags attribs/* IN */) +{ + jitInstance->mc->cr->AddCall("setMethodAttribs"); + jitInstance->mc->cr->recSetMethodAttribs(ftn, attribs); +} + + +// Given a method descriptor ftnHnd, extract signature information into sigInfo +// +// 'memberParent' is typically only set when verifying. It should be the +// result of calling getMemberParent. +void MyICJI::getMethodSig ( + CORINFO_METHOD_HANDLE ftn, /* IN */ + CORINFO_SIG_INFO *sig, /* OUT */ + CORINFO_CLASS_HANDLE memberParent/* IN */ + ) +{ + jitInstance->mc->cr->AddCall("getMethodSig"); + jitInstance->mc->repGetMethodSig(ftn, sig, memberParent); +} + + + /********************************************************************* + * Note the following methods can only be used on functions known + * to be IL. This includes the method being compiled and any method + * that 'getMethodInfo' returns true for + *********************************************************************/ + + // return information about a method private to the implementation + // returns false if method is not IL, or is otherwise unavailable. + // This method is used to fetch data needed to inline functions + bool MyICJI::getMethodInfo ( + CORINFO_METHOD_HANDLE ftn, /* IN */ + CORINFO_METHOD_INFO* info /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getMethodInfo"); + DWORD exceptionCode = 0; + bool value = jitInstance->mc->repGetMethodInfo(ftn, info, &exceptionCode); + if(exceptionCode != 0) + ThrowException(exceptionCode); + return value; +} + +// Decides if you have any limitations for inlining. If everything's OK, it will return +// INLINE_PASS and will fill out pRestrictions with a mask of restrictions the caller of this +// function must respect. If caller passes pRestrictions = nullptr, if there are any restrictions +// INLINE_FAIL will be returned +// +// The callerHnd must be the immediate caller (i.e. when we have a chain of inlined calls) +// +// The inlined method need not be verified + +CorInfoInline MyICJI::canInline ( + CORINFO_METHOD_HANDLE callerHnd, /* IN */ + CORINFO_METHOD_HANDLE calleeHnd, /* IN */ + DWORD* pRestrictions /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("canInline"); + + DWORD exceptionCode = 0; + CorInfoInline result = jitInstance->mc->repCanInline(callerHnd, calleeHnd, pRestrictions, &exceptionCode); + if(exceptionCode != 0) + ThrowException(exceptionCode); + return result; +} + +// Reports whether or not a method can be inlined, and why. canInline is responsible for reporting all +// inlining results when it returns INLINE_FAIL and INLINE_NEVER. All other results are reported by the +// JIT. +void MyICJI::reportInliningDecision (CORINFO_METHOD_HANDLE inlinerHnd, + CORINFO_METHOD_HANDLE inlineeHnd, + CorInfoInline inlineResult, + const char * reason) +{ + jitInstance->mc->cr->AddCall("reportInliningDecision"); + jitInstance->mc->cr->recReportInliningDecision(inlinerHnd, inlineeHnd, inlineResult, reason); +} + + +// Returns false if the call is across security boundaries thus we cannot tailcall +// +// The callerHnd must be the immediate caller (i.e. when we have a chain of inlined calls) +bool MyICJI::canTailCall ( + CORINFO_METHOD_HANDLE callerHnd, /* IN */ + CORINFO_METHOD_HANDLE declaredCalleeHnd, /* IN */ + CORINFO_METHOD_HANDLE exactCalleeHnd, /* IN */ + bool fIsTailPrefix /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("canTailCall"); + return jitInstance->mc->repCanTailCall(callerHnd, declaredCalleeHnd, exactCalleeHnd, fIsTailPrefix); +} + +// Reports whether or not a method can be tail called, and why. +// canTailCall is responsible for reporting all results when it returns +// false. All other results are reported by the JIT. +void MyICJI::reportTailCallDecision (CORINFO_METHOD_HANDLE callerHnd, + CORINFO_METHOD_HANDLE calleeHnd, + bool fIsTailPrefix, + CorInfoTailCall tailCallResult, + const char * reason) +{ + jitInstance->mc->cr->AddCall("reportTailCallDecision"); + jitInstance->mc->cr->recReportTailCallDecision(callerHnd, calleeHnd, fIsTailPrefix, tailCallResult, reason); +} + +// get individual exception handler +void MyICJI::getEHinfo( + CORINFO_METHOD_HANDLE ftn, /* IN */ + unsigned EHnumber, /* IN */ + CORINFO_EH_CLAUSE* clause /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getEHinfo"); + jitInstance->mc->repGetEHinfo(ftn, EHnumber, clause); +} + +// return class it belongs to +CORINFO_CLASS_HANDLE MyICJI::getMethodClass ( + CORINFO_METHOD_HANDLE method + ) +{ + jitInstance->mc->cr->AddCall("getMethodClass"); + return jitInstance->mc->repGetMethodClass(method); +} + +// return module it belongs to +CORINFO_MODULE_HANDLE MyICJI::getMethodModule ( + CORINFO_METHOD_HANDLE method + ) +{ + jitInstance->mc->cr->AddCall("getMethodModule"); + LogError("Hit unimplemented getMethodModule"); + DebugBreakorAV(7); + return 0; +} + +// This function returns the offset of the specified method in the +// vtable of it's owning class or interface. +void MyICJI::getMethodVTableOffset ( + CORINFO_METHOD_HANDLE method, /* IN */ + unsigned* offsetOfIndirection, /* OUT */ + unsigned* offsetAfterIndirection /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getMethodVTableOffset"); + jitInstance->mc->repGetMethodVTableOffset(method, offsetOfIndirection, offsetAfterIndirection); +} + +// If a method's attributes have (getMethodAttribs) CORINFO_FLG_INTRINSIC set, +// getIntrinsicID() returns the intrinsic ID. +CorInfoIntrinsics MyICJI::getIntrinsicID( + CORINFO_METHOD_HANDLE method, + bool* pMustExpand /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getIntrinsicID"); + return jitInstance->mc->repGetIntrinsicID(method, pMustExpand); +} + +// Is the given module the System.Numerics.Vectors module? +bool MyICJI::isInSIMDModule( + CORINFO_CLASS_HANDLE classHnd + ) +{ + jitInstance->mc->cr->AddCall("isInSIMDModule"); + return jitInstance->mc->repIsInSIMDModule(classHnd) ? true : false; +} + +// return the unmanaged calling convention for a PInvoke +CorInfoUnmanagedCallConv MyICJI::getUnmanagedCallConv( + CORINFO_METHOD_HANDLE method + ) +{ + jitInstance->mc->cr->AddCall("getUnmanagedCallConv"); + return jitInstance->mc->repGetUnmanagedCallConv(method); +} + +// return if any marshaling is required for PInvoke methods. Note that +// method == 0 => calli. The call site sig is only needed for the varargs or calli case +BOOL MyICJI::pInvokeMarshalingRequired( + CORINFO_METHOD_HANDLE method, + CORINFO_SIG_INFO* callSiteSig + ) +{ + jitInstance->mc->cr->AddCall("pInvokeMarshalingRequired"); + return jitInstance->mc->repPInvokeMarshalingRequired(method, callSiteSig); +} + +// Check constraints on method type arguments (only). +// The parent class should be checked separately using satisfiesClassConstraints(parent). +BOOL MyICJI::satisfiesMethodConstraints( + CORINFO_CLASS_HANDLE parent, // the exact parent of the method + CORINFO_METHOD_HANDLE method + ) +{ + jitInstance->mc->cr->AddCall("satisfiesMethodConstraints"); + return jitInstance->mc->repSatisfiesMethodConstraints(parent, method); +} + +// Given a delegate target class, a target method parent class, a target method, +// a delegate class, check if the method signature is compatible with the Invoke method of the delegate +// (under the typical instantiation of any free type variables in the memberref signatures). +BOOL MyICJI::isCompatibleDelegate( + CORINFO_CLASS_HANDLE objCls, /* type of the delegate target, if any */ + CORINFO_CLASS_HANDLE methodParentCls, /* exact parent of the target method, if any */ + CORINFO_METHOD_HANDLE method, /* (representative) target method, if any */ + CORINFO_CLASS_HANDLE delegateCls, /* exact type of the delegate */ + BOOL *pfIsOpenDelegate /* is the delegate open */ + ) +{ + jitInstance->mc->cr->AddCall("isCompatibleDelegate"); + return jitInstance->mc->repIsCompatibleDelegate(objCls, methodParentCls, method, delegateCls, pfIsOpenDelegate); +} + +// Determines whether the delegate creation obeys security transparency rules +BOOL MyICJI::isDelegateCreationAllowed ( + CORINFO_CLASS_HANDLE delegateHnd, + CORINFO_METHOD_HANDLE calleeHnd + ) +{ + jitInstance->mc->cr->AddCall("isDelegateCreationAllowed"); + return jitInstance->mc->repIsDelegateCreationAllowed(delegateHnd, calleeHnd); +} + + +// Indicates if the method is an instance of the generic +// method that passes (or has passed) verification +CorInfoInstantiationVerification MyICJI::isInstantiationOfVerifiedGeneric ( + CORINFO_METHOD_HANDLE method /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("isInstantiationOfVerifiedGeneric"); + return jitInstance->mc->repIsInstantiationOfVerifiedGeneric(method); +} + +// Loads the constraints on a typical method definition, detecting cycles; +// for use in verification. +void MyICJI::initConstraintsForVerification( + CORINFO_METHOD_HANDLE method, /* IN */ + BOOL *pfHasCircularClassConstraints, /* OUT */ + BOOL *pfHasCircularMethodConstraint /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("initConstraintsForVerification"); + jitInstance->mc->repInitConstraintsForVerification(method, pfHasCircularClassConstraints, pfHasCircularMethodConstraint); +} + +// Returns enum whether the method does not require verification +// Also see ICorModuleInfo::canSkipVerification +CorInfoCanSkipVerificationResult MyICJI::canSkipMethodVerification ( + CORINFO_METHOD_HANDLE ftnHandle + ) +{ + jitInstance->mc->cr->AddCall("canSkipMethodVerification"); + return jitInstance->mc->repCanSkipMethodVerification(ftnHandle, FALSE); +} + +// load and restore the method +void MyICJI::methodMustBeLoadedBeforeCodeIsRun( + CORINFO_METHOD_HANDLE method + ) +{ + jitInstance->mc->cr->AddCall("methodMustBeLoadedBeforeCodeIsRun"); + jitInstance->mc->cr->recMethodMustBeLoadedBeforeCodeIsRun(method); +} + +CORINFO_METHOD_HANDLE MyICJI::mapMethodDeclToMethodImpl( + CORINFO_METHOD_HANDLE method + ) +{ + jitInstance->mc->cr->AddCall("mapMethodDeclToMethodImpl"); + LogError("Hit unimplemented mapMethodDeclToMethodImpl"); + DebugBreakorAV(17); + return 0; +} + +// Returns the global cookie for the /GS unsafe buffer checks +// The cookie might be a constant value (JIT), or a handle to memory location (Ngen) +void MyICJI::getGSCookie( + GSCookie * pCookieVal, // OUT + GSCookie ** ppCookieVal // OUT + ) +{ + jitInstance->mc->cr->AddCall("getGSCookie"); + jitInstance->mc->repGetGSCookie(pCookieVal, ppCookieVal); +} + +/**********************************************************************************/ +// +// ICorModuleInfo +// +/**********************************************************************************/ + +// Resolve metadata token into runtime method handles. +void MyICJI::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken) +{ + DWORD exceptionCode = 0; + jitInstance->mc->cr->AddCall("resolveToken"); + jitInstance->mc->repResolveToken(pResolvedToken, &exceptionCode); + if(exceptionCode != 0) + ThrowException(exceptionCode); +} + +// Resolve metadata token into runtime method handles. +bool MyICJI::tryResolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken) +{ + jitInstance->mc->cr->AddCall("tryResolveToken"); + return jitInstance->mc->repTryResolveToken(pResolvedToken); +} + +// Signature information about the call sig +void MyICJI::findSig ( + CORINFO_MODULE_HANDLE module, /* IN */ + unsigned sigTOK, /* IN */ + CORINFO_CONTEXT_HANDLE context, /* IN */ + CORINFO_SIG_INFO *sig /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("findSig"); + jitInstance->mc->repFindSig(module, sigTOK, context, sig); +} + +// for Varargs, the signature at the call site may differ from +// the signature at the definition. Thus we need a way of +// fetching the call site information +void MyICJI::findCallSiteSig ( + CORINFO_MODULE_HANDLE module, /* IN */ + unsigned methTOK, /* IN */ + CORINFO_CONTEXT_HANDLE context, /* IN */ + CORINFO_SIG_INFO *sig /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("findCallSiteSig"); + jitInstance->mc->repFindCallSiteSig(module, methTOK, context, sig); +} + +CORINFO_CLASS_HANDLE MyICJI::getTokenTypeAsHandle ( + CORINFO_RESOLVED_TOKEN * pResolvedToken /* IN */) +{ + jitInstance->mc->cr->AddCall("getTokenTypeAsHandle"); + return jitInstance->mc->repGetTokenTypeAsHandle(pResolvedToken); +} + +// Returns true if the module does not require verification +// +// If fQuickCheckOnlyWithoutCommit=TRUE, the function only checks that the +// module does not currently require verification in the current AppDomain. +// This decision could change in the future, and so should not be cached. +// If it is cached, it should only be used as a hint. +// This is only used by ngen for calculating certain hints. +// + +// Returns enum whether the module does not require verification +// Also see ICorMethodInfo::canSkipMethodVerification(); +CorInfoCanSkipVerificationResult MyICJI::canSkipVerification ( + CORINFO_MODULE_HANDLE module /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("canSkipVerification"); + LogError("Hit unimplemented canSkipVerification"); + DebugBreakorAV(22); + return CORINFO_VERIFICATION_CANNOT_SKIP; +} + +// Checks if the given metadata token is valid +BOOL MyICJI::isValidToken ( + CORINFO_MODULE_HANDLE module, /* IN */ + unsigned metaTOK /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("isValidToken"); + return jitInstance->mc->repIsValidToken(module, metaTOK); +} + +// Checks if the given metadata token is valid StringRef +BOOL MyICJI::isValidStringRef ( + CORINFO_MODULE_HANDLE module, /* IN */ + unsigned metaTOK /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("isValidStringRef"); + return jitInstance->mc->repIsValidStringRef(module, metaTOK); +} + +BOOL MyICJI::shouldEnforceCallvirtRestriction( + CORINFO_MODULE_HANDLE scope + ) +{ + jitInstance->mc->cr->AddCall("shouldEnforceCallvirtRestriction"); + return jitInstance->mc->repShouldEnforceCallvirtRestriction(scope); +} + +/**********************************************************************************/ +// +// ICorClassInfo +// +/**********************************************************************************/ + +// If the value class 'cls' is isomorphic to a primitive type it will +// return that type, otherwise it will return CORINFO_TYPE_VALUECLASS +CorInfoType MyICJI::asCorInfoType ( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("asCorInfoType"); + return jitInstance->mc->repAsCorInfoType(cls); +} + +// for completeness +const char* MyICJI::getClassName ( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getClassName"); + const char* result = jitInstance->mc->repGetClassName(cls); + return result; +} + + +// Append a (possibly truncated) representation of the type cls to the preallocated buffer ppBuf of length pnBufLen +// If fNamespace=TRUE, include the namespace/enclosing classes +// If fFullInst=TRUE (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters +// If fAssembly=TRUE, suffix with a comma and the full assembly qualification +// return size of representation +int MyICJI::appendClassName( + __deref_inout_ecount(*pnBufLen) WCHAR** ppBuf, + int* pnBufLen, + CORINFO_CLASS_HANDLE cls, + BOOL fNamespace, + BOOL fFullInst, + BOOL fAssembly + ) +{ + jitInstance->mc->cr->AddCall("appendClassName"); + const WCHAR* result = jitInstance->mc->repAppendClassName(cls, fNamespace, fFullInst, fAssembly); + int nLen = 0; + if (ppBuf != nullptr && result != nullptr) + { + nLen = (int)wcslen(result); + if (*pnBufLen > nLen) + { + wcscpy_s(*ppBuf, *pnBufLen, result ); + (*ppBuf) += nLen; + (*pnBufLen) -= nLen; + } + } + return nLen; +} + +// Quick check whether the type is a value class. Returns the same value as getClassAttribs(cls) & CORINFO_FLG_VALUECLASS, except faster. +BOOL MyICJI::isValueClass(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("isValueClass"); + return jitInstance->mc->repIsValueClass(cls); +} + +// If this method returns true, JIT will do optimization to inline the check for +// GetTypeFromHandle(handle) == obj.GetType() +BOOL MyICJI::canInlineTypeCheckWithObjectVTable(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("canInlineTypeCheckWithObjectVTable"); + return jitInstance->mc->repCanInlineTypeCheckWithObjectVTable(cls); +} + +// return flags (defined above, CORINFO_FLG_PUBLIC ...) +DWORD MyICJI::getClassAttribs ( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getClassAttribs"); + return jitInstance->mc->repGetClassAttribs(cls); +} + +// Returns "TRUE" iff "cls" is a struct type such that return buffers used for returning a value +// of this type must be stack-allocated. This will generally be true only if the struct +// contains GC pointers, and does not exceed some size limit. Maintaining this as an invariant allows +// an optimization: the JIT may assume that return buffer pointers for return types for which this predicate +// returns TRUE are always stack allocated, and thus, that stores to the GC-pointer fields of such return +// buffers do not require GC write barriers. +BOOL MyICJI::isStructRequiringStackAllocRetBuf(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("isStructRequiringStackAllocRetBuf"); + return jitInstance->mc->repIsStructRequiringStackAllocRetBuf(cls); +} + +CORINFO_MODULE_HANDLE MyICJI::getClassModule ( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getClassModule"); + LogError("Hit unimplemented getClassModule"); + DebugBreakorAV(28); + return (CORINFO_MODULE_HANDLE)0; + //jitInstance->mc->getClassModule(cls); +} + +// Returns the assembly that contains the module "mod". +CORINFO_ASSEMBLY_HANDLE MyICJI::getModuleAssembly ( + CORINFO_MODULE_HANDLE mod + ) +{ + jitInstance->mc->cr->AddCall("getModuleAssembly"); + LogError("Hit unimplemented getModuleAssembly"); + DebugBreakorAV(28); + return (CORINFO_ASSEMBLY_HANDLE)0; + +// return jitInstance->mc->getModuleAssembly(mod); +} + +// Returns the name of the assembly "assem". +const char* MyICJI::getAssemblyName ( + CORINFO_ASSEMBLY_HANDLE assem + ) +{ + jitInstance->mc->cr->AddCall("getAssemblyName"); + LogError("Hit unimplemented getAssemblyName"); + DebugBreakorAV(28); + return nullptr; +// return jitInstance->mc->getAssemblyName(assem); +} + +// Allocate and delete process-lifetime objects. Should only be +// referred to from static fields, lest a leak occur. +// Note that "LongLifetimeFree" does not execute destructors, if "obj" +// is an array of a struct type with a destructor. +void* MyICJI::LongLifetimeMalloc(size_t sz) +{ + jitInstance->mc->cr->AddCall("LongLifetimeMalloc"); + LogError("Hit unimplemented LongLifetimeMalloc"); + DebugBreakorAV(32); + return 0; +} + +void MyICJI::LongLifetimeFree(void* obj) +{ + jitInstance->mc->cr->AddCall("LongLifetimeFree"); + LogError("Hit unimplemented LongLifetimeFree"); + DebugBreakorAV(33); +} + +size_t MyICJI::getClassModuleIdForStatics ( + CORINFO_CLASS_HANDLE cls, + CORINFO_MODULE_HANDLE *pModule, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getClassModuleIdForStatics"); + return jitInstance->mc->repGetClassModuleIdForStatics(cls, pModule, ppIndirection); +} + +// return the number of bytes needed by an instance of the class +unsigned MyICJI::getClassSize ( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getClassSize"); + return jitInstance->mc->repGetClassSize(cls); +} + +unsigned MyICJI::getClassAlignmentRequirement ( + CORINFO_CLASS_HANDLE cls, + BOOL fDoubleAlignHint + ) +{ + jitInstance->mc->cr->AddCall("getClassAlignmentRequirement"); + return jitInstance->mc->repGetClassAlignmentRequirement(cls, fDoubleAlignHint); +} + +// This is only called for Value classes. It returns a boolean array +// in representing of 'cls' from a GC perspective. The class is +// assumed to be an array of machine words +// (of length // getClassSize(cls) / sizeof(void*)), +// 'gcPtrs' is a poitner to an array of BYTEs of this length. +// getClassGClayout fills in this array so that gcPtrs[i] is set +// to one of the CorInfoGCType values which is the GC type of +// the i-th machine word of an object of type 'cls' +// returns the number of GC pointers in the array +unsigned MyICJI::getClassGClayout ( + CORINFO_CLASS_HANDLE cls, /* IN */ + BYTE *gcPtrs /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getClassGClayout"); + return jitInstance->mc->repGetClassGClayout(cls, gcPtrs); +} + +// returns the number of instance fields in a class +unsigned MyICJI::getClassNumInstanceFields ( + CORINFO_CLASS_HANDLE cls /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("getClassNumInstanceFields"); + return jitInstance->mc->repGetClassNumInstanceFields(cls); +} + +CORINFO_FIELD_HANDLE MyICJI::getFieldInClass( + CORINFO_CLASS_HANDLE clsHnd, + INT num + ) +{ + jitInstance->mc->cr->AddCall("getFieldInClass"); + return jitInstance->mc->repGetFieldInClass(clsHnd, num); +} + +BOOL MyICJI::checkMethodModifier( + CORINFO_METHOD_HANDLE hMethod, + LPCSTR modifier, + BOOL fOptional + ) +{ + jitInstance->mc->cr->AddCall("checkMethodModifier"); + BOOL result = jitInstance->mc->repCheckMethodModifier(hMethod, modifier, fOptional); + return result; +} + +// returns the "NEW" helper optimized for "newCls." +CorInfoHelpFunc MyICJI::getNewHelper( + CORINFO_RESOLVED_TOKEN * pResolvedToken, + CORINFO_METHOD_HANDLE callerHandle + ) +{ + jitInstance->mc->cr->AddCall("getNewHelper"); + return jitInstance->mc->repGetNewHelper(pResolvedToken, callerHandle); +} + +// returns the newArr (1-Dim array) helper optimized for "arrayCls." +CorInfoHelpFunc MyICJI::getNewArrHelper( + CORINFO_CLASS_HANDLE arrayCls + ) +{ + jitInstance->mc->cr->AddCall("getNewArrHelper"); + return jitInstance->mc->repGetNewArrHelper(arrayCls); +} + +// returns the optimized "IsInstanceOf" or "ChkCast" helper +CorInfoHelpFunc MyICJI::getCastingHelper( + CORINFO_RESOLVED_TOKEN * pResolvedToken, + bool fThrowing + ) +{ + jitInstance->mc->cr->AddCall("getCastingHelper"); + return jitInstance->mc->repGetCastingHelper(pResolvedToken, fThrowing); +} + +// returns helper to trigger static constructor +CorInfoHelpFunc MyICJI::getSharedCCtorHelper( + CORINFO_CLASS_HANDLE clsHnd + ) +{ + jitInstance->mc->cr->AddCall("getSharedCCtorHelper"); + return jitInstance->mc->repGetSharedCCtorHelper(clsHnd); +} + +CorInfoHelpFunc MyICJI::getSecurityPrologHelper( + CORINFO_METHOD_HANDLE ftn + ) +{ + jitInstance->mc->cr->AddCall("getSecurityPrologHelper"); + return jitInstance->mc->repGetSecurityPrologHelper(ftn); +} + +// This is not pretty. Boxing nullable<T> actually returns +// a boxed<T> not a boxed Nullable<T>. This call allows the verifier +// to call back to the EE on the 'box' instruction and get the transformed +// type to use for verification. +CORINFO_CLASS_HANDLE MyICJI::getTypeForBox( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getTypeForBox"); + return jitInstance->mc->repGetTypeForBox(cls); +} + +// returns the correct box helper for a particular class. Note +// that if this returns CORINFO_HELP_BOX, the JIT can assume +// 'standard' boxing (allocate object and copy), and optimize +CorInfoHelpFunc MyICJI::getBoxHelper( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getBoxHelper"); + return jitInstance->mc->repGetBoxHelper(cls); +} + +// returns the unbox helper. If 'helperCopies' points to a true +// value it means the JIT is requesting a helper that unboxes the +// value into a particular location and thus has the signature +// void unboxHelper(void* dest, CORINFO_CLASS_HANDLE cls, Object* obj) +// Otherwise (it is null or points at a FALSE value) it is requesting +// a helper that returns a poitner to the unboxed data +// void* unboxHelper(CORINFO_CLASS_HANDLE cls, Object* obj) +// The EE has the option of NOT returning the copy style helper +// (But must be able to always honor the non-copy style helper) +// The EE set 'helperCopies' on return to indicate what kind of +// helper has been created. + +CorInfoHelpFunc MyICJI::getUnBoxHelper( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getUnBoxHelper"); + CorInfoHelpFunc result = jitInstance->mc->repGetUnBoxHelper(cls); + return result; +} + +bool MyICJI::getReadyToRunHelper( + CORINFO_RESOLVED_TOKEN * pResolvedToken, + CORINFO_LOOKUP_KIND * pGenericLookupKind, + CorInfoHelpFunc id, + CORINFO_CONST_LOOKUP * pLookup + ) +{ + jitInstance->mc->cr->AddCall("getReadyToRunHelper"); + return jitInstance->mc->repGetReadyToRunHelper(pResolvedToken, pGenericLookupKind, id, pLookup); +} + +void MyICJI::getReadyToRunDelegateCtorHelper( + CORINFO_RESOLVED_TOKEN * pTargetMethod, + CORINFO_CLASS_HANDLE delegateType, + CORINFO_CONST_LOOKUP * pLookup + ) +{ + jitInstance->mc->cr->AddCall("getReadyToRunDelegateCtorHelper"); + jitInstance->mc->repGetReadyToRunDelegateCtorHelper(pTargetMethod, delegateType, pLookup); +} + +const char* MyICJI::getHelperName( + CorInfoHelpFunc funcNum + ) +{ + jitInstance->mc->cr->AddCall("getHelperName"); + return jitInstance->mc->repGetHelperName(funcNum); +} + +// This function tries to initialize the class (run the class constructor). +// this function returns whether the JIT must insert helper calls before +// accessing static field or method. +// +// See code:ICorClassInfo#ClassConstruction. +CorInfoInitClassResult MyICJI::initClass( + CORINFO_FIELD_HANDLE field, // Non-nullptr - inquire about cctor trigger before static field access + // nullptr - inquire about cctor trigger in method prolog + CORINFO_METHOD_HANDLE method, // Method referencing the field or prolog + CORINFO_CONTEXT_HANDLE context, // Exact context of method + BOOL speculative // TRUE means don't actually run it + ) +{ + jitInstance->mc->cr->AddCall("initClass"); + return jitInstance->mc->repInitClass(field, method, context, speculative); +} + +// This used to be called "loadClass". This records the fact +// that the class must be loaded (including restored if necessary) before we execute the +// code that we are currently generating. When jitting code +// the function loads the class immediately. When zapping code +// the zapper will if necessary use the call to record the fact that we have +// to do a fixup/restore before running the method currently being generated. +// +// This is typically used to ensure value types are loaded before zapped +// code that manipulates them is executed, so that the GC can access information +// about those value types. +void MyICJI::classMustBeLoadedBeforeCodeIsRun( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("classMustBeLoadedBeforeCodeIsRun"); + jitInstance->mc->cr->recClassMustBeLoadedBeforeCodeIsRun(cls); +} + +// returns the class handle for the special builtin classes +CORINFO_CLASS_HANDLE MyICJI::getBuiltinClass ( + CorInfoClassId classId + ) +{ + jitInstance->mc->cr->AddCall("getBuiltinClass"); + return jitInstance->mc->repGetBuiltinClass(classId); +} + +// "System.Int32" ==> CORINFO_TYPE_INT.. +CorInfoType MyICJI::getTypeForPrimitiveValueClass( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getTypeForPrimitiveValueClass"); + return jitInstance->mc->repGetTypeForPrimitiveValueClass(cls); +} + +// TRUE if child is a subtype of parent +// if parent is an interface, then does child implement / extend parent +BOOL MyICJI::canCast( + CORINFO_CLASS_HANDLE child, // subtype (extends parent) + CORINFO_CLASS_HANDLE parent // base type + ) +{ + jitInstance->mc->cr->AddCall("canCast"); + return jitInstance->mc->repCanCast(child, parent); +} + +// TRUE if cls1 and cls2 are considered equivalent types. +BOOL MyICJI::areTypesEquivalent( + CORINFO_CLASS_HANDLE cls1, + CORINFO_CLASS_HANDLE cls2 + ) +{ + jitInstance->mc->cr->AddCall("areTypesEquivalent"); + return jitInstance->mc->repAreTypesEquivalent(cls1, cls2); +} + +// returns is the intersection of cls1 and cls2. +CORINFO_CLASS_HANDLE MyICJI::mergeClasses( + CORINFO_CLASS_HANDLE cls1, + CORINFO_CLASS_HANDLE cls2 + ) +{ + jitInstance->mc->cr->AddCall("mergeClasses"); + return jitInstance->mc->repMergeClasses(cls1, cls2); +} + +// Given a class handle, returns the Parent type. +// For COMObjectType, it returns Class Handle of System.Object. +// Returns 0 if System.Object is passed in. +CORINFO_CLASS_HANDLE MyICJI::getParentType ( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getParentType"); + return jitInstance->mc->repGetParentType(cls); +} + +// Returns the CorInfoType of the "child type". If the child type is +// not a primitive type, *clsRet will be set. +// Given an Array of Type Foo, returns Foo. +// Given BYREF Foo, returns Foo +CorInfoType MyICJI::getChildType ( + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_CLASS_HANDLE *clsRet + ) +{ + jitInstance->mc->cr->AddCall("getChildType"); + return jitInstance->mc->repGetChildType(clsHnd, clsRet); +} + +// Check constraints on type arguments of this class and parent classes +BOOL MyICJI::satisfiesClassConstraints( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("satisfiesClassConstraints"); + return jitInstance->mc->repSatisfiesClassConstraints(cls); +} + +// Check if this is a single dimensional array type +BOOL MyICJI::isSDArray( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("isSDArray"); + return jitInstance->mc->repIsSDArray(cls); +} + +// Get the numbmer of dimensions in an array +unsigned MyICJI::getArrayRank( + CORINFO_CLASS_HANDLE cls + ) +{ + jitInstance->mc->cr->AddCall("getArrayRank"); + return jitInstance->mc->repGetArrayRank(cls); +} + +// Get static field data for an array +void * MyICJI::getArrayInitializationData( + CORINFO_FIELD_HANDLE field, + DWORD size + ) +{ + jitInstance->mc->cr->AddCall("getArrayInitializationData"); + return jitInstance->mc->repGetArrayInitializationData(field, size); +} + +// Check Visibility rules. +CorInfoIsAccessAllowedResult MyICJI::canAccessClass( + CORINFO_RESOLVED_TOKEN * pResolvedToken, + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_HELPER_DESC *pAccessHelper /* If canAccessMethod returns something other + than ALLOWED, then this is filled in. */ + ) +{ + jitInstance->mc->cr->AddCall("canAccessClass"); + return jitInstance->mc->repCanAccessClass(pResolvedToken, callerHandle, pAccessHelper); +} + +/**********************************************************************************/ +// +// ICorFieldInfo +// +/**********************************************************************************/ + +// this function is for debugging only. It returns the field name +// and if 'moduleName' is non-null, it sets it to something that will +// says which method (a class name, or a module name) +const char* MyICJI::getFieldName ( + CORINFO_FIELD_HANDLE ftn, /* IN */ + const char **moduleName /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getFieldName"); + return jitInstance->mc->repGetFieldName(ftn, moduleName); +} + +// return class it belongs to +CORINFO_CLASS_HANDLE MyICJI::getFieldClass ( + CORINFO_FIELD_HANDLE field + ) +{ + jitInstance->mc->cr->AddCall("getFieldClass"); + return jitInstance->mc->repGetFieldClass(field); +} + +// Return the field's type, if it is CORINFO_TYPE_VALUECLASS 'structType' is set +// the field's value class (if 'structType' == 0, then don't bother +// the structure info). +// +// 'memberParent' is typically only set when verifying. It should be the +// result of calling getMemberParent. +CorInfoType MyICJI::getFieldType( + CORINFO_FIELD_HANDLE field, + CORINFO_CLASS_HANDLE *structType, + CORINFO_CLASS_HANDLE memberParent/* IN */ + ) +{ + jitInstance->mc->cr->AddCall("getFieldType"); + return jitInstance->mc->repGetFieldType(field, structType, memberParent); +} + +// return the data member's instance offset +unsigned MyICJI::getFieldOffset( + CORINFO_FIELD_HANDLE field + ) +{ + jitInstance->mc->cr->AddCall("getFieldOffset"); + return jitInstance->mc->repGetFieldOffset(field); +} + +// TODO: jit64 should be switched to the same plan as the i386 jits - use +// getClassGClayout to figure out the need for writebarrier helper, and inline the copying. +// The interpretted value class copy is slow. Once this happens, USE_WRITE_BARRIER_HELPERS +bool MyICJI::isWriteBarrierHelperRequired( + CORINFO_FIELD_HANDLE field) +{ + jitInstance->mc->cr->AddCall("isWriteBarrierHelperRequired"); + bool result = jitInstance->mc->repIsWriteBarrierHelperRequired(field); + return result; +} + +void MyICJI::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken, + CORINFO_METHOD_HANDLE callerHandle, + CORINFO_ACCESS_FLAGS flags, + CORINFO_FIELD_INFO *pResult + ) +{ + jitInstance->mc->cr->AddCall("getFieldInfo"); + jitInstance->mc->repGetFieldInfo(pResolvedToken, callerHandle, flags, pResult); +} + +// Returns true iff "fldHnd" represents a static field. +bool MyICJI::isFieldStatic(CORINFO_FIELD_HANDLE fldHnd) +{ + jitInstance->mc->cr->AddCall("isFieldStatic"); + return jitInstance->mc->repIsFieldStatic(fldHnd); +} + +/*********************************************************************************/ +// +// ICorDebugInfo +// +/*********************************************************************************/ + +// Query the EE to find out where interesting break points +// in the code are. The native compiler will ensure that these places +// have a corresponding break point in native code. +// +// Note that unless CORJIT_FLG_DEBUG_CODE is specified, this function will +// be used only as a hint and the native compiler should not change its +// code generation. +void MyICJI::getBoundaries( + CORINFO_METHOD_HANDLE ftn, // [IN] method of interest + unsigned int *cILOffsets, // [OUT] size of pILOffsets + DWORD **pILOffsets, // [OUT] IL offsets of interest + // jit MUST free with freeArray! + ICorDebugInfo::BoundaryTypes *implictBoundaries // [OUT] tell jit, all boundries of this type + ) +{ + jitInstance->mc->cr->AddCall("getBoundaries"); + jitInstance->mc->repGetBoundaries(ftn, cILOffsets, pILOffsets, implictBoundaries); + + //The JIT will want to call freearray on the array we pass back, so move the data into a form that complies with this + if(*cILOffsets > 0) + { + DWORD *realOffsets = (DWORD*)allocateArray(*cILOffsets*sizeof(ICorDebugInfo::BoundaryTypes)); + memcpy(realOffsets, *pILOffsets, *cILOffsets*sizeof(ICorDebugInfo::BoundaryTypes)); + *pILOffsets = realOffsets; + } + else + *pILOffsets = 0; +} + +// Report back the mapping from IL to native code, +// this map should include all boundaries that 'getBoundaries' +// reported as interesting to the debugger. + +// Note that debugger (and profiler) is assuming that all of the +// offsets form a contiguous block of memory, and that the +// OffsetMapping is sorted in order of increasing native offset. +void MyICJI::setBoundaries( + CORINFO_METHOD_HANDLE ftn, // [IN] method of interest + ULONG32 cMap, // [IN] size of pMap + ICorDebugInfo::OffsetMapping *pMap // [IN] map including all points of interest. + // jit allocated with allocateArray, EE frees + ) +{ + jitInstance->mc->cr->AddCall("setBoundaries"); + jitInstance->mc->cr->recSetBoundaries(ftn, cMap, pMap); + + freeArray(pMap);//see note in recSetBoundaries... we own this array and own destroying it. +} + +// Query the EE to find out the scope of local varables. +// normally the JIT would trash variables after last use, but +// under debugging, the JIT needs to keep them live over their +// entire scope so that they can be inspected. +// +// Note that unless CORJIT_FLG_DEBUG_CODE is specified, this function will +// be used only as a hint and the native compiler should not change its +// code generation. +void MyICJI::getVars( + CORINFO_METHOD_HANDLE ftn, // [IN] method of interest + ULONG32 *cVars, // [OUT] size of 'vars' + ICorDebugInfo::ILVarInfo **vars, // [OUT] scopes of variables of interest + // jit MUST free with freeArray! + bool *extendOthers // [OUT] it TRUE, then assume the scope + // of unmentioned vars is entire method + ) +{ + jitInstance->mc->cr->AddCall("getVars"); + jitInstance->mc->repGetVars(ftn, cVars, vars, extendOthers); + + //The JIT will want to call freearray on the array we pass back, so move the data into a form that complies with this + if(*cVars > 0) + { + ICorDebugInfo::ILVarInfo *realOffsets = (ICorDebugInfo::ILVarInfo*)allocateArray(*cVars*sizeof(ICorDebugInfo::ILVarInfo)); + memcpy(realOffsets, *vars, *cVars*sizeof(ICorDebugInfo::ILVarInfo)); + *vars = realOffsets; + } + else + *vars = nullptr; +} + +// Report back to the EE the location of every variable. +// note that the JIT might split lifetimes into different +// locations etc. + +void MyICJI::setVars( + CORINFO_METHOD_HANDLE ftn, // [IN] method of interest + ULONG32 cVars, // [IN] size of 'vars' + ICorDebugInfo::NativeVarInfo *vars // [IN] map telling where local vars are stored at what points + // jit allocated with allocateArray, EE frees + ) +{ + jitInstance->mc->cr->AddCall("setVars"); + jitInstance->mc->cr->recSetVars(ftn, cVars, vars); + freeArray(vars);//See note in recSetVars... we own destroying this array +} + +/*-------------------------- Misc ---------------------------------------*/ + +// Used to allocate memory that needs to handed to the EE. +// For eg, use this to allocated memory for reporting debug info, +// which will be handed to the EE by setVars() and setBoundaries() +void * MyICJI::allocateArray( + ULONG cBytes + ) +{ + return jitInstance->allocateArray(cBytes); +} + +// JitCompiler will free arrays passed by the EE using this +// For eg, The EE returns memory in getVars() and getBoundaries() +// to the JitCompiler, which the JitCompiler should release using +// freeArray() +void MyICJI::freeArray( + void *array + ) +{ + jitInstance->freeArray(array); +} + +/*********************************************************************************/ +// +// ICorArgInfo +// +/*********************************************************************************/ + +// advance the pointer to the argument list. +// a ptr of 0, is special and always means the first argument +CORINFO_ARG_LIST_HANDLE MyICJI::getArgNext ( + CORINFO_ARG_LIST_HANDLE args /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("getArgNext"); + return jitInstance->mc->repGetArgNext(args); +} + +// Get the type of a particular argument +// CORINFO_TYPE_UNDEF is returned when there are no more arguments +// If the type returned is a primitive type (or an enum) *vcTypeRet set to nullptr +// otherwise it is set to the TypeHandle associted with the type +// Enumerations will always look their underlying type (probably should fix this) +// Otherwise vcTypeRet is the type as would be seen by the IL, +// The return value is the type that is used for calling convention purposes +// (Thus if the EE wants a value class to be passed like an int, then it will +// return CORINFO_TYPE_INT +CorInfoTypeWithMod MyICJI::getArgType ( + CORINFO_SIG_INFO* sig, /* IN */ + CORINFO_ARG_LIST_HANDLE args, /* IN */ + CORINFO_CLASS_HANDLE *vcTypeRet /* OUT */ + ) +{ + DWORD exceptionCode = 0; + jitInstance->mc->cr->AddCall("getArgType"); + CorInfoTypeWithMod value = jitInstance->mc->repGetArgType(sig, args, vcTypeRet, &exceptionCode); + if(exceptionCode != 0) + ThrowException(exceptionCode); + return value; +} + +// If the Arg is a CORINFO_TYPE_CLASS fetch the class handle associated with it +CORINFO_CLASS_HANDLE MyICJI::getArgClass ( + CORINFO_SIG_INFO* sig, /* IN */ + CORINFO_ARG_LIST_HANDLE args /* IN */ + ) +{ + DWORD exceptionCode = 0; + jitInstance->mc->cr->AddCall("getArgClass"); + CORINFO_CLASS_HANDLE value = jitInstance->mc->repGetArgClass(sig, args, &exceptionCode); + if(exceptionCode != 0) + ThrowException(exceptionCode); + return value; +} + +// Returns type of HFA for valuetype +CorInfoType MyICJI::getHFAType ( + CORINFO_CLASS_HANDLE hClass + ) +{ + jitInstance->mc->cr->AddCall("getHFAType"); + LogError("Hit unimplemented getHFAType"); + DebugBreakorAV(75); + return (CorInfoType)0; +} + +/***************************************************************************** +* ICorErrorInfo contains methods to deal with SEH exceptions being thrown +* from the corinfo interface. These methods may be called when an exception +* with code EXCEPTION_COMPLUS is caught. +*****************************************************************************/ + +// Returns the HRESULT of the current exception +HRESULT MyICJI::GetErrorHRESULT( + struct _EXCEPTION_POINTERS *pExceptionPointers + ) +{ + jitInstance->mc->cr->AddCall("GetErrorHRESULT"); + LogError("Hit unimplemented GetErrorHRESULT"); + DebugBreakorAV(76); + return 0; +} + +// Fetches the message of the current exception +// Returns the size of the message (including terminating null). This can be +// greater than bufferLength if the buffer is insufficient. +ULONG MyICJI::GetErrorMessage( + __inout_ecount(bufferLength) LPWSTR buffer, + ULONG bufferLength + ) +{ + jitInstance->mc->cr->AddCall("GetErrorMessage"); + LogError("Hit unimplemented GetErrorMessage"); + DebugBreakorAV(77); + return 0; +} + +// returns EXCEPTION_EXECUTE_HANDLER if it is OK for the compile to handle the +// exception, abort some work (like the inlining) and continue compilation +// returns EXCEPTION_CONTINUE_SEARCH if exception must always be handled by the EE +// things like ThreadStoppedException ... +// returns EXCEPTION_CONTINUE_EXECUTION if exception is fixed up by the EE + +int MyICJI::FilterException( + struct _EXCEPTION_POINTERS *pExceptionPointers + ) +{ + jitInstance->mc->cr->AddCall("FilterException"); + int result = jitInstance->mc->repFilterException(pExceptionPointers); + return result; +} + +// Cleans up internal EE tracking when an exception is caught. +void MyICJI::HandleException( + struct _EXCEPTION_POINTERS *pExceptionPointers + ) +{ + jitInstance->mc->cr->AddCall("HandleException"); +} + +void MyICJI::ThrowExceptionForJitResult( + HRESULT result) +{ + jitInstance->mc->cr->AddCall("ThrowExceptionForJitResult"); + LogError("Hit unimplemented ThrowExceptionForJitResult"); + DebugBreakorAV(80); +} + +//Throws an exception defined by the given throw helper. +void MyICJI::ThrowExceptionForHelper( + const CORINFO_HELPER_DESC * throwHelper) +{ + jitInstance->mc->cr->AddCall("ThrowExceptionForHelper"); + LogError("Hit unimplemented ThrowExceptionForHelper"); + DebugBreakorAV(81); +} + +/***************************************************************************** + * ICorStaticInfo contains EE interface methods which return values that are + * constant from invocation to invocation. Thus they may be embedded in + * persisted information like statically generated code. (This is of course + * assuming that all code versions are identical each time.) + *****************************************************************************/ + +// Return details about EE internal data structures +void MyICJI::getEEInfo( + CORINFO_EE_INFO *pEEInfoOut + ) +{ + jitInstance->mc->cr->AddCall("getEEInfo"); + jitInstance->mc->repGetEEInfo(pEEInfoOut); +} + +// Returns name of the JIT timer log +LPCWSTR MyICJI::getJitTimeLogFilename() +{ + jitInstance->mc->cr->AddCall("getJitTimeLogFilename"); + //we have the ability to replay this, but we treat it in this case as EE context +// return jitInstance->eec->jitTimeLogFilename; + + // We want to be able to set COMPLUS_JitTimeLogFile when replaying, to collect JIT + // statistics. So, just do a getenv() call. This isn't quite as thorough as + // the normal CLR config value functions (which also check the registry), and we've + // also hard-coded the variable name here instead of using: + // CLRConfig::GetConfigValue(CLRConfig::INTERNAL_JitTimeLogFile); + // like in the VM, but it works for our purposes. + return GetEnvironmentVariableWithDefaultW(W("COMPlus_JitTimeLogFile")); +} + + /*********************************************************************************/ + // + // Diagnostic methods + // + /*********************************************************************************/ + +// this function is for debugging only. Returns method token. +// Returns mdMethodDefNil for dynamic methods. +mdMethodDef MyICJI::getMethodDefFromMethod( + CORINFO_METHOD_HANDLE hMethod + ) +{ + jitInstance->mc->cr->AddCall("getMethodDefFromMethod"); + mdMethodDef result = jitInstance->mc->repGetMethodDefFromMethod(hMethod); + return result; +} + +// this function is for debugging only. It returns the method name +// and if 'moduleName' is non-null, it sets it to something that will +// says which method (a class name, or a module name) +const char* MyICJI::getMethodName ( + CORINFO_METHOD_HANDLE ftn, /* IN */ + const char **moduleName /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getMethodName"); + return jitInstance->mc->repGetMethodName(ftn, moduleName); +} + +// this function is for debugging only. It returns a value that +// is will always be the same for a given method. It is used +// to implement the 'jitRange' functionality +unsigned MyICJI::getMethodHash ( + CORINFO_METHOD_HANDLE ftn /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("getMethodHash"); + return jitInstance->mc->repGetMethodHash(ftn); +} + +// this function is for debugging only. +size_t MyICJI::findNameOfToken ( + CORINFO_MODULE_HANDLE module, /* IN */ + mdToken metaTOK, /* IN */ + __out_ecount (FQNameCapacity) char * szFQName, /* OUT */ + size_t FQNameCapacity /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("findNameOfToken"); + return jitInstance->mc->repFindNameOfToken(module, metaTOK, szFQName, FQNameCapacity); +} + +bool MyICJI::getSystemVAmd64PassStructInRegisterDescriptor( + /* IN */ CORINFO_CLASS_HANDLE structHnd, + /* OUT */ SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr + ) +{ + jitInstance->mc->cr->AddCall("getSystemVAmd64PassStructInRegisterDescriptor"); + return jitInstance->mc->repGetSystemVAmd64PassStructInRegisterDescriptor(structHnd, structPassInRegDescPtr); +} + +//Stuff on ICorDynamicInfo +DWORD MyICJI::getThreadTLSIndex( + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getThreadTLSIndex"); + return jitInstance->mc->repGetThreadTLSIndex(ppIndirection); +} + +const void * MyICJI::getInlinedCallFrameVptr( + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getInlinedCallFrameVptr"); + return jitInstance->mc->repGetInlinedCallFrameVptr(ppIndirection); +} + +LONG * MyICJI::getAddrOfCaptureThreadGlobal( + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getAddrOfCaptureThreadGlobal"); + return jitInstance->mc->repGetAddrOfCaptureThreadGlobal(ppIndirection); +} + +SIZE_T* MyICJI::getAddrModuleDomainID(CORINFO_MODULE_HANDLE module) +{ + jitInstance->mc->cr->AddCall("getAddrModuleDomainID"); + LogError("Hit unimplemented getAddrModuleDomainID"); + DebugBreakorAV(88); + return 0; +} + +// return the native entry point to an EE helper (see CorInfoHelpFunc) +void* MyICJI::getHelperFtn ( + CorInfoHelpFunc ftnNum, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getHelperFtn"); + return jitInstance->mc->repGetHelperFtn(ftnNum, ppIndirection); +} + +// return a callable address of the function (native code). This function +// may return a different value (depending on whether the method has +// been JITed or not. +void MyICJI::getFunctionEntryPoint( + CORINFO_METHOD_HANDLE ftn, /* IN */ + CORINFO_CONST_LOOKUP * pResult, /* OUT */ + CORINFO_ACCESS_FLAGS accessFlags) +{ + jitInstance->mc->cr->AddCall("getFunctionEntryPoint"); + jitInstance->mc->repGetFunctionEntryPoint(ftn, pResult, accessFlags); +} + +// return a directly callable address. This can be used similarly to the +// value returned by getFunctionEntryPoint() except that it is +// guaranteed to be multi callable entrypoint. +void MyICJI::getFunctionFixedEntryPoint( + CORINFO_METHOD_HANDLE ftn, + CORINFO_CONST_LOOKUP * pResult) +{ + jitInstance->mc->cr->AddCall("getFunctionFixedEntryPoint"); + jitInstance->mc->repGetFunctionFixedEntryPoint(ftn, pResult); +} + +// get the synchronization handle that is passed to monXstatic function +void* MyICJI::getMethodSync( + CORINFO_METHOD_HANDLE ftn, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getMethodSync"); + return jitInstance->mc->repGetMethodSync(ftn, ppIndirection); +} + +// These entry points must be called if a handle is being embedded in +// the code to be passed to a JIT helper function. (as opposed to just +// being passed back into the ICorInfo interface.) + +// get slow lazy string literal helper to use (CORINFO_HELP_STRCNS*). +// Returns CORINFO_HELP_UNDEF if lazy string literal helper cannot be used. +CorInfoHelpFunc MyICJI::getLazyStringLiteralHelper( + CORINFO_MODULE_HANDLE handle + ) +{ + jitInstance->mc->cr->AddCall("getLazyStringLiteralHelper"); + return jitInstance->mc->repGetLazyStringLiteralHelper(handle); +} + + +CORINFO_MODULE_HANDLE MyICJI::embedModuleHandle( + CORINFO_MODULE_HANDLE handle, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("embedModuleHandle"); + return jitInstance->mc->repEmbedModuleHandle(handle, ppIndirection); +} + +CORINFO_CLASS_HANDLE MyICJI::embedClassHandle( + CORINFO_CLASS_HANDLE handle, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("embedClassHandle"); + return jitInstance->mc->repEmbedClassHandle(handle, ppIndirection); +} + +CORINFO_METHOD_HANDLE MyICJI::embedMethodHandle( + CORINFO_METHOD_HANDLE handle, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("embedMethodHandle"); + return jitInstance->mc->repEmbedMethodHandle(handle, ppIndirection); +} + +CORINFO_FIELD_HANDLE MyICJI::embedFieldHandle( + CORINFO_FIELD_HANDLE handle, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("embedFieldHandle"); + return jitInstance->mc->repEmbedFieldHandle(handle, ppIndirection); +} + +// Given a module scope (module), a method handle (context) and +// a metadata token (metaTOK), fetch the handle +// (type, field or method) associated with the token. +// If this is not possible at compile-time (because the current method's +// code is shared and the token contains generic parameters) +// then indicate how the handle should be looked up at run-time. +// +void MyICJI::embedGenericHandle( + CORINFO_RESOLVED_TOKEN * pResolvedToken, + BOOL fEmbedParent, // TRUE - embeds parent type handle of the field/method handle + CORINFO_GENERICHANDLE_RESULT * pResult) +{ + jitInstance->mc->cr->AddCall("embedGenericHandle"); + jitInstance->mc->repEmbedGenericHandle(pResolvedToken, fEmbedParent, pResult); +} + +// Return information used to locate the exact enclosing type of the current method. +// Used only to invoke .cctor method from code shared across generic instantiations +// !needsRuntimeLookup statically known (enclosing type of method itself) +// needsRuntimeLookup: +// CORINFO_LOOKUP_THISOBJ use vtable pointer of 'this' param +// CORINFO_LOOKUP_CLASSPARAM use vtable hidden param +// CORINFO_LOOKUP_METHODPARAM use enclosing type of method-desc hidden param +CORINFO_LOOKUP_KIND MyICJI::getLocationOfThisType( + CORINFO_METHOD_HANDLE context + ) +{ + jitInstance->mc->cr->AddCall("getLocationOfThisType"); + return jitInstance->mc->repGetLocationOfThisType(context); +} + +// return the unmanaged target *if method has already been prelinked.* +void* MyICJI::getPInvokeUnmanagedTarget( + CORINFO_METHOD_HANDLE method, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getPInvokeUnmanagedTarget"); + void *result = jitInstance->mc->repGetPInvokeUnmanagedTarget(method, ppIndirection); + return result; +} + +// return address of fixup area for late-bound PInvoke calls. +void* MyICJI::getAddressOfPInvokeFixup( + CORINFO_METHOD_HANDLE method, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getAddressOfPInvokeFixup"); + return jitInstance->mc->repGetAddressOfPInvokeFixup(method, ppIndirection); +} + +// return address of fixup area for late-bound PInvoke calls. +void MyICJI::getAddressOfPInvokeTarget( + CORINFO_METHOD_HANDLE method, + CORINFO_CONST_LOOKUP *pLookup + ) +{ + jitInstance->mc->cr->AddCall("getAddressOfPInvokeTarget"); + jitInstance->mc->repGetAddressOfPInvokeTarget(method, pLookup); +} + +// Generate a cookie based on the signature that would needs to be passed +// to CORINFO_HELP_PINVOKE_CALLI +LPVOID MyICJI::GetCookieForPInvokeCalliSig( + CORINFO_SIG_INFO* szMetaSig, + void ** ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("GetCookieForPInvokeCalliSig"); + return jitInstance->mc->repGetCookieForPInvokeCalliSig(szMetaSig, ppIndirection); +} + +// returns true if a VM cookie can be generated for it (might be false due to cross-module +// inlining, in which case the inlining should be aborted) +bool MyICJI::canGetCookieForPInvokeCalliSig( + CORINFO_SIG_INFO* szMetaSig + ) +{ + jitInstance->mc->cr->AddCall("canGetCookieForPInvokeCalliSig"); + return jitInstance->mc->repCanGetCookieForPInvokeCalliSig(szMetaSig); +} + +// Gets a handle that is checked to see if the current method is +// included in "JustMyCode" +CORINFO_JUST_MY_CODE_HANDLE MyICJI::getJustMyCodeHandle( + CORINFO_METHOD_HANDLE method, + CORINFO_JUST_MY_CODE_HANDLE**ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getJustMyCodeHandle"); + return jitInstance->mc->repGetJustMyCodeHandle(method, ppIndirection); +} + +// Gets a method handle that can be used to correlate profiling data. +// This is the IP of a native method, or the address of the descriptor struct +// for IL. Always guaranteed to be unique per process, and not to move. */ +void MyICJI::GetProfilingHandle( + BOOL *pbHookFunction, + void **pProfilerHandle, + BOOL *pbIndirectedHandles + ) +{ + jitInstance->mc->cr->AddCall("GetProfilingHandle"); + jitInstance->mc->repGetProfilingHandle(pbHookFunction, pProfilerHandle, pbIndirectedHandles); +} + +// Returns instructions on how to make the call. See code:CORINFO_CALL_INFO for possible return values. +void MyICJI::getCallInfo( + // Token info + CORINFO_RESOLVED_TOKEN * pResolvedToken, + + //Generics info + CORINFO_RESOLVED_TOKEN * pConstrainedResolvedToken, + + //Security info + CORINFO_METHOD_HANDLE callerHandle, + + //Jit info + CORINFO_CALLINFO_FLAGS flags, + + //out params + CORINFO_CALL_INFO *pResult + ) +{ + jitInstance->mc->cr->AddCall("getCallInfo"); + DWORD exceptionCode = 0; + jitInstance->mc->repGetCallInfo(pResolvedToken, pConstrainedResolvedToken, callerHandle, flags, pResult, &exceptionCode); + if(exceptionCode != 0) + ThrowException(exceptionCode); +} + +BOOL MyICJI::canAccessFamily(CORINFO_METHOD_HANDLE hCaller, + CORINFO_CLASS_HANDLE hInstanceType) + +{ + jitInstance->mc->cr->AddCall("canAccessFamily"); + return jitInstance->mc->repCanAccessFamily(hCaller, hInstanceType); +} +// Returns TRUE if the Class Domain ID is the RID of the class (currently true for every class +// except reflection emitted classes and generics) +BOOL MyICJI::isRIDClassDomainID(CORINFO_CLASS_HANDLE cls) +{ + jitInstance->mc->cr->AddCall("isRIDClassDomainID"); + LogError("Hit unimplemented isRIDClassDomainID"); + DebugBreakorAV(107); + return false; +} + +// returns the class's domain ID for accessing shared statics +unsigned MyICJI::getClassDomainID ( + CORINFO_CLASS_HANDLE cls, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getClassDomainID"); + return jitInstance->mc->repGetClassDomainID(cls, ppIndirection); +} + + +// return the data's address (for static fields only) +void* MyICJI::getFieldAddress( + CORINFO_FIELD_HANDLE field, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getFieldAddress"); + return jitInstance->mc->repGetFieldAddress(field, ppIndirection); +} + +// registers a vararg sig & returns a VM cookie for it (which can contain other stuff) +CORINFO_VARARGS_HANDLE MyICJI::getVarArgsHandle( + CORINFO_SIG_INFO *pSig, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getVarArgsHandle"); + return jitInstance->mc->repGetVarArgsHandle(pSig, ppIndirection); +} + +// returns true if a VM cookie can be generated for it (might be false due to cross-module +// inlining, in which case the inlining should be aborted) +bool MyICJI::canGetVarArgsHandle( + CORINFO_SIG_INFO *pSig + ) +{ + jitInstance->mc->cr->AddCall("canGetVarArgsHandle"); + return jitInstance->mc->repCanGetVarArgsHandle(pSig); +} + +// Allocate a string literal on the heap and return a handle to it +InfoAccessType MyICJI::constructStringLiteral( + CORINFO_MODULE_HANDLE module, + mdToken metaTok, + void **ppValue + ) +{ + jitInstance->mc->cr->AddCall("constructStringLiteral"); + return jitInstance->mc->repConstructStringLiteral(module, metaTok, ppValue); +} + +InfoAccessType MyICJI::emptyStringLiteral( + void **ppValue + ) +{ + jitInstance->mc->cr->AddCall("emptyStringLiteral"); + return jitInstance->mc->repEmptyStringLiteral(ppValue); +} + +// (static fields only) given that 'field' refers to thread local store, +// return the ID (TLS index), which is used to find the begining of the +// TLS data area for the particular DLL 'field' is associated with. +DWORD MyICJI::getFieldThreadLocalStoreID ( + CORINFO_FIELD_HANDLE field, + void **ppIndirection + ) +{ + jitInstance->mc->cr->AddCall("getFieldThreadLocalStoreID"); + return jitInstance->mc->repGetFieldThreadLocalStoreID(field, ppIndirection); +} + +// Sets another object to intercept calls to "self" and current method being compiled +void MyICJI::setOverride( + ICorDynamicInfo *pOverride, + CORINFO_METHOD_HANDLE currentMethod + ) +{ + jitInstance->mc->cr->AddCall("setOverride"); + LogError("Hit unimplemented setOverride"); + DebugBreakorAV(115); +} + +// Adds an active dependency from the context method's module to the given module +// This is internal callback for the EE. JIT should not call it directly. +void MyICJI::addActiveDependency( + CORINFO_MODULE_HANDLE moduleFrom, + CORINFO_MODULE_HANDLE moduleTo + ) +{ + jitInstance->mc->cr->AddCall("addActiveDependency"); + LogError("Hit unimplemented addActiveDependency"); + DebugBreakorAV(116); +} + +CORINFO_METHOD_HANDLE MyICJI::GetDelegateCtor( + CORINFO_METHOD_HANDLE methHnd, + CORINFO_CLASS_HANDLE clsHnd, + CORINFO_METHOD_HANDLE targetMethodHnd, + DelegateCtorArgs * pCtorData + ) +{ + jitInstance->mc->cr->AddCall("GetDelegateCtor"); + return jitInstance->mc->repGetDelegateCtor(methHnd, clsHnd, targetMethodHnd, pCtorData); +} + +void MyICJI::MethodCompileComplete( + CORINFO_METHOD_HANDLE methHnd + ) +{ + jitInstance->mc->cr->AddCall("MethodCompileComplete"); + LogError("Hit unimplemented MethodCompileComplete"); + DebugBreakorAV(118); +} + +// return a thunk that will copy the arguments for the given signature. +void* MyICJI::getTailCallCopyArgsThunk ( + CORINFO_SIG_INFO *pSig, + CorInfoHelperTailCallSpecialHandling flags + ) +{ + jitInstance->mc->cr->AddCall("getTailCallCopyArgsThunk"); + return jitInstance->mc->repGetTailCallCopyArgsThunk(pSig, flags); +} + +//Stuff directly on ICorJitInfo + +// Returns extended flags for a particular compilation instance. +DWORD MyICJI::getJitFlags(CORJIT_FLAGS *jitFlags, DWORD sizeInBytes) +{ + jitInstance->mc->cr->AddCall("getJitFlags"); + return jitInstance->mc->repGetJitFlags(jitFlags, sizeInBytes); +} + +// Runs the given function with the given parameter under an error trap +// and returns true if the function completes successfully. We fake this +// up a bit for SuperPMI and simply catch all exceptions. +bool MyICJI::runWithErrorTrap(void (*function)(void*), void *param) +{ + return RunWithErrorTrap(function, param); +} + +// return memory manager that the JIT can use to allocate a regular memory +IEEMemoryManager* MyICJI::getMemoryManager() +{ + return InitIEEMemoryManager(jitInstance); +} + +// get a block of memory for the code, readonly data, and read-write data +void MyICJI::allocMem ( + ULONG hotCodeSize, /* IN */ + ULONG coldCodeSize, /* IN */ + ULONG roDataSize, /* IN */ + ULONG xcptnsCount, /* IN */ + CorJitAllocMemFlag flag, /* IN */ + void ** hotCodeBlock, /* OUT */ + void ** coldCodeBlock, /* OUT */ + void ** roDataBlock /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("allocMem"); + //TODO-Cleanup: investigate if we need to check roDataBlock as well. Could hot block size be ever 0? + *hotCodeBlock = HeapAlloc(jitInstance->mc->cr->getCodeHeap(),0,hotCodeSize); + if (coldCodeSize>0) + *coldCodeBlock = HeapAlloc(jitInstance->mc->cr->getCodeHeap(),0,coldCodeSize); + else + *coldCodeBlock = nullptr; + *roDataBlock = HeapAlloc(jitInstance->mc->cr->getCodeHeap(),0,roDataSize); + jitInstance->mc->cr->recAllocMem(hotCodeSize, coldCodeSize, roDataSize, xcptnsCount, flag, hotCodeBlock, coldCodeBlock, roDataBlock); +} + +// Reserve memory for the method/funclet's unwind information. +// Note that this must be called before allocMem. It should be +// called once for the main method, once for every funclet, and +// once for every block of cold code for which allocUnwindInfo +// will be called. +// +// This is necessary because jitted code must allocate all the +// memory needed for the unwindInfo at the allocMem call. +// For prejitted code we split up the unwinding information into +// separate sections .rdata and .pdata. +// +void MyICJI::reserveUnwindInfo ( + BOOL isFunclet, /* IN */ + BOOL isColdCode, /* IN */ + ULONG unwindSize /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("reserveUnwindInfo"); + jitInstance->mc->cr->recReserveUnwindInfo(isFunclet, isColdCode, unwindSize); +} + +// Allocate and initialize the .rdata and .pdata for this method or +// funclet, and get the block of memory needed for the machine-specific +// unwind information (the info for crawling the stack frame). +// Note that allocMem must be called first. +// +// Parameters: +// +// pHotCode main method code buffer, always filled in +// pColdCode cold code buffer, only filled in if this is cold code, +// null otherwise +// startOffset start of code block, relative to appropriate code buffer +// (e.g. pColdCode if cold, pHotCode if hot). +// endOffset end of code block, relative to appropriate code buffer +// unwindSize size of unwind info pointed to by pUnwindBlock +// pUnwindBlock pointer to unwind info +// funcKind type of funclet (main method code, handler, filter) +// +void MyICJI::allocUnwindInfo ( + BYTE * pHotCode, /* IN */ + BYTE * pColdCode, /* IN */ + ULONG startOffset, /* IN */ + ULONG endOffset, /* IN */ + ULONG unwindSize, /* IN */ + BYTE * pUnwindBlock, /* IN */ + CorJitFuncKind funcKind /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("allocUnwindInfo"); + jitInstance->mc->cr->recAllocUnwindInfo(pHotCode, pColdCode, startOffset, endOffset, unwindSize, pUnwindBlock, funcKind); +} + +// Get a block of memory needed for the code manager information, +// (the info for enumerating the GC pointers while crawling the +// stack frame). +// Note that allocMem must be called first +void * MyICJI::allocGCInfo ( + size_t size /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("allocGCInfo"); + void *temp = (unsigned char*)HeapAlloc(jitInstance->mc->cr->getCodeHeap(),0,size); + jitInstance->mc->cr->recAllocGCInfo(size, temp); + + return temp; +} + +// Only used on x64. +void MyICJI::yieldExecution() +{ + jitInstance->mc->cr->AddCall("yieldExecution"); +} + +// Indicate how many exception handler blocks are to be returned. +// This is guaranteed to be called before any 'setEHinfo' call. +// Note that allocMem must be called before this method can be called. +void MyICJI::setEHcount ( + unsigned cEH /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("setEHcount"); + jitInstance->mc->cr->recSetEHcount(cEH); +} + +// Set the values for one particular exception handler block. +// +// Handler regions should be lexically contiguous. +// This is because FinallyIsUnwinding() uses lexicality to +// determine if a "finally" clause is executing. +void MyICJI::setEHinfo ( + unsigned EHnumber, /* IN */ + const CORINFO_EH_CLAUSE *clause /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("setEHinfo"); + jitInstance->mc->cr->recSetEHinfo(EHnumber, clause); +} + +// Level 1 -> fatalError, Level 2 -> Error, Level 3 -> Warning +// Level 4 means happens 10 times in a run, level 5 means 100, level 6 means 1000 ... +// returns non-zero if the logging succeeded +BOOL MyICJI::logMsg(unsigned level, const char* fmt, va_list args) +{ + jitInstance->mc->cr->AddCall("logMsg"); + +// if(level<=2) +// { + //jitInstance->mc->cr->recMessageLog(fmt, args); + //DebugBreakorAV(0x99); + //} + jitInstance->mc->cr->recMessageLog(fmt, args); + return 0; +} + +// do an assert. will return true if the code should retry (DebugBreak) +// returns false, if the assert should be igored. +int MyICJI::doAssert(const char* szFile, int iLine, const char* szExpr) +{ + jitInstance->mc->cr->AddCall("doAssert"); + char buff[16 * 1024]; + sprintf_s(buff, sizeof(buff), "%s (%d) - %s", szFile, iLine, szExpr); + + LogIssue(ISSUE_ASSERT, "%s", buff); + jitInstance->mc->cr->recMessageLog(buff); + + // Under "/boa", ask the user if they want to attach a debugger. If they do, the debugger will be attached, + // then we'll call DebugBreakorAV(), which will issue a __debugbreak() and actually cause + // us to stop in the debugger. + if (breakOnDebugBreakorAV) + { + DbgBreakCheck(szFile, iLine, szExpr); + } + + DebugBreakorAV(0x7b); + return 0; +} + +void MyICJI::reportFatalError(CorJitResult result) +{ + jitInstance->mc->cr->AddCall("reportFatalError"); + jitInstance->mc->cr->recReportFatalError(result); +} + +// allocate a basic block profile buffer where execution counts will be stored +// for jitted basic blocks. +HRESULT MyICJI::allocBBProfileBuffer ( + ULONG count, // The number of basic blocks that we have + ProfileBuffer ** profileBuffer + ) +{ + jitInstance->mc->cr->AddCall("allocBBProfileBuffer"); + return jitInstance->mc->cr->repAllocBBProfileBuffer(count, profileBuffer); +} + +// get profile information to be used for optimizing the current method. The format +// of the buffer is the same as the format the JIT passes to allocBBProfileBuffer. +HRESULT MyICJI::getBBProfileData( + CORINFO_METHOD_HANDLE ftnHnd, + ULONG * count, // The number of basic blocks that we have + ProfileBuffer ** profileBuffer, + ULONG * numRuns + ) +{ + jitInstance->mc->cr->AddCall("getBBProfileData"); + return jitInstance->mc->repGetBBProfileData(ftnHnd, count, profileBuffer, numRuns); +} + +// Associates a native call site, identified by its offset in the native code stream, with +// the signature information and method handle the JIT used to lay out the call site. If +// the call site has no signature information (e.g. a helper call) or has no method handle +// (e.g. a CALLI P/Invoke), then null should be passed instead. +void MyICJI::recordCallSite( + ULONG instrOffset, /* IN */ + CORINFO_SIG_INFO * callSig, /* IN */ + CORINFO_METHOD_HANDLE methodHandle /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("recordCallSite"); + jitInstance->mc->cr->repRecordCallSite(instrOffset, callSig, methodHandle); +} + +// A relocation is recorded if we are pre-jitting. +// A jump thunk may be inserted if we are jitting +void MyICJI::recordRelocation( + void * location, /* IN */ + void * target, /* IN */ + WORD fRelocType, /* IN */ + WORD slotNum, /* IN */ + INT32 addlDelta /* IN */ + ) +{ + jitInstance->mc->cr->AddCall("recordRelocation"); + jitInstance->mc->cr->repRecordRelocation(location, target, fRelocType, slotNum, addlDelta); +} + +WORD MyICJI::getRelocTypeHint(void * target) +{ + jitInstance->mc->cr->AddCall("getRelocTypeHint"); + WORD result = jitInstance->mc->repGetRelocTypeHint(target); + return result; +} + +// A callback to identify the range of address known to point to +// compiler-generated native entry points that call back into +// MSIL. +void MyICJI::getModuleNativeEntryPointRange( + void ** pStart, /* OUT */ + void ** pEnd /* OUT */ + ) +{ + jitInstance->mc->cr->AddCall("getModuleNativeEntryPointRange"); + LogError("Hit unimplemented getModuleNativeEntryPointRange"); + DebugBreakorAV(128); +} + +// For what machine does the VM expect the JIT to generate code? The VM +// returns one of the IMAGE_FILE_MACHINE_* values. Note that if the VM +// is cross-compiling (such as the case for crossgen), it will return a +// different value than if it was compiling for the host architecture. +// +DWORD MyICJI::getExpectedTargetArchitecture() +{ +#if defined(_TARGET_X86_) + return IMAGE_FILE_MACHINE_I386; +#elif defined(_TARGET_AMD64_) + return IMAGE_FILE_MACHINE_AMD64; +#elif defined(_TARGET_ARM_) + return IMAGE_FILE_MACHINE_ARMNT; +#elif defined(_TARGET_ARM64_) + return IMAGE_FILE_MACHINE_ARM64; +#else + return IMAGE_FILE_MACHINE_UNKNOWN; +#endif +} diff --git a/src/ToolBox/superpmi/superpmi/icorjitinfo.h b/src/ToolBox/superpmi/superpmi/icorjitinfo.h new file mode 100644 index 0000000000..2182db3be6 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/icorjitinfo.h @@ -0,0 +1,26 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _ICorJitInfo +#define _ICorJitInfo + +#include "runtimedetails.h" +#include "ieememorymanager.h" + +extern ICorJitInfo *pICJI; + +class MyICJI : public ICorJitInfo +{ + +#include "icorjitinfoimpl.h" + +public: + + //Added extras... todo add padding to detect corruption? + JitInstance *jitInstance; +}; + +ICorJitInfo* InitICorJitInfo(JitInstance *jitInstance); +#endif diff --git a/src/ToolBox/superpmi/superpmi/ieememorymanager.cpp b/src/ToolBox/superpmi/superpmi/ieememorymanager.cpp new file mode 100644 index 0000000000..8bb95b1f4d --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/ieememorymanager.cpp @@ -0,0 +1,110 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "spmiutil.h" +#include "ieememorymanager.h" + +IEEMemoryManager *pIEEMM = nullptr; +HANDLE processHeap = INVALID_HANDLE_VALUE; + +//*************************************************************************** +// IUnknown methods +//*************************************************************************** + +HRESULT STDMETHODCALLTYPE MyIEEMM::QueryInterface(REFIID id, void **pInterface) +{ + DebugBreakorAV(133); + return 0; +} +ULONG STDMETHODCALLTYPE MyIEEMM::AddRef() +{ + DebugBreakorAV(134); + return 0; +} +ULONG STDMETHODCALLTYPE MyIEEMM::Release() +{ + DebugBreakorAV(135); + return 0; +} + +HANDLE virtHeap = INVALID_HANDLE_VALUE; + +//*************************************************************************** +// IEEMemoryManager methods for locking +//*************************************************************************** +LPVOID STDMETHODCALLTYPE MyIEEMM::ClrVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) +{ + if(virtHeap == INVALID_HANDLE_VALUE) + virtHeap = HeapCreate(0, 0xFFFF, 0); + if(virtHeap != INVALID_HANDLE_VALUE) + return HeapAlloc(virtHeap, HEAP_ZERO_MEMORY, dwSize); + return nullptr; +} +BOOL STDMETHODCALLTYPE MyIEEMM::ClrVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) +{ + return HeapFree(virtHeap, 0, lpAddress); +} +SIZE_T STDMETHODCALLTYPE MyIEEMM::ClrVirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) +{ + DebugBreakorAV(136); + return 0; +} +BOOL STDMETHODCALLTYPE MyIEEMM::ClrVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) +{ + DebugBreakorAV(137); + return 0; +} +HANDLE STDMETHODCALLTYPE MyIEEMM::ClrGetProcessHeap() +{ + DebugBreakorAV(138); + return 0; +} +HANDLE STDMETHODCALLTYPE MyIEEMM::ClrHeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize) +{ + DebugBreakorAV(139); + return 0; +} +BOOL STDMETHODCALLTYPE MyIEEMM::ClrHeapDestroy(HANDLE hHeap) +{ + DebugBreakorAV(140); + return 0; +} +LPVOID STDMETHODCALLTYPE MyIEEMM::ClrHeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) +{ + return HeapAlloc(hHeap,dwFlags,dwBytes); +} +BOOL STDMETHODCALLTYPE MyIEEMM::ClrHeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) +{ + return HeapFree(hHeap, dwFlags, lpMem); +} +BOOL STDMETHODCALLTYPE MyIEEMM::ClrHeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem) +{ + DebugBreakorAV(141); + return 0; +} +HANDLE STDMETHODCALLTYPE MyIEEMM::ClrGetProcessExecutableHeap() +{ + if (processHeap == INVALID_HANDLE_VALUE) + { + DWORD flOptions = 0; +#ifndef FEATURE_PAL // TODO-Review: PAL doesn't have HEAP_CREATE_ENABLE_EXECUTE. Is this ok? + flOptions = HEAP_CREATE_ENABLE_EXECUTE; +#endif // !FEATURE_PAL + processHeap = HeapCreate(flOptions, 10000, 0); + } + return processHeap; +} + +IEEMemoryManager *InitIEEMemoryManager(JitInstance *jitInstance) +{ + if(pIEEMM==nullptr) + { + MyIEEMM *ieemm = new MyIEEMM(); + ieemm->jitInstance = jitInstance; + pIEEMM = ieemm; + } + return pIEEMM; +} diff --git a/src/ToolBox/superpmi/superpmi/ieememorymanager.h b/src/ToolBox/superpmi/superpmi/ieememorymanager.h new file mode 100644 index 0000000000..90763bc233 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/ieememorymanager.h @@ -0,0 +1,113 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _IEEMemoryManager +#define _IEEMemoryManager + +#include "runtimedetails.h" +#include "jitinstance.h" + +/* +interface IEEMemoryManager : IUnknown +{ + LPVOID ClrVirtualAlloc( + [in] LPVOID lpAddress, // region to reserve or commit + [in] SIZE_T dwSize, // size of region + [in] DWORD flAllocationType, // type of allocation + [in] DWORD flProtect // type of access protection + ) + + BOOL ClrVirtualFree( + [in] LPVOID lpAddress, // address of region + [in] SIZE_T dwSize, // size of region + [in] DWORD dwFreeType // operation type + ) + + SIZE_T ClrVirtualQuery( + [in] const void* lpAddress, // address of region + [in] PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer + [in] SIZE_T dwLength // size of buffer + ) + + BOOL ClrVirtualProtect( + [in] LPVOID lpAddress, // region of committed pages + [in] SIZE_T dwSize, // size of the region + [in] DWORD flNewProtect, // desired access protection + [in] DWORD* lpflOldProtect // old protection + ) + + HANDLE ClrGetProcessHeap() + + HANDLE ClrHeapCreate( + [in] DWORD flOptions, // heap allocation attributes + [in] SIZE_T dwInitialSize, // initial heap size + [in] SIZE_T dwMaximumSize // maximum heap size + ) + + BOOL ClrHeapDestroy( + [in] HANDLE hHeap // handle to heap + ) + + LPVOID ClrHeapAlloc( + [in] HANDLE hHeap, // handle to private heap block + [in] DWORD dwFlags, // heap allocation control + [in] SIZE_T dwBytes // number of bytes to allocate + ) + + BOOL ClrHeapFree( + [in] HANDLE hHeap, // handle to heap + [in] DWORD dwFlags, // heap free options + [in] LPVOID lpMem // pointer to memory + ) + + BOOL ClrHeapValidate( + [in] HANDLE hHeap, // handle to heap + [in] DWORD dwFlags, // heap access options + [in] const void* lpMem // optional pointer to memory block + ) + + HANDLE ClrGetProcessExecutableHeap() + +}; // interface IEEMemoryManager + +*/ +extern HANDLE virtHeap; +extern IEEMemoryManager *pIEEMM; +extern HANDLE processHeap; +class MyIEEMM : public IEEMemoryManager +{ +private: + + //*************************************************************************** + // IUnknown methods + //*************************************************************************** + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, void **pInterface); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + //*************************************************************************** + // IEEMemoryManager methods for locking + //*************************************************************************** + LPVOID STDMETHODCALLTYPE ClrVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); + BOOL STDMETHODCALLTYPE ClrVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType); + SIZE_T STDMETHODCALLTYPE ClrVirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength); + BOOL STDMETHODCALLTYPE ClrVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); + HANDLE STDMETHODCALLTYPE ClrGetProcessHeap(); + HANDLE STDMETHODCALLTYPE ClrHeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); + BOOL STDMETHODCALLTYPE ClrHeapDestroy(HANDLE hHeap); + LPVOID STDMETHODCALLTYPE ClrHeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes); + BOOL STDMETHODCALLTYPE ClrHeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem); + BOOL STDMETHODCALLTYPE ClrHeapValidate(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem); + HANDLE STDMETHODCALLTYPE ClrGetProcessExecutableHeap(); + +public: + //Added extras... todo add padding to detect corruption? + JitInstance *jitInstance; +}; + +IEEMemoryManager *InitIEEMemoryManager(JitInstance *jitInstance); + +#endif diff --git a/src/ToolBox/superpmi/superpmi/iexecutionengine.cpp b/src/ToolBox/superpmi/superpmi/iexecutionengine.cpp new file mode 100644 index 0000000000..db9e121d8e --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/iexecutionengine.cpp @@ -0,0 +1,213 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "spmiutil.h" +#include "iexecutionengine.h" + +LPVOID TLS_Slots[MAX_PREDEFINED_TLS_SLOT]; +class MyIEE; +IExecutionEngine *pIEE = nullptr; + +//*************************************************************************** +// IUnknown methods +//*************************************************************************** + +HRESULT STDMETHODCALLTYPE MyIEE::QueryInterface(REFIID id, void **pInterface) +{ + //TODO-Cleanup: check the rid + *pInterface = InitIEEMemoryManager(nullptr); + return 0; +} +ULONG STDMETHODCALLTYPE MyIEE::AddRef() +{ + DebugBreakorAV(142); + return 0; +} +ULONG STDMETHODCALLTYPE MyIEE::Release() +{ + DebugBreakorAV(143); + return 0; +} + +//*************************************************************************** +// IExecutionEngine methods for TLS +//*************************************************************************** + +DWORD TlsIndex = 42; + +// Associate a callback for cleanup with a TLS slot +VOID STDMETHODCALLTYPE MyIEE::TLS_AssociateCallback(DWORD slot, PTLS_CALLBACK_FUNCTION callback) +{ + //TODO-Cleanup: figure an appropriate realish value for this +} + +// Get the TLS block for fast Get/Set operations +LPVOID* STDMETHODCALLTYPE MyIEE::TLS_GetDataBlock() +{ + //We were previously allocating a TlsIndex with + //the master slot index set to a nullptr + //so in the new version we just return nullptr + //and it seems to be working for now + return nullptr; +} + +// Get the value at a slot +LPVOID STDMETHODCALLTYPE MyIEE::TLS_GetValue(DWORD slot) +{ + /* if(slot>MAX_PREDEFINED_TLS_SLOT) + __debugbreak(); + void *thing = TlsGetValue(TlsIndex); + + // if(slot == 0x9) + //return 0; //trick out the contract system to be as off as possible. + //TODO-Cleanup: does anything beyond contracts care? This seems like a pretty thin mock. + */ + return TLS_Slots[slot]; +} + +// Get the value at a slot, return FALSE if TLS info block doesn't exist +BOOL STDMETHODCALLTYPE MyIEE::TLS_CheckValue(DWORD slot, LPVOID * pValue) +{ + DebugBreakorAV(144); + //TODO-Cleanup: does anything beyond contracts care? This seems like a pretty thin mock. + return true; +} +// Set the value at a slot +VOID STDMETHODCALLTYPE MyIEE::TLS_SetValue(DWORD slot, LPVOID pData) +{ + if (slot > MAX_PREDEFINED_TLS_SLOT) + { + DebugBreakorAV(143); + return; + } + void *thing = TlsGetValue(TlsIndex);//TODO-Cleanup: this seems odd.. explain? + + //TODO-Cleanup: does anything beyond contracts care? This seems like a pretty thin mock. + TLS_Slots[slot] = pData; +} +// Free TLS memory block and make callback +VOID STDMETHODCALLTYPE MyIEE::TLS_ThreadDetaching() +{ + DebugBreakorAV(145); +} + +//*************************************************************************** +// IExecutionEngine methods for locking +//*************************************************************************** + +CRITSEC_COOKIE STDMETHODCALLTYPE MyIEE::CreateLock(LPCSTR szTag, LPCSTR level, CrstFlags flags) +{ + return (CRITSEC_COOKIE)(size_t)0xbad01241; +} +void STDMETHODCALLTYPE MyIEE::DestroyLock(CRITSEC_COOKIE lock) +{ + DebugBreakorAV(146); +} +void STDMETHODCALLTYPE MyIEE::AcquireLock(CRITSEC_COOKIE lock) +{ +} +void STDMETHODCALLTYPE MyIEE::ReleaseLock(CRITSEC_COOKIE lock) +{ +} + +EVENT_COOKIE STDMETHODCALLTYPE MyIEE::CreateAutoEvent(BOOL bInitialState) +{ + DebugBreakorAV(147); + return 0; +} +EVENT_COOKIE STDMETHODCALLTYPE MyIEE::CreateManualEvent(BOOL bInitialState) +{ + DebugBreakorAV(148); + return 0; +} +void STDMETHODCALLTYPE MyIEE::CloseEvent(EVENT_COOKIE event) +{ + DebugBreakorAV(149); +} +BOOL STDMETHODCALLTYPE MyIEE::ClrSetEvent(EVENT_COOKIE event) +{ + DebugBreakorAV(150); + return 0; +} +BOOL STDMETHODCALLTYPE MyIEE::ClrResetEvent(EVENT_COOKIE event) +{ + DebugBreakorAV(151); + return 0; +} +DWORD STDMETHODCALLTYPE MyIEE::WaitForEvent(EVENT_COOKIE event, DWORD dwMilliseconds, BOOL bAlertable) +{ + DebugBreakorAV(152); + return 0; +} +DWORD STDMETHODCALLTYPE MyIEE::WaitForSingleObject(HANDLE handle, DWORD dwMilliseconds) +{ + DebugBreakorAV(153); + return 0; +} +SEMAPHORE_COOKIE STDMETHODCALLTYPE MyIEE::ClrCreateSemaphore(DWORD dwInitial, DWORD dwMax) +{ + DebugBreakorAV(154); + return 0; +} +void STDMETHODCALLTYPE MyIEE::ClrCloseSemaphore(SEMAPHORE_COOKIE semaphore) +{ + DebugBreakorAV(155); +} +DWORD STDMETHODCALLTYPE MyIEE::ClrWaitForSemaphore(SEMAPHORE_COOKIE semaphore, DWORD dwMilliseconds, BOOL bAlertable) +{ + DebugBreakorAV(156); + return 0; +} +BOOL STDMETHODCALLTYPE MyIEE::ClrReleaseSemaphore(SEMAPHORE_COOKIE semaphore, LONG lReleaseCount, LONG *lpPreviousCount) +{ + DebugBreakorAV(157); + return 0; +} +MUTEX_COOKIE STDMETHODCALLTYPE MyIEE::ClrCreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, + BOOL bInitialOwner, + LPCTSTR lpName) +{ + DebugBreakorAV(158); + return 0; +} +void STDMETHODCALLTYPE MyIEE::ClrCloseMutex(MUTEX_COOKIE mutex) +{ + DebugBreakorAV(159); +} +BOOL STDMETHODCALLTYPE MyIEE::ClrReleaseMutex(MUTEX_COOKIE mutex) +{ + DebugBreakorAV(160); + return 0; +} +DWORD STDMETHODCALLTYPE MyIEE::ClrWaitForMutex(MUTEX_COOKIE mutex, + DWORD dwMilliseconds, + BOOL bAlertable) +{ + DebugBreakorAV(161); + return 0; +} + +DWORD STDMETHODCALLTYPE MyIEE::ClrSleepEx(DWORD dwMilliseconds, BOOL bAlertable) +{ + DebugBreakorAV(162); + return 0; +} +BOOL STDMETHODCALLTYPE MyIEE::ClrAllocationDisallowed() +{ + DebugBreakorAV(163); + return 0; +} +void STDMETHODCALLTYPE MyIEE::GetLastThrownObjectExceptionFromThread(void **ppvException) +{ + DebugBreakorAV(164); +} + +MyIEE *InitIExecutionEngine() +{ + MyIEE *iee = new MyIEE(); + pIEE = iee; + return iee; +} diff --git a/src/ToolBox/superpmi/superpmi/iexecutionengine.h b/src/ToolBox/superpmi/superpmi/iexecutionengine.h new file mode 100644 index 0000000000..f1a76b9a4f --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/iexecutionengine.h @@ -0,0 +1,151 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _IExecutionEngine +#define _IExecutionEngine + +#include "ieememorymanager.h" + +/* +interface IExecutionEngine : IUnknown +{ + // Thread Local Storage is based on logical threads. The underlying + // implementation could be threads, fibers, or something more exotic. + // Slot numbers are predefined. This is not a general extensibility + // mechanism. + + // Associate a callback function for releasing TLS on thread/fiber death. + // This can be NULL. + void TLS_AssociateCallback([in] DWORD slot, [in] PTLS_CALLBACK_FUNCTION callback) + + // May be called once to get the master TLS block slot for fast Get/Set operations + DWORD TLS_GetMasterSlotIndex() + + // Get the value at a slot + PVOID TLS_GetValue([in] DWORD slot) + + // Get the value at a slot, return FALSE if TLS info block doesn't exist + BOOL TLS_CheckValue([in] DWORD slot, [out] PVOID * pValue) + + // Set the value at a slot + void TLS_SetValue([in] DWORD slot, [in] PVOID pData) + + // Free TLS memory block and make callback + void TLS_ThreadDetaching() + + // Critical Sections are sometimes exposed to the host and therefore need to be + // reflected from all CLR DLLs to the EE. + // + // In addition, we always monitor interactions between the lock & the GC, based + // on the GC mode in which the lock is acquired and we restrict what operations + // are permitted while holding the lock based on this. + // + // Finally, we we rank all our locks to prevent deadlock across all the DLLs of + // the CLR. This is the level argument to CreateLock. + // + // All usage of these locks must be exception-safe. To achieve this, we suggest + // using Holders (see holder.h & crst.h). In fact, within the EE code cannot + // hold locks except by using exception-safe holders. + + CRITSEC_COOKIE CreateLock([in] LPCSTR szTag, [in] LPCSTR level, [in] CrstFlags flags) + + void DestroyLock([in] CRITSEC_COOKIE lock) + + void AcquireLock([in] CRITSEC_COOKIE lock) + + void ReleaseLock([in] CRITSEC_COOKIE lock) + + EVENT_COOKIE CreateAutoEvent([in] BOOL bInitialState) + EVENT_COOKIE CreateManualEvent([in] BOOL bInitialState) + void CloseEvent([in] EVENT_COOKIE event) + BOOL ClrSetEvent([in] EVENT_COOKIE event) + BOOL ClrResetEvent([in] EVENT_COOKIE event) + DWORD WaitForEvent([in] EVENT_COOKIE event, [in] DWORD dwMilliseconds, [in] BOOL bAlertable) + DWORD WaitForSingleObject([in] HANDLE handle, [in] DWORD dwMilliseconds) + + // OS header file defines CreateSemaphore. + SEMAPHORE_COOKIE ClrCreateSemaphore([in] DWORD dwInitial, [in] DWORD dwMax) + void ClrCloseSemaphore([in] SEMAPHORE_COOKIE semaphore) + DWORD ClrWaitForSemaphore([in] SEMAPHORE_COOKIE semaphore, [in] DWORD dwMilliseconds, [in] BOOL bAlertable) + BOOL ClrReleaseSemaphore([in] SEMAPHORE_COOKIE semaphore, [in] LONG lReleaseCount, [in] LONG *lpPreviousCount) + + MUTEX_COOKIE ClrCreateMutex([in]LPSECURITY_ATTRIBUTES lpMutexAttributes, [in]BOOL bInitialOwner, [in]LPCTSTR lpName) + DWORD ClrWaitForMutex([in] MUTEX_COOKIE mutex, [in] DWORD dwMilliseconds, [in] BOOL bAlertable) + BOOL ClrReleaseMutex([in] MUTEX_COOKIE mutex) + void ClrCloseMutex([in] MUTEX_COOKIE mutex) + + DWORD ClrSleepEx([in] DWORD dwMilliseconds, [in] BOOL bAlertable) + + BOOL ClrAllocationDisallowed() + + void GetLastThrownObjectExceptionFromThread([out] void **ppvException) + +}; // interface IExecutionEngine +*/ + +extern LPVOID TLS_Slots[MAX_PREDEFINED_TLS_SLOT]; +extern IExecutionEngine *pIEE; + +class MyIEE : public IExecutionEngine +{ +private: + + //*************************************************************************** + // IUnknown methods + //*************************************************************************** + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, void **pInterface); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + //*************************************************************************** + // IExecutionEngine methods for TLS + //*************************************************************************** + // Associate a callback for cleanup with a TLS slot + VOID STDMETHODCALLTYPE TLS_AssociateCallback(DWORD slot, PTLS_CALLBACK_FUNCTION callback); + + // Get the TLS block for fast Get/Set operations + LPVOID* STDMETHODCALLTYPE TLS_GetDataBlock(); + // Get the value at a slot + LPVOID STDMETHODCALLTYPE TLS_GetValue(DWORD slot); + // Get the value at a slot, return FALSE if TLS info block doesn't exist + BOOL STDMETHODCALLTYPE TLS_CheckValue(DWORD slot, LPVOID * pValue); + // Set the value at a slot + VOID STDMETHODCALLTYPE TLS_SetValue(DWORD slot, LPVOID pData); + // Free TLS memory block and make callback + VOID STDMETHODCALLTYPE TLS_ThreadDetaching(); + + //*************************************************************************** + // IExecutionEngine methods for locking + //*************************************************************************** + CRITSEC_COOKIE STDMETHODCALLTYPE CreateLock(LPCSTR szTag, LPCSTR level, CrstFlags flags); + void STDMETHODCALLTYPE DestroyLock(CRITSEC_COOKIE lock); + void STDMETHODCALLTYPE AcquireLock(CRITSEC_COOKIE lock); + void STDMETHODCALLTYPE ReleaseLock(CRITSEC_COOKIE lock); + EVENT_COOKIE STDMETHODCALLTYPE CreateAutoEvent(BOOL bInitialState); + EVENT_COOKIE STDMETHODCALLTYPE CreateManualEvent(BOOL bInitialState); + void STDMETHODCALLTYPE CloseEvent(EVENT_COOKIE event); + BOOL STDMETHODCALLTYPE ClrSetEvent(EVENT_COOKIE event); + BOOL STDMETHODCALLTYPE ClrResetEvent(EVENT_COOKIE event); + DWORD STDMETHODCALLTYPE WaitForEvent(EVENT_COOKIE event, DWORD dwMilliseconds, BOOL bAlertable); + DWORD STDMETHODCALLTYPE WaitForSingleObject(HANDLE handle, DWORD dwMilliseconds); + SEMAPHORE_COOKIE STDMETHODCALLTYPE ClrCreateSemaphore(DWORD dwInitial, DWORD dwMax); + void STDMETHODCALLTYPE ClrCloseSemaphore(SEMAPHORE_COOKIE semaphore); + DWORD STDMETHODCALLTYPE ClrWaitForSemaphore(SEMAPHORE_COOKIE semaphore, DWORD dwMilliseconds, BOOL bAlertable); + BOOL STDMETHODCALLTYPE ClrReleaseSemaphore(SEMAPHORE_COOKIE semaphore, LONG lReleaseCount, LONG *lpPreviousCount); + MUTEX_COOKIE STDMETHODCALLTYPE ClrCreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, + BOOL bInitialOwner, + LPCTSTR lpName); + void STDMETHODCALLTYPE ClrCloseMutex(MUTEX_COOKIE mutex); + BOOL STDMETHODCALLTYPE ClrReleaseMutex(MUTEX_COOKIE mutex); + DWORD STDMETHODCALLTYPE ClrWaitForMutex(MUTEX_COOKIE mutex, + DWORD dwMilliseconds, + BOOL bAlertable); + DWORD STDMETHODCALLTYPE ClrSleepEx(DWORD dwMilliseconds, BOOL bAlertable); + BOOL STDMETHODCALLTYPE ClrAllocationDisallowed(); + void STDMETHODCALLTYPE GetLastThrownObjectExceptionFromThread(void **ppvException); +}; + +MyIEE *InitIExecutionEngine(); +#endif
\ No newline at end of file diff --git a/src/ToolBox/superpmi/superpmi/jitdebugger.cpp b/src/ToolBox/superpmi/superpmi/jitdebugger.cpp new file mode 100644 index 0000000000..867664e82f --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/jitdebugger.cpp @@ -0,0 +1,459 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//***************************************************************************** +// JitDebugger.cpp +// +// Code to help with invoking the just-in-time debugger +// +//***************************************************************************** + +#include "standardpch.h" +#include "runtimedetails.h" +#include "logging.h" +#include "jitdebugger.h" + +// JIT debugging is broken due to utilcode changes to support LongFile. We need to re-copy +// or adjust the implementation of the below functions so they link properly. +#if 0 +#ifndef FEATURE_PAL // No just-in-time debugger under PAL +#define FEATURE_JIT_DEBUGGING +#endif // !FEATURE_PAL +#endif // 0 + +#ifndef FEATURE_JIT_DEBUGGING + +int DbgBreakCheck( + const char* szFile, + int iLine, + const char* szExpr) +{ + LogError("SuperPMI: Assert Failure (PID %d, Thread %d/%x)\n" + "%s\n" + "\n" + "%s, Line: %d\n", + GetCurrentProcessId(), GetCurrentThreadId(), GetCurrentThreadId(), + szExpr, szFile, iLine); + + return 1; +} + +#else // FEATURE_JIT_DEBUGGING + +// Some definitions to make this code look more like the CLR utilcode versions it was stolen from. +#define WszCreateEvent CreateEventW +#define WszGetModuleFileName GetModuleFileNameW + +#ifdef WszRegOpenKeyEx +#undef WszRegOpenKeyEx +#define WszRegOpenKeyEx RegOpenKeyExW +#endif + +#ifndef _WIN64 +//------------------------------------------------------------------------------ +// Returns TRUE if we are running on a 64-bit OS in WoW, FALSE otherwise. +BOOL RunningInWow64() +{ + 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 + +//------------------------------------------------------------------------------ +// +// 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) +{ + DWORD ret; // Return value from registry operation. + HKEY 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 = RegQueryValueExW(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 +// pcchBuffer - the number of characters of the string buffer +// +// Return Value: +// S_OK on success, else detailed error code. +// +// Note: +// +//---------------------------------------------------------------------------- +HRESULT GetCurrentModuleFileName(__out_ecount(*pcchBuffer) LPWSTR pBuffer, __inout DWORD *pcchBuffer) +{ + LIMITED_METHOD_CONTRACT; + + if ((pBuffer == NULL) || (pcchBuffer == NULL)) + { + return E_INVALIDARG; + } + + // Get the appname to look up in the exclusion or inclusion list. + WCHAR appPath[MAX_PATH + 2]; + + DWORD ret = WszGetModuleFileName(NULL, appPath, NumItems(appPath)); + + if ((ret == NumItems(appPath)) || (ret == 0)) + { + // The module file name exceeded maxpath, or GetModuleFileName failed. + return E_UNEXPECTED; + } + + // Pick off the part after the path. + WCHAR* appName = wcsrchr(appPath, L'\\'); + + // If no backslash, use the whole name; if there is a backslash, skip it. + appName = appName ? appName+1 : appPath; + + if (*pcchBuffer < wcslen(appName)) + { + *pcchBuffer = static_cast<DWORD>(wcslen(appName)) + 1; + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + wcscpy_s(pBuffer, *pcchBuffer, appName); + 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() +{ + HKEY 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; + } + + WCHAR wszAppName[MAX_PATH]; + DWORD cchAppName = NumItems(wszAppName); + + // Get the appname to look up in the exclusion or inclusion list. + if (GetCurrentModuleFileName(wszAppName, &cchAppName) != 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 = RegQueryValueExW(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(LPWSTR wszDebuggerString, DWORD cchDebuggerString, BOOL *pfAuto) +{ + HRESULT hr = GetDebuggerSettingInfoWorker(wszDebuggerString, &cchDebuggerString, pfAuto); + + if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + // error! + } +} // 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) +{ + if ((pcchDebuggerString == NULL) || ((wszDebuggerString == NULL) && (*pcchDebuggerString != 0))) + { + return E_INVALIDARG; + } + + // Initialize the output values before we start. + if ((wszDebuggerString != NULL) && (*pcchDebuggerString > 0)) + { + *wszDebuggerString = L'\0'; + } + + if (pfAuto != NULL) + { + *pfAuto = FALSE; + } + + HKEY hKey; + + // Look for "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug" + DWORD ret = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, kUnmanagedDebuggerKey, 0, KEY_READ, &hKey); + + 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; + ret = RegQueryValueExW(hKey, kUnmanagedDebuggerValue, 0, &valueType, 0, &valueSize); + + if ((wszDebuggerString == NULL) || (*pcchDebuggerString < valueSize / sizeof(WCHAR))) + { + *pcchDebuggerString = valueSize / sizeof(WCHAR) + 1; + RegCloseKey(hKey); + 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) + { + RegCloseKey(hKey); + return S_OK; + } + + ret = RegQueryValueExW(hKey, kUnmanagedDebuggerValue, NULL, NULL, reinterpret_cast< LPBYTE >(wszDebuggerString), &valueSize); + if (ret != ERROR_SUCCESS) + { + *wszDebuggerString = L'\0'; + RegCloseKey(hKey); + return S_OK; + } + + if (pfAuto != NULL) + { + BOOL fAuto = FALSE; + + // Get the appname to look up in DebugApplications key. + WCHAR wzAppName[MAX_PATH]; + DWORD cchAppName = NumItems(wzAppName); + long iValue; + + // Check DebugApplications setting + if ((SUCCEEDED(GetCurrentModuleFileName(wzAppName, &cchAppName))) && + ( + 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 = RegQueryValueExW(hKey, kUnmanagedDebuggerAutoValue, 0, &valueType, 0, &valueSize); + if ((ret == ERROR_SUCCESS) && (valueType == REG_SZ) && (valueSize / sizeof(WCHAR) < MAX_PATH)) + { + WCHAR wzAutoKey[MAX_PATH]; + valueSize = NumItems(wzAutoKey) * sizeof(WCHAR); + RegQueryValueExW(hKey, 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] == L'1') && !IsCurrentModuleFileNameInAutoExclusionList()) + { + fAuto = TRUE; + } + } + } + + *pfAuto = fAuto; + } + + RegCloseKey(hKey); + return S_OK; +} // GetDebuggerSettingInfoWorker + +BOOL LaunchJITDebugger() +{ + BOOL fSuccess = FALSE; + + WCHAR debugger[1000]; + GetDebuggerSettingInfo(debugger, NumItems(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. + HANDLE eventHandle = WszCreateEvent(&sa, TRUE, FALSE, NULL); + if (eventHandle == NULL) { + return FALSE; + } + + WCHAR cmdLine[1000]; + swprintf_s(cmdLine, debugger, GetCurrentProcessId(), eventHandle); + + STARTUPINFOW StartupInfo; + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + StartupInfo.lpDesktop = L"Winsta0\\Default"; + + PROCESS_INFORMATION ProcessInformation; + if (CreateProcessW(NULL, cmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInformation)) + { + WaitForSingleObject(eventHandle, INFINITE); + fSuccess = TRUE; + } + + CloseHandle(eventHandle); + + return fSuccess; +} + + +// See if we should invoke the just-in-time debugger on an assert. +int DbgBreakCheck( + const char* szFile, + int iLine, + const char* szExpr) +{ + char dialogText[1000]; + char dialogTitle[1000]; + + sprintf_s(dialogText, sizeof(dialogText), "%s\n\n%s, Line: %d\n\nAbort - Kill program\nRetry - Debug\nIgnore - Keep running\n", + szExpr, szFile, iLine); + sprintf_s(dialogTitle, sizeof(dialogTitle), "SuperPMI: Assert Failure (PID %d, Thread %d/%x) ", + GetCurrentProcessId(), GetCurrentThreadId(), GetCurrentThreadId()); + + // Tell user there was an error. + int ret = MessageBoxA(NULL, dialogText, dialogTitle, MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION | MB_TOPMOST); + + switch(ret) + { + case IDABORT: + TerminateProcess(GetCurrentProcess(), 1); + break; + + // Tell caller to break at the correct loction. + case IDRETRY: + + if (IsDebuggerPresent()) + { + SetErrorMode(0); + } + else + { + LaunchJITDebugger(); + } + + return 1; + + case IDIGNORE: + // nothing to do + break; + } + + return 0; +} + +#endif // FEATURE_JIT_DEBUGGING diff --git a/src/ToolBox/superpmi/superpmi/jitdebugger.h b/src/ToolBox/superpmi/superpmi/jitdebugger.h new file mode 100644 index 0000000000..eee2318f8f --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/jitdebugger.h @@ -0,0 +1,33 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _JitDebugger +#define _JitDebugger + +extern bool breakOnDebugBreakorAV; // It's kind of awful that I'm making this global, but it was kind of awful that it was file-global already. + +// +// Functions to support just-in-time debugging. +// + +BOOL GetRegistryLongValue(HKEY hKeyParent, // Parent key. + LPCWSTR szKey, // Key name to look at. + LPCWSTR szName, // Name of value to get. + long *pValue, // Put value here, if found. + BOOL fReadNonVirtualizedKey); // Whether to read 64-bit hive on WOW64 + +HRESULT GetCurrentModuleFileName(__out_ecount(*pcchBuffer) LPWSTR pBuffer, __inout DWORD *pcchBuffer); + +#ifndef _WIN64 +BOOL RunningInWow64(); +#endif + +BOOL IsCurrentModuleFileNameInAutoExclusionList(); +HRESULT GetDebuggerSettingInfoWorker(__out_ecount_part_opt(*pcchDebuggerString, *pcchDebuggerString) LPWSTR wszDebuggerString, DWORD * pcchDebuggerString, BOOL * pfAuto); +void GetDebuggerSettingInfo(LPWSTR wszDebuggerString, DWORD cchDebuggerString, BOOL *pfAuto); + +int DbgBreakCheck(const char* szFile, int iLine, const char* szExpr); + +#endif // !_JitDebugger diff --git a/src/ToolBox/superpmi/superpmi/jithost.cpp b/src/ToolBox/superpmi/superpmi/jithost.cpp new file mode 100644 index 0000000000..cb61e48de2 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/jithost.cpp @@ -0,0 +1,120 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "superpmi.h" +#include "jitinstance.h" +#include "icorjitinfo.h" +#include "jithost.h" + +// Look for 'key' as an environment variable named COMPlus_<key>. The returned value +// is nullptr if it is not found, or a string if found. If not nullptr, the returned +// value must be freed with jitInstance.freeLongLivedArray(value). +wchar_t* GetCOMPlusVariable(const wchar_t* key, JitInstance& jitInstance) +{ + static const wchar_t Prefix[] = W("COMPlus_"); + static const size_t PrefixLen = (sizeof(Prefix) / sizeof(Prefix[0])) - 1; + + // Prepend "COMPlus_" to the provided key + size_t keyLen = wcslen(key); + size_t keyBufferLen = keyLen + PrefixLen + 1; + wchar_t* keyBuffer = reinterpret_cast<wchar_t*>(jitInstance.allocateArray(static_cast<ULONG>(sizeof(wchar_t) * keyBufferLen))); + wcscpy_s(keyBuffer, keyBufferLen, Prefix); + wcscpy_s(&keyBuffer[PrefixLen], keyLen + 1, key); + + // Look up the environment variable + DWORD valueLen = GetEnvironmentVariableW(keyBuffer, nullptr, 0); + if (valueLen == 0) + { + jitInstance.freeArray(keyBuffer); + return nullptr; + } + + // Note this value must live as long as the jit instance does. + wchar_t* value = reinterpret_cast<wchar_t*>(jitInstance.allocateLongLivedArray(sizeof(wchar_t) * valueLen)); + DWORD newValueLen = GetEnvironmentVariableW(keyBuffer, value, valueLen); + + jitInstance.freeArray(keyBuffer); + if (valueLen < newValueLen) + { + jitInstance.freeLongLivedArray(value); + return nullptr; + } + + return value; +} + +JitHost::JitHost(JitInstance& jitInstance) : jitInstance(jitInstance) +{ +} + +void* JitHost::allocateMemory(size_t size, bool usePageAllocator) +{ + return InitIEEMemoryManager(&jitInstance)->ClrVirtualAlloc(nullptr, size, 0, 0); +} + +void JitHost::freeMemory(void* block, bool usePageAllocator) +{ + InitIEEMemoryManager(&jitInstance)->ClrVirtualFree(block, 0, 0); +} + +int JitHost::getIntConfigValue(const wchar_t* key, int defaultValue) +{ + jitInstance.mc->cr->AddCall("getIntConfigValue"); + int result = jitInstance.mc->repGetIntConfigValue(key, defaultValue); + + if (result != defaultValue) + { + return result; + } + + // Look for special case keys. + if (wcscmp(key, W("SuperPMIMethodContextNumber")) == 0) + { + return jitInstance.mc->index; + } + + // If the result is the default value, probe the environment for + // a COMPlus variable with the same name. + wchar_t* complus = GetCOMPlusVariable(key, jitInstance); + if (complus == nullptr) + { + return defaultValue; + } + + // Parse the value as a hex integer. + wchar_t* endPtr; + result = static_cast<int>(wcstoul(complus, &endPtr, 16)); + bool succeeded = (errno != ERANGE) && (endPtr != complus); + jitInstance.freeLongLivedArray(complus); + + return succeeded ? result : defaultValue; +} + +const wchar_t* JitHost::getStringConfigValue(const wchar_t* key) +{ + jitInstance.mc->cr->AddCall("getStringConfigValue"); + const wchar_t *result = jitInstance.mc->repGetStringConfigValue(key); + + if (result != nullptr) + { + // Now we need to dup it, so you can call freeStringConfigValue() on what we return. + size_t resultLenInChars = wcslen(result) + 1; + wchar_t *dupResult = (wchar_t*)jitInstance.allocateLongLivedArray((ULONG)(sizeof(wchar_t) * resultLenInChars)); + wcscpy_s(dupResult, resultLenInChars, result); + + return dupResult; + } + + // If the result is the default value, probe the environment for + // a COMPlus variable with the same name. + return GetCOMPlusVariable(key, jitInstance); +} + +void JitHost::freeStringConfigValue(const wchar_t* value) +{ + jitInstance.mc->cr->AddCall("freeStringConfigValue"); + jitInstance.freeLongLivedArray((void*)value); +} diff --git a/src/ToolBox/superpmi/superpmi/jithost.h b/src/ToolBox/superpmi/superpmi/jithost.h new file mode 100644 index 0000000000..60181cd907 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/jithost.h @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _JITHOST +#define _JITHOST + +class JitHost : public ICorJitHost +{ +public: + JitHost(JitInstance& jitInstance); + +#include "icorjithostimpl.h" + +private: + JitInstance& jitInstance; +}; + +#endif diff --git a/src/ToolBox/superpmi/superpmi/jitinstance.cpp b/src/ToolBox/superpmi/superpmi/jitinstance.cpp new file mode 100644 index 0000000000..5003e91f96 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/jitinstance.cpp @@ -0,0 +1,436 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "superpmi.h" +#include "jitinstance.h" +#include "coreclrcallbacks.h" +#include "icorjitinfo.h" +#include "jithost.h" +#include "errorhandling.h" +#include "spmiutil.h" + +JitInstance *JitInstance::InitJit(char *nameOfJit, bool breakOnAssert, SimpleTimer *st1, MethodContext* firstContext) +{ + JitInstance *jit = new JitInstance(); + if (jit == nullptr) + { + LogError("Failed to allocate a JitInstance"); + return nullptr; + } + + if (st1 != nullptr) + st1->Start(); + HRESULT hr = jit->StartUp(nameOfJit, false, breakOnAssert, firstContext); + if (st1 != nullptr) + st1->Stop(); + if (hr != S_OK) + { + LogError("Startup of JIT(%s) failed %d", nameOfJit, hr); + return nullptr; + } + if (st1 != nullptr) + LogVerbose("Jit startup took %fms", st1->GetMilliseconds()); + return jit; +} + +HRESULT JitInstance::StartUp(char * PathToJit, bool copyJit, bool parambreakOnDebugBreakorAV, MethodContext* firstContext) +{ + //startup jit + DWORD dwRetVal = 0; + UINT uRetVal = 0; + BOOL bRetVal = FALSE; + + breakOnDebugBreakorAV = parambreakOnDebugBreakorAV; + + char pFullPathName[MAX_PATH]; + char lpTempPathBuffer[MAX_PATH]; + char szTempFileName[MAX_PATH]; + +//Get an allocator instance +//Note: we do this to keep cleanup somewhat simple... + ourHeap = ::HeapCreate(0,0,0); + if(ourHeap == nullptr) + { + LogError("Failed to get a new heap (0x%08x)", ::GetLastError()); + return E_FAIL; + } + +//find the full jit path + dwRetVal = ::GetFullPathNameA(PathToJit, MAX_PATH, pFullPathName, nullptr); + if (dwRetVal == 0) + { + LogError("GetFullPathName failed (0x%08x)", ::GetLastError()); + return E_FAIL; + } + +//Store the full path to the jit + PathToOriginalJit = (char *)::HeapAlloc(ourHeap, 0, MAX_PATH); + if(PathToOriginalJit == nullptr) + { + LogError("1st HeapAlloc failed (0x%08x)", ::GetLastError()); + return E_FAIL; + } + ::strcpy_s(PathToOriginalJit, MAX_PATH, pFullPathName); + + if(copyJit) + { + //Get a temp file location + dwRetVal = ::GetTempPathA(MAX_PATH, lpTempPathBuffer); + if (dwRetVal == 0) + { + LogError("GetTempPath failed (0x%08x)", ::GetLastError()); + return E_FAIL; + } + if (dwRetVal > MAX_PATH) + { + LogError("GetTempPath returned a path that was larger than MAX_PATH"); + return E_FAIL; + } + //Get a temp filename + uRetVal = ::GetTempFileNameA(lpTempPathBuffer, "Jit", 0, szTempFileName); + if (uRetVal == 0) + { + LogError("GetTempFileName failed (0x%08x)", ::GetLastError()); + return E_FAIL; + } + dwRetVal = (DWORD)::strlen(szTempFileName); + + //Store the full path to the temp jit + PathToTempJit = (char *)::HeapAlloc(ourHeap, 0, MAX_PATH); + if(PathToTempJit == nullptr) + { + LogError("2nd HeapAlloc failed 0x%08x)", ::GetLastError()); + return E_FAIL; + } + ::strcpy_s(PathToTempJit, MAX_PATH, szTempFileName); + + //Copy Temp File + bRetVal = ::CopyFileA(PathToOriginalJit, PathToTempJit, FALSE); + if (bRetVal == FALSE) + { + LogError("CopyFile failed (0x%08x)", ::GetLastError()); + return E_FAIL; + } + } + else + PathToTempJit = PathToOriginalJit; + +#ifndef FEATURE_PAL // No file version APIs in the PAL + //Do a quick version check + DWORD dwHandle = 0; + DWORD fviSize = GetFileVersionInfoSizeA(PathToTempJit, &dwHandle); + + if ((fviSize != 0)&&(dwHandle==0)) + { + unsigned char *fviData = new unsigned char[fviSize]; + if (GetFileVersionInfoA(PathToTempJit, dwHandle, fviSize, fviData)) + { + UINT size = 0; + VS_FIXEDFILEINFO *verInfo = nullptr; + if (VerQueryValueA(fviData, "\\", (LPVOID*)&verInfo, &size)) + { + if (size) + { + if (verInfo->dwSignature == 0xfeef04bd) + LogDebug("'%s' is version %u.%u.%u.%u", PathToTempJit, + (verInfo->dwFileVersionMS)>>16, (verInfo->dwFileVersionMS)&0xFFFF, + (verInfo->dwFileVersionLS)>>16, (verInfo->dwFileVersionLS)&0xFFFF); + } + } + } + delete[] fviData; + } +#endif // !FEATURE_PAL + +//Load Library + hLib = ::LoadLibraryA(PathToTempJit); + if(hLib == 0) + { + LogError("LoadLibrary failed (0x%08x)", ::GetLastError()); + return E_FAIL; + } + +//get entry points + pngetJit = (PgetJit)::GetProcAddress(hLib, "getJit"); + if(pngetJit == 0) + { + LogError("GetProcAddress 'getJit' failed (0x%08x)", ::GetLastError()); + return -1; + } + pnsxsJitStartup = (PsxsJitStartup)::GetProcAddress(hLib, "sxsJitStartup"); + if(pnsxsJitStartup == 0) + { + LogError("GetProcAddress 'sxsJitStartup' failed (0x%08x)", ::GetLastError()); + return -1; + } + pnjitStartup = (PjitStartup)::GetProcAddress(hLib, "jitStartup"); + + //Setup CoreClrCallbacks and call sxsJitStartup + CoreClrCallbacks *cccallbacks = InitCoreClrCallbacks(); + pnsxsJitStartup(*cccallbacks); + + // Setup ICorJitHost and call jitStartup if necessary + if (pnjitStartup != nullptr) + { + mc = firstContext; + jitHost = new JitHost(*this); + pnjitStartup(jitHost); + } + + pJitInstance = pngetJit(); + if(pJitInstance == nullptr) + { + LogError("pngetJit gave us null"); + return -1; + } + + // Check the JIT version identifier. + + GUID versionId; + memset(&versionId, 0, sizeof(GUID)); + pJitInstance->getVersionIdentifier(&versionId); + + if (memcmp(&versionId, &JITEEVersionIdentifier, sizeof(GUID)) != 0) + { + // Mismatched version ID. Fail the load. + pJitInstance = NULL; + + LogError("Jit Compiler has wrong version identifier"); + return -1; + } + + + icji = InitICorJitInfo(this); + + return S_OK; +} + +bool JitInstance::reLoad(MethodContext* firstContext) +{ + FreeLibrary(hLib); + +//Load Library + hLib = ::LoadLibraryA(PathToTempJit); + if(hLib == 0) + { + LogError("LoadLibrary failed (0x%08x)", ::GetLastError()); + return false; + } + +//get entry points + pngetJit = (PgetJit)::GetProcAddress(hLib, "getJit"); + if(pngetJit == 0) + { + LogError("GetProcAddress 'getJit' failed (0x%08x)", ::GetLastError()); + return false; + } + pnsxsJitStartup = (PsxsJitStartup)::GetProcAddress(hLib, "sxsJitStartup"); + if(pnsxsJitStartup == 0) + { + LogError("GetProcAddress 'sxsJitStartup' failed (0x%08x)", ::GetLastError()); + return false; + } + pnjitStartup = (PjitStartup)::GetProcAddress(hLib, "jitStartup"); + + //Setup CoreClrCallbacks and call sxsJitStartup + CoreClrCallbacks *cccallbacks = InitCoreClrCallbacks(); + pnsxsJitStartup(*cccallbacks); + + // Setup ICorJitHost and call jitStartup if necessary + if (pnjitStartup != nullptr) + { + mc = firstContext; + jitHost = new JitHost(*this); + pnjitStartup(jitHost); + } + + pJitInstance = pngetJit(); + if(pJitInstance == nullptr) + { + LogError("pngetJit gave us null"); + return false; + } + + icji = InitICorJitInfo(this); + + return true; +} + +JitInstance::Result JitInstance::CompileMethod(MethodContext *MethodToCompile, int mcIndex, bool collectThroughput) +{ + struct Param : FilterSuperPMIExceptionsParam_CaptureException + { + JitInstance* pThis; + JitInstance::Result result; + CORINFO_METHOD_INFO info; + unsigned flags; + int mcIndex; + bool collectThroughput; + } param; + param.pThis = this; + param.result = RESULT_SUCCESS; // assume success + param.flags = 0; + param.mcIndex = mcIndex; + param.collectThroughput = collectThroughput; + + //store to instance field our raw values, so we can figure things out a bit later... + mc = MethodToCompile; + + times[0] = 0; + times[1] = 0; + + mc->repEnvironmentSet(); //Sets envvars + stj.Start(); + + PAL_TRY(Param*, pParam, ¶m) + { + BYTE *NEntryBlock = nullptr; + ULONG NCodeSizeBlock = 0; + + pParam->pThis->mc->repCompileMethod(&pParam->info, &pParam->flags); + if (pParam->collectThroughput) + { + pParam->pThis->lt.Start(); + } + CorJitResult temp = pParam->pThis->pJitInstance->compileMethod(pParam->pThis->icji, &pParam->info, pParam->flags, &NEntryBlock, &NCodeSizeBlock); + if (pParam->collectThroughput) + { + pParam->pThis->lt.Stop(); + pParam->pThis->times[0] = pParam->pThis->lt.GetCycles(); + } + if ((SpmiTargetArchitecture == SPMI_TARGET_ARCHITECTURE_ARM64) && (temp == CORJIT_SKIPPED)) + { + // For altjit, treat SKIPPED as OK + temp = CORJIT_OK; + } + if (temp == CORJIT_OK) + { + //capture the results of compilation + pParam->pThis->mc->cr->recCompileMethod(&NEntryBlock, &NCodeSizeBlock, temp); + pParam->pThis->mc->cr->recAllocMemCapture(); + pParam->pThis->mc->cr->recAllocGCInfoCapture(); + + pParam->pThis->mc->cr->recMessageLog("Successful Compile"); + } + else + { + LogDebug("compileMethod failed with result %d", temp); + pParam->result = RESULT_ERROR; + } + } + PAL_EXCEPT_FILTER(FilterSuperPMIExceptions_CaptureExceptionAndStop) + { + SpmiException e(¶m.exceptionPointers); + + if (e.GetCode() == EXCEPTIONCODE_MC) + { + char *message = e.GetExceptionMessage(); + LogMissing("Method context %d failed to replay: %s", mcIndex, message); + e.DeleteMessage(); + param.result = RESULT_MISSING; + } + else + { + e.ShowAndDeleteMessage(); + param.result = RESULT_ERROR; + } + } + PAL_ENDTRY + + stj.Stop(); + if (collectThroughput) + { + // If we get here, we know it compiles + timeResult(param.info, param.flags); + } + mc->repEnvironmentUnset(); //Unsets envvars + + mc->cr->secondsToCompile = stj.GetSeconds(); + + return param.result; +} + +void JitInstance::timeResult(CORINFO_METHOD_INFO info, unsigned flags) +{ + BYTE *NEntryBlock = nullptr; + ULONG NCodeSizeBlock = 0; + + int sampleSize = 10; + // Save 2 smallest times. To help reduce noise, we will look at the closest pair of these. + unsigned __int64 time; + + for (int i = 0; i < sampleSize; i++) + { + delete mc->cr; + mc->cr = new CompileResult(); + lt.Start(); + pJitInstance->compileMethod(icji, &info, flags, &NEntryBlock, &NCodeSizeBlock); + lt.Stop(); + time = lt.GetCycles(); + if (times[1] == 0) + { + if (time < times[0]) + { + times[1] = times[0]; + times[0] = time; + } + else + times[1] = time; + } + else if (time < times[1]) + { + if (time < times[0]) + { + times[1] = times[0]; + times[0] = time; + } + else + times[1] = time; + } + } +} + +/*-------------------------- Misc ---------------------------------------*/ + +// Used to allocate memory that needs to handed to the EE. +// For eg, use this to allocated memory for reporting debug info, +// which will be handed to the EE by setVars() and setBoundaries() +void * JitInstance::allocateArray( + ULONG cBytes + ) +{ + mc->cr->AddCall("allocateArray"); + return HeapAlloc(mc->cr->getCodeHeap(),0,cBytes); +} + +// Used to allocate memory that needs to live as long as the jit +// instance does. +void * JitInstance::allocateLongLivedArray( + ULONG cBytes + ) +{ + return HeapAlloc(ourHeap,0,cBytes); +} + +// JitCompiler will free arrays passed by the EE using this +// For eg, The EE returns memory in getVars() and getBoundaries() +// to the JitCompiler, which the JitCompiler should release using +// freeArray() +void JitInstance::freeArray( + void *array + ) +{ + mc->cr->AddCall("freeArray"); + HeapFree(mc->cr->getCodeHeap(),0,array); +} + +// Used to free memory allocated by JitInstance::allocateLongLivedArray. +void JitInstance::freeLongLivedArray( + void *array + ) +{ + HeapFree(ourHeap,0,array); +} diff --git a/src/ToolBox/superpmi/superpmi/jitinstance.h b/src/ToolBox/superpmi/superpmi/jitinstance.h new file mode 100644 index 0000000000..c85c2f5bee --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/jitinstance.h @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _JitInstance +#define _JitInstance + +#include "superpmi.h" +#include "simpletimer.h" +#include "methodcontext.h" +#include "cycletimer.h" + +class JitInstance +{ +private: + char *PathToOriginalJit; + char *PathToTempJit; + HANDLE ourHeap; + HMODULE hLib; + PgetJit pngetJit; + PjitStartup pnjitStartup; + PsxsJitStartup pnsxsJitStartup; + ICorJitHost *jitHost; + ICorJitInfo *icji; + SimpleTimer stj; + + JitInstance() {}; + void timeResult(CORINFO_METHOD_INFO info, unsigned flags); + +public: + enum Result + { + RESULT_ERROR, + RESULT_SUCCESS, + RESULT_MISSING + }; + CycleTimer lt; + MethodContext *mc; + ULONGLONG times[2]; + ICorJitCompiler *pJitInstance; + + // Allocate and initialize the jit provided + static JitInstance *InitJit(char *nameOfJit, bool breakOnAssert, SimpleTimer *st1, MethodContext* firstContext); + + HRESULT StartUp(char *PathToJit, bool copyJit, bool breakOnDebugBreakorAV, MethodContext* firstContext); + bool reLoad(MethodContext* firstContext); + + Result CompileMethod(MethodContext *MethodToCompile, int mcIndex, bool collectThroughput); + + void* allocateArray(ULONG size); + void* allocateLongLivedArray(ULONG size); + void freeArray(void* array); + void freeLongLivedArray(void* array); +}; + +#endif diff --git a/src/ToolBox/superpmi/superpmi/methodstatsemitter.cpp b/src/ToolBox/superpmi/superpmi/methodstatsemitter.cpp new file mode 100644 index 0000000000..0a43f02dd9 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/methodstatsemitter.cpp @@ -0,0 +1,126 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//----------------------------------------------------------------------------- +// MethodStatsEmitter.cpp - Emits useful method stats for compiled methods for analysis +//----------------------------------------------------------------------------- + +#include "standardpch.h" +#include "methodstatsemitter.h" +#include "logging.h" + +MethodStatsEmitter::MethodStatsEmitter(char *nameOfInput) +{ + char filename[MAX_PATH + 1]; + sprintf_s(filename, MAX_PATH + 1, "%s.stats", nameOfInput); + + hStatsFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hStatsFile == INVALID_HANDLE_VALUE) + { + LogError("Failed to open output file '%s'. GetLastError()=%u", filename, GetLastError()); + } +} + +MethodStatsEmitter::~MethodStatsEmitter() +{ + if (hStatsFile != INVALID_HANDLE_VALUE) + { + if (CloseHandle(hStatsFile) == 0) + { + LogError("CloseHandle failed. GetLastError()=%u", GetLastError()); + } + } +} + +void MethodStatsEmitter::Emit(int methodNumber, MethodContext *mc, ULONGLONG firstTime, ULONGLONG secondTime) +{ + if (hStatsFile != INVALID_HANDLE_VALUE) + { + //Print the CSV header row + char rowData[2048]; + DWORD charCount = 0; + DWORD bytesWritten = 0; + + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'h') != NULL || strchr(statsTypes, 'H') != NULL) + { + //Obtain the method Hash + char md5Hash[MD5_HASH_BUFFER_SIZE]; + if (mc->dumpMethodMD5HashToBuffer(md5Hash, MD5_HASH_BUFFER_SIZE) != MD5_HASH_BUFFER_SIZE) + md5Hash[0] = 0; + + charCount += sprintf(rowData + charCount, "%s,", md5Hash); + } + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'n') != NULL || strchr(statsTypes, 'N') != NULL) + { + charCount += sprintf(rowData + charCount, "%d,", methodNumber); + } + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'i') != NULL || strchr(statsTypes, 'I') != NULL) + { + //Obtain the IL code size for this method + CORINFO_METHOD_INFO info; + unsigned flags = 0; + mc->repCompileMethod(&info, &flags); + + charCount += sprintf(rowData + charCount, "%d,", info.ILCodeSize); + } + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'a') != NULL || strchr(statsTypes, 'A') != NULL) + { + //Obtain the compiled method ASM size + BYTE *temp; + DWORD codeSize; + CorJitResult result; + if (mc->cr->CompileMethod != nullptr) + mc->cr->repCompileMethod(&temp, &codeSize, &result); + else + codeSize = 0;//this is likely a thin mc + + charCount += sprintf(rowData + charCount, "%d,", codeSize); + } + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 't') != NULL || strchr(statsTypes, 'T') != NULL) + { + charCount += sprintf(rowData + charCount, "%llu,%llu,", firstTime, secondTime); + } + + //get rid of the final ',' and replace it with a '\n' + rowData[charCount - 1] = '\n'; + + if (!WriteFile(hStatsFile, rowData, charCount, &bytesWritten, nullptr) || bytesWritten != charCount) + { + LogError("Failed to write row header '%s'. GetLastError()=%u", rowData, GetLastError()); + } + } +} + +void MethodStatsEmitter::SetStatsTypes(char *types) +{ + statsTypes = types; + + if (hStatsFile != INVALID_HANDLE_VALUE) + { + //Print the CSV header row + char rowHeader[1024]; + DWORD charCount = 0; + DWORD bytesWritten = 0; + + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'h') != NULL || strchr(statsTypes, 'H') != NULL) + charCount += sprintf(rowHeader + charCount, "HASH,"); + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'n') != NULL || strchr(statsTypes, 'N') != NULL) + charCount += sprintf(rowHeader + charCount, "METHOD_NUMBER,"); + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'i') != NULL || strchr(statsTypes, 'I') != NULL) + charCount += sprintf(rowHeader + charCount, "IL_CODE_SIZE,"); + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 'a') != NULL || strchr(statsTypes, 'A') != NULL) + charCount += sprintf(rowHeader + charCount, "ASM_CODE_SIZE,"); + if (strchr(statsTypes, '*') != NULL || strchr(statsTypes, 't') != NULL || strchr(statsTypes, 'T') != NULL) + charCount += sprintf(rowHeader + charCount, "Time1,Time2,"); + + //get rid of the final ',' and replace it with a '\n' + rowHeader[charCount - 1] = '\n'; + + if (!WriteFile(hStatsFile, rowHeader, charCount, &bytesWritten, nullptr) || bytesWritten != charCount) + { + LogError("Failed to write row header '%s'. GetLastError()=%u", rowHeader, GetLastError()); + } + } +}
\ No newline at end of file diff --git a/src/ToolBox/superpmi/superpmi/methodstatsemitter.h b/src/ToolBox/superpmi/superpmi/methodstatsemitter.h new file mode 100644 index 0000000000..fb651b04b2 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/methodstatsemitter.h @@ -0,0 +1,29 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//----------------------------------------------------------------------------- +// MethodStatsEmitter.h - Emits useful method stats for compiled methods for analysis +//----------------------------------------------------------------------------- +#ifndef _MethodStatsEmitter +#define _MethodStatsEmitter + +#include "methodcontext.h" +#include "jitinstance.h" + +class MethodStatsEmitter +{ + +private: + char *statsTypes; + HANDLE hStatsFile; + +public: + MethodStatsEmitter(char *nameOfInput); + ~MethodStatsEmitter(); + + void Emit(int methodNumber, MethodContext *mc, ULONGLONG firstTime, ULONGLONG secondTime); + void SetStatsTypes(char *types); +}; +#endif
\ No newline at end of file diff --git a/src/ToolBox/superpmi/superpmi/neardiffer.cpp b/src/ToolBox/superpmi/superpmi/neardiffer.cpp new file mode 100644 index 0000000000..5b2e3b1b57 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/neardiffer.cpp @@ -0,0 +1,1031 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//---------------------------------------------------------- +// nearDiffer.cpp - differ that handles code that is very similar +//---------------------------------------------------------- + +#include "standardpch.h" + +#ifdef USE_COREDISTOOLS +#include "coredistools.h" +#endif // USE_COREDISTOOLS + +#include "logging.h" +#include "neardiffer.h" + +#ifdef USE_COREDISTOOLS + +// +// Helper functions to print messages from CoreDisTools Library +// The file/linenumber information is from this helper itself, +// since we are only linking with the CoreDisTools library. +// +static void LogFromCoreDisToolsHelper(LogLevel level, const char *msg, va_list argList) +{ + Logger::LogVprintf(__func__, __FILE__, __LINE__, level, argList, msg); +} + +#define LOGGER(L) \ +static void Log##L(const char *msg, ...) \ +{\ + va_list argList; \ + va_start(argList, msg); \ + LogFromCoreDisToolsHelper (LOGLEVEL_##L, msg, argList); \ + va_end(argList); \ +} + +LOGGER(VERBOSE) +LOGGER(ERROR) +LOGGER(WARNING) + +const PrintControl CorPrinter= { LogERROR, LogWARNING, LogVERBOSE, LogVERBOSE }; + +#endif // USE_COREDISTOOLS + +// +// The NearDiff Disassembler Initialization +// +void NearDiffer::InitAsmDiff() +{ +#ifdef USE_COREDISTOOLS + if (UseCoreDisTools) + { + corAsmDiff = NewDiffer(Target_Host, &CorPrinter, NearDiffer::compareOffsets); + } +#endif // USE_COREDISTOOLS +} + +// +// The NearDiff destructor +// +NearDiffer::~NearDiffer() +{ +#ifdef USE_COREDISTOOLS + if (corAsmDiff != nullptr) + { + FinishDiff(corAsmDiff); + } +#endif // USE_COREDISTOOLS +} + +// At a high level, the near differ takes in a method context and two compile results, performs +// some simple fixups, and then compares the main artifacts of the compile result (i.e. generated +// code, GC info, EH info, debug info, etc.) for equality. In order to be fast, the fixups and +// definitions of "equality" are minimal; for example, the GC info check just does a simple memcmp. +// +// The entrypoint into the near differ is nearDiffer::compare; its doc comments will have more +// details on what it does. That function in turn fans out to various other components. For asm +// diffing, the main function of interest will be nearDiffer::compareCodeSection. +// +// Most of the diffing logic is architecture-independent, with the following exceptions: +// +// - The MSDIS instance must be created with knowledge of the architecture it is working with. +// - The heuristics to compare different literal operand values has some architecture-specific +// assumptions. +// - The code stream is fixed up using relocations recorded during compilation time. The logic +// for applying these should, in theory, be architecture independent, but depending on how +// the runtime implements this from platform to platform, there might be subtle differences here. +// + +#ifdef USE_MSVCDIS + +DIS* NearDiffer::GetMsVcDis() +{ + DIS *disasm; + +#ifdef _TARGET_AMD64_ + if ((TargetArchitecture != nullptr) && (0 == _stricmp(TargetArchitecture, "arm64"))) + { + disasm = DIS::PdisNew(DIS::distArm64); + } + else + { + disasm = DIS::PdisNew(DIS::distX8664); + } +#elif defined(_TARGET_X86_) + disasm = DIS::PdisNew(DIS::distX86); +#endif + + return disasm; +} + +#endif // USE_MSVCDIS + +// +// Simple, quick-and-dirty disassembler. If NearDiffer::compareCodeSection finds that two code +// streams differ, it will call this to dump the two differing code blocks to the log. The dump +// is logged under the verbose logging level. +// +// The output format is in MSDIS's disassembly format. +// +// Arguments: +// block - A pointer to the code block to diassemble. +// blocksize - The size of the code block to disassemble. +// originalAddr - The original base address of the code block. +// +void NearDiffer::DumpCodeBlock(unsigned char *block, ULONG blocksize, void *originalAddr) +{ +#ifdef USE_MSVCDIS + DIS *disasm = GetMsVcDis(); + size_t offset = 0; + std::string codeBlock; + + while (offset < blocksize) + { + DIS::INSTRUCTION instr; + DIS::OPERAND ops[3]; + + size_t instrSize = disasm->CbDisassemble((DIS::ADDR)originalAddr + offset, (void *)(block + offset), 15); + if(instrSize==0) + { + LogWarning("Zero sized instruction"); + break; + } + disasm->FDecode(&instr, ops, 3); + + wchar_t instrMnemonicWide[64]; // I never know how much to allocate... + disasm->CchFormatInstr(instrMnemonicWide, 64); + char instrMnemonic[128]; + size_t count; + wcstombs_s(&count, instrMnemonic, 128, instrMnemonicWide, 64); + + const size_t minInstrBytes = 7; + size_t instrBytes = max(instrSize, minInstrBytes); + size_t buffSize = _snprintf(nullptr, 0, "%p %s\n", (void*)((size_t)originalAddr+offset), instrMnemonic) + 3 * instrBytes + 1; + char *buff = new char[buffSize]; + int written = 0; + written += sprintf_s(buff, buffSize, "%p ", (void*)((size_t)originalAddr+offset)); + for (size_t i = 0; i < instrBytes; i++) + { + if (i < instrSize) + { + written += sprintf_s(buff + written, buffSize - written, "%02X ", *(const uint8_t*)(block + offset + i)); + } + else + { + written += sprintf_s(buff + written, buffSize - written, " "); + } + } + written += sprintf_s(buff + written, buffSize - written, "%s\n", instrMnemonic); + codeBlock += buff; + delete[] buff; + offset += instrSize; + } + LogVerbose("Code dump:\n%s", codeBlock.c_str()); + delete disasm; +#else // !USE_MSVCDIS + LogVerbose("No disassembler"); +#endif // !USE_MSVCDIS +} + +// +// Struct to capture the information required by offset comparator. +// +struct DiffData +{ + // Common Data + CompileResult *cr; + + // Details of the first block + size_t blocksize1; + size_t datablock1; + size_t datablockSize1; + size_t originalBlock1; + size_t originalDataBlock1; + size_t otherCodeBlock1; + size_t otherCodeBlockSize1; + + // Details of the second block + size_t blocksize2; + size_t datablock2; + size_t datablockSize2; + size_t originalBlock2; + size_t originalDataBlock2; + size_t otherCodeBlock2; + size_t otherCodeBlockSize2; +}; + +// +// NearDiff Offset Comparator. +// Determine whether two syntactically different constants are +// semantically equivalent, using certain heuristics. +// +bool NearDiffer::compareOffsets(const void *payload, + size_t blockOffset, + size_t instrLen, + uint64_t offset1, + uint64_t offset2) +{ + // The trivial case + if (offset1 == offset2) + { + return true; + } + + const DiffData *data = (const DiffData *)payload; + size_t ip1 = data->originalBlock1 + blockOffset; + size_t ip2 = data->originalBlock2 + blockOffset; + size_t ipRelOffset1 = ip1 + instrLen + (size_t)offset1; + size_t ipRelOffset2 = ip2 + instrLen + (size_t)offset2; + + // Case where we have a call into flat address -- the most common case. + size_t gOffset1 = ipRelOffset1; + size_t gOffset2 = ipRelOffset2; + if ((DWORD)gOffset1 == (DWORD)gOffset2) //make sure the lower 32bits match (best we can do in the current replay form) + return true; + + //Case where we have an offset into the read only section (e.g. loading a float value) + size_t roOffset1a = (size_t)offset1 - data->originalDataBlock1; + size_t roOffset2a = (size_t)offset2 - data->originalDataBlock2; + if ((roOffset1a == roOffset2a) && (roOffset1a < data->datablockSize1)) //Confirm its an offset that fits inside our RoRegion + return true; + + // This case is written to catch IP-relative offsets to the RO data-section + // For example: + // + size_t roOffset1b = ipRelOffset1 - data->originalDataBlock1; + size_t roOffset2b = ipRelOffset2 - data->originalDataBlock2; + if ((roOffset1b == roOffset2b) && (roOffset1b < data->datablockSize1)) //Confirm its an offset that fits inside our RoRegion + return true; + + //Case where we push an address to our own code section. + size_t gOffset1a = (size_t)offset1 - data->originalBlock1; + size_t gOffset2a = (size_t)offset2 - data->originalBlock2; + if ((gOffset1a == gOffset2a) && (gOffset1a < data->blocksize1)) //Confirm its in our code region + return true; + + //Case where we push an address in the other codeblock. + size_t gOffset1b = (size_t)offset1 - data->otherCodeBlock1; + size_t gOffset2b = (size_t)offset2 - data->otherCodeBlock2; + if ((gOffset1b == gOffset2b) && (gOffset1b < data->otherCodeBlockSize1)) //Confirm it's in the other code region + return true; + + //Case where we have an offset into the hot codeblock from the cold code block (why?) + size_t ocOffset1 = ipRelOffset1 - data->otherCodeBlock1; + size_t ocOffset2 = ipRelOffset2 - data->otherCodeBlock2; + if (ocOffset1 == ocOffset2) //Would be nice to check to see if it fits in the other code block + return true; + + //VSD calling case. + size_t Offset1 = (ipRelOffset1 - 8); + if (data->cr->CallTargetTypes->GetIndex((DWORDLONG)Offset1) != (DWORD)-1) + { + // This logging is too noisy, so disable it. + //LogVerbose("Found VSD callsite, did softer compare than ideal"); + return true; + } + + //x86 VSD calling cases. + size_t Offset1b = (size_t)offset1 - 4; + size_t Offset2b = (size_t)offset2; + if (data->cr->CallTargetTypes->GetIndex((DWORDLONG)Offset1b) != (DWORD)-1) + { + // This logging is too noisy, so disable it. + //LogVerbose("Found VSD callsite, did softer compare than ideal"); + return true; + } + if (data->cr->CallTargetTypes->GetIndex((DWORDLONG)Offset2b) != (DWORD)-1) + { + // This logging is too noisy, so disable it. + //LogVerbose("Found VSD callsite, did softer compare than ideal"); + return true; + } + + //Case might be a field address that we handed out to handle inlined values being loaded into + //a register as an immediate value (and where the address is encoded as an indirect immediate load) + size_t realTargetAddr = (size_t)data->cr->searchAddressMap((void*)gOffset2); + if (realTargetAddr == gOffset1) + return true; + + //Case might be a field address that we handed out to handle inlined values being loaded into + //a register as an immediate value (and where the address is encoded and loaded by immediate into a register) + realTargetAddr = (size_t)data->cr->searchAddressMap((void*)offset2); + if (realTargetAddr == offset1) + return true; + if (realTargetAddr == 0x424242)//this offset matches what we got back from a getTailCallCopyArgsThunk + return true; + + realTargetAddr = (size_t)data->cr->searchAddressMap((void*)(gOffset2)); + if (realTargetAddr != -1) //we know this was passed out as a bbloc + return true; + + return false; +} + +// +// Compares two code sections for syntactic equality. This is the core of the asm diffing logic. +// +// This mostly relies on MSDIS's decoded representation of an instruction to compare for equality. +// That is, using MSDIS's internal IR, this goes through the code stream and compares, instruction +// by instruction, op code and operand values for equality. +// +// Obviously, just blindly comparing operand values will raise a lot of false alarms. In order to +// compensate for phenomena like literal pointer addresses in the code stream changing, this applies +// some heuristics on mismatching operand values to try to normalize them a little bit. Essentially, +// if operand values don't match, they are re-interpreted as various relative deltas from known base +// addresses. For example, a common case is a pointer into the read-only data section. One of the +// heuristics subtracts both operand values from the base address of the read-only data section and +// checks to see if they are the same distance away from their respective read-only base addresses. +// +// Notes: +// - The core syntactic comparison is platform agnostic; we compare op codes and operand values +// using MSDIS's architecture-independent IR (i.e. the data structures defined in msvcdis.h). +// Only the disassembler instance itself is initialized differently based on the target arch- +// itecture. +// - That being said, the heuristics themselves are not guaranteed to be platform agnostic. For +// instance, there is a case that applies only to x86 VSD calls. When porting the near differ +// to new platforms, these special cases should be examined and ported with care. +// +// Arguments: +// mc - The method context of the method to diff. Unused. +// cr1 - The first compile result to compare. Unused. +// cr2 - The second compile result to compare. Unused. +// block1 - A pointer to the first code block to diassemble. +// blocksize1 - The size of the first code block to compare. +// datablock1 - A pointer to the first read-only data block to compare. Unused. +// datablockSize1 - The size of the first read-only data block to compare. +// originalBlock1 - The original base address of the first code block. +// originalDataBlock1 - The original base address of the first read-only data block. +// otherCodeBlock1 - The original base address of the first cold code block. Note that this is +// just an address; we don't need the cold code buffer. +// otherCodeBlockSize1- The size of the first cold code block. +// block2 - A pointer to the second code block to diassemble. +// blocksize2 - The size of the second code block to compare. +// datablock2 - A pointer to the second read-only data block to compare. +// datablockSize2 - The size of the second read-only data block to compare. +// originalBlock2 - The original base address of the second code block. +// originalDataBlock2 - The original base address of the second read-only data block. +// otherCodeBlock2 - The original base address of the second cold code block. Note that this is +// just an address; we don't need the cold code buffer. +// otherCodeBlockSize2- The size of the second cold code block. +// +// Return Value: +// True if the code sections are syntactically identical; false otherwise. +// + +bool NearDiffer::compareCodeSection( + MethodContext *mc, + CompileResult *cr1, + CompileResult *cr2, + unsigned char *block1, + ULONG blocksize1, + unsigned char *datablock1, + ULONG datablockSize1, + void *originalBlock1, + void *originalDataBlock1, + void *otherCodeBlock1, + ULONG otherCodeBlockSize1, + unsigned char *block2, + ULONG blocksize2, + unsigned char *datablock2, + ULONG datablockSize2, + void *originalBlock2, + void *originalDataBlock2, + void *otherCodeBlock2, + ULONG otherCodeBlockSize2) +{ + DiffData data = + { + cr2, + + // Details of the first block + (size_t)blocksize1, + (size_t)datablock1, + (size_t)datablockSize1, + (size_t)originalBlock1, + (size_t)originalDataBlock1, + (size_t)otherCodeBlock1, + (size_t)otherCodeBlockSize1, + + // Details of the second block + (size_t)blocksize2, + (size_t)datablock2, + (size_t)datablockSize2, + (size_t)originalBlock2, + (size_t)originalDataBlock2, + (size_t)otherCodeBlock2, + (size_t)otherCodeBlockSize2 + }; + +#ifdef USE_COREDISTOOLS + if (UseCoreDisTools) + { + bool areSame = NearDiffCodeBlocks(corAsmDiff, &data, + (const uint8_t *)originalBlock1, block1, blocksize1, + (const uint8_t *)originalBlock2, block2, blocksize2); + + if (!areSame) + { + DumpDiffBlocks(corAsmDiff, (const uint8_t *) originalBlock1, + block1, blocksize1, (const uint8_t *) originalBlock2, + block2, blocksize2); + } + + return areSame; + } +#endif // USE_COREDISTOOLS + +#ifdef USE_MSVCDIS + bool haveSeenRet = false; + DIS *disasm_1 = GetMsVcDis(); + DIS *disasm_2 = GetMsVcDis(); + + size_t offset = 0; + + if (blocksize1 != blocksize2) + { + LogVerbose("Code sizes don't match %u != %u", blocksize1, blocksize2); + goto DumpDetails; + } + + while (offset < blocksize1) + { + DIS::INSTRUCTION instr_1; + DIS::INSTRUCTION instr_2; + const int MaxOperandCount = 5; + DIS::OPERAND ops_1[MaxOperandCount]; + DIS::OPERAND ops_2[MaxOperandCount]; + + // Zero out the locals, just in case. + memset(&instr_1, 0, sizeof(instr_1)); + memset(&instr_2, 0, sizeof(instr_2)); + memset(&ops_1, 0, sizeof(ops_1)); + memset(&ops_2, 0, sizeof(ops_2)); + + size_t instrSize_1 = disasm_1->CbDisassemble((DIS::ADDR)originalBlock1 + offset, (void *)(block1 + offset), 15); + size_t instrSize_2 = disasm_2->CbDisassemble((DIS::ADDR)originalBlock2 + offset, (void *)(block2 + offset), 15); + + if (instrSize_1 != instrSize_2) + { + LogVerbose("Different instruction sizes %llu %llu", instrSize_1, instrSize_2); + goto DumpDetails; + } + if (instrSize_1 == 0) + { + if (haveSeenRet) + { + // This logging is pretty noisy, so disable it. + //LogVerbose("instruction size of zero after seeing a ret (soft issue?)."); + break; + } + LogWarning("instruction size of zero."); + goto DumpDetails; + } + + bool FDecodeError = false; + if (!disasm_1->FDecode(&instr_1, ops_1, MaxOperandCount)) + { + LogWarning("FDecode of instr_1 returned false."); + FDecodeError = true; + } + if (!disasm_2->FDecode(&instr_2, ops_2, MaxOperandCount)) + { + LogWarning("FDecode of instr_2 returned false."); + FDecodeError = true; + } + + wchar_t instrMnemonic_1[64]; // I never know how much to allocate... + disasm_1->CchFormatInstr(instrMnemonic_1, 64); + wchar_t instrMnemonic_2[64]; // I never know how much to allocate... + disasm_2->CchFormatInstr(instrMnemonic_2, 64); + if (wcscmp(instrMnemonic_1, L"ret") == 0) + haveSeenRet = true; + if (wcscmp(instrMnemonic_1, L"rep ret") == 0) + haveSeenRet = true; + + // First, check to see if these instructions are actually identical. + // This is done 1) to avoid the detailed comparison of the fields of instr_1 + // and instr_2 if they are identical, and 2) because in the event that + // there are bugs or unimplemented instructions in FDecode, we don't want + // to count them as diffs if they are bitwise identical. + + if (memcmp((block1 + offset), (block2 + offset), instrSize_1) != 0) + { + if (FDecodeError) + { + LogWarning("FDecode returned false."); + goto DumpDetails; + } + + if (instr_1.opa != instr_2.opa) + { + LogVerbose("different opa %d %d", instr_1.opa, instr_2.opa); + goto DumpDetails; + } + if (instr_1.coperand != instr_2.coperand) + { + LogVerbose("different coperand %u %u", (unsigned int)instr_1.coperand, (unsigned int)instr_2.coperand); + goto DumpDetails; + } + if (instr_1.dwModifiers != instr_2.dwModifiers) + { + LogVerbose("different dwModifiers %u %u", instr_1.dwModifiers, instr_2.dwModifiers); + goto DumpDetails; + } + + for (size_t i = 0; i < instr_1.coperand; i++) + { + if (ops_1[i].cb != ops_2[i].cb) + { + LogVerbose("different cb %llu %llu", ops_1[i].cb, ops_2[i].cb); + goto DumpDetails; + } + if (ops_1[i].imcls != ops_2[i].imcls) + { + LogVerbose("different imcls %d %d", ops_1[i].imcls, ops_2[i].imcls); + goto DumpDetails; + } + if (ops_1[i].opcls != ops_2[i].opcls) + { + LogVerbose("different opcls %d %d", ops_1[i].opcls, ops_2[i].opcls); + goto DumpDetails; + } + if (ops_1[i].rega1 != ops_2[i].rega1) + { + LogVerbose("different rega1 %d %d", ops_1[i].rega1, ops_2[i].rega1); + goto DumpDetails; + } + if (ops_1[i].rega2 != ops_2[i].rega2) + { + LogVerbose("different rega2 %d %d", ops_1[i].rega2, ops_2[i].rega2); + goto DumpDetails; + } + if (ops_1[i].rega3 != ops_2[i].rega3) + { + LogVerbose("different rega3 %d %d", ops_1[i].rega3, ops_2[i].rega3); + goto DumpDetails; + } + if (ops_1[i].wScale != ops_2[i].wScale) + { + LogVerbose("different wScale %u %u", ops_1[i].wScale, ops_2[i].wScale); + goto DumpDetails; + } + + // + // These are special.. we can often reason out exactly why these values + // are different using heuristics. + // + // Why is Instruction size passed as zero? + // Ans: Because the implementation of areOffsetsEquivalent() uses + // the instruction size to compute absolute offsets in the case of + // PC-relative addressing, and MSVCDis already reports the + // absolute offsets! For example: + // 0F 2E 05 67 00 9A FD ucomiss xmm0, dword ptr[FFFFFFFFFD9A006Eh] + // + + if (compareOffsets(&data, offset, 0, ops_1[i].dwl, ops_2[i].dwl)) + { + continue; + } + else + { + size_t gOffset1 = (size_t)originalBlock1 + offset + (size_t)ops_1[i].dwl; + size_t gOffset2 = (size_t)originalBlock2 + offset + (size_t)ops_2[i].dwl; + + LogVerbose("operand %d dwl is different", i); +#ifdef _TARGET_AMD64_ + LogVerbose("gOffset1 %016llX", gOffset1); + LogVerbose("gOffset2 %016llX", gOffset2); + LogVerbose("gOffset1 - gOffset2 %016llX", gOffset1 - gOffset2); +#elif defined(_TARGET_X86_) + LogVerbose("gOffset1 %08X", gOffset1); + LogVerbose("gOffset2 %08X", gOffset2); + LogVerbose("gOffset1 - gOffset2 %08X", gOffset1 - gOffset2); +#endif + LogVerbose("dwl1 %016llX", ops_1[i].dwl); + LogVerbose("dwl2 %016llX", ops_2[i].dwl); + goto DumpDetails; + } + } + } + offset += instrSize_1; + } + delete disasm_1; + delete disasm_2; + return true; + +DumpDetails: + LogVerbose("block1 %p", block1); + LogVerbose("block2 %p", block2); + LogVerbose("originalBlock1 [%p,%p)", originalBlock1, (const uint8_t *)originalBlock1 + blocksize1); + LogVerbose("originalBlock2 [%p,%p)", originalBlock2, (const uint8_t *)originalBlock2 + blocksize2); + LogVerbose("blocksize1 %08X", blocksize1); + LogVerbose("blocksize2 %08X", blocksize2); + LogVerbose("dataBlock1 [%p,%p)", originalDataBlock1, (const uint8_t *)originalDataBlock1 + datablockSize1); + LogVerbose("dataBlock2 [%p,%p)", originalDataBlock2, (const uint8_t *)originalDataBlock2 + datablockSize2); + LogVerbose("datablockSize1 %08X", datablockSize1); + LogVerbose("datablockSize2 %08X", datablockSize2); + LogVerbose("otherCodeBlock1 [%p,%p)", otherCodeBlock1, (const uint8_t *)otherCodeBlock1 + otherCodeBlockSize1); + LogVerbose("otherCodeBlock2 [%p,%p)", otherCodeBlock2, (const uint8_t *)otherCodeBlock2 + otherCodeBlockSize2); + LogVerbose("otherCodeBlockSize1 %08X", otherCodeBlockSize1); + LogVerbose("otherCodeBlockSize2 %08X", otherCodeBlockSize2); + +#ifdef _TARGET_AMD64_ + LogVerbose("offset %016llX", offset); + LogVerbose("addr1 %016llX", (size_t)originalBlock1 + offset); + LogVerbose("addr2 %016llX", (size_t)originalBlock2 + offset); +#elif defined(_TARGET_X86_) + LogVerbose("offset %08X", offset); + LogVerbose("addr1 %08X", (size_t)originalBlock1 + offset); + LogVerbose("addr2 %08X", (size_t)originalBlock2 + offset); +#endif + + LogVerbose("Block1:"); + DumpCodeBlock(block1, blocksize1, originalBlock1); + LogVerbose("Block2:"); + DumpCodeBlock(block2, blocksize2, originalBlock2); + + if (disasm_1 != nullptr) + delete disasm_1; + if (disasm_2 != nullptr) + delete disasm_2; + return false; +#else // !USE_MSVCDIS + return false; // No disassembler; assume there are differences +#endif // !USE_MSVCDIS +} + +// +// Compares two read-only data sections for equality. +// +// Arguments: +// mc - The method context of the method to diff. +// cr1 - The first compile result to compare. +// cr2 - The second compile result to compare. +// block1 - A pointer to the first code block to diassemble. +// blocksize1 - The size of the first code block to compare. +// originalDataBlock1 - The original base address of the first read-only data block. +// block2 - A pointer to the second code block to diassemble. +// blocksize2 - The size of the second code block to compare. +// originalDataBlock2 - The original base address of the second read-only data block. +// +// Return Value: +// True if the read-only data sections are identical; false otherwise. +// +bool NearDiffer::compareReadOnlyDataBlock(MethodContext *mc, CompileResult *cr1, CompileResult *cr2, + unsigned char *block1, ULONG blocksize1, void *originalDataBlock1, + unsigned char *block2, ULONG blocksize2, void *originalDataBlock2) +{ + //no rodata + if(blocksize1==0 && blocksize2==0) + return true; + + if(blocksize1!=blocksize2) + { + LogVerbose("compareReadOnlyDataBlock found non-matching sizes %u %u", blocksize1, blocksize2); + return false; + } + + //TODO-Cleanup: The values on the datablock seem to wobble. Need further investigation to evaluate a good near comparison for these + return true; +} + +// +// Compares two EH info blocks for equality. +// +// Arguments: +// mc - The method context of the method to diff. +// cr1 - The first compile result to compare. +// cr2 - The second compile result to compare. +// +// Return Value: +// True if the EH info blocks are identical; false otherwise. +// +bool NearDiffer::compareEHInfo(MethodContext *mc, CompileResult *cr1, CompileResult *cr2) +{ + ULONG cEHSize_1; + ULONG ehFlags_1; + ULONG tryOffset_1; + ULONG tryLength_1; + ULONG handlerOffset_1; + ULONG handlerLength_1; + ULONG classToken_1; + + ULONG cEHSize_2; + ULONG ehFlags_2; + ULONG tryOffset_2; + ULONG tryLength_2; + ULONG handlerOffset_2; + ULONG handlerLength_2; + ULONG classToken_2; + + + cEHSize_1 = cr1->repSetEHcount(); + cEHSize_2 = cr2->repSetEHcount(); + + //no exception + if(cEHSize_1==0 && cEHSize_2==0) + return true; + + if(cEHSize_1!=cEHSize_2) + { + LogVerbose("compareEHInfo found non-matching sizes %u %u", cEHSize_1, cEHSize_2); + return false; + } + + for(unsigned int i=0;i<cEHSize_1;i++) + { + cr1->repSetEHinfo(i, &ehFlags_1, &tryOffset_1, &tryLength_1, &handlerOffset_1, &handlerLength_1, &classToken_1); + cr2->repSetEHinfo(i, &ehFlags_2, &tryOffset_2, &tryLength_2, &handlerOffset_2, &handlerLength_2, &classToken_2); + if(ehFlags_1!=ehFlags_2) + { + LogVerbose("EH flags don't match %u != %u", ehFlags_1, ehFlags_2); + return false; + } + if((tryOffset_1!=tryOffset_2) || (tryLength_1!=tryLength_2)) + { + LogVerbose("EH try information don't match, offset: %u %u, length: %u %u", tryOffset_1, tryOffset_2, tryLength_1, tryLength_2); + return false; + } + if((handlerOffset_1!=handlerOffset_2) || (handlerLength_1!=handlerLength_2)) + { + LogVerbose("EH handler information don't match, offset: %u %u, length: %u %u", handlerOffset_1, handlerOffset_2, handlerLength_1, handlerLength_2); + return false; + } + if(classToken_1!=classToken_2) + { + LogVerbose("EH class tokens don't match %u!=%u", classToken_1, classToken_2); + return false; + } + } + + return true; +} + +// +// Compares two GC info blocks for equality. +// +// Arguments: +// mc - The method context of the method to diff. +// cr1 - The first compile result to compare. +// cr2 - The second compile result to compare. +// +// Return Value: +// True if the GC info blocks are identical; false otherwise. +// +bool NearDiffer::compareGCInfo(MethodContext *mc, CompileResult *cr1, CompileResult *cr2) +{ + void *gcInfo1; + size_t gcInfo1Size; + void *gcInfo2; + size_t gcInfo2Size; + + cr1->repAllocGCInfo(&gcInfo1Size, &gcInfo1); + cr2->repAllocGCInfo(&gcInfo2Size, &gcInfo2); + + if (gcInfo1Size != gcInfo2Size) + { + LogVerbose("Reported GCInfo sizes don't match: %u != %u", (unsigned int)gcInfo1Size, (unsigned int)gcInfo2Size); + return false; + } + + if (memcmp(gcInfo1, gcInfo2, gcInfo1Size) != 0) + { + LogVerbose("GCInfo doesn't match."); + return false; + } + + return true; +} + +// +// Compares two sets of native var info for equality. +// +// Arguments: +// mc - The method context of the method to diff. +// cr1 - The first compile result to compare. +// cr2 - The second compile result to compare. +// +// Return Value: +// True if the native var info is identical; false otherwise. +// +bool NearDiffer::compareVars(MethodContext *mc, CompileResult *cr1, CompileResult *cr2) +{ + CORINFO_METHOD_HANDLE ftn_1; + ULONG32 cVars_1; + ICorDebugInfo::NativeVarInfo *vars_1; + + CORINFO_METHOD_HANDLE ftn_2; + ULONG32 cVars_2; + ICorDebugInfo::NativeVarInfo *vars_2; + + CORINFO_METHOD_INFO info; + unsigned flags = 0; + mc->repCompileMethod(&info, &flags); + + bool set1 = cr1->repSetVars(&ftn_1, &cVars_1, &vars_1); + bool set2 = cr2->repSetVars(&ftn_2, &cVars_2, &vars_2); + if((set1==false)&&(set2==false)) + return true; // we don't have boundaries for either of these. + if(((set1==true)&&(set2==false))||((set1==false)&&(set2==true))) + { + LogVerbose("missing matching vars sets"); + return false; + } + + //no vars + if(cVars_1==0 && cVars_2==0) + { + return true; + } + + if(ftn_1!=ftn_2) + { + //We would like to find out this situation + __debugbreak(); + LogVerbose("compareVars found non-matching CORINFO_METHOD_HANDLE %p %p", ftn_1, ftn_2); + return false; + } + if(ftn_1!=info.ftn) + { + LogVerbose("compareVars found issues with the CORINFO_METHOD_HANDLE %p %p", ftn_1, info.ftn); + return false; + } + + if(cVars_1!=cVars_2) + { + LogVerbose("compareVars found non-matching var count %u %u", cVars_1, cVars_2); + return false; + } + + //TODO-Cleanup: The values on the NativeVarInfo array seem to wobble. Need further investigation to evaluate a good near comparison for these + //for(unsigned int i=0;i<cVars_1;i++) + //{ + // if(vars_1[i].startOffset!=vars_2[i].startOffset) + // { + // LogVerbose("compareVars found non-matching startOffsets %u %u for var: %u", vars_1[i].startOffset, vars_2[i].startOffset, i); + // return false; + // } + //} + + return true; +} + +// +// Compares two sets of native offset mappings for equality. +// +// Arguments: +// mc - The method context of the method to diff. +// cr1 - The first compile result to compare. +// cr2 - The second compile result to compare. +// +// Return Value: +// True if the native offset mappings are identical; false otherwise. +// +bool NearDiffer::compareBoundaries(MethodContext *mc, CompileResult *cr1, CompileResult *cr2) +{ + CORINFO_METHOD_HANDLE ftn_1; + ULONG32 cMap_1; + ICorDebugInfo::OffsetMapping *map_1; + + CORINFO_METHOD_HANDLE ftn_2; + ULONG32 cMap_2; + ICorDebugInfo::OffsetMapping *map_2; + + CORINFO_METHOD_INFO info; + unsigned flags = 0; + mc->repCompileMethod(&info, &flags); + + bool set1 = cr1->repSetBoundaries(&ftn_1, &cMap_1, &map_1); + bool set2 = cr2->repSetBoundaries(&ftn_2, &cMap_2, &map_2); + if((set1==false)&&(set2==false)) + return true; // we don't have boundaries for either of these. + if(((set1==true)&&(set2==false))||((set1==false)&&(set2==true))) + { + LogVerbose("missing matching boundary sets"); + return false; + } + + if(ftn_1!=ftn_2) + { + LogVerbose("compareBoundaries found non-matching CORINFO_METHOD_HANDLE %p %p", ftn_1, ftn_2); + return false; + } + + //no maps + if(cMap_1==0 && cMap_2==0) + return true; + + if(cMap_1!=cMap_2) + { + LogVerbose("compareBoundaries found non-matching var count %u %u", cMap_1, cMap_2); + return false; + } + + for(unsigned int i=0;i<cMap_1;i++) + { + if(map_1[i].ilOffset!=map_2[i].ilOffset) + { + LogVerbose("compareBoundaries found non-matching ilOffset %u %u for map: %u", map_1[i].ilOffset, map_2[i].ilOffset, i); + return false; + } + if(map_1[i].nativeOffset!=map_2[i].nativeOffset) + { + LogVerbose("compareBoundaries found non-matching nativeOffset %u %u for map: %u", map_1[i].nativeOffset, map_2[i].nativeOffset, i); + return false; + } + if(map_1[i].source!=map_2[i].source) + { + LogVerbose("compareBoundaries found non-matching source %u %u for map: %u", (unsigned int)map_1[i].source, (unsigned int)map_2[i].source, i); + return false; + } + } + + + return true; +} + +// +// Compares two compiled versions of a method for equality. This is the main driver for the various +// components of near diffing. +// +// Before starting the diffing process, this applies some fixups to the code stream based on relocations +// recorded during compilation, using the original base address that was used when compiling the method. +// +// Arguments: +// mc - The method context of the method to diff. +// cr1 - The first compile result to compare. +// cr2 - The second compile result to compare. +// +// Return Value: +// True if the compile results are identical; false otherwise. +// +bool NearDiffer::compare(MethodContext *mc, CompileResult *cr1, CompileResult *cr2) +{ + ULONG hotCodeSize_1; + ULONG coldCodeSize_1; + ULONG roDataSize_1; + ULONG xcptnsCount_1; + CorJitAllocMemFlag flag_1; + unsigned char *hotCodeBlock_1; + unsigned char *coldCodeBlock_1; + unsigned char *roDataBlock_1; + void *orig_hotCodeBlock_1; + void *orig_coldCodeBlock_1; + void *orig_roDataBlock_1; + + ULONG hotCodeSize_2; + ULONG coldCodeSize_2; + ULONG roDataSize_2; + ULONG xcptnsCount_2; + CorJitAllocMemFlag flag_2; + unsigned char *hotCodeBlock_2; + unsigned char *coldCodeBlock_2; + unsigned char *roDataBlock_2; + void *orig_hotCodeBlock_2; + void *orig_coldCodeBlock_2; + void *orig_roDataBlock_2; + + cr1->repAllocMem(&hotCodeSize_1, &coldCodeSize_1, &roDataSize_1, &xcptnsCount_1, &flag_1, + &hotCodeBlock_1, &coldCodeBlock_1, &roDataBlock_1, &orig_hotCodeBlock_1, &orig_coldCodeBlock_1, &orig_roDataBlock_1); + cr2->repAllocMem(&hotCodeSize_2, &coldCodeSize_2, &roDataSize_2, &xcptnsCount_2, &flag_2, + &hotCodeBlock_2, &coldCodeBlock_2, &roDataBlock_2, &orig_hotCodeBlock_2, &orig_coldCodeBlock_2, &orig_roDataBlock_2); + + LogDebug("HCS1 %d CCS1 %d RDS1 %d xcpnt1 %d flag1 %08X, HCB %p CCB %p RDB %p ohcb %p occb %p odb %p", + hotCodeSize_1, coldCodeSize_1, roDataSize_1, xcptnsCount_1, flag_1, + hotCodeBlock_1, coldCodeBlock_1, roDataBlock_1, + orig_hotCodeBlock_1, orig_coldCodeBlock_1, orig_roDataBlock_1); + LogDebug("HCS2 %d CCS2 %d RDS2 %d xcpnt2 %d flag2 %08X, HCB %p CCB %p RDB %p ohcb %p occb %p odb %p", + hotCodeSize_2, coldCodeSize_2, roDataSize_2, xcptnsCount_2, flag_2, + hotCodeBlock_2, coldCodeBlock_2, roDataBlock_2, + orig_hotCodeBlock_2, orig_coldCodeBlock_2, orig_roDataBlock_2); + + cr1->applyRelocs(hotCodeBlock_1, hotCodeSize_1, orig_hotCodeBlock_1); + cr2->applyRelocs(hotCodeBlock_2, hotCodeSize_2, orig_hotCodeBlock_2); + cr1->applyRelocs(coldCodeBlock_1, coldCodeSize_1, orig_coldCodeBlock_1); + cr2->applyRelocs(coldCodeBlock_2, coldCodeSize_2, orig_coldCodeBlock_2); + cr1->applyRelocs(roDataBlock_1, roDataSize_1, orig_roDataBlock_1); + cr2->applyRelocs(roDataBlock_2, roDataSize_2, orig_roDataBlock_2); + + if(!compareCodeSection(mc, cr1, cr2, + hotCodeBlock_1, hotCodeSize_1, roDataBlock_1, roDataSize_1, orig_hotCodeBlock_1, orig_roDataBlock_1, orig_coldCodeBlock_1, coldCodeSize_1, + hotCodeBlock_2, hotCodeSize_2, roDataBlock_2, roDataSize_2, orig_hotCodeBlock_2, orig_roDataBlock_2, orig_coldCodeBlock_2, coldCodeSize_2)) + return false; + + if(!compareCodeSection(mc, cr1, cr2, + coldCodeBlock_1, coldCodeSize_1, roDataBlock_1, roDataSize_1, orig_coldCodeBlock_1, orig_roDataBlock_1, orig_hotCodeBlock_1, hotCodeSize_1, + coldCodeBlock_2, coldCodeSize_2, roDataBlock_2, roDataSize_2, orig_coldCodeBlock_2, orig_roDataBlock_2, orig_hotCodeBlock_2, hotCodeSize_2)) + return false; + + if(!compareReadOnlyDataBlock(mc, cr1, cr2, + roDataBlock_1, roDataSize_1, orig_roDataBlock_1, + roDataBlock_2, roDataSize_2, orig_roDataBlock_2)) + return false; + + if(!compareEHInfo(mc, cr1, cr2)) + return false; + + if (!compareGCInfo(mc, cr1, cr2)) + return false; + + if (!compareVars(mc, cr1, cr2)) + return false; + + if(!compareBoundaries(mc, cr1, cr2)) + return false; + + return true; +} diff --git a/src/ToolBox/superpmi/superpmi/neardiffer.h b/src/ToolBox/superpmi/superpmi/neardiffer.h new file mode 100644 index 0000000000..e3ffe1c790 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/neardiffer.h @@ -0,0 +1,86 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//---------------------------------------------------------- +// nearDiffer.h - differ that handles code that is very similar +//---------------------------------------------------------- +#ifndef _nearDiffer +#define _nearDiffer + +#include "methodcontext.h" +#include "compileresult.h" + +class NearDiffer +{ +public: + + NearDiffer(const char *targetArch, bool useCorDisTools) + : TargetArchitecture(targetArch) + , UseCoreDisTools(useCorDisTools) +#ifdef USE_COREDISTOOLS + , corAsmDiff(nullptr) +#endif // USE_COREDISTOOLS + { + } + + ~NearDiffer(); + + void InitAsmDiff(); + + bool compare(MethodContext *mc, CompileResult *cr1,CompileResult *cr2); + + const char* TargetArchitecture; + const bool UseCoreDisTools; + +private: + + void DumpCodeBlock(unsigned char *block, ULONG blocksize, void *originalAddr); + + bool compareCodeSection( + MethodContext *mc, + CompileResult *cr1, + CompileResult *cr2, + unsigned char *block1, + ULONG blocksize1, + unsigned char *datablock1, + ULONG datablockSize1, + void *originalBlock1, + void *originalDataBlock1, + void *otherCodeBlock1, + ULONG otherCodeBlockSize1, + unsigned char *block2, + ULONG blocksize2, + unsigned char *datablock2, + ULONG datablockSize2, + void *originalBlock2, + void *originalDataBlock2, + void *otherCodeBlock2, + ULONG otherCodeBlockSize2); + + bool compareReadOnlyDataBlock(MethodContext *mc, CompileResult *cr1, CompileResult *cr2, + unsigned char *block1, ULONG blocksize1, void *originalDataBlock1, + unsigned char *block2, ULONG blocksize2, void *originalDataBlock2); + bool compareEHInfo(MethodContext *mc, CompileResult *cr1, CompileResult *cr2); + bool compareGCInfo(MethodContext *mc, CompileResult *cr1, CompileResult *cr2); + bool compareVars(MethodContext *mc, CompileResult *cr1, CompileResult *cr2); + bool compareBoundaries(MethodContext *mc, CompileResult *cr1, CompileResult *cr2); + + static bool compareOffsets(const void *payload, + size_t blockOffset, + size_t instrLen, + uint64_t offset1, + uint64_t offset2); + +#ifdef USE_COREDISTOOLS + CorAsmDiff *corAsmDiff; +#endif // USE_COREDISTOOLS + +#ifdef USE_MSVCDIS + DIS* GetMsVcDis(); +#endif // USE_MSVCDIS + +}; + +#endif // _nearDiffer diff --git a/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp b/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp new file mode 100644 index 0000000000..8c5232315e --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/parallelsuperpmi.cpp @@ -0,0 +1,587 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" +#include "superpmi.h" +#include "simpletimer.h" +#include "mclist.h" +#include "commandline.h" +#include "errorhandling.h" + +#define MAX_LOG_LINE_SIZE 0x1000 //4 KB + +bool closeRequested = false; // global variable to communicate CTRL+C between threads. + +bool StartProcess(char *commandLine, HANDLE hStdOutput, HANDLE hStdError, HANDLE *hProcess) +{ + LogDebug("StartProcess commandLine=%s", commandLine); + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = hStdOutput; + si.hStdError = hStdError; + + ZeroMemory(&pi, sizeof(pi)); + + // Start the child process. + if (!CreateProcess(NULL, // No module name (use command line) + commandLine, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + TRUE, // Set handle inheritance to TRUE (required to use STARTF_USESTDHANDLES) + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi)) // Pointer to PROCESS_INFORMATION structure + { + LogError("CreateProcess failed (%d). CommandLine: %s", GetLastError(), commandLine); + *hProcess = INVALID_HANDLE_VALUE; + return false; + } + + *hProcess = pi.hProcess; + return true; +} + +void ReadMCLToArray(char *mclFilename, int **arr, int *count) +{ + *count = 0; + *arr = nullptr; + char* buff = nullptr; + + HANDLE hFile = CreateFileA(mclFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + LogError("Unable to open '%s'. GetLastError()=%u", mclFilename, GetLastError()); + goto Cleanup; + } + + LARGE_INTEGER DataTemp; + if (GetFileSizeEx(hFile, &DataTemp) == 0) + { + LogError("GetFileSizeEx failed. GetLastError()=%u", GetLastError()); + goto Cleanup; + } + + if (DataTemp.QuadPart > MAXMCLFILESIZE) + { + LogError("Size %d exceeds max size of %d", DataTemp.QuadPart, MAXMCLFILESIZE); + goto Cleanup; + } + + int sz; + sz = (int)(DataTemp.QuadPart); + + buff = new char[sz]; + DWORD bytesRead; + if (ReadFile(hFile, buff, sz, &bytesRead, nullptr) == 0) + { + LogError("ReadFile failed. GetLastError()=%u", GetLastError()); + goto Cleanup; + } + + for (int i = 0; i < sz; i++) + { + if (buff[i] == 0x0d) + (*count)++; + } + + if (*count <= 0) + return; + + *arr = new int[*count]; + for (int j = 0, arrIndex = 0; j < sz; ) + { + //seek the first number on the line + while (!isdigit((unsigned char)buff[j])) + j++; + //read in the number + (*arr)[arrIndex++] = atoi(&buff[j]); + //seek to the start of next line + while ((j < sz) && (buff[j] != 0x0a)) + j++; + j++; + } + +Cleanup: + if (buff != nullptr) + delete[] buff; + + if (hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); +} + +bool WriteArrayToMCL(char *mclFilename, int *arr, int count) +{ + HANDLE hMCLFile = CreateFileA(mclFilename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + bool result = true; + + if (hMCLFile == INVALID_HANDLE_VALUE) + { + LogError("Failed to open output file '%s'. GetLastError()=%u", mclFilename, GetLastError()); + result = false; + goto Cleanup; + } + + for (int i = 0; i < count; i++) + { + char strMethodIndex[12]; + DWORD charCount = 0; + DWORD bytesWritten = 0; + + charCount = sprintf(strMethodIndex, "%d\r\n", arr[i]); + + if (!WriteFile(hMCLFile, strMethodIndex, charCount, &bytesWritten, nullptr) || (bytesWritten != charCount)) + { + LogError("Failed to write method index '%d'. GetLastError()=%u", strMethodIndex, GetLastError()); + result = false; + goto Cleanup; + } + } + +Cleanup: + if (hMCLFile != INVALID_HANDLE_VALUE) + CloseHandle(hMCLFile); + + return result; +} + +void ProcessChildStdErr(char *stderrFilename) +{ + char buff[MAX_LOG_LINE_SIZE]; + + FILE *fp = fopen(stderrFilename, "r"); + + if (fp == NULL) + { + LogError("Unable to open '%s'.", stderrFilename); + goto Cleanup; + } + + while (fgets(buff, MAX_LOG_LINE_SIZE, fp) != NULL) + { + //get rid of the '\n' at the end of line + size_t buffLen = strlen(buff); + if (buff[buffLen - 1] == '\n') + buff[buffLen - 1] = 0; + + if (strncmp(buff, "ERROR: ", 7) == 0) + LogError("%s", &buff[7]); //log as Error and remove the "ERROR: " in front + else if (strncmp(buff, "WARNING: ", 9) == 0) + LogWarning("%s", &buff[9]); //log as Warning and remove the "WARNING: " in front + else if (strlen(buff) > 0) + LogWarning("%s", buff); //unknown output, log it as a warning + } + +Cleanup: + if (fp != NULL) + fclose(fp); +} + +void ProcessChildStdOut(const CommandLine::Options& o, char *stdoutFilename, int *loaded, int *jitted, int *failed, int *diffs, bool *usageError) +{ + char buff[MAX_LOG_LINE_SIZE]; + + FILE *fp = fopen(stdoutFilename, "r"); + + if (fp == NULL) + { + LogError("Unable to open '%s'.", stdoutFilename); + goto Cleanup; + } + + while (fgets(buff, MAX_LOG_LINE_SIZE, fp) != NULL) + { + //get rid of the '\n' at the end of line + size_t buffLen = strlen(buff); + if (buff[buffLen - 1] == '\n') + buff[buffLen - 1] = 0; + + if (strncmp(buff, "MISSING: ", 9) == 0) + LogMissing("%s", &buff[9]); //log as Missing and remove the "MISSING: " in front + else if (strncmp(buff, "ISSUE: ", 7) == 0) + { + if (strncmp(&buff[7], "<ASM_DIFF> ", 11) == 0) + LogIssue(ISSUE_ASM_DIFF, "%s", &buff[18]); //log as Issue and remove the "ISSUE: <ASM_DIFF>" in front + else if (strncmp(&buff[7], "<ASSERT> ", 9) == 0) + LogIssue(ISSUE_ASSERT, "%s", &buff[16]); //log as Issue and remove the "ISSUE: <ASSERT>" in front + } + else if (strncmp(buff, g_SuperPMIUsageFirstLine, strlen(g_SuperPMIUsageFirstLine)) == 0) + { + *usageError = true; //Signals that we had a SuperPMI command line usage error + + //Read the entire stdout file and printf it + printf("%s", buff); + while (fgets(buff, MAX_LOG_LINE_SIZE, fp) != NULL) + { + printf("%s", buff); + } + break; + } + else if (strncmp(buff, g_AllFormatStringFixedPrefix, strlen(g_AllFormatStringFixedPrefix)) == 0) + { + if (o.applyDiff) + { + int temp1 = 0, temp2 = 0, temp3 = 0, temp4 = 0; + int converted = sscanf(buff, g_AsmDiffsSummaryFormatString, &temp1, &temp2, &temp3, &temp4); + if (converted != 4) + { + LogError("Couldn't parse status message: \"%s\"", buff); + } + else + { + *loaded += temp1; + *jitted += temp2; + *failed += temp3; + *diffs += temp4; + } + } + else + { + int temp1 = 0, temp2 = 0, temp3 = 0; + int converted = sscanf(buff, g_SummaryFormatString, &temp1, &temp2, &temp3); + if (converted != 3) + { + LogError("Couldn't parse status message: \"%s\"", buff); + } + else + { + *loaded += temp1; + *jitted += temp2; + *failed += temp3; + *diffs = -1; + } + } + } + } + +Cleanup: + if (fp != NULL) + fclose(fp); +} + +#ifndef FEATURE_PAL // TODO-Porting: handle Ctrl-C signals gracefully on Unix +BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) +{ + //Since the child SuperPMI.exe processes share the same console + //We don't need to kill them individually as they also receive the Ctrl-C + + closeRequested = true; //set a flag to indicate we need to quit + return TRUE; +} +#endif // !FEATURE_PAL + +int __cdecl compareInt(const void *arg1, const void *arg2) +{ + return (*(const int *)arg1) - (*(const int *)arg2); +} + +// 'arrWorkerMCLPath' is an array of strings of size 'workerCount'. +void MergeWorkerMCLs(char *mclFilename, char **arrWorkerMCLPath, int workerCount) +{ + int **MCL = new int*[workerCount], *MCLCount = new int[workerCount], totalCount = 0; + + for (int i = 0; i < workerCount; i++) + { + //Read the next partial MCL file + ReadMCLToArray(arrWorkerMCLPath[i], &MCL[i], &MCLCount[i]); + + totalCount += MCLCount[i]; + } + + int *mergedMCL = new int[totalCount]; + int index = 0; + + for (int i = 0; i < workerCount; i++) + { + for (int j = 0; j < MCLCount[i]; j++) + mergedMCL[index++] = MCL[i][j]; + } + + qsort(mergedMCL, totalCount, sizeof(int), compareInt); + + //Write the merged MCL array back to disk + if (!WriteArrayToMCL(mclFilename, mergedMCL, totalCount)) + LogError("Unable to write to MCL file %s.", mclFilename); +} + +// From the arguments that we parsed, construct the arguments to pass to the child processes. +#define MAX_CMDLINE_SIZE 0x1000 //4 KB +char* ConstructChildProcessArgs(const CommandLine::Options& o) +{ + int bytesWritten = 0; + char* spmiArgs = new char[MAX_CMDLINE_SIZE]; + *spmiArgs = '\0'; + + // We don't pass through /parallel, /skipCleanup, /verbosity, /failingMCList, or /diffMCList. Everything else we need to reconstruct and pass through. + +#define ADDSTRING(s) if (s != nullptr) { bytesWritten += sprintf_s(spmiArgs + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " %s", s); } +#define ADDARG_BOOL(b,arg) if (b) { bytesWritten += sprintf_s(spmiArgs + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " %s", arg); } +#define ADDARG_STRING(s,arg) if (s != nullptr) { bytesWritten += sprintf_s(spmiArgs + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " %s %s", arg, s); } + + ADDARG_BOOL(o.breakOnError, "-boe"); + ADDARG_BOOL(o.breakOnAssert, "-boa"); + ADDARG_BOOL(o.applyDiff, "-applyDiff"); + ADDARG_STRING(o.reproName, "-reproName"); + ADDARG_STRING(o.writeLogFile, "-writeLogFile"); + ADDARG_STRING(o.methodStatsTypes, "-emitMethodStats"); + ADDARG_STRING(o.reproName, "-reproName"); + ADDARG_STRING(o.hash, "-matchHash"); + ADDARG_STRING(o.targetArchitecture, "-target"); + ADDARG_STRING(o.compileList, "-compile"); + + ADDSTRING(o.nameOfJit); + ADDSTRING(o.nameOfJit2); + ADDSTRING(o.nameOfInputMethodContextFile); + +#undef ADDSTRING +#undef ADDARG_BOOL +#undef ADDARG_STRING + + return spmiArgs; +} + +int doParallelSuperPMI(CommandLine::Options& o) +{ + HRESULT hr = E_FAIL; + SimpleTimer st; + st.Start(); + +#ifndef FEATURE_PAL // TODO-Porting: handle Ctrl-C signals gracefully on Unix + //Register a ConsoleCtrlHandler + if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) + { + LogError("Failed to set control handler."); + return 1; + } +#endif // !FEATURE_PAL + + char tempPath[MAX_PATH]; + if (!GetTempPath(MAX_PATH, tempPath)) + { + LogError("Failed to get path to temp folder."); + return 1; + } + + if (o.workerCount <= 0) + { + //Use the default value which is the number of processors on the machine. + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + + o.workerCount = sysinfo.dwNumberOfProcessors; + + //If we ever execute on a machine which has more than MAXIMUM_WAIT_OBJECTS(64) CPU cores + //we still can't spawn more than the max supported by WaitForMultipleObjects() + if (o.workerCount > MAXIMUM_WAIT_OBJECTS) + o.workerCount = MAXIMUM_WAIT_OBJECTS; + } + + // Obtain the folder path of the current executable, which we will use to spawn ourself. + char* spmiFilename = new char[MAX_PATH]; + if (!GetModuleFileName(NULL, spmiFilename, MAX_PATH)) + { + LogError("Failed to get current exe path."); + return 1; + } + + char* spmiArgs = ConstructChildProcessArgs(o); + + // TODO: merge all this output to a single call to LogVerbose to avoid all the newlines. + LogVerbose("Using child (%s) with args (%s)", spmiFilename, spmiArgs); + if (o.mclFilename != nullptr) + LogVerbose(" failingMCList=%s", o.mclFilename); + if (o.diffMCLFilename != nullptr) + LogVerbose(" diffMCLFilename=%s", o.diffMCLFilename); + LogVerbose(" workerCount=%d, skipCleanup=%d.", o.workerCount, o.skipCleanup); + + HANDLE *hProcesses = new HANDLE[o.workerCount]; + HANDLE *hStdOutput = new HANDLE[o.workerCount]; + HANDLE *hStdError = new HANDLE[o.workerCount]; + + char** arrFailingMCListPath = new char*[o.workerCount]; + char** arrDiffMCListPath = new char*[o.workerCount]; + char** arrStdOutputPath = new char*[o.workerCount]; + char** arrStdErrorPath = new char*[o.workerCount]; + + // Add a random number to the temporary file names to allow multiple parallel SuperPMI to happen at once. + unsigned int randNumber = 0; +#ifdef FEATURE_PAL + PAL_Random(/* bStrong */ FALSE, &randNumber, sizeof(randNumber)); +#else // !FEATURE_PAL + rand_s(&randNumber); +#endif // !FEATURE_PAL + + for (int i = 0; i < o.workerCount; i++) + { + if (o.mclFilename != nullptr) + { + arrFailingMCListPath[i] = new char[MAX_PATH]; + sprintf_s(arrFailingMCListPath[i], MAX_PATH, "%sParallelSuperPMI-%u-%d.mcl", tempPath, randNumber, i); + } + else + { + arrFailingMCListPath[i] = nullptr; + } + + if (o.diffMCLFilename != nullptr) + { + arrDiffMCListPath[i] = new char[MAX_PATH]; + sprintf_s(arrDiffMCListPath[i], MAX_PATH, "%sParallelSuperPMI-Diff-%u-%d.mcl", tempPath, randNumber, i); + } + else + { + arrDiffMCListPath[i] = nullptr; + } + + arrStdOutputPath[i] = new char[MAX_PATH]; + arrStdErrorPath[i] = new char[MAX_PATH]; + + sprintf_s(arrStdOutputPath[i], MAX_PATH, "%sParallelSuperPMI-stdout-%u-%d.txt", tempPath, randNumber, i); + sprintf_s(arrStdErrorPath[i], MAX_PATH, "%sParallelSuperPMI-stderr-%u-%d.txt", tempPath, randNumber, i); + } + + char cmdLine[MAX_CMDLINE_SIZE]; + cmdLine[0] = '\0'; + int bytesWritten; + + for (int i = 0; i < o.workerCount; i++) + { + bytesWritten = sprintf_s(cmdLine, MAX_CMDLINE_SIZE, "%s -stride %d %d", spmiFilename, i + 1, o.workerCount); + + if (o.mclFilename != nullptr) + { + bytesWritten += sprintf_s(cmdLine + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " -failingMCList %s", arrFailingMCListPath[i]); + } + + if (o.diffMCLFilename != nullptr) + { + bytesWritten += sprintf_s(cmdLine + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " -diffMCList %s", arrDiffMCListPath[i]); + } + + bytesWritten += sprintf_s(cmdLine + bytesWritten, MAX_CMDLINE_SIZE - bytesWritten, " -v ewmin %s", spmiArgs); + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; // Let newly created stdout/stderr handles be inherited. + + LogDebug("stdout %i=%s", i, arrStdOutputPath[i]); + hStdOutput[i] = CreateFileA(arrStdOutputPath[i], GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hStdOutput[i] == INVALID_HANDLE_VALUE) + { + LogError("Unable to open '%s'. GetLastError()=%u", arrStdOutputPath[i], GetLastError()); + return -1; + } + + LogDebug("stderr %i=%s", i, arrStdErrorPath[i]); + hStdError[i] = CreateFileA(arrStdErrorPath[i], GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hStdError[i] == INVALID_HANDLE_VALUE) + { + LogError("Unable to open '%s'. GetLastError()=%u", arrStdErrorPath[i], GetLastError()); + return -1; + } + + //Create a SuperPMI worker process and redirect its output to file + if (!StartProcess(cmdLine, hStdOutput[i], hStdError[i], &hProcesses[i])) + { + return -1; + } + } + + WaitForMultipleObjects(o.workerCount, hProcesses, true, INFINITE); + + // Close stdout/stderr + for (int i = 0; i < o.workerCount; i++) + { + CloseHandle(hStdOutput[i]); + CloseHandle(hStdError[i]); + } + + DWORD exitCode = 0; // 0 == assume success + + if (!closeRequested) + { + // Figure out the error code to use. We use the largest magnitude error code of the children. + // Mainly, if any child returns non-zero, we want to return non-zero, to indicate failure. + for (int i = 0; i < o.workerCount; i++) + { + DWORD exitCodeTmp; + BOOL ok = GetExitCodeProcess(hProcesses[i], &exitCodeTmp); + if (ok && (exitCodeTmp > exitCode)) + { + exitCode = exitCodeTmp; + } + } + + bool usageError = false; //variable to flag if we hit a usage error in SuperPMI + + int loaded = 0, jitted = 0, failed = 0, diffs = 0; + + //Read the stderr files and log them as errors + //Read the stdout files and parse them for counts and log any MISSING or ISSUE errors + for (int i = 0; i < o.workerCount; i++) + { + ProcessChildStdErr(arrStdErrorPath[i]); + ProcessChildStdOut(o, arrStdOutputPath[i], &loaded, &jitted, &failed, &diffs, &usageError); + if (usageError) + break; + } + + if (o.mclFilename != nullptr && !usageError) + { + //Concat the resulting .mcl files + MergeWorkerMCLs(o.mclFilename, arrFailingMCListPath, o.workerCount); + } + + if (o.diffMCLFilename != nullptr && !usageError) + { + //Concat the resulting diff .mcl files + MergeWorkerMCLs(o.diffMCLFilename, arrDiffMCListPath, o.workerCount); + } + + if (!usageError) + { + if (o.applyDiff) + { + LogInfo(g_AsmDiffsSummaryFormatString, loaded, jitted, failed, diffs); + } + else + { + LogInfo(g_SummaryFormatString, loaded, jitted, failed); + } + } + + st.Stop(); + LogVerbose("Total time: %fms", st.GetMilliseconds()); + } + + if (!o.skipCleanup) + { + // Delete all temporary files generated + for (int i = 0; i < o.workerCount; i++) + { + if (arrFailingMCListPath[i] != nullptr) + { + DeleteFile(arrFailingMCListPath[i]); + } + if (arrDiffMCListPath[i] != nullptr) + { + DeleteFile(arrDiffMCListPath[i]); + } + DeleteFile(arrStdOutputPath[i]); + DeleteFile(arrStdErrorPath[i]); + } + } + + return (int)exitCode; +} diff --git a/src/ToolBox/superpmi/superpmi/superpmi.cpp b/src/ToolBox/superpmi/superpmi/superpmi.cpp new file mode 100644 index 0000000000..ce352070f8 --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/superpmi.cpp @@ -0,0 +1,564 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#include "standardpch.h" + +#ifdef USE_COREDISTOOLS +#include "coredistools.h" +#endif // USE_COREDISTOOLS + +#include "commandline.h" +#include "superpmi.h" +#include "jitinstance.h" +#include "neardiffer.h" +#include "simpletimer.h" +#include "methodcontext.h" +#include "methodcontextreader.h" +#include "mclist.h" +#include "methodstatsemitter.h" + +extern int doParallelSuperPMI(CommandLine::Options& o); + +// NOTE: these output status strings are parsed by parallelsuperpmi.cpp::ProcessChildStdOut(). +// There must be a single, fixed prefix common to all strings, to ease the determination of when +// to parse the string fully. +const char* const g_AllFormatStringFixedPrefix = "Loaded "; +const char* const g_SummaryFormatString = "Loaded %d Jitted %d FailedCompile %d"; +const char* const g_AsmDiffsSummaryFormatString = "Loaded %d Jitted %d FailedCompile %d Diffs %d"; + +//#define SuperPMI_ChewMemory 0x7FFFFFFF //Amount of address space to consume on startup + +SPMI_TARGET_ARCHITECTURE SpmiTargetArchitecture; + +void SetSuperPmiTargetArchitecture(const char* targetArchitecture) +{ +#ifdef _TARGET_AMD64_ + if ((targetArchitecture != nullptr) && (0 == _stricmp(targetArchitecture, "arm64"))) + { + SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTURE_ARM64; + } + else + { + SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTURE_AMD64; + } +#elif defined(_TARGET_X86_) + SpmiTargetArchitecture = SPMI_TARGET_ARCHITECTURE_X86; +#endif +} + +// This function uses PAL_TRY, so it can't be in the a function that requires object unwinding. Extracting it out here +// avoids compiler error. +// +void InvokeNearDiffer( + NearDiffer* nearDiffer, + CommandLine::Options* o, + MethodContext** mc, + CompileResult** crl, + int* matchCount, + MethodContextReader** reader, + MCList* failingMCL, + MCList* diffMCL + ) +{ + struct Param : FilterSuperPMIExceptionsParam_CaptureException + { + NearDiffer* nearDiffer; + CommandLine::Options* o; + MethodContext** mc; + CompileResult** crl; + int* matchCount; + MethodContextReader** reader; + MCList* failingMCL; + MCList* diffMCL; + } param; + param.nearDiffer = nearDiffer; + param.o = o; + param.mc = mc; + param.crl = crl; + param.matchCount = matchCount; + param.reader = reader; + param.failingMCL = failingMCL; + param.diffMCL = diffMCL; + + PAL_TRY(Param*, pParam, ¶m) + { + if (pParam->nearDiffer->compare(*pParam->mc, *pParam->crl, (*pParam->mc)->cr)) + { + (*pParam->matchCount)++; + } + else + { + LogIssue(ISSUE_ASM_DIFF, + "main method %d of size %d differs", (*pParam->reader)->GetMethodContextIndex(), (*pParam->mc)->methodSize); + + //This is a difference in ASM outputs from Jit1 & Jit2 and not a playback failure + //We will add this MC to the diffMCList if one is requested + //Otherwise this will end up in failingMCList + if ((*pParam->o).diffMCLFilename != nullptr) + (*pParam->diffMCL).AddMethodToMCL((*pParam->reader)->GetMethodContextIndex()); + else if ((*pParam->o).mclFilename != nullptr) + (*pParam->failingMCL).AddMethodToMCL((*pParam->reader)->GetMethodContextIndex()); + } + } + PAL_EXCEPT_FILTER(FilterSuperPMIExceptions_CaptureExceptionAndStop) + { + SpmiException e(¶m.exceptionPointers); + + LogError("main method %d of size %d failed to load and compile correctly. EnvCnt=%d", + (*reader)->GetMethodContextIndex(), (*mc)->methodSize, (*mc)->repEnvironmentGetCount()); + e.ShowAndDeleteMessage(); + if ((*o).mclFilename != nullptr) + (*failingMCL).AddMethodToMCL((*reader)->GetMethodContextIndex()); + } + PAL_ENDTRY +} + +// Run superpmi. The return value is as follows: +// 0 : success +// -1 : general fatal error (e.g., failed to initialize, failed to read files) +// -2 : JIT failed to initialize +// 1 : there were compilation failures +// 2 : there were asm diffs +int __cdecl main(int argc, char* argv[]) +{ +#ifdef FEATURE_PAL + if (0 != PAL_Initialize(argc, argv)) + { + fprintf(stderr, "Error: Fail to PAL_Initialize\n"); + return -1; + } +#endif // FEATURE_PAL + + Logger::Initialize(); + + SimpleTimer st1; + SimpleTimer st2; + SimpleTimer st3; + SimpleTimer st4; + st2.Start(); + JitInstance::Result res, res2; + HRESULT hr = E_FAIL; + MethodContext *mc = nullptr; + JitInstance *jit = nullptr, *jit2 = nullptr; + MethodStatsEmitter *methodStatsEmitter = nullptr; + +#ifdef SuperPMI_ChewMemory + //Chew up the base 2gb of memory on x86... helpful in finding any places where classhandles etc are de-ref'd + SYSTEM_INFO sSysInfo; + GetSystemInfo(&sSysInfo); + + LPVOID lpvAddr; +#undef VirtualAlloc + do + { + lpvAddr = VirtualAlloc(NULL, sSysInfo.dwPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS); + } while ((size_t)lpvAddr < SuperPMI_ChewMemory); +#endif + + bool collectThroughput = false; + MCList failingMCL, diffMCL; + + CommandLine::Options o; + if (!CommandLine::Parse(argc, argv, &o)) + { + return -1; + } + + if (o.parallel) + { + return doParallelSuperPMI(o); + } + + SetSuperPmiTargetArchitecture(o.targetArchitecture); + + if (o.methodStatsTypes != NULL && (strchr(o.methodStatsTypes, '*') != NULL || strchr(o.methodStatsTypes, 't') != NULL || strchr(o.methodStatsTypes, 'T') != NULL)) + { + collectThroughput = true; + } + + LogVerbose("Using jit(%s) with input (%s)", o.nameOfJit, o.nameOfInputMethodContextFile); + std::string indexesStr = " indexCount="; + indexesStr += std::to_string(o.indexCount); + indexesStr += " ("; + for (int i = 0; i < o.indexCount; i++) + { + indexesStr += std::to_string(o.indexes[i]); + if (i < (o.indexCount - 1)) + indexesStr += ","; + } + indexesStr += ")"; + LogVerbose(indexesStr.c_str()); + + if (o.methodStatsTypes != nullptr) + LogVerbose(" EmitMethodStats-Types=%s", o.methodStatsTypes); + + if (o.hash != nullptr) + LogVerbose(" MD5Hash=%s", o.hash); + + if (o.mclFilename != nullptr) + LogVerbose(" failingMCList=%s", o.mclFilename); + + if (o.offset > 0 && o.increment > 0) + LogVerbose(" offset=%d increment=%d", o.offset, o.increment); + + if (o.methodStatsTypes != nullptr) + { + methodStatsEmitter = new MethodStatsEmitter(o.nameOfInputMethodContextFile); + methodStatsEmitter->SetStatsTypes(o.methodStatsTypes); + } + + if (o.mclFilename != nullptr) + { + failingMCL.InitializeMCL(o.mclFilename); + } + if (o.diffMCLFilename != nullptr) + { + diffMCL.InitializeMCL(o.diffMCLFilename); + } + + // The method context reader handles skipping any unrequested method contexts + // Used in conjunction with an MCI file, it does a lot less work... + MethodContextReader *reader = new MethodContextReader(o.nameOfInputMethodContextFile, o.indexes, o.indexCount, o.hash, o.offset, o.increment); + if (!reader->isValid()) + { + return -1; + } + + int loadedCount = 0; + int jittedCount = 0; + int matchCount = 0; + int failCount = 0; + int index = 0; + + st1.Start(); + NearDiffer nearDiffer(o.targetArchitecture, o.useCoreDisTools); + + if (o.applyDiff) + { + nearDiffer.InitAsmDiff(); + } + + while (true) + { + MethodContextBuffer mcb = reader->GetNextMethodContext(); + if (mcb.Error()) + { + return -1; + } + else if (mcb.allDone()) + { + LogDebug("Done processing method contexts"); + break; + } + if ((loadedCount % 500 == 0) && (loadedCount > 0)) + { + st1.Stop(); + if (o.applyDiff) + { + LogVerbose(" %2.1f%% - Loaded %d Jitted %d Matching %d FailedCompile %d at %d per second", + reader->PercentComplete(), + loadedCount, + jittedCount, + matchCount, + failCount, + (int)((double)500 / st1.GetSeconds())); + } + else + { + LogVerbose(" %2.1f%% - Loaded %d Jitted %d FailedCompile %d at %d per second", + reader->PercentComplete(), + loadedCount, + jittedCount, + failCount, + (int)((double)500 / st1.GetSeconds())); + } + st1.Start(); + } + + // Now read the data into a MethodContext. This could throw if the method context data is corrupt. + + loadedCount++; + if (!MethodContext::Initialize(loadedCount, mcb.buff, mcb.size, &mc)) + return -1; + + if (jit == nullptr) + { + SimpleTimer st4; + + jit = JitInstance::InitJit(o.nameOfJit, o.breakOnAssert, &st4, mc); + if (jit == nullptr) + { + // InitJit already printed a failure message + return -2; + } + + if (o.nameOfJit2 != nullptr) + { + jit2 = JitInstance::InitJit(o.nameOfJit2, o.breakOnAssert, &st4, mc); + if (jit2 == nullptr) + { + // InitJit already printed a failure message + return -2; + } + } + } + + // I needed to reason about what crl contains at any point in time + // Here is my guess based on reading the code so far + // crl initially contains the CompileResult from the MCH file + // However if we have a second jit it has the CompileResult from Jit1 + CompileResult *crl = mc->cr; + + mc->cr = new CompileResult(); + mc->originalCR = crl; + + jittedCount++; + st3.Start(); + res = jit->CompileMethod(mc, reader->GetMethodContextIndex(), collectThroughput); + st3.Stop(); + LogDebug("Method %d compiled in %fms, result %d", reader->GetMethodContextIndex(), st3.GetMilliseconds(), res); + + if ((res == JitInstance::RESULT_SUCCESS) && Logger::IsLogLevelEnabled(LOGLEVEL_DEBUG)) + { + mc->cr->dumpToConsole(); // Dump the compile results if doing debug logging + } + + if (o.nameOfJit2 != nullptr) + { + // Lets get the results for the 2nd JIT + // We will save the first JIT's CR to save space for the 2nd JIT CR + // Note that the recorded CR is still stored in MC->originalCR + crl = mc->cr; + mc->cr = new CompileResult(); + + st4.Start(); + res2 = jit2->CompileMethod(mc, reader->GetMethodContextIndex(), collectThroughput); + st4.Stop(); + LogDebug("Method %d compiled by JIT2 in %fms, result %d", reader->GetMethodContextIndex(), st4.GetMilliseconds(), res2); + + if ((res2 == JitInstance::RESULT_SUCCESS) && Logger::IsLogLevelEnabled(LOGLEVEL_DEBUG)) + { + mc->cr->dumpToConsole(); // Dump the compile results if doing debug logging + } + + if (res2 == JitInstance::RESULT_ERROR) + { + LogError("JIT2 main method %d of size %d failed to load and compile correctly. EnvCnt=%d", + reader->GetMethodContextIndex(), mc->methodSize, mc->repEnvironmentGetCount()); + } + + // Methods that don't compile due to missing JIT-EE information + // should still be added to the failing MC list. + // However, we will not add this MC# if JIT1 also failed, Else there will be duplicate logging + if ((res == JitInstance::RESULT_SUCCESS) && + (res2 != JitInstance::RESULT_SUCCESS) && + (o.mclFilename != nullptr)) + { + failingMCL.AddMethodToMCL(reader->GetMethodContextIndex()); + } + } + + if (res == JitInstance::RESULT_SUCCESS) + { + if (collectThroughput) + { + if (o.nameOfJit2 != nullptr && res2 == JitInstance::RESULT_SUCCESS) + { + //TODO-Bug?: bug in getting the lowest cycle time?? + ULONGLONG dif1, dif2, dif3, dif4; + dif1 = (jit->times[0] - jit2->times[0]) * (jit->times[0] - jit2->times[0]); + dif2 = (jit->times[0] - jit2->times[1]) * (jit->times[0] - jit2->times[1]); + dif3 = (jit->times[1] - jit2->times[0]) * (jit->times[1] - jit2->times[0]); + dif4 = (jit->times[1] - jit2->times[1]) * (jit->times[1] - jit2->times[1]); + + if (dif1 < dif2) + { + if (dif3 < dif4) + { + if (dif1 < dif3) + { + crl->clockCyclesToCompile = jit->times[0]; + mc->cr->clockCyclesToCompile = jit2->times[0]; + } + else + { + crl->clockCyclesToCompile = jit->times[1]; + mc->cr->clockCyclesToCompile = jit2->times[0]; + } + } + else + { + if (dif1 < dif4) + { + crl->clockCyclesToCompile = jit->times[0]; + mc->cr->clockCyclesToCompile = jit2->times[0]; + } + else + { + crl->clockCyclesToCompile = jit->times[1]; + mc->cr->clockCyclesToCompile = jit2->times[1]; + } + } + } + else + { + if (dif3 < dif4) + { + if (dif2 < dif3) + { + crl->clockCyclesToCompile = jit->times[0]; + mc->cr->clockCyclesToCompile = jit2->times[1]; + } + else + { + crl->clockCyclesToCompile = jit->times[1]; + mc->cr->clockCyclesToCompile = jit2->times[0]; + } + } + else + { + if (dif2 < dif4) + { + crl->clockCyclesToCompile = jit->times[0]; + mc->cr->clockCyclesToCompile = jit2->times[1]; + } + else + { + crl->clockCyclesToCompile = jit->times[1]; + mc->cr->clockCyclesToCompile = jit2->times[1]; + } + } + } + + if (methodStatsEmitter != nullptr) + { + methodStatsEmitter->Emit(reader->GetMethodContextIndex(), mc, crl->clockCyclesToCompile, mc->cr->clockCyclesToCompile); + } + } + else + { + if (jit->times[0] > jit->times[1]) + mc->cr->clockCyclesToCompile = jit->times[1]; + else + mc->cr->clockCyclesToCompile = jit->times[0]; + if (methodStatsEmitter != nullptr) + { + methodStatsEmitter->Emit(reader->GetMethodContextIndex(), mc, mc->cr->clockCyclesToCompile, 0); + } + } + } + + if (!collectThroughput && methodStatsEmitter != nullptr) + { + //We have a separate call to Emit for collectThroughput + methodStatsEmitter->Emit(reader->GetMethodContextIndex(), mc, -1, -1); + } + + if (o.applyDiff) + { + // We need at least two compile results to diff: they can either both come from JIT + // invocations, or one can be loaded from the method context file. + + // We need to check both CompileResults to ensure we have a valid CR + if (crl->AllocMem == nullptr || mc->cr->AllocMem == nullptr) + { + LogError("method %d is missing a compileResult, cannot do diffing", reader->GetMethodContextIndex()); + + // If we are here this means that either we have 2 Jits and the second Jit failed to compile + // Or we have single Jit and the MethodContext doesn't have an originalCR + // In both cases we don't need to add this to the MCList again + } + else + { + InvokeNearDiffer(&nearDiffer, &o, &mc, &crl, &matchCount, &reader, &failingMCL, &diffMCL); + } + } + } + else + { + failCount++; + + if (o.mclFilename != nullptr) + failingMCL.AddMethodToMCL(reader->GetMethodContextIndex()); + + // The following only apply specifically to failures caused by errors (as opposed + // to, for instance, failures caused by missing JIT-EE details). + if (res == JitInstance::RESULT_ERROR) + { + LogError("main method %d of size %d failed to load and compile correctly. EnvCnt=%d", reader->GetMethodContextIndex(), mc->methodSize, mc->repEnvironmentGetCount()); + if ((o.reproName != nullptr) && (o.indexCount == -1)) + { + char buff[500]; + sprintf_s(buff, 500, "%s-%d.mc", o.reproName, reader->GetMethodContextIndex()); + HANDLE hFileOut = CreateFileA(buff, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (hFileOut == INVALID_HANDLE_VALUE) + { + LogError("Failed to open output '%s'. GetLastError()=%u", buff, GetLastError()); + return -1; + } + mc->saveToFile(hFileOut); + if (CloseHandle(hFileOut) == 0) + { + LogError("CloseHandle for output file failed. GetLastError()=%u", GetLastError()); + return -1; + } + LogInfo("Wrote out repro to '%s'", buff); + } + if (o.breakOnError) + { + if (o.indexCount == -1) + LogInfo("HINT: to repro add '/c %d' to cmdline", reader->GetMethodContextIndex()); + __debugbreak(); + } + } + } + + delete crl; + delete mc; + } + delete reader; + + int result = 0; + + // NOTE: these output status strings are parsed by parallelsuperpmi.cpp::ProcessChildStdOut(). + if (o.applyDiff) + { + LogInfo(g_AsmDiffsSummaryFormatString, loadedCount, jittedCount, failCount, jittedCount - failCount - matchCount); + + if (matchCount != jittedCount) + { + result = 2; + } + } + else + { + LogInfo(g_SummaryFormatString, loadedCount, jittedCount, failCount); + } + + // Failure to JIT overrides diffs for the error code. + if (failCount > 0) + { + result = 1; + } + + st2.Stop(); + LogVerbose("Total time: %fms", st2.GetMilliseconds()); + + if (methodStatsEmitter != nullptr) + { + delete methodStatsEmitter; + } + + if (o.mclFilename != nullptr) + { + failingMCL.CloseMCL(); + } + if (o.diffMCLFilename != nullptr) + { + diffMCL.CloseMCL(); + } + Logger::Shutdown(); + return result; +} diff --git a/src/ToolBox/superpmi/superpmi/superpmi.h b/src/ToolBox/superpmi/superpmi/superpmi.h new file mode 100644 index 0000000000..d5b7bdaa2b --- /dev/null +++ b/src/ToolBox/superpmi/superpmi/superpmi.h @@ -0,0 +1,27 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _SuperPMI +#define _SuperPMI + +#include "errorhandling.h" + +enum SPMI_TARGET_ARCHITECTURE +{ + SPMI_TARGET_ARCHITECTURE_X86, + SPMI_TARGET_ARCHITECTURE_AMD64, + SPMI_TARGET_ARCHITECTURE_ARM64 +}; + +extern SPMI_TARGET_ARCHITECTURE SpmiTargetArchitecture; +extern void SetSuperPmiTargetArchitecture(const char* targetArchitecture); + +extern const char* const g_SuperPMIUsageFirstLine; + +extern const char* const g_AllFormatStringFixedPrefix; +extern const char* const g_SummaryFormatString; +extern const char* const g_AsmDiffsSummaryFormatString; + +#endif |