Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| //------------------------------------------------------------------------------------------------------- | |
| // Copyright (C) Microsoft Corporation and contributors. All rights reserved. | |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. | |
| //------------------------------------------------------------------------------------------------------- | |
| #include "Backend.h" | |
| #include "ExternalHelperMethod.h" | |
| // Parser includes | |
| #include "RegexCommon.h" | |
| #include "Library/RegexHelper.h" | |
| #ifdef ENABLE_SCRIPT_DEBUGGING | |
| #include "Debug/DiagHelperMethodWrapper.h" | |
| #endif | |
| #include "Math/CrtSSE2Math.h" | |
| #include "Library/JavascriptGeneratorFunction.h" | |
| #include "RuntimeMathPch.h" | |
| namespace IR | |
| { | |
| intptr_t const JnHelperMethodAddresses[] = | |
| { | |
| #define HELPERCALL(Name, Address, Attributes) reinterpret_cast<intptr_t>(Address), | |
| // Because of order-of-initialization problems with the vtable address static field | |
| // and this array, we're going to have to fill these in as we go along. | |
| #include "JnHelperMethodList.h" | |
| NULL | |
| }; | |
| intptr_t const *GetHelperMethods() | |
| { | |
| return JnHelperMethodAddresses; | |
| } | |
| #if ENABLE_DEBUG_CONFIG_OPTIONS && defined(_CONTROL_FLOW_GUARD) | |
| class HelperTableCheck | |
| { | |
| public: | |
| HelperTableCheck() { | |
| CheckJnHelperTable(JnHelperMethodAddresses); | |
| } | |
| }; | |
| // Dummy global to trigger CheckJnHelperTable call at load time. | |
| static HelperTableCheck LoadTimeHelperTableCheck; | |
| void CheckJnHelperTable(intptr_t const* table) | |
| { | |
| MEMORY_BASIC_INFORMATION memBuffer; | |
| // Make sure the helper table is in read-only memory for security reasons. | |
| SIZE_T byteCount; | |
| byteCount = VirtualQuery(table, &memBuffer, sizeof(memBuffer)); | |
| Assert(byteCount); | |
| // Note: .rdata is merged with .text on x86. | |
| if (memBuffer.Protect != PAGE_READONLY && memBuffer.Protect != PAGE_EXECUTE_READ) | |
| { | |
| AssertMsg(false, "JnHelperMethodAddress table needs to be read-only for security reasons"); | |
| Fatal(); | |
| } | |
| } | |
| #endif | |
| #ifdef ENABLE_SCRIPT_DEBUGGING | |
| static intptr_t const helperMethodWrappers[] = { | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper0), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper1), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper2), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper3), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper4), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper5), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper6), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper7), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper8), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper9), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper10), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper11), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper12), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper13), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper14), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper15), | |
| reinterpret_cast<intptr_t>(&Js::HelperMethodWrapper16), | |
| }; | |
| #endif | |
| ///---------------------------------------------------------------------------- | |
| /// | |
| /// GetMethodAddress | |
| /// | |
| /// returns the memory address of the helperMethod, | |
| /// which can the address of debugger wrapper that intercept the original helper. | |
| /// | |
| ///---------------------------------------------------------------------------- | |
| intptr_t | |
| GetMethodAddress(ThreadContextInfo * context, IR::HelperCallOpnd* opnd) | |
| { | |
| Assert(opnd); | |
| #ifdef ENABLE_SCRIPT_DEBUGGING | |
| #if defined(_M_ARM32_OR_ARM64) | |
| #define LowererMDFinal LowererMD | |
| #else | |
| #define LowererMDFinal LowererMDArch | |
| #endif | |
| CompileAssert(_countof(helperMethodWrappers) == LowererMDFinal::MaxArgumentsToHelper + 1); | |
| if (opnd->IsDiagHelperCallOpnd()) | |
| { | |
| // Note: all arguments are already loaded for the original helper. Here we just return the address. | |
| IR::DiagHelperCallOpnd* diagOpnd = (IR::DiagHelperCallOpnd*)opnd; | |
| if (0 <= diagOpnd->m_argCount && diagOpnd->m_argCount <= LowererMDFinal::MaxArgumentsToHelper) | |
| { | |
| return ShiftAddr(context, helperMethodWrappers[diagOpnd->m_argCount]); | |
| } | |
| else | |
| { | |
| AssertMsg(FALSE, "Unsupported arg count (need to implement)."); | |
| } | |
| } | |
| #endif | |
| return GetMethodOriginalAddress(context, opnd->m_fnHelper); | |
| } | |
| // TODO: Remove this define once makes it into WINNT.h | |
| #ifndef DECLSPEC_GUARDIGNORE | |
| #if (_MSC_FULL_VER >= 170065501) && !defined(__clang__) | |
| #define DECLSPEC_GUARDIGNORE __declspec(guard(ignore)) | |
| #else | |
| #define DECLSPEC_GUARDIGNORE | |
| #endif | |
| #endif | |
| // We need the helper table to be in read-only memory for obvious security reasons. | |
| // Import function ptr require dynamic initialization, and cause the table to be in read-write memory. | |
| // Additionally, all function ptrs are automatically marked as safe CFG addresses by the compiler. | |
| // __declspec(guard(ignore)) can be used on methods to have the compiler not mark these as valid CFG targets. | |
| DECLSPEC_GUARDIGNORE _NOINLINE intptr_t GetNonTableMethodAddress(ThreadContextInfo * context, JnHelperMethod helperMethod) | |
| { | |
| switch (helperMethod) | |
| { | |
| // | |
| // DllImport methods | |
| // | |
| #if defined(_M_IX86) | |
| // These are internal CRT functions which don't use a standard calling convention | |
| case HelperDirectMath_Acos: | |
| return ShiftAddr(context, __libm_sse2_acos); | |
| case HelperDirectMath_Asin: | |
| return ShiftAddr(context, __libm_sse2_asin); | |
| case HelperDirectMath_Atan: | |
| return ShiftAddr(context, __libm_sse2_atan); | |
| case HelperDirectMath_Atan2: | |
| return ShiftAddr(context, __libm_sse2_atan2); | |
| case HelperDirectMath_Cos: | |
| return ShiftAddr(context, __libm_sse2_cos); | |
| case HelperDirectMath_Exp: | |
| return ShiftAddr(context, __libm_sse2_exp); | |
| case HelperDirectMath_Log: | |
| return ShiftAddr(context, __libm_sse2_log); | |
| case HelperDirectMath_Sin: | |
| return ShiftAddr(context, __libm_sse2_sin); | |
| case HelperDirectMath_Tan: | |
| return ShiftAddr(context, __libm_sse2_tan); | |
| case HelperAtomicStore64: | |
| return ShiftAddr(context, (double(*)(double))InterlockedExchange64); | |
| case HelperMemoryBarrier: | |
| #ifdef _M_HYBRID_X86_ARM64 | |
| AssertOrFailFastMsg(false, "The usage below fails to build for CHPE, and HelperMemoryBarrier is only required " | |
| "for WASM threads, which are currently disabled"); | |
| return 0; | |
| #else | |
| return ShiftAddr(context, (void(*)())MemoryBarrier); | |
| #endif // !_M_HYBRID_X86_ARM64 | |
| #endif | |
| case HelperDirectMath_FloorDb: | |
| return ShiftStdcallAddr(context, Js::JavascriptMath::Floor); | |
| case HelperDirectMath_CeilDb: | |
| return ShiftStdcallAddr(context, Js::JavascriptMath::Ceil); | |
| case HelperDirectMath_FloorFlt: | |
| return ShiftStdcallAddr(context, Js::JavascriptMath::FloorF); | |
| case HelperDirectMath_CeilFlt: | |
| return ShiftStdcallAddr(context, Js::JavascriptMath::CeilF); | |
| // | |
| // These are statically initialized to an import thunk, but let's keep them out of the table in case a new CRT changes this | |
| // | |
| case HelperWMemCmp: | |
| return ShiftCdeclAddr(context, wmemcmp); | |
| case HelperMemCpy: | |
| return ShiftCdeclAddr(context, (void *(__cdecl *)(void *, void const*, size_t))memcpy); | |
| #if defined(_M_X64) || defined(_M_ARM32_OR_ARM64) | |
| case HelperDirectMath_Acos: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))acos); | |
| case HelperDirectMath_Asin: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))asin); | |
| case HelperDirectMath_Atan: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))atan); | |
| case HelperDirectMath_Atan2: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double, double))atan2); | |
| case HelperDirectMath_Cos: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))cos); | |
| case HelperDirectMath_Exp: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))exp); | |
| case HelperDirectMath_Log: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))log); | |
| case HelperDirectMath_Sin: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))sin); | |
| case HelperDirectMath_Tan: | |
| return ShiftCdeclAddr(context, (double(__cdecl *)(double))tan); | |
| #endif | |
| // | |
| // Methods that we don't want to get marked as CFG targets as they make unprotected calls | |
| // | |
| #ifdef _CONTROL_FLOW_GUARD | |
| case HelperGuardCheckCall: | |
| return (intptr_t)__guard_check_icall_fptr; // OOP JIT: ntdll load at same address across all process | |
| #endif | |
| case HelperOp_TryCatch: | |
| return ShiftStdcallAddr(context, Js::JavascriptExceptionOperators::OP_TryCatch); | |
| case HelperOp_TryFinally: | |
| return ShiftStdcallAddr(context, Js::JavascriptExceptionOperators::OP_TryFinally); | |
| case HelperOp_TryFinallyNoOpt: | |
| return ShiftStdcallAddr(context, Js::JavascriptExceptionOperators::OP_TryFinallyNoOpt); | |
| // | |
| // Methods that we don't want to get marked as CFG targets as they dump all registers to a controlled address | |
| // | |
| case HelperSaveAllRegistersAndBailOut: | |
| return ShiftStdcallAddr(context, LinearScanMD::SaveAllRegistersAndBailOut); | |
| case HelperSaveAllRegistersAndBranchBailOut: | |
| return ShiftStdcallAddr(context, LinearScanMD::SaveAllRegistersAndBranchBailOut); | |
| #ifdef _M_IX86 | |
| case HelperSaveAllRegistersNoSse2AndBailOut: | |
| return ShiftStdcallAddr(context, LinearScanMD::SaveAllRegistersNoSse2AndBailOut); | |
| case HelperSaveAllRegistersNoSse2AndBranchBailOut: | |
| return ShiftStdcallAddr(context, LinearScanMD::SaveAllRegistersNoSse2AndBranchBailOut); | |
| #endif | |
| } | |
| Assume(UNREACHED); | |
| return 0; | |
| } | |
| ///---------------------------------------------------------------------------- | |
| /// | |
| /// GetMethodOriginalAddress | |
| /// | |
| /// returns the memory address of the helperMethod, | |
| /// this one is never the intercepted by debugger helper. | |
| /// | |
| ///---------------------------------------------------------------------------- | |
| intptr_t GetMethodOriginalAddress(ThreadContextInfo * context, JnHelperMethod helperMethod) | |
| { | |
| AssertOrFailFast(helperMethod >= 0 && helperMethod < IR::JnHelperMethodCount); | |
| intptr_t address = GetHelperMethods()[static_cast<WORD>(helperMethod)]; | |
| if (address == 0) | |
| { | |
| return GetNonTableMethodAddress(context, helperMethod); | |
| } | |
| return ShiftAddr(context, address); | |
| } | |
| #if DBG_DUMP || defined(ENABLE_IR_VIEWER) | |
| char16 const * const JnHelperMethodNames[] = | |
| { | |
| #define HELPERCALL(Name, Address, Attributes) _u("") STRINGIZEW(Name) _u(""), | |
| #include "JnHelperMethodList.h" | |
| NULL | |
| }; | |
| ///---------------------------------------------------------------------------- | |
| /// | |
| /// GetMethodName | |
| /// | |
| /// returns the string representing the name of the helperMethod. | |
| /// | |
| ///---------------------------------------------------------------------------- | |
| char16 const* | |
| GetMethodName(JnHelperMethod helperMethod) | |
| { | |
| return JnHelperMethodNames[static_cast<WORD>(helperMethod)]; | |
| } | |
| #endif //#if DBG_DUMP | |
| } //namespace IR | |
| #if DBG_DUMP || defined(ENABLE_IR_VIEWER) | |
| const char16 *GetVtableName(VTableValue value) | |
| { | |
| switch (value) | |
| { | |
| #if !defined(_M_X64) | |
| case VtableJavascriptNumber: | |
| return _u("vtable JavascriptNumber"); | |
| break; | |
| #endif | |
| case VtableDynamicObject: | |
| return _u("vtable DynamicObject"); | |
| break; | |
| case VtableInvalid: | |
| return _u("vtable Invalid"); | |
| break; | |
| case VtablePropertyString: | |
| return _u("vtable PropertyString"); | |
| break; | |
| case VtableJavascriptBoolean: | |
| return _u("vtable JavascriptBoolean"); | |
| break; | |
| case VtableJavascriptArray: | |
| return _u("vtable JavascriptArray"); | |
| break; | |
| case VtableInt8Array: | |
| return _u("vtable Int8Array"); | |
| break; | |
| case VtableUint8Array: | |
| return _u("vtable Uint8Array"); | |
| break; | |
| case VtableUint8ClampedArray: | |
| return _u("vtable Uint8ClampedArray"); | |
| break; | |
| case VtableInt16Array: | |
| return _u("vtable Int16Array"); | |
| break; | |
| case VtableUint16Array: | |
| return _u("vtable Uint16Array"); | |
| break; | |
| case VtableInt32Array: | |
| return _u("vtable Int32Array"); | |
| break; | |
| case VtableUint32Array: | |
| return _u("vtable Uint32Array"); | |
| break; | |
| case VtableFloat32Array: | |
| return _u("vtable Float32Array"); | |
| break; | |
| case VtableFloat64Array: | |
| return _u("vtable Float64Array"); | |
| break; | |
| case VtableJavascriptPixelArray: | |
| return _u("vtable JavascriptPixelArray"); | |
| break; | |
| case VtableInt64Array: | |
| return _u("vtable Int64Array"); | |
| break; | |
| case VtableUint64Array: | |
| return _u("vtable Uint64Array"); | |
| break; | |
| case VtableInt8VirtualArray: | |
| return _u("vtable Int8VirtualArray"); | |
| break; | |
| case VtableUint8VirtualArray: | |
| return _u("vtable Uint8VirtualArray"); | |
| break; | |
| case VtableUint8ClampedVirtualArray: | |
| return _u("vtable Uint8ClampedVirtualArray"); | |
| break; | |
| case VtableInt16VirtualArray: | |
| return _u("vtable Int16VirtualArray"); | |
| break; | |
| case VtableUint16VirtualArray: | |
| return _u("vtable Uint16VirtualArray"); | |
| break; | |
| case VtableInt32VirtualArray: | |
| return _u("vtable Int32VirtualArray"); | |
| break; | |
| case VtableUint32VirtualArray: | |
| return _u("vtable Uint32VirtualArray"); | |
| break; | |
| case VtableFloat32VirtualArray: | |
| return _u("vtable Float32VirtualArray"); | |
| break; | |
| case VtableFloat64VirtualArray: | |
| return _u("vtable Float64VirtualArray"); | |
| break; | |
| case VtableBoolArray: | |
| return _u("vtable BoolArray"); | |
| break; | |
| case VtableCharArray: | |
| return _u("vtable CharArray"); | |
| break; | |
| case VtableNativeIntArray: | |
| return _u("vtable NativeIntArray"); | |
| break; | |
| case VtableNativeFloatArray: | |
| return _u("vtable NativeFloatArray"); | |
| break; | |
| case VtableJavascriptNativeIntArray: | |
| return _u("vtable JavascriptNativeIntArray"); | |
| break; | |
| case VtableJavascriptRegExp: | |
| return _u("vtable JavascriptRegExp"); | |
| break; | |
| case VtableStackScriptFunction: | |
| return _u("vtable StackScriptFunction"); | |
| break; | |
| case VtableScriptFunctionWithInlineCacheAndHomeObj: | |
| return _u("vtable ScriptFunctionWithInlineCacheAndHomeObj"); | |
| break; | |
| case VtableScriptFunctionWithInlineCacheHomeObjAndComputedName: | |
| return _u("vtable ScriptFunctionWithInlineCacheHomeObjAndComputedName"); | |
| break; | |
| case VtableConcatStringMulti: | |
| return _u("vtable ConcatStringMulti"); | |
| break; | |
| case VtableCompoundString: | |
| return _u("vtable CompoundString"); | |
| break; | |
| default: | |
| Assert(false); | |
| break; | |
| } | |
| return _u("vtable unknown"); | |
| } | |
| #endif | |
| namespace HelperMethodAttributes | |
| { | |
| // Position: same as in JnHelperMethod enum. | |
| // Value: one or more of OR'ed HelperMethodAttribute values. | |
| static const BYTE JnHelperMethodAttributes[] = | |
| { | |
| #define HELPERCALL(Name, Address, Attributes) Attributes, | |
| #include "JnHelperMethodList.h" | |
| }; | |
| // Returns true if the helper can throw non-OOM / non-SO exception. | |
| bool CanThrow(IR::JnHelperMethod helper) | |
| { | |
| return (JnHelperMethodAttributes[helper] & AttrCanThrow) != 0; | |
| } | |
| bool IsInVariant(IR::JnHelperMethod helper) | |
| { | |
| return (JnHelperMethodAttributes[helper] & AttrInVariant) != 0; | |
| } | |
| bool CanBeReentrant(IR::JnHelperMethod helper) | |
| { | |
| return (JnHelperMethodAttributes[helper] & AttrCanNotBeReentrant) == 0; | |
| } | |
| bool TempObjectProducing(IR::JnHelperMethod helper) | |
| { | |
| return (JnHelperMethodAttributes[helper] & AttrTempObjectProducing) != 0; | |
| } | |
| #ifdef DBG_DUMP | |
| struct ValidateHelperHeaders | |
| { | |
| ValidateHelperHeaders() | |
| { | |
| #define HELPERCALL(Name, Address, Attributes) | |
| #define HELPERCALLCHK(Name, Address, Attributes) \ | |
| Assert(JitHelperUtils::helper##Name##_implemented); | |
| #include "../Backend/JnHelperMethodList.h" | |
| } | |
| }; | |
| ValidateHelperHeaders validateHelperHeaders; | |
| #endif | |
| } //namespace HelperMethodAttributes |