| 1 | // Licensed to the .NET Foundation under one or more agreements. |
| 2 | // The .NET Foundation licenses this file to you under the MIT license. |
| 3 | // See the LICENSE file in the project root for more information. |
| 4 | // |
| 5 | // Various helper routines for generating AMD64 assembly code. |
| 6 | // |
| 7 | |
| 8 | // Precompiled Header |
| 9 | |
| 10 | #include "common.h" |
| 11 | |
| 12 | #include "stublink.h" |
| 13 | #include "cgensys.h" |
| 14 | #include "siginfo.hpp" |
| 15 | #include "excep.h" |
| 16 | #include "ecall.h" |
| 17 | #include "dllimport.h" |
| 18 | #include "dllimportcallback.h" |
| 19 | #include "dbginterface.h" |
| 20 | #include "fcall.h" |
| 21 | #include "array.h" |
| 22 | #include "virtualcallstub.h" |
| 23 | #include "jitinterface.h" |
| 24 | |
| 25 | #ifdef FEATURE_COMINTEROP |
| 26 | #include "clrtocomcall.h" |
| 27 | #endif // FEATURE_COMINTEROP |
| 28 | |
| 29 | void UpdateRegDisplayFromCalleeSavedRegisters(REGDISPLAY * pRD, CalleeSavedRegisters * pRegs) |
| 30 | { |
| 31 | LIMITED_METHOD_CONTRACT; |
| 32 | |
| 33 | T_CONTEXT * pContext = pRD->pCurrentContext; |
| 34 | #define CALLEE_SAVED_REGISTER(regname) pContext->regname = pRegs->regname; |
| 35 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 36 | #undef CALLEE_SAVED_REGISTER |
| 37 | |
| 38 | KNONVOLATILE_CONTEXT_POINTERS * pContextPointers = pRD->pCurrentContextPointers; |
| 39 | #define CALLEE_SAVED_REGISTER(regname) pContextPointers->regname = (PULONG64)&pRegs->regname; |
| 40 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 41 | #undef CALLEE_SAVED_REGISTER |
| 42 | } |
| 43 | |
| 44 | void ClearRegDisplayArgumentAndScratchRegisters(REGDISPLAY * pRD) |
| 45 | { |
| 46 | LIMITED_METHOD_CONTRACT; |
| 47 | |
| 48 | KNONVOLATILE_CONTEXT_POINTERS * pContextPointers = pRD->pCurrentContextPointers; |
| 49 | pContextPointers->Rax = NULL; |
| 50 | #ifdef UNIX_AMD64_ABI |
| 51 | pContextPointers->Rsi = NULL; |
| 52 | pContextPointers->Rdi = NULL; |
| 53 | #endif |
| 54 | pContextPointers->Rcx = NULL; |
| 55 | pContextPointers->Rdx = NULL; |
| 56 | pContextPointers->R8 = NULL; |
| 57 | pContextPointers->R9 = NULL; |
| 58 | pContextPointers->R10 = NULL; |
| 59 | pContextPointers->R11 = NULL; |
| 60 | } |
| 61 | |
| 62 | void TransitionFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 63 | { |
| 64 | LIMITED_METHOD_CONTRACT; |
| 65 | |
| 66 | pRD->IsCallerContextValid = FALSE; |
| 67 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 68 | |
| 69 | pRD->pCurrentContext->Rip = GetReturnAddress(); |
| 70 | pRD->pCurrentContext->Rsp = GetSP(); |
| 71 | |
| 72 | UpdateRegDisplayFromCalleeSavedRegisters(pRD, GetCalleeSavedRegisters()); |
| 73 | ClearRegDisplayArgumentAndScratchRegisters(pRD); |
| 74 | |
| 75 | SyncRegDisplayToCurrentContext(pRD); |
| 76 | |
| 77 | LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK TransitionFrame::UpdateRegDisplay(rip:%p, rsp:%p)\n" , pRD->ControlPC, pRD->SP)); |
| 78 | } |
| 79 | |
| 80 | #ifndef DACCESS_COMPILE |
| 81 | |
| 82 | extern "C" TADDR s_pStubHelperFrameVPtr; |
| 83 | TADDR s_pStubHelperFrameVPtr = StubHelperFrame::GetMethodFrameVPtr(); |
| 84 | |
| 85 | void TailCallFrame::InitFromContext(T_CONTEXT * pContext) |
| 86 | { |
| 87 | WRAPPER_NO_CONTRACT; |
| 88 | |
| 89 | #define CALLEE_SAVED_REGISTER(regname) m_calleeSavedRegisters.regname = pContext->regname; |
| 90 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 91 | #undef CALLEE_SAVED_REGISTER |
| 92 | |
| 93 | m_pGCLayout = 0; |
| 94 | m_ReturnAddress = pContext->Rip; |
| 95 | } |
| 96 | |
| 97 | #endif // !DACCESS_COMPILE |
| 98 | |
| 99 | void TailCallFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 100 | { |
| 101 | LIMITED_METHOD_CONTRACT; |
| 102 | |
| 103 | pRD->IsCallerContextValid = FALSE; |
| 104 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 105 | |
| 106 | pRD->pCurrentContext->Rip = m_ReturnAddress; |
| 107 | pRD->pCurrentContext->Rsp = dac_cast<TADDR>(this) + sizeof(*this); |
| 108 | |
| 109 | UpdateRegDisplayFromCalleeSavedRegisters(pRD, &m_calleeSavedRegisters); |
| 110 | ClearRegDisplayArgumentAndScratchRegisters(pRD); |
| 111 | |
| 112 | SyncRegDisplayToCurrentContext(pRD); |
| 113 | |
| 114 | LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK TransitionFrame::UpdateRegDisplay(rip:%p, rsp:%p)\n" , pRD->ControlPC, pRD->SP)); |
| 115 | } |
| 116 | |
| 117 | void InlinedCallFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 118 | { |
| 119 | CONTRACTL |
| 120 | { |
| 121 | NOTHROW; |
| 122 | GC_NOTRIGGER; |
| 123 | #ifdef PROFILING_SUPPORTED |
| 124 | PRECONDITION(CORProfilerStackSnapshotEnabled() || InlinedCallFrame::FrameHasActiveCall(this)); |
| 125 | #endif |
| 126 | HOST_NOCALLS; |
| 127 | MODE_ANY; |
| 128 | SUPPORTS_DAC; |
| 129 | } |
| 130 | CONTRACTL_END; |
| 131 | |
| 132 | if (!InlinedCallFrame::FrameHasActiveCall(this)) |
| 133 | { |
| 134 | LOG((LF_CORDB, LL_ERROR, "WARNING: InlinedCallFrame::UpdateRegDisplay called on inactive frame %p\n" , this)); |
| 135 | return; |
| 136 | } |
| 137 | |
| 138 | pRD->IsCallerContextValid = FALSE; |
| 139 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 140 | |
| 141 | pRD->pCurrentContext->Rip = *(DWORD64 *)&m_pCallerReturnAddress; |
| 142 | pRD->pCurrentContext->Rsp = *(DWORD64 *)&m_pCallSiteSP; |
| 143 | pRD->pCurrentContext->Rbp = *(DWORD64 *)&m_pCalleeSavedFP; |
| 144 | |
| 145 | ClearRegDisplayArgumentAndScratchRegisters(pRD); |
| 146 | |
| 147 | #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = NULL; |
| 148 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 149 | #undef CALLEE_SAVED_REGISTER |
| 150 | |
| 151 | pRD->pCurrentContextPointers->Rbp = (DWORD64 *)&m_pCalleeSavedFP; |
| 152 | |
| 153 | SyncRegDisplayToCurrentContext(pRD); |
| 154 | |
| 155 | LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK InlinedCallFrame::UpdateRegDisplay(rip:%p, rsp:%p)\n" , pRD->ControlPC, pRD->SP)); |
| 156 | } |
| 157 | |
| 158 | void HelperMethodFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 159 | { |
| 160 | CONTRACTL |
| 161 | { |
| 162 | NOTHROW; |
| 163 | GC_NOTRIGGER; |
| 164 | MODE_ANY; |
| 165 | PRECONDITION(m_MachState._pRetAddr == PTR_TADDR(&m_MachState.m_Rip)); |
| 166 | SUPPORTS_DAC; |
| 167 | } |
| 168 | CONTRACTL_END; |
| 169 | |
| 170 | pRD->IsCallerContextValid = FALSE; |
| 171 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 172 | |
| 173 | // |
| 174 | // Copy the saved state from the frame to the current context. |
| 175 | // |
| 176 | |
| 177 | LOG((LF_GCROOTS, LL_INFO100000, "STACKWALK HelperMethodFrame::UpdateRegDisplay cached ip:%p, sp:%p\n" , m_MachState.m_Rip, m_MachState.m_Rsp)); |
| 178 | |
| 179 | #if defined(DACCESS_COMPILE) |
| 180 | // For DAC, we may get here when the HMF is still uninitialized. |
| 181 | // So we may need to unwind here. |
| 182 | if (!m_MachState.isValid()) |
| 183 | { |
| 184 | // This allocation throws on OOM. |
| 185 | MachState* pUnwoundState = (MachState*)DacAllocHostOnlyInstance(sizeof(*pUnwoundState), true); |
| 186 | |
| 187 | InsureInit(false, pUnwoundState); |
| 188 | |
| 189 | pRD->pCurrentContext->Rip = pRD->ControlPC = pUnwoundState->m_Rip; |
| 190 | pRD->pCurrentContext->Rsp = pRD->SP = pUnwoundState->m_Rsp; |
| 191 | |
| 192 | #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = pUnwoundState->m_Capture.regname; |
| 193 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 194 | #undef CALLEE_SAVED_REGISTER |
| 195 | |
| 196 | #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = pUnwoundState->m_Ptrs.p##regname; |
| 197 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 198 | #undef CALLEE_SAVED_REGISTER |
| 199 | |
| 200 | ClearRegDisplayArgumentAndScratchRegisters(pRD); |
| 201 | |
| 202 | return; |
| 203 | } |
| 204 | #endif // DACCESS_COMPILE |
| 205 | |
| 206 | pRD->pCurrentContext->Rip = pRD->ControlPC = m_MachState.m_Rip; |
| 207 | pRD->pCurrentContext->Rsp = pRD->SP = m_MachState.m_Rsp; |
| 208 | |
| 209 | #ifdef FEATURE_PAL |
| 210 | |
| 211 | #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = (m_MachState.m_Ptrs.p##regname != NULL) ? \ |
| 212 | *m_MachState.m_Ptrs.p##regname : m_MachState.m_Unwound.regname; |
| 213 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 214 | #undef CALLEE_SAVED_REGISTER |
| 215 | |
| 216 | #else // FEATURE_PAL |
| 217 | |
| 218 | #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContext->regname = *m_MachState.m_Ptrs.p##regname; |
| 219 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 220 | #undef CALLEE_SAVED_REGISTER |
| 221 | |
| 222 | #endif // FEATURE_PAL |
| 223 | |
| 224 | #define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = m_MachState.m_Ptrs.p##regname; |
| 225 | ENUM_CALLEE_SAVED_REGISTERS(); |
| 226 | #undef CALLEE_SAVED_REGISTER |
| 227 | |
| 228 | // |
| 229 | // Clear all knowledge of scratch registers. We're skipping to any |
| 230 | // arbitrary point on the stack, and frames aren't required to preserve or |
| 231 | // keep track of these anyways. |
| 232 | // |
| 233 | |
| 234 | ClearRegDisplayArgumentAndScratchRegisters(pRD); |
| 235 | } |
| 236 | |
| 237 | void FaultingExceptionFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 238 | { |
| 239 | LIMITED_METHOD_DAC_CONTRACT; |
| 240 | |
| 241 | memcpy(pRD->pCurrentContext, &m_ctx, sizeof(CONTEXT)); |
| 242 | |
| 243 | pRD->ControlPC = m_ctx.Rip; |
| 244 | |
| 245 | pRD->SP = m_ctx.Rsp; |
| 246 | |
| 247 | pRD->pCurrentContextPointers->Rax = &m_ctx.Rax; |
| 248 | pRD->pCurrentContextPointers->Rcx = &m_ctx.Rcx; |
| 249 | pRD->pCurrentContextPointers->Rdx = &m_ctx.Rdx; |
| 250 | pRD->pCurrentContextPointers->Rbx = &m_ctx.Rbx; |
| 251 | pRD->pCurrentContextPointers->Rbp = &m_ctx.Rbp; |
| 252 | pRD->pCurrentContextPointers->Rsi = &m_ctx.Rsi; |
| 253 | pRD->pCurrentContextPointers->Rdi = &m_ctx.Rdi; |
| 254 | pRD->pCurrentContextPointers->R8 = &m_ctx.R8; |
| 255 | pRD->pCurrentContextPointers->R9 = &m_ctx.R9; |
| 256 | pRD->pCurrentContextPointers->R10 = &m_ctx.R10; |
| 257 | pRD->pCurrentContextPointers->R11 = &m_ctx.R11; |
| 258 | pRD->pCurrentContextPointers->R12 = &m_ctx.R12; |
| 259 | pRD->pCurrentContextPointers->R13 = &m_ctx.R13; |
| 260 | pRD->pCurrentContextPointers->R14 = &m_ctx.R14; |
| 261 | pRD->pCurrentContextPointers->R15 = &m_ctx.R15; |
| 262 | |
| 263 | pRD->IsCallerContextValid = FALSE; |
| 264 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 265 | } |
| 266 | |
| 267 | #ifdef FEATURE_HIJACK |
| 268 | TADDR ResumableFrame::GetReturnAddressPtr() |
| 269 | { |
| 270 | LIMITED_METHOD_DAC_CONTRACT; |
| 271 | return dac_cast<TADDR>(m_Regs) + offsetof(CONTEXT, Rip); |
| 272 | } |
| 273 | |
| 274 | void ResumableFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 275 | { |
| 276 | CONTRACT_VOID |
| 277 | { |
| 278 | NOTHROW; |
| 279 | GC_NOTRIGGER; |
| 280 | MODE_ANY; |
| 281 | SUPPORTS_DAC; |
| 282 | } |
| 283 | CONTRACT_END; |
| 284 | |
| 285 | CopyMemory(pRD->pCurrentContext, m_Regs, sizeof(CONTEXT)); |
| 286 | |
| 287 | pRD->ControlPC = m_Regs->Rip; |
| 288 | |
| 289 | pRD->SP = m_Regs->Rsp; |
| 290 | |
| 291 | pRD->pCurrentContextPointers->Rax = &m_Regs->Rax; |
| 292 | pRD->pCurrentContextPointers->Rcx = &m_Regs->Rcx; |
| 293 | pRD->pCurrentContextPointers->Rdx = &m_Regs->Rdx; |
| 294 | pRD->pCurrentContextPointers->Rbx = &m_Regs->Rbx; |
| 295 | pRD->pCurrentContextPointers->Rbp = &m_Regs->Rbp; |
| 296 | pRD->pCurrentContextPointers->Rsi = &m_Regs->Rsi; |
| 297 | pRD->pCurrentContextPointers->Rdi = &m_Regs->Rdi; |
| 298 | pRD->pCurrentContextPointers->R8 = &m_Regs->R8; |
| 299 | pRD->pCurrentContextPointers->R9 = &m_Regs->R9; |
| 300 | pRD->pCurrentContextPointers->R10 = &m_Regs->R10; |
| 301 | pRD->pCurrentContextPointers->R11 = &m_Regs->R11; |
| 302 | pRD->pCurrentContextPointers->R12 = &m_Regs->R12; |
| 303 | pRD->pCurrentContextPointers->R13 = &m_Regs->R13; |
| 304 | pRD->pCurrentContextPointers->R14 = &m_Regs->R14; |
| 305 | pRD->pCurrentContextPointers->R15 = &m_Regs->R15; |
| 306 | |
| 307 | pRD->IsCallerContextValid = FALSE; |
| 308 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 309 | |
| 310 | RETURN; |
| 311 | } |
| 312 | |
| 313 | // The HijackFrame has to know the registers that are pushed by OnHijackTripThread |
| 314 | void HijackFrame::UpdateRegDisplay(const PREGDISPLAY pRD) |
| 315 | { |
| 316 | CONTRACTL { |
| 317 | NOTHROW; |
| 318 | GC_NOTRIGGER; |
| 319 | SUPPORTS_DAC; |
| 320 | } |
| 321 | CONTRACTL_END; |
| 322 | |
| 323 | pRD->IsCallerContextValid = FALSE; |
| 324 | pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. |
| 325 | |
| 326 | pRD->pCurrentContext->Rip = m_ReturnAddress; |
| 327 | pRD->pCurrentContext->Rsp = PTR_TO_MEMBER_TADDR(HijackArgs, m_Args, Rip) + sizeof(void *); |
| 328 | |
| 329 | UpdateRegDisplayFromCalleeSavedRegisters(pRD, &(m_Args->Regs)); |
| 330 | |
| 331 | #ifdef UNIX_AMD64_ABI |
| 332 | pRD->pCurrentContextPointers->Rsi = NULL; |
| 333 | pRD->pCurrentContextPointers->Rdi = NULL; |
| 334 | #endif |
| 335 | pRD->pCurrentContextPointers->Rcx = NULL; |
| 336 | #ifdef UNIX_AMD64_ABI |
| 337 | pRD->pCurrentContextPointers->Rdx = (PULONG64)&m_Args->Rdx; |
| 338 | #else // UNIX_AMD64_ABI |
| 339 | pRD->pCurrentContextPointers->Rdx = NULL; |
| 340 | #endif // UNIX_AMD64_ABI |
| 341 | pRD->pCurrentContextPointers->R8 = NULL; |
| 342 | pRD->pCurrentContextPointers->R9 = NULL; |
| 343 | pRD->pCurrentContextPointers->R10 = NULL; |
| 344 | pRD->pCurrentContextPointers->R11 = NULL; |
| 345 | |
| 346 | pRD->pCurrentContextPointers->Rax = (PULONG64)&m_Args->Rax; |
| 347 | |
| 348 | SyncRegDisplayToCurrentContext(pRD); |
| 349 | |
| 350 | /* |
| 351 | // This only describes the top-most frame |
| 352 | pRD->pContext = NULL; |
| 353 | |
| 354 | |
| 355 | pRD->PCTAddr = dac_cast<TADDR>(m_Args) + offsetof(HijackArgs, Rip); |
| 356 | //pRD->pPC = PTR_SLOT(pRD->PCTAddr); |
| 357 | pRD->SP = (ULONG64)(pRD->PCTAddr + sizeof(TADDR)); |
| 358 | */ |
| 359 | } |
| 360 | #endif // FEATURE_HIJACK |
| 361 | |
| 362 | BOOL isJumpRel32(PCODE pCode) |
| 363 | { |
| 364 | CONTRACTL { |
| 365 | NOTHROW; |
| 366 | GC_NOTRIGGER; |
| 367 | SO_TOLERANT; |
| 368 | SUPPORTS_DAC; |
| 369 | } CONTRACTL_END; |
| 370 | |
| 371 | PTR_BYTE pbCode = PTR_BYTE(pCode); |
| 372 | |
| 373 | return 0xE9 == pbCode[0]; |
| 374 | } |
| 375 | |
| 376 | // |
| 377 | // Given the same pBuffer that was used by emitJump this |
| 378 | // method decodes the instructions and returns the jump target |
| 379 | // |
| 380 | PCODE decodeJump32(PCODE pBuffer) |
| 381 | { |
| 382 | CONTRACTL |
| 383 | { |
| 384 | NOTHROW; |
| 385 | GC_NOTRIGGER; |
| 386 | SO_TOLERANT; |
| 387 | SUPPORTS_DAC; |
| 388 | } |
| 389 | CONTRACTL_END; |
| 390 | |
| 391 | // jmp rel32 |
| 392 | _ASSERTE(isJumpRel32(pBuffer)); |
| 393 | |
| 394 | return rel32Decode(pBuffer+1); |
| 395 | } |
| 396 | |
| 397 | BOOL isJumpRel64(PCODE pCode) |
| 398 | { |
| 399 | CONTRACTL { |
| 400 | NOTHROW; |
| 401 | GC_NOTRIGGER; |
| 402 | SO_TOLERANT; |
| 403 | SUPPORTS_DAC; |
| 404 | } CONTRACTL_END; |
| 405 | |
| 406 | PTR_BYTE pbCode = PTR_BYTE(pCode); |
| 407 | |
| 408 | return 0x48 == pbCode[0] && |
| 409 | 0xB8 == pbCode[1] && |
| 410 | 0xFF == pbCode[10] && |
| 411 | 0xE0 == pbCode[11]; |
| 412 | } |
| 413 | |
| 414 | PCODE decodeJump64(PCODE pBuffer) |
| 415 | { |
| 416 | CONTRACTL |
| 417 | { |
| 418 | NOTHROW; |
| 419 | GC_NOTRIGGER; |
| 420 | SO_TOLERANT; |
| 421 | SUPPORTS_DAC; |
| 422 | } |
| 423 | CONTRACTL_END; |
| 424 | |
| 425 | // mov rax, xxx |
| 426 | // jmp rax |
| 427 | _ASSERTE(isJumpRel64(pBuffer)); |
| 428 | |
| 429 | return *PTR_UINT64(pBuffer+2); |
| 430 | } |
| 431 | |
| 432 | #ifdef DACCESS_COMPILE |
| 433 | BOOL GetAnyThunkTarget (CONTEXT *pctx, TADDR *pTarget, TADDR *pTargetMethodDesc) |
| 434 | { |
| 435 | TADDR pThunk = GetIP(pctx); |
| 436 | |
| 437 | *pTargetMethodDesc = NULL; |
| 438 | |
| 439 | // |
| 440 | // Check for something generated by emitJump. |
| 441 | // |
| 442 | if (isJumpRel64(pThunk)) |
| 443 | { |
| 444 | *pTarget = decodeJump64(pThunk); |
| 445 | return TRUE; |
| 446 | } |
| 447 | |
| 448 | return FALSE; |
| 449 | } |
| 450 | #endif // DACCESS_COMPILE |
| 451 | |
| 452 | |
| 453 | #ifndef DACCESS_COMPILE |
| 454 | |
| 455 | // Note: This is only used on server GC on Windows. |
| 456 | // |
| 457 | // This function returns the number of logical processors on a given physical chip. If it cannot |
| 458 | // determine the number of logical cpus, or the machine is not populated uniformly with the same |
| 459 | // type of processors, this function returns 1. |
| 460 | |
| 461 | void EncodeLoadAndJumpThunk (LPBYTE pBuffer, LPVOID pv, LPVOID pTarget) |
| 462 | { |
| 463 | CONTRACTL |
| 464 | { |
| 465 | THROWS; |
| 466 | GC_NOTRIGGER; |
| 467 | MODE_ANY; |
| 468 | |
| 469 | PRECONDITION(CheckPointer(pBuffer)); |
| 470 | } |
| 471 | CONTRACTL_END; |
| 472 | |
| 473 | // mov r10, pv 49 ba xx xx xx xx xx xx xx xx |
| 474 | |
| 475 | pBuffer[0] = 0x49; |
| 476 | pBuffer[1] = 0xBA; |
| 477 | |
| 478 | *((UINT64 UNALIGNED *)&pBuffer[2]) = (UINT64)pv; |
| 479 | |
| 480 | // mov rax, pTarget 48 b8 xx xx xx xx xx xx xx xx |
| 481 | |
| 482 | pBuffer[10] = 0x48; |
| 483 | pBuffer[11] = 0xB8; |
| 484 | |
| 485 | *((UINT64 UNALIGNED *)&pBuffer[12]) = (UINT64)pTarget; |
| 486 | |
| 487 | // jmp rax ff e0 |
| 488 | |
| 489 | pBuffer[20] = 0xFF; |
| 490 | pBuffer[21] = 0xE0; |
| 491 | |
| 492 | _ASSERTE(DbgIsExecutable(pBuffer, 22)); |
| 493 | } |
| 494 | |
| 495 | void emitCOMStubCall (ComCallMethodDesc *pCOMMethod, PCODE target) |
| 496 | { |
| 497 | CONTRACT_VOID |
| 498 | { |
| 499 | THROWS; |
| 500 | GC_NOTRIGGER; |
| 501 | MODE_ANY; |
| 502 | } |
| 503 | CONTRACT_END; |
| 504 | |
| 505 | BYTE *pBuffer = (BYTE*)pCOMMethod - COMMETHOD_CALL_PRESTUB_SIZE; |
| 506 | |
| 507 | // We need the target to be in a 64-bit aligned memory location and the call instruction |
| 508 | // to immediately precede the ComCallMethodDesc. We'll generate an indirect call to avoid |
| 509 | // consuming 3 qwords for this (mov rax, | target | nops & call rax). |
| 510 | |
| 511 | // dq 123456789abcdef0h |
| 512 | // nop 90 |
| 513 | // nop 90 |
| 514 | // call [$ - 10] ff 15 f0 ff ff ff |
| 515 | |
| 516 | *((UINT64 *)&pBuffer[COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET]) = (UINT64)target; |
| 517 | |
| 518 | pBuffer[-2] = 0x90; |
| 519 | pBuffer[-1] = 0x90; |
| 520 | |
| 521 | pBuffer[0] = 0xFF; |
| 522 | pBuffer[1] = 0x15; |
| 523 | *((UINT32 UNALIGNED *)&pBuffer[2]) = (UINT32)(COMMETHOD_CALL_PRESTUB_ADDRESS_OFFSET - COMMETHOD_CALL_PRESTUB_SIZE); |
| 524 | |
| 525 | _ASSERTE(DbgIsExecutable(pBuffer, COMMETHOD_CALL_PRESTUB_SIZE)); |
| 526 | |
| 527 | RETURN; |
| 528 | } |
| 529 | |
| 530 | void emitJump(LPBYTE pBuffer, LPVOID target) |
| 531 | { |
| 532 | CONTRACTL |
| 533 | { |
| 534 | THROWS; |
| 535 | GC_NOTRIGGER; |
| 536 | MODE_ANY; |
| 537 | |
| 538 | PRECONDITION(CheckPointer(pBuffer)); |
| 539 | } |
| 540 | CONTRACTL_END; |
| 541 | |
| 542 | // mov rax, 123456789abcdef0h 48 b8 xx xx xx xx xx xx xx xx |
| 543 | // jmp rax ff e0 |
| 544 | |
| 545 | pBuffer[0] = 0x48; |
| 546 | pBuffer[1] = 0xB8; |
| 547 | |
| 548 | *((UINT64 UNALIGNED *)&pBuffer[2]) = (UINT64)target; |
| 549 | |
| 550 | pBuffer[10] = 0xFF; |
| 551 | pBuffer[11] = 0xE0; |
| 552 | |
| 553 | _ASSERTE(DbgIsExecutable(pBuffer, 12)); |
| 554 | } |
| 555 | |
| 556 | void UMEntryThunkCode::Encode(BYTE* pTargetCode, void* pvSecretParam) |
| 557 | { |
| 558 | CONTRACTL |
| 559 | { |
| 560 | NOTHROW; |
| 561 | GC_NOTRIGGER; |
| 562 | MODE_ANY; |
| 563 | } |
| 564 | CONTRACTL_END; |
| 565 | |
| 566 | // padding // CC CC CC CC |
| 567 | // mov r10, pUMEntryThunk // 49 ba xx xx xx xx xx xx xx xx // METHODDESC_REGISTER |
| 568 | // mov rax, pJmpDest // 48 b8 xx xx xx xx xx xx xx xx // need to ensure this imm64 is qword aligned |
| 569 | // TAILJMP_RAX // 48 FF E0 |
| 570 | |
| 571 | #ifdef _DEBUG |
| 572 | m_padding[0] = X86_INSTR_INT3; |
| 573 | m_padding[1] = X86_INSTR_INT3; |
| 574 | m_padding[2] = X86_INSTR_INT3; |
| 575 | m_padding[3] = X86_INSTR_INT3; |
| 576 | #endif // _DEBUG |
| 577 | m_movR10[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT | REX_OPCODE_REG_EXT; |
| 578 | m_movR10[1] = 0xBA; |
| 579 | m_uet = pvSecretParam; |
| 580 | m_movRAX[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT; |
| 581 | m_movRAX[1] = 0xB8; |
| 582 | m_execstub = pTargetCode; |
| 583 | m_jmpRAX[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT; |
| 584 | m_jmpRAX[1] = 0xFF; |
| 585 | m_jmpRAX[2] = 0xE0; |
| 586 | |
| 587 | _ASSERTE(DbgIsExecutable(&m_movR10[0], &m_jmpRAX[3]-&m_movR10[0])); |
| 588 | } |
| 589 | |
| 590 | void UMEntryThunkCode::Poison() |
| 591 | { |
| 592 | CONTRACTL |
| 593 | { |
| 594 | NOTHROW; |
| 595 | GC_NOTRIGGER; |
| 596 | MODE_ANY; |
| 597 | } |
| 598 | CONTRACTL_END; |
| 599 | |
| 600 | m_execstub = (BYTE *)UMEntryThunk::ReportViolation; |
| 601 | |
| 602 | m_movR10[0] = REX_PREFIX_BASE | REX_OPERAND_SIZE_64BIT; |
| 603 | #ifdef _WIN32 |
| 604 | // mov rcx, pUMEntryThunk // 48 b9 xx xx xx xx xx xx xx xx |
| 605 | m_movR10[1] = 0xB9; |
| 606 | #else |
| 607 | // mov rdi, pUMEntryThunk // 48 bf xx xx xx xx xx xx xx xx |
| 608 | m_movR10[1] = 0xBF; |
| 609 | #endif |
| 610 | |
| 611 | ClrFlushInstructionCache(&m_movR10[0], &m_jmpRAX[3]-&m_movR10[0]); |
| 612 | } |
| 613 | |
| 614 | UMEntryThunk* UMEntryThunk::Decode(LPVOID pCallback) |
| 615 | { |
| 616 | LIMITED_METHOD_CONTRACT; |
| 617 | |
| 618 | UMEntryThunkCode *pThunkCode = (UMEntryThunkCode*)((BYTE*)pCallback - UMEntryThunkCode::GetEntryPointOffset()); |
| 619 | |
| 620 | return (UMEntryThunk*)pThunkCode->m_uet; |
| 621 | } |
| 622 | |
| 623 | INT32 rel32UsingJumpStub(INT32 UNALIGNED * pRel32, PCODE target, MethodDesc *pMethod, |
| 624 | LoaderAllocator *pLoaderAllocator /* = NULL */, bool throwOnOutOfMemoryWithinRange /*= true*/) |
| 625 | { |
| 626 | CONTRACTL |
| 627 | { |
| 628 | THROWS; // Creating a JumpStub could throw OutOfMemory |
| 629 | GC_NOTRIGGER; |
| 630 | |
| 631 | PRECONDITION(pMethod != NULL || pLoaderAllocator != NULL); |
| 632 | // If a loader allocator isn't explicitly provided, we must be able to get one via the MethodDesc. |
| 633 | PRECONDITION(pLoaderAllocator != NULL || pMethod->GetLoaderAllocator() != NULL); |
| 634 | // If a domain is provided, the MethodDesc mustn't yet be set up to have one, or it must match the MethodDesc's domain, |
| 635 | // unless we're in a compilation domain (NGen loads assemblies as domain-bound but compiles them as domain neutral). |
| 636 | PRECONDITION(!pLoaderAllocator || !pMethod || pMethod->GetMethodDescChunk()->GetMethodTablePtr()->IsNull() || |
| 637 | pLoaderAllocator == pMethod->GetMethodDescChunk()->GetFirstMethodDesc()->GetLoaderAllocatorForCode() || IsCompilationProcess()); |
| 638 | } |
| 639 | CONTRACTL_END; |
| 640 | |
| 641 | TADDR baseAddr = (TADDR)pRel32 + 4; |
| 642 | |
| 643 | INT_PTR offset = target - baseAddr; |
| 644 | |
| 645 | if (!FitsInI4(offset) INDEBUG(|| PEDecoder::GetForceRelocs())) |
| 646 | { |
| 647 | TADDR loAddr = baseAddr + INT32_MIN; |
| 648 | if (loAddr > baseAddr) loAddr = UINT64_MIN; // overflow |
| 649 | |
| 650 | TADDR hiAddr = baseAddr + INT32_MAX; |
| 651 | if (hiAddr < baseAddr) hiAddr = UINT64_MAX; // overflow |
| 652 | |
| 653 | // Always try to allocate with throwOnOutOfMemoryWithinRange:false first to conserve reserveForJumpStubs until when |
| 654 | // it is really needed. LoaderCodeHeap::CreateCodeHeap and EEJitManager::CanUseCodeHeap won't use the reserved |
| 655 | // space when throwOnOutOfMemoryWithinRange is false. |
| 656 | // |
| 657 | // The reserved space should be only used by jump stubs for precodes and other similar code fragments. It should |
| 658 | // not be used by JITed code. And since the accounting of the reserved space is not precise, we are conservative |
| 659 | // and try to save the reserved space until it is really needed to avoid throwing out of memory within range exception. |
| 660 | PCODE jumpStubAddr = ExecutionManager::jumpStub(pMethod, |
| 661 | target, |
| 662 | (BYTE *)loAddr, |
| 663 | (BYTE *)hiAddr, |
| 664 | pLoaderAllocator, |
| 665 | /* throwOnOutOfMemoryWithinRange */ false); |
| 666 | if (jumpStubAddr == NULL) |
| 667 | { |
| 668 | if (!throwOnOutOfMemoryWithinRange) |
| 669 | return 0; |
| 670 | |
| 671 | jumpStubAddr = ExecutionManager::jumpStub(pMethod, |
| 672 | target, |
| 673 | (BYTE *)loAddr, |
| 674 | (BYTE *)hiAddr, |
| 675 | pLoaderAllocator, |
| 676 | /* throwOnOutOfMemoryWithinRange */ true); |
| 677 | } |
| 678 | |
| 679 | offset = jumpStubAddr - baseAddr; |
| 680 | |
| 681 | if (!FitsInI4(offset)) |
| 682 | { |
| 683 | _ASSERTE(!"jump stub was not in expected range" ); |
| 684 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); |
| 685 | } |
| 686 | } |
| 687 | |
| 688 | _ASSERTE(FitsInI4(offset)); |
| 689 | return static_cast<INT32>(offset); |
| 690 | } |
| 691 | |
| 692 | INT32 rel32UsingPreallocatedJumpStub(INT32 UNALIGNED * pRel32, PCODE target, PCODE jumpStubAddr, bool emitJump) |
| 693 | { |
| 694 | CONTRACTL |
| 695 | { |
| 696 | THROWS; // emitBackToBackJump may throw (see emitJump) |
| 697 | GC_NOTRIGGER; |
| 698 | } |
| 699 | CONTRACTL_END; |
| 700 | |
| 701 | TADDR baseAddr = (TADDR)pRel32 + 4; |
| 702 | _ASSERTE(FitsInI4(jumpStubAddr - baseAddr)); |
| 703 | |
| 704 | INT_PTR offset = target - baseAddr; |
| 705 | if (!FitsInI4(offset) INDEBUG(|| PEDecoder::GetForceRelocs())) |
| 706 | { |
| 707 | offset = jumpStubAddr - baseAddr; |
| 708 | if (!FitsInI4(offset)) |
| 709 | { |
| 710 | _ASSERTE(!"jump stub was not in expected range" ); |
| 711 | EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); |
| 712 | } |
| 713 | |
| 714 | if (emitJump) |
| 715 | { |
| 716 | emitBackToBackJump((LPBYTE)jumpStubAddr, (LPVOID)target); |
| 717 | } |
| 718 | else |
| 719 | { |
| 720 | _ASSERTE(decodeBackToBackJump(jumpStubAddr) == target); |
| 721 | } |
| 722 | } |
| 723 | |
| 724 | _ASSERTE(FitsInI4(offset)); |
| 725 | return static_cast<INT32>(offset); |
| 726 | } |
| 727 | |
| 728 | BOOL DoesSlotCallPrestub(PCODE pCode) |
| 729 | { |
| 730 | CONTRACTL { |
| 731 | NOTHROW; |
| 732 | GC_NOTRIGGER; |
| 733 | SO_TOLERANT; |
| 734 | PRECONDITION(pCode != GetPreStubEntryPoint()); |
| 735 | } CONTRACTL_END; |
| 736 | |
| 737 | // AMD64 has the following possible sequences for prestub logic: |
| 738 | // 1. slot -> temporary entrypoint -> prestub |
| 739 | // 2. slot -> precode -> prestub |
| 740 | // 3. slot -> precode -> jumprel64 (jump stub) -> prestub |
| 741 | // 4. slot -> precode -> jumprel64 (NGEN case) -> prestub |
| 742 | |
| 743 | #ifdef HAS_COMPACT_ENTRYPOINTS |
| 744 | if (MethodDescChunk::GetMethodDescFromCompactEntryPoint(pCode, TRUE) != NULL) |
| 745 | { |
| 746 | return TRUE; |
| 747 | } |
| 748 | #endif |
| 749 | |
| 750 | if (!IS_ALIGNED(pCode, PRECODE_ALIGNMENT)) |
| 751 | { |
| 752 | return FALSE; |
| 753 | } |
| 754 | |
| 755 | #ifdef HAS_FIXUP_PRECODE |
| 756 | if (*PTR_BYTE(pCode) == X86_INSTR_CALL_REL32) |
| 757 | { |
| 758 | // Note that call could have been patched to jmp in the meantime |
| 759 | pCode = rel32Decode(pCode+1); |
| 760 | |
| 761 | #ifdef FEATURE_PREJIT |
| 762 | // NGEN helper |
| 763 | if (*PTR_BYTE(pCode) == X86_INSTR_JMP_REL32) { |
| 764 | pCode = (TADDR)rel32Decode(pCode+1); |
| 765 | } |
| 766 | #endif |
| 767 | |
| 768 | // JumpStub |
| 769 | if (isJumpRel64(pCode)) { |
| 770 | pCode = decodeJump64(pCode); |
| 771 | } |
| 772 | |
| 773 | return pCode == (TADDR)PrecodeFixupThunk; |
| 774 | } |
| 775 | #endif |
| 776 | |
| 777 | if (*PTR_USHORT(pCode) != X86_INSTR_MOV_R10_IMM64 || // mov rax,XXXX |
| 778 | *PTR_BYTE(pCode+10) != X86_INSTR_NOP || // nop |
| 779 | *PTR_BYTE(pCode+11) != X86_INSTR_JMP_REL32) // jmp rel32 |
| 780 | { |
| 781 | return FALSE; |
| 782 | } |
| 783 | pCode = rel32Decode(pCode+12); |
| 784 | |
| 785 | #ifdef FEATURE_PREJIT |
| 786 | // NGEN helper |
| 787 | if (*PTR_BYTE(pCode) == X86_INSTR_JMP_REL32) { |
| 788 | pCode = (TADDR)rel32Decode(pCode+1); |
| 789 | } |
| 790 | #endif |
| 791 | |
| 792 | // JumpStub |
| 793 | if (isJumpRel64(pCode)) { |
| 794 | pCode = decodeJump64(pCode); |
| 795 | } |
| 796 | |
| 797 | return pCode == GetPreStubEntryPoint(); |
| 798 | } |
| 799 | |
| 800 | // |
| 801 | // Some AMD64 assembly functions have one or more DWORDS at the end of the function |
| 802 | // that specify the offsets where significant instructions are |
| 803 | // we use this function to get at these offsets |
| 804 | // |
| 805 | DWORD GetOffsetAtEndOfFunction(ULONGLONG uImageBase, |
| 806 | PT_RUNTIME_FUNCTION pFunctionEntry, |
| 807 | int offsetNum /* = 1*/) |
| 808 | { |
| 809 | CONTRACTL |
| 810 | { |
| 811 | MODE_ANY; |
| 812 | NOTHROW; |
| 813 | GC_NOTRIGGER; |
| 814 | SO_TOLERANT; |
| 815 | PRECONDITION((offsetNum > 0) && (offsetNum < 20)); /* we only allow reasonable offsetNums 1..19 */ |
| 816 | } |
| 817 | CONTRACTL_END; |
| 818 | |
| 819 | DWORD functionSize = pFunctionEntry->EndAddress - pFunctionEntry->BeginAddress; |
| 820 | BYTE* pEndOfFunction = (BYTE*) (uImageBase + pFunctionEntry->EndAddress); |
| 821 | DWORD* pOffset = (DWORD*) (pEndOfFunction) - offsetNum; |
| 822 | DWORD offsetInFunc = *pOffset; |
| 823 | |
| 824 | _ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/cGenAMD64.cpp" , (offsetInFunc >= 0) && (offsetInFunc < functionSize)); |
| 825 | |
| 826 | return offsetInFunc; |
| 827 | } |
| 828 | |
| 829 | //========================================================================================== |
| 830 | // In NGen image, virtual slots inherited from cross-module dependencies point to jump thunks. |
| 831 | // These jump thunk initially point to VirtualMethodFixupStub which transfers control here. |
| 832 | // This method 'VirtualMethodFixupWorker' will patch the jump thunk to point to the actual |
| 833 | // inherited method body after we have execute the precode and a stable entry point. |
| 834 | // |
| 835 | EXTERN_C PCODE VirtualMethodFixupWorker(TransitionBlock * pTransitionBlock, CORCOMPILE_VIRTUAL_IMPORT_THUNK * pThunk) |
| 836 | { |
| 837 | CONTRACTL |
| 838 | { |
| 839 | THROWS; |
| 840 | GC_TRIGGERS; // GC not allowed until we call pEMFrame->SetFunction(pMD); |
| 841 | |
| 842 | ENTRY_POINT; |
| 843 | } |
| 844 | CONTRACTL_END; |
| 845 | |
| 846 | MAKE_CURRENT_THREAD_AVAILABLE(); |
| 847 | |
| 848 | PCODE pCode = NULL; |
| 849 | MethodDesc * pMD = NULL; |
| 850 | |
| 851 | #ifdef _DEBUG |
| 852 | Thread::ObjectRefFlush(CURRENT_THREAD); |
| 853 | #endif |
| 854 | |
| 855 | BEGIN_SO_INTOLERANT_CODE(CURRENT_THREAD); |
| 856 | |
| 857 | _ASSERTE(IS_ALIGNED((size_t)pThunk, sizeof(INT64))); |
| 858 | |
| 859 | FrameWithCookie<ExternalMethodFrame> frame(pTransitionBlock); |
| 860 | ExternalMethodFrame * pEMFrame = &frame; |
| 861 | |
| 862 | OBJECTREF pThisPtr = pEMFrame->GetThis(); |
| 863 | _ASSERTE(pThisPtr != NULL); |
| 864 | VALIDATEOBJECT(pThisPtr); |
| 865 | |
| 866 | MethodTable * pMT = pThisPtr->GetMethodTable(); |
| 867 | |
| 868 | WORD slotNumber = pThunk->slotNum; |
| 869 | _ASSERTE(slotNumber != (WORD)-1); |
| 870 | |
| 871 | pCode = pMT->GetRestoredSlot(slotNumber); |
| 872 | |
| 873 | if (!DoesSlotCallPrestub(pCode)) |
| 874 | { |
| 875 | pMD = MethodTable::GetMethodDescForSlotAddress(pCode); |
| 876 | |
| 877 | pEMFrame->SetFunction(pMD); // We will use the pMD to enumerate the GC refs in the arguments |
| 878 | pEMFrame->Push(CURRENT_THREAD); |
| 879 | |
| 880 | INSTALL_MANAGED_EXCEPTION_DISPATCHER; |
| 881 | INSTALL_UNWIND_AND_CONTINUE_HANDLER_NO_PROBE; |
| 882 | |
| 883 | // Skip fixup precode jump for better perf |
| 884 | PCODE pDirectTarget = Precode::TryToSkipFixupPrecode(pCode); |
| 885 | if (pDirectTarget != NULL) |
| 886 | pCode = pDirectTarget; |
| 887 | |
| 888 | INT64 oldValue = *(INT64*)pThunk; |
| 889 | BYTE* pOldValue = (BYTE*)&oldValue; |
| 890 | |
| 891 | if (pOldValue[0] == X86_INSTR_CALL_REL32) |
| 892 | { |
| 893 | INT64 newValue = oldValue; |
| 894 | BYTE* pNewValue = (BYTE*)&newValue; |
| 895 | pNewValue[0] = X86_INSTR_JMP_REL32; |
| 896 | |
| 897 | *(INT32 *)(pNewValue+1) = rel32UsingJumpStub((INT32*)(&pThunk->callJmp[1]), pCode, pMD, NULL); |
| 898 | |
| 899 | _ASSERTE(IS_ALIGNED(pThunk, sizeof(INT64))); |
| 900 | EnsureWritableExecutablePages(pThunk, sizeof(INT64)); |
| 901 | FastInterlockCompareExchangeLong((INT64*)pThunk, newValue, oldValue); |
| 902 | |
| 903 | FlushInstructionCache(GetCurrentProcess(), pThunk, 8); |
| 904 | } |
| 905 | |
| 906 | UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_NO_PROBE; |
| 907 | UNINSTALL_MANAGED_EXCEPTION_DISPATCHER; |
| 908 | pEMFrame->Pop(CURRENT_THREAD); |
| 909 | } |
| 910 | |
| 911 | // Ready to return |
| 912 | |
| 913 | END_SO_INTOLERANT_CODE; |
| 914 | |
| 915 | return pCode; |
| 916 | } |
| 917 | |
| 918 | #ifdef FEATURE_READYTORUN |
| 919 | |
| 920 | // |
| 921 | // Allocation of dynamic helpers |
| 922 | // |
| 923 | |
| 924 | #define DYNAMIC_HELPER_ALIGNMENT sizeof(TADDR) |
| 925 | |
| 926 | #define BEGIN_DYNAMIC_HELPER_EMIT(size) \ |
| 927 | SIZE_T cb = size; \ |
| 928 | SIZE_T cbAligned = ALIGN_UP(cb, DYNAMIC_HELPER_ALIGNMENT); \ |
| 929 | BYTE * pStart = (BYTE *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(cbAligned, DYNAMIC_HELPER_ALIGNMENT); \ |
| 930 | BYTE * p = pStart; |
| 931 | |
| 932 | #define END_DYNAMIC_HELPER_EMIT() \ |
| 933 | _ASSERTE(pStart + cb == p); \ |
| 934 | while (p < pStart + cbAligned) *p++ = X86_INSTR_INT3; \ |
| 935 | ClrFlushInstructionCache(pStart, cbAligned); \ |
| 936 | return (PCODE)pStart |
| 937 | |
| 938 | PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, PCODE target) |
| 939 | { |
| 940 | STANDARD_VM_CONTRACT; |
| 941 | |
| 942 | BEGIN_DYNAMIC_HELPER_EMIT(15); |
| 943 | |
| 944 | #ifdef UNIX_AMD64_ABI |
| 945 | *(UINT16 *)p = 0xBF48; // mov rdi, XXXXXX |
| 946 | #else |
| 947 | *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX |
| 948 | #endif |
| 949 | p += 2; |
| 950 | *(TADDR *)p = arg; |
| 951 | p += 8; |
| 952 | |
| 953 | *p++ = X86_INSTR_JMP_REL32; // jmp rel32 |
| 954 | *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); |
| 955 | p += 4; |
| 956 | |
| 957 | END_DYNAMIC_HELPER_EMIT(); |
| 958 | } |
| 959 | |
| 960 | void DynamicHelpers::EmitHelperWithArg(BYTE*& p, LoaderAllocator * pAllocator, TADDR arg, PCODE target) |
| 961 | { |
| 962 | CONTRACTL |
| 963 | { |
| 964 | GC_NOTRIGGER; |
| 965 | PRECONDITION(p != NULL && target != NULL); |
| 966 | } |
| 967 | CONTRACTL_END; |
| 968 | |
| 969 | // Move an an argument into the second argument register and jump to a target function. |
| 970 | |
| 971 | #ifdef UNIX_AMD64_ABI |
| 972 | *(UINT16 *)p = 0xBE48; // mov rsi, XXXXXX |
| 973 | #else |
| 974 | *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX |
| 975 | #endif |
| 976 | p += 2; |
| 977 | *(TADDR *)p = arg; |
| 978 | p += 8; |
| 979 | |
| 980 | *p++ = X86_INSTR_JMP_REL32; // jmp rel32 |
| 981 | *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); |
| 982 | p += 4; |
| 983 | } |
| 984 | |
| 985 | PCODE DynamicHelpers::CreateHelperWithArg(LoaderAllocator * pAllocator, TADDR arg, PCODE target) |
| 986 | { |
| 987 | BEGIN_DYNAMIC_HELPER_EMIT(15); |
| 988 | |
| 989 | EmitHelperWithArg(p, pAllocator, arg, target); |
| 990 | |
| 991 | END_DYNAMIC_HELPER_EMIT(); |
| 992 | } |
| 993 | |
| 994 | PCODE DynamicHelpers::CreateHelper(LoaderAllocator * pAllocator, TADDR arg, TADDR arg2, PCODE target) |
| 995 | { |
| 996 | BEGIN_DYNAMIC_HELPER_EMIT(25); |
| 997 | |
| 998 | #ifdef UNIX_AMD64_ABI |
| 999 | *(UINT16 *)p = 0xBF48; // mov rdi, XXXXXX |
| 1000 | #else |
| 1001 | *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX |
| 1002 | #endif |
| 1003 | p += 2; |
| 1004 | *(TADDR *)p = arg; |
| 1005 | p += 8; |
| 1006 | |
| 1007 | #ifdef UNIX_AMD64_ABI |
| 1008 | *(UINT16 *)p = 0xBE48; // mov rsi, XXXXXX |
| 1009 | #else |
| 1010 | *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX |
| 1011 | #endif |
| 1012 | p += 2; |
| 1013 | *(TADDR *)p = arg2; |
| 1014 | p += 8; |
| 1015 | |
| 1016 | *p++ = X86_INSTR_JMP_REL32; // jmp rel32 |
| 1017 | *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); |
| 1018 | p += 4; |
| 1019 | |
| 1020 | END_DYNAMIC_HELPER_EMIT(); |
| 1021 | } |
| 1022 | |
| 1023 | PCODE DynamicHelpers::CreateHelperArgMove(LoaderAllocator * pAllocator, TADDR arg, PCODE target) |
| 1024 | { |
| 1025 | BEGIN_DYNAMIC_HELPER_EMIT(18); |
| 1026 | |
| 1027 | #ifdef UNIX_AMD64_ABI |
| 1028 | *p++ = 0x48; // mov rsi, rdi |
| 1029 | *(UINT16 *)p = 0xF78B; |
| 1030 | #else |
| 1031 | *p++ = 0x48; // mov rdx, rcx |
| 1032 | *(UINT16 *)p = 0xD18B; |
| 1033 | #endif |
| 1034 | p += 2; |
| 1035 | |
| 1036 | #ifdef UNIX_AMD64_ABI |
| 1037 | *(UINT16 *)p = 0xBF48; // mov rdi, XXXXXX |
| 1038 | #else |
| 1039 | *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX |
| 1040 | #endif |
| 1041 | p += 2; |
| 1042 | *(TADDR *)p = arg; |
| 1043 | p += 8; |
| 1044 | |
| 1045 | *p++ = X86_INSTR_JMP_REL32; // jmp rel32 |
| 1046 | *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); |
| 1047 | p += 4; |
| 1048 | |
| 1049 | END_DYNAMIC_HELPER_EMIT(); |
| 1050 | } |
| 1051 | |
| 1052 | PCODE DynamicHelpers::CreateReturn(LoaderAllocator * pAllocator) |
| 1053 | { |
| 1054 | BEGIN_DYNAMIC_HELPER_EMIT(1); |
| 1055 | |
| 1056 | *p++ = 0xC3; // ret |
| 1057 | |
| 1058 | END_DYNAMIC_HELPER_EMIT(); |
| 1059 | } |
| 1060 | |
| 1061 | PCODE DynamicHelpers::CreateReturnConst(LoaderAllocator * pAllocator, TADDR arg) |
| 1062 | { |
| 1063 | BEGIN_DYNAMIC_HELPER_EMIT(11); |
| 1064 | |
| 1065 | *(UINT16 *)p = 0xB848; // mov rax, XXXXXX |
| 1066 | p += 2; |
| 1067 | *(TADDR *)p = arg; |
| 1068 | p += 8; |
| 1069 | |
| 1070 | *p++ = 0xC3; // ret |
| 1071 | |
| 1072 | END_DYNAMIC_HELPER_EMIT(); |
| 1073 | } |
| 1074 | |
| 1075 | PCODE DynamicHelpers::CreateReturnIndirConst(LoaderAllocator * pAllocator, TADDR arg, INT8 offset) |
| 1076 | { |
| 1077 | BEGIN_DYNAMIC_HELPER_EMIT((offset != 0) ? 15 : 11); |
| 1078 | |
| 1079 | *(UINT16 *)p = 0xA148; // mov rax, [XXXXXX] |
| 1080 | p += 2; |
| 1081 | *(TADDR *)p = arg; |
| 1082 | p += 8; |
| 1083 | |
| 1084 | if (offset != 0) |
| 1085 | { |
| 1086 | // add rax, <offset> |
| 1087 | *p++ = 0x48; |
| 1088 | *p++ = 0x83; |
| 1089 | *p++ = 0xC0; |
| 1090 | *p++ = offset; |
| 1091 | } |
| 1092 | |
| 1093 | *p++ = 0xC3; // ret |
| 1094 | |
| 1095 | END_DYNAMIC_HELPER_EMIT(); |
| 1096 | } |
| 1097 | |
| 1098 | PCODE DynamicHelpers::CreateHelperWithTwoArgs(LoaderAllocator * pAllocator, TADDR arg, PCODE target) |
| 1099 | { |
| 1100 | BEGIN_DYNAMIC_HELPER_EMIT(15); |
| 1101 | |
| 1102 | #ifdef UNIX_AMD64_ABI |
| 1103 | *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX |
| 1104 | #else |
| 1105 | *(UINT16 *)p = 0xB849; // mov r8, XXXXXX |
| 1106 | #endif |
| 1107 | p += 2; |
| 1108 | *(TADDR *)p = arg; |
| 1109 | p += 8; |
| 1110 | |
| 1111 | *p++ = X86_INSTR_JMP_REL32; // jmp rel32 |
| 1112 | *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); |
| 1113 | p += 4; |
| 1114 | |
| 1115 | END_DYNAMIC_HELPER_EMIT(); |
| 1116 | } |
| 1117 | |
| 1118 | PCODE DynamicHelpers::CreateHelperWithTwoArgs(LoaderAllocator * pAllocator, TADDR arg, TADDR arg2, PCODE target) |
| 1119 | { |
| 1120 | BEGIN_DYNAMIC_HELPER_EMIT(25); |
| 1121 | |
| 1122 | #ifdef UNIX_AMD64_ABI |
| 1123 | *(UINT16 *)p = 0xBA48; // mov rdx, XXXXXX |
| 1124 | #else |
| 1125 | *(UINT16 *)p = 0xB849; // mov r8, XXXXXX |
| 1126 | #endif |
| 1127 | p += 2; |
| 1128 | *(TADDR *)p = arg; |
| 1129 | p += 8; |
| 1130 | |
| 1131 | #ifdef UNIX_AMD64_ABI |
| 1132 | *(UINT16 *)p = 0xB948; // mov rcx, XXXXXX |
| 1133 | #else |
| 1134 | *(UINT16 *)p = 0xB949; // mov r9, XXXXXX |
| 1135 | #endif |
| 1136 | p += 2; |
| 1137 | *(TADDR *)p = arg2; |
| 1138 | p += 8; |
| 1139 | |
| 1140 | *p++ = X86_INSTR_JMP_REL32; // jmp rel32 |
| 1141 | *(INT32 *)p = rel32UsingJumpStub((INT32 *)p, target, NULL, pAllocator); |
| 1142 | p += 4; |
| 1143 | |
| 1144 | END_DYNAMIC_HELPER_EMIT(); |
| 1145 | } |
| 1146 | |
| 1147 | PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, CORINFO_RUNTIME_LOOKUP * pLookup, DWORD dictionaryIndexAndSlot, Module * pModule) |
| 1148 | { |
| 1149 | STANDARD_VM_CONTRACT; |
| 1150 | |
| 1151 | PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ? |
| 1152 | GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) : |
| 1153 | GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule)); |
| 1154 | |
| 1155 | GenericHandleArgs * pArgs = (GenericHandleArgs *)(void *)pAllocator->GetDynamicHelpersHeap()->AllocAlignedMem(sizeof(GenericHandleArgs), DYNAMIC_HELPER_ALIGNMENT); |
| 1156 | pArgs->dictionaryIndexAndSlot = dictionaryIndexAndSlot; |
| 1157 | pArgs->signature = pLookup->signature; |
| 1158 | pArgs->module = (CORINFO_MODULE_HANDLE)pModule; |
| 1159 | |
| 1160 | // It's available only via the run-time helper function |
| 1161 | if (pLookup->indirections == CORINFO_USEHELPER) |
| 1162 | { |
| 1163 | BEGIN_DYNAMIC_HELPER_EMIT(15); |
| 1164 | |
| 1165 | // rcx/rdi contains the generic context parameter |
| 1166 | // mov rdx/rsi,pArgs |
| 1167 | // jmp helperAddress |
| 1168 | EmitHelperWithArg(p, pAllocator, (TADDR)pArgs, helperAddress); |
| 1169 | |
| 1170 | END_DYNAMIC_HELPER_EMIT(); |
| 1171 | } |
| 1172 | else |
| 1173 | { |
| 1174 | int indirectionsSize = 0; |
| 1175 | for (WORD i = 0; i < pLookup->indirections; i++) |
| 1176 | indirectionsSize += (pLookup->offsets[i] >= 0x80 ? 7 : 4); |
| 1177 | |
| 1178 | int codeSize = indirectionsSize + (pLookup->testForNull ? 30 : 4); |
| 1179 | |
| 1180 | BEGIN_DYNAMIC_HELPER_EMIT(codeSize); |
| 1181 | |
| 1182 | if (pLookup->testForNull) |
| 1183 | { |
| 1184 | // rcx/rdi contains the generic context parameter. Save a copy of it in the rax register |
| 1185 | #ifdef UNIX_AMD64_ABI |
| 1186 | *(UINT32*)p = 0x00f88948; p += 3; // mov rax,rdi |
| 1187 | #else |
| 1188 | *(UINT32*)p = 0x00c88948; p += 3; // mov rax,rcx |
| 1189 | #endif |
| 1190 | } |
| 1191 | |
| 1192 | for (WORD i = 0; i < pLookup->indirections; i++) |
| 1193 | { |
| 1194 | #ifdef UNIX_AMD64_ABI |
| 1195 | // mov rdi,qword ptr [rdi+offset] |
| 1196 | if (pLookup->offsets[i] >= 0x80) |
| 1197 | { |
| 1198 | *(UINT32*)p = 0x00bf8b48; p += 3; |
| 1199 | *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4; |
| 1200 | } |
| 1201 | else |
| 1202 | { |
| 1203 | *(UINT32*)p = 0x007f8b48; p += 3; |
| 1204 | *p++ = (BYTE)pLookup->offsets[i]; |
| 1205 | } |
| 1206 | #else |
| 1207 | // mov rcx,qword ptr [rcx+offset] |
| 1208 | if (pLookup->offsets[i] >= 0x80) |
| 1209 | { |
| 1210 | *(UINT32*)p = 0x00898b48; p += 3; |
| 1211 | *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4; |
| 1212 | } |
| 1213 | else |
| 1214 | { |
| 1215 | *(UINT32*)p = 0x00498b48; p += 3; |
| 1216 | *p++ = (BYTE)pLookup->offsets[i]; |
| 1217 | } |
| 1218 | #endif |
| 1219 | } |
| 1220 | |
| 1221 | // No null test required |
| 1222 | if (!pLookup->testForNull) |
| 1223 | { |
| 1224 | // No fixups needed for R2R |
| 1225 | |
| 1226 | #ifdef UNIX_AMD64_ABI |
| 1227 | *(UINT32*)p = 0x00f88948; p += 3; // mov rax,rdi |
| 1228 | #else |
| 1229 | *(UINT32*)p = 0x00c88948; p += 3; // mov rax,rcx |
| 1230 | #endif |
| 1231 | *p++ = 0xC3; // ret |
| 1232 | } |
| 1233 | else |
| 1234 | { |
| 1235 | // rcx/rdi contains the value of the dictionary slot entry |
| 1236 | |
| 1237 | _ASSERTE(pLookup->indirections != 0); |
| 1238 | |
| 1239 | #ifdef UNIX_AMD64_ABI |
| 1240 | *(UINT32*)p = 0x00ff8548; p += 3; // test rdi,rdi |
| 1241 | #else |
| 1242 | *(UINT32*)p = 0x00c98548; p += 3; // test rcx,rcx |
| 1243 | #endif |
| 1244 | |
| 1245 | // je 'HELPER_CALL' (a jump of 4 bytes) |
| 1246 | *(UINT16*)p = 0x0474; p += 2; |
| 1247 | |
| 1248 | #ifdef UNIX_AMD64_ABI |
| 1249 | *(UINT32*)p = 0x00f88948; p += 3; // mov rax,rdi |
| 1250 | #else |
| 1251 | *(UINT32*)p = 0x00c88948; p += 3; // mov rax,rcx |
| 1252 | #endif |
| 1253 | *p++ = 0xC3; // ret |
| 1254 | |
| 1255 | // 'HELPER_CALL' |
| 1256 | { |
| 1257 | // Put the generic context back into rcx (was previously saved in rax) |
| 1258 | #ifdef UNIX_AMD64_ABI |
| 1259 | *(UINT32*)p = 0x00c78948; p += 3; // mov rdi,rax |
| 1260 | #else |
| 1261 | *(UINT32*)p = 0x00c18948; p += 3; // mov rcx,rax |
| 1262 | #endif |
| 1263 | |
| 1264 | // mov rdx,pArgs |
| 1265 | // jmp helperAddress |
| 1266 | EmitHelperWithArg(p, pAllocator, (TADDR)pArgs, helperAddress); |
| 1267 | } |
| 1268 | } |
| 1269 | |
| 1270 | END_DYNAMIC_HELPER_EMIT(); |
| 1271 | } |
| 1272 | } |
| 1273 | |
| 1274 | #endif // FEATURE_READYTORUN |
| 1275 | |
| 1276 | #endif // DACCESS_COMPILE |
| 1277 | |